feat(backend,frontend): search everywhere in buttons, links and message data, a jump from search or an alert lights up the found words and buttons
This commit is contained in:
1 parent
acfa726e61
commit
d660079c02
21 files changed
+840
-76
No files matched your search
@@ -11,6 +11,7 @@ import type {
|
||||
SearchQuery,
|
||||
} from "#lib/api/types.js";
|
||||
import { accounts } from "#lib/stores/accounts.svelte.js";
|
||||
import { MARK_PARAM } from "#lib/stores/feed.svelte.js";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
|
||||
@@ -30,6 +31,7 @@ const DEBOUNCE_MS = 250;
|
||||
const REMOTE_DELAY_MS = 450;
|
||||
const CHAT_LIMIT = 30;
|
||||
const PAGE_SIZE = 30;
|
||||
export const EVERYWHERE_MIN = 3;
|
||||
const KINDS = new Set<string>(["private", "group", "channel"]);
|
||||
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
||||
const PARAMS = [
|
||||
@@ -41,6 +43,7 @@ const PARAMS = [
|
||||
"from",
|
||||
"since",
|
||||
"until",
|
||||
"everywhere",
|
||||
] as const;
|
||||
|
||||
type Param = (typeof PARAMS)[number];
|
||||
@@ -90,6 +93,7 @@ export function createHitPager() {
|
||||
let hits = $state<SearchHit[]>([]);
|
||||
let terms = $state<string[]>([]);
|
||||
let cursor = $state<string | null>(null);
|
||||
let scannedTo = $state<string | null>(null);
|
||||
let loading = $state(false);
|
||||
let appending = $state(false);
|
||||
let failed = $state(false);
|
||||
@@ -106,6 +110,7 @@ export function createHitPager() {
|
||||
hits = request.cursor ? [...hits, ...result.hits] : result.hits;
|
||||
({ terms } = result);
|
||||
cursor = result.next_cursor;
|
||||
scannedTo = result.scanned_to;
|
||||
} catch {
|
||||
if (current === seq) {
|
||||
failed = true;
|
||||
@@ -122,6 +127,7 @@ export function createHitPager() {
|
||||
seq += 1;
|
||||
base = request;
|
||||
cursor = null;
|
||||
scannedTo = null;
|
||||
failed = false;
|
||||
appending = false;
|
||||
loading = request !== null;
|
||||
@@ -160,12 +166,21 @@ export function createHitPager() {
|
||||
run(base);
|
||||
},
|
||||
run,
|
||||
get scannedTo() {
|
||||
return scannedTo;
|
||||
},
|
||||
get terms() {
|
||||
return terms;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type HitPager = ReturnType<typeof createHitPager>;
|
||||
|
||||
export function hitKey(hit: SearchHit): string {
|
||||
return `${hit.chat_id}:${hit.message_id}`;
|
||||
}
|
||||
|
||||
function createSearch() {
|
||||
let open = $state(false);
|
||||
let draft = $state("");
|
||||
@@ -177,7 +192,9 @@ function createSearch() {
|
||||
let lastRequest = "";
|
||||
let lastText = "";
|
||||
let seq = 0;
|
||||
let lastEverywhere = "";
|
||||
const messages = createHitPager();
|
||||
const everywhere = createHitPager();
|
||||
|
||||
const params = $derived(page.url.searchParams);
|
||||
const urlQuery = $derived(params.get("q") ?? "");
|
||||
@@ -192,6 +209,14 @@ function createSearch() {
|
||||
}
|
||||
return JSON.stringify([accounts.selectedId, toRequest(query, filters)]);
|
||||
});
|
||||
const everywhereRequest = $derived.by(() => {
|
||||
const query = urlQuery.trim();
|
||||
if (!params.has("everywhere") || query.length < EVERYWHERE_MIN) {
|
||||
return "";
|
||||
}
|
||||
const deep = { ...toRequest(query, filters), everywhere: true };
|
||||
return JSON.stringify([accounts.selectedId, deep]);
|
||||
});
|
||||
const peerHits = $derived.by(() => {
|
||||
const shown = new Set(chatHits.map((chat) => chat.chat_id));
|
||||
return peerResults.filter((item) => !shown.has(item.chat_id));
|
||||
@@ -272,7 +297,9 @@ function createSearch() {
|
||||
stopTyping();
|
||||
lastRequest = "";
|
||||
lastText = "";
|
||||
lastEverywhere = "";
|
||||
messages.run(null);
|
||||
everywhere.run(null);
|
||||
runText("");
|
||||
}
|
||||
|
||||
@@ -288,14 +315,21 @@ function createSearch() {
|
||||
const update = Object.fromEntries(PARAMS.map((key) => [key, null]));
|
||||
write(update).catch(() => undefined);
|
||||
},
|
||||
everywhere,
|
||||
get everywhereRequest() {
|
||||
return everywhereRequest;
|
||||
},
|
||||
get filtered() {
|
||||
return filtered;
|
||||
},
|
||||
get filters() {
|
||||
return filters;
|
||||
},
|
||||
hitHref(chatId: number, messageId: number): string {
|
||||
hitHref(chatId: number, messageId: number, marks: string[]): string {
|
||||
const next = new URLSearchParams({ message: String(messageId) });
|
||||
for (const mark of marks) {
|
||||
next.append(MARK_PARAM, mark);
|
||||
}
|
||||
for (const key of PARAMS) {
|
||||
const value = params.get(key);
|
||||
if (value) {
|
||||
@@ -315,6 +349,9 @@ function createSearch() {
|
||||
return request;
|
||||
},
|
||||
reset,
|
||||
searchEverywhere() {
|
||||
write({ everywhere: "1" }).catch(() => undefined);
|
||||
},
|
||||
setFilter(key: "from" | "kind" | "since" | "until", value: string | null) {
|
||||
write({ [key]: value }).catch(() => undefined);
|
||||
},
|
||||
@@ -329,7 +366,11 @@ function createSearch() {
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
write({ q: draft.trim() ? draft : null })
|
||||
const same = draft.trim() === urlQuery.trim();
|
||||
write({
|
||||
everywhere: same ? params.get("everywhere") : null,
|
||||
q: draft.trim() ? draft : null,
|
||||
})
|
||||
.catch(() => undefined)
|
||||
.finally(() => {
|
||||
if (timer === null) {
|
||||
@@ -352,6 +393,12 @@ function createSearch() {
|
||||
runText(value);
|
||||
}
|
||||
},
|
||||
syncEverywhere(key: string) {
|
||||
if (key !== lastEverywhere) {
|
||||
lastEverywhere = key;
|
||||
everywhere.run(key ? (JSON.parse(key)[1] as SearchQuery) : null);
|
||||
}
|
||||
},
|
||||
get text() {
|
||||
return text;
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user