mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
feat(app): keep search history per conversation and allow in: in DMs (#1886)
This commit is contained in:
1 parent
406340fa65
commit
202856c0f7
4 files changed
+68
-31
No files matched your search
@@ -254,7 +254,7 @@ export const ChannelSearchResults = observer(
|
||||
(page: number, scopeOverride?: MessageSearchScope | null): MessageSearchParams => {
|
||||
const params: MessageSearchParams = {
|
||||
...parseSearchQueryWithSegments(currentSearchQuery.current, currentSearchSegments.current, {
|
||||
channelId: channelId.current,
|
||||
historyKey: contextId ?? undefined,
|
||||
guildId: channelGuildId.current,
|
||||
}),
|
||||
page,
|
||||
@@ -265,7 +265,7 @@ export const ChannelSearchResults = observer(
|
||||
applyMatureContentToParamsIfNeeded(params, channelId.current);
|
||||
return params;
|
||||
},
|
||||
[applyScopeToParams],
|
||||
[applyScopeToParams, contextId],
|
||||
);
|
||||
const performSearch = useCallback(
|
||||
async (page = 1, sortOverride?: ChannelSearchSortMode, scopeOverride?: MessageSearchScope | null) => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
transitionSearchMachineSnapshot,
|
||||
} from '@app/features/channel/components/SearchStateMachine';
|
||||
import type {Channel} from '@app/features/channel/models/Channel';
|
||||
import {getChannelSearchContextId} from '@app/features/channel/state/ChannelSearch';
|
||||
import Channels from '@app/features/channel/state/Channels';
|
||||
import {getChannelSearchIndexingPollInterval} from '@app/features/channel/utils/ChannelSearchPolling';
|
||||
import GuildMatureContentAgree from '@app/features/guild/state/GuildMatureContentAgree';
|
||||
@@ -269,7 +270,7 @@ export const useChannelSearch = ({
|
||||
currentSegmentsRef.current = segments;
|
||||
currentFiltersRef.current = null;
|
||||
const params = parseSearchQueryWithSegments(query, segments, {
|
||||
channelId: channel.id,
|
||||
historyKey: getChannelSearchContextId(channel) ?? undefined,
|
||||
guildId: channel.guildId ?? null,
|
||||
});
|
||||
await executeSearch(params, page, overrides);
|
||||
@@ -293,7 +294,7 @@ export const useChannelSearch = ({
|
||||
executeSearch(params, page);
|
||||
} else if (currentQueryRef.current) {
|
||||
const params = parseSearchQueryWithSegments(currentQueryRef.current, currentSegmentsRef.current, {
|
||||
channelId: channel.id,
|
||||
historyKey: getChannelSearchContextId(channel) ?? undefined,
|
||||
guildId: channel.guildId ?? null,
|
||||
});
|
||||
executeSearch(params, page);
|
||||
@@ -364,7 +365,7 @@ export const useChannelSearch = ({
|
||||
params = filtersToParams(currentFiltersRef.current);
|
||||
} else {
|
||||
params = parseSearchQueryWithSegments(currentQueryRef.current, currentSegmentsRef.current, {
|
||||
channelId: channel.id,
|
||||
historyKey: getChannelSearchContextId(channel) ?? undefined,
|
||||
guildId: channel.guildId ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import {makePersistent} from '@app/features/platform/utils/MobXPersistence';
|
||||
import type {SearchHints} from '@app/features/search/utils/SearchQueryParser';
|
||||
import {action, makeAutoObservable} from 'mobx';
|
||||
|
||||
export const SEARCH_HISTORY_LIMIT = 10;
|
||||
export const SEARCH_HISTORY_DISPLAY_LIMIT = 5;
|
||||
|
||||
export interface SearchHistoryEntry {
|
||||
query: string;
|
||||
hints?: SearchHints;
|
||||
@@ -44,17 +47,42 @@ class SearchHistory {
|
||||
}
|
||||
const entries = this.entriesByChannel[channelId];
|
||||
const ts = Date.now();
|
||||
const existingIdx = entries.findIndex((e) => e.query === q);
|
||||
const entry: SearchHistoryEntry = {query: q, hints, ts};
|
||||
const existingIdx = entries.findIndex((e) => e.query === q);
|
||||
if (existingIdx !== -1) {
|
||||
entries.splice(existingIdx, 1);
|
||||
}
|
||||
entries.unshift(entry);
|
||||
if (entries.length > 10) {
|
||||
this.entriesByChannel[channelId] = entries.slice(0, 10);
|
||||
if (entries.length > SEARCH_HISTORY_LIMIT) {
|
||||
this.entriesByChannel[channelId] = entries.slice(0, SEARCH_HISTORY_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
adoptLegacyEntries(targetKey: string | undefined, legacyKeys: ReadonlyArray<string>): void {
|
||||
if (!targetKey) return;
|
||||
if (this.getEntries(targetKey).length > 0) return;
|
||||
const merged: Array<SearchHistoryEntry> = [];
|
||||
for (const legacyKey of legacyKeys) {
|
||||
if (legacyKey === targetKey) continue;
|
||||
const legacyEntries = this.entriesByChannel[legacyKey];
|
||||
if (legacyEntries == null) continue;
|
||||
merged.push(...legacyEntries);
|
||||
delete this.entriesByChannel[legacyKey];
|
||||
}
|
||||
if (merged.length === 0) return;
|
||||
merged.sort((left, right) => right.ts - left.ts);
|
||||
const seen = new Set<string>();
|
||||
const adopted: Array<SearchHistoryEntry> = [];
|
||||
for (const entry of merged) {
|
||||
if (seen.has(entry.query)) continue;
|
||||
seen.add(entry.query);
|
||||
adopted.push(entry);
|
||||
if (adopted.length >= SEARCH_HISTORY_LIMIT) break;
|
||||
}
|
||||
this.entriesByChannel[targetKey] = adopted;
|
||||
}
|
||||
|
||||
@action
|
||||
clear(channelId?: string): void {
|
||||
if (!channelId) return;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Channel} from '@app/features/channel/models/Channel';
|
||||
import {Message} from '@app/features/messaging/models/MessagingMessage';
|
||||
import {http} from '@app/features/platform/transport/RestTransport';
|
||||
import SearchHistory from '@app/features/search/state/SearchHistory';
|
||||
import {parseQuery} from '@app/features/search/utils/SearchQueryParser';
|
||||
import {addUniqueSearchParam as addUnique, parseQuery} from '@app/features/search/utils/SearchQueryParser';
|
||||
import type {SearchSegment} from '@app/features/search/utils/SearchSegmentManager';
|
||||
import type {Channel as WireChannel} from '@fluxer/schema/src/domains/channel/ChannelSchemas';
|
||||
import type {Message as WireMessage} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
|
||||
@@ -245,7 +245,7 @@ export interface SearchContext {
|
||||
}
|
||||
|
||||
interface SearchQueryExecutionContext {
|
||||
channelId?: string;
|
||||
historyKey?: string;
|
||||
guildId?: string | null;
|
||||
}
|
||||
|
||||
@@ -299,20 +299,11 @@ export interface SearchFilterOption {
|
||||
values?: Array<SearchValueOption>;
|
||||
requiresValue?: boolean;
|
||||
requiresGuild?: boolean;
|
||||
dmEligible?: boolean;
|
||||
}
|
||||
|
||||
export function getSearchFilterOptions(i18n: I18n): Array<SearchFilterOption> {
|
||||
const hasContentValues: Array<SearchValueOption> = [
|
||||
{
|
||||
value: 'link',
|
||||
label: i18n._(VALUE_LINK_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_LINK_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'embed',
|
||||
label: i18n._(VALUE_EMBED_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_EMBED_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'image',
|
||||
label: i18n._(VALUE_IMAGE_LABEL_DESCRIPTOR),
|
||||
@@ -324,9 +315,9 @@ export function getSearchFilterOptions(i18n: I18n): Array<SearchFilterOption> {
|
||||
description: i18n._(VALUE_VIDEO_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'sound',
|
||||
label: i18n._(VALUE_SOUND_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_SOUND_DESCRIPTION_DESCRIPTOR),
|
||||
value: 'link',
|
||||
label: i18n._(VALUE_LINK_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_LINK_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'file',
|
||||
@@ -334,15 +325,25 @@ export function getSearchFilterOptions(i18n: I18n): Array<SearchFilterOption> {
|
||||
description: i18n._(VALUE_FILE_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'sticker',
|
||||
label: i18n._(VALUE_STICKER_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_STICKER_DESCRIPTION_DESCRIPTOR),
|
||||
value: 'embed',
|
||||
label: i18n._(VALUE_EMBED_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_EMBED_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'sound',
|
||||
label: i18n._(VALUE_SOUND_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_SOUND_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'poll',
|
||||
label: i18n._(VALUE_POLL_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_POLL_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'sticker',
|
||||
label: i18n._(VALUE_STICKER_LABEL_DESCRIPTOR),
|
||||
description: i18n._(VALUE_STICKER_DESCRIPTION_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: 'forward',
|
||||
label: i18n._(VALUE_FORWARD_LABEL_DESCRIPTOR),
|
||||
@@ -427,6 +428,7 @@ export function getSearchFilterOptions(i18n: I18n): Array<SearchFilterOption> {
|
||||
syntax: 'in:',
|
||||
requiresValue: true,
|
||||
requiresGuild: true,
|
||||
dmEligible: true,
|
||||
},
|
||||
{
|
||||
key: '-in',
|
||||
@@ -435,6 +437,7 @@ export function getSearchFilterOptions(i18n: I18n): Array<SearchFilterOption> {
|
||||
syntax: '-in:',
|
||||
requiresValue: true,
|
||||
requiresGuild: true,
|
||||
dmEligible: true,
|
||||
},
|
||||
{
|
||||
key: 'pinned',
|
||||
@@ -603,10 +606,6 @@ function buildHintsFromSegments(segments: Array<SearchSegment>) {
|
||||
};
|
||||
}
|
||||
|
||||
function addUnique<T>(values: Array<T> | undefined, value: T): Array<T> {
|
||||
return values?.includes(value) ? values : [...(values ?? []), value];
|
||||
}
|
||||
|
||||
function segmentMatchesQuery(query: string, segment: SearchSegment): boolean {
|
||||
return (
|
||||
segment.start >= 0 && segment.end <= query.length && query.slice(segment.start, segment.end) === segment.displayText
|
||||
@@ -654,12 +653,21 @@ function applySegmentsToParams(
|
||||
return params;
|
||||
}
|
||||
|
||||
export function hasSearchableParams(params: MessageSearchParams): boolean {
|
||||
for (const value of Object.values(params)) {
|
||||
if (value !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function parseSearchQueryWithSegments(
|
||||
query: string,
|
||||
segments: Array<SearchSegment>,
|
||||
ctx?: SearchQueryExecutionContext,
|
||||
): MessageSearchParams {
|
||||
const entry = SearchHistory.recent(ctx?.channelId).find((historyEntry) => historyEntry.query === query);
|
||||
const entry = SearchHistory.recent(ctx?.historyKey).find((historyEntry) => historyEntry.query === query);
|
||||
const segmentHints = buildHintsFromSegments(segments);
|
||||
const segmentUserValues = new Set<string>();
|
||||
for (const segment of segments) {
|
||||
|
||||
Reference in new issue
Block a user