@lapis-notes/search 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/dist/native-search.d.ts +5 -0
- package/dist/native-search.js +24 -0
- package/dist/search-history-versions.svelte +197 -0
- package/dist/search-history-versions.svelte.d.ts +8 -0
- package/dist/search-manager.d.ts +3 -0
- package/dist/search-manager.js +72 -7
- package/dist/search-panel.css +103 -0
- package/dist/search-panel.svelte +343 -41
- package/dist/search-panel.svelte.d.ts +8 -1
- package/dist/search-plugin.d.ts +7 -1
- package/dist/search-plugin.js +97 -15
- package/dist/search-provider-controller.d.ts +25 -0
- package/dist/search-provider-controller.js +157 -0
- package/dist/search-provider-groups.d.ts +14 -0
- package/dist/search-provider-groups.js +60 -0
- package/dist/search-settings-observer.d.ts +4 -0
- package/dist/search-settings-observer.js +10 -0
- package/dist/search-settings-tab.js +2 -2
- package/dist/search-tool-result.css +2 -7
- package/dist/search-tool-result.svelte +9 -2
- package/dist/search-view.d.ts +5 -1
- package/dist/search-view.js +32 -4
- package/manifest.json +2 -2
- package/package.json +5 -5
package/dist/search-panel.svelte
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
searchQueryLanguageSupport,
|
|
9
9
|
Notice,
|
|
10
10
|
type App,
|
|
11
|
+
type Plugin,
|
|
12
|
+
type SearchResultScope,
|
|
11
13
|
type EditorPosition,
|
|
12
14
|
type TFile,
|
|
13
15
|
TextFileView,
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
import * as Popover from "@lapismd/design-core/shadcn/popover";
|
|
25
27
|
import { ScrollArea } from "@lapismd/design-core/shadcn/scroll-area";
|
|
26
28
|
import * as Sidebar from "@lapismd/design-core/shadcn/sidebar";
|
|
29
|
+
import { Skeleton } from "@lapismd/design-core/shadcn/skeleton";
|
|
27
30
|
import { Switch } from "@lapismd/design-core/shadcn/switch";
|
|
28
31
|
import ChevronDown from "@lucide/svelte/icons/chevron-down";
|
|
29
32
|
import ChevronRight from "@lucide/svelte/icons/chevron-right";
|
|
@@ -57,8 +60,18 @@
|
|
|
57
60
|
type SearchResult,
|
|
58
61
|
} from "./search-result-model";
|
|
59
62
|
import { SearchViewType } from "./search-view-type";
|
|
63
|
+
import { observeRecoveredSearchSettings } from "./search-settings-observer";
|
|
64
|
+
|
|
65
|
+
import {
|
|
66
|
+
SearchProviderController,
|
|
67
|
+
searchProviderFields,
|
|
68
|
+
type ProviderResults,
|
|
69
|
+
} from "./search-provider-controller";
|
|
70
|
+
import { groupProviderResults } from "./search-provider-groups";
|
|
71
|
+
import SearchHistoryVersions from "./search-history-versions.svelte";
|
|
60
72
|
|
|
61
73
|
type SearchPanelPlugin = {
|
|
74
|
+
readonly state: Plugin["state"];
|
|
62
75
|
searchManager: SearchManager;
|
|
63
76
|
getSettings(): SearchPluginSettings;
|
|
64
77
|
updateSettings(patch: SearchPluginSettingsPatch): Promise<void>;
|
|
@@ -81,9 +94,13 @@
|
|
|
81
94
|
let {
|
|
82
95
|
app,
|
|
83
96
|
initialQuery = "",
|
|
97
|
+
initialScope = "files",
|
|
98
|
+
onStateChange,
|
|
84
99
|
}: {
|
|
85
100
|
app: App;
|
|
86
101
|
initialQuery?: string;
|
|
102
|
+
initialScope?: SearchResultScope;
|
|
103
|
+
onStateChange?: () => void;
|
|
87
104
|
} = $props();
|
|
88
105
|
|
|
89
106
|
const plugin = untrack(() => {
|
|
@@ -94,7 +111,97 @@
|
|
|
94
111
|
return registered;
|
|
95
112
|
});
|
|
96
113
|
|
|
97
|
-
let query = $state(
|
|
114
|
+
let query = $state(untrack(() => initialQuery));
|
|
115
|
+
let scope = $state<SearchResultScope>(untrack(() => initialScope));
|
|
116
|
+
let previousViewState = untrack(() => JSON.stringify({ query, scope }));
|
|
117
|
+
$effect(() => {
|
|
118
|
+
const next = JSON.stringify({ query, scope });
|
|
119
|
+
if (next === previousViewState) return;
|
|
120
|
+
previousViewState = next;
|
|
121
|
+
untrack(() => onStateChange?.());
|
|
122
|
+
});
|
|
123
|
+
export function getSearchState() {
|
|
124
|
+
return { query, scope };
|
|
125
|
+
}
|
|
126
|
+
let providerRows = $state<ProviderResults[]>([]);
|
|
127
|
+
const providerController = untrack(
|
|
128
|
+
() =>
|
|
129
|
+
new SearchProviderController(
|
|
130
|
+
app.searchResultProviders,
|
|
131
|
+
(error) =>
|
|
132
|
+
new Notice(
|
|
133
|
+
`History search failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
134
|
+
undefined,
|
|
135
|
+
app,
|
|
136
|
+
),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
const reportProviderError = (error: unknown) => {
|
|
140
|
+
new Notice(
|
|
141
|
+
`History search failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
142
|
+
undefined,
|
|
143
|
+
app,
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
const historyGroups = $derived(groupProviderResults(providerRows));
|
|
147
|
+
const historyCount = $derived(
|
|
148
|
+
historyGroups.reduce((sum, group) => sum + group.versions.length, 0),
|
|
149
|
+
);
|
|
150
|
+
const historyPending = $derived(
|
|
151
|
+
providerRows.some(
|
|
152
|
+
(row) => row.busy || row.next || row.status?.complete === false,
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
const parsedQuery = $derived(parseSearchQueryAst(query));
|
|
156
|
+
const currentUnsupported = $derived(
|
|
157
|
+
searchProviderFields(parsedQuery.expression).some((field) =>
|
|
158
|
+
["author", "after", "before", "commit"].includes(field),
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
const displayGroups = $derived.by(() => {
|
|
162
|
+
const groups = new Map(
|
|
163
|
+
historyGroups.map((history) => [
|
|
164
|
+
history.id,
|
|
165
|
+
{
|
|
166
|
+
id: history.id,
|
|
167
|
+
history,
|
|
168
|
+
current: undefined as SearchResult | undefined,
|
|
169
|
+
},
|
|
170
|
+
]),
|
|
171
|
+
);
|
|
172
|
+
for (const current of filteredResults) {
|
|
173
|
+
const existing = groups.get(current.file.path);
|
|
174
|
+
if (existing) existing.current = current;
|
|
175
|
+
else
|
|
176
|
+
groups.set(current.file.path, {
|
|
177
|
+
id: current.file.path,
|
|
178
|
+
current,
|
|
179
|
+
history: undefined!,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const mode = settings.view.sortMode;
|
|
183
|
+
return [...groups.values()].sort((a, b) => {
|
|
184
|
+
if (mode.startsWith("filename"))
|
|
185
|
+
return (
|
|
186
|
+
(mode.endsWith("desc") ? -1 : 1) *
|
|
187
|
+
(a.current?.file.name ?? a.history?.title ?? a.id).localeCompare(
|
|
188
|
+
b.current?.file.name ?? b.history?.title ?? b.id,
|
|
189
|
+
undefined,
|
|
190
|
+
{ numeric: true, sensitivity: "base" },
|
|
191
|
+
) || a.id.localeCompare(b.id)
|
|
192
|
+
);
|
|
193
|
+
const time = (item: typeof a) =>
|
|
194
|
+
item.current
|
|
195
|
+
? mode.startsWith("created")
|
|
196
|
+
? item.current.file.stat.ctime
|
|
197
|
+
: item.current.file.stat.mtime
|
|
198
|
+
: (item.history?.versions[0]?.hit.timestamp ?? 0);
|
|
199
|
+
return (
|
|
200
|
+
(mode.endsWith("desc") ? -1 : 1) * (time(a) - time(b)) ||
|
|
201
|
+
a.id.localeCompare(b.id)
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
98
205
|
let settings = $state(untrack(() => plugin.getSettings()));
|
|
99
206
|
let results = $state<SearchResult[]>([]);
|
|
100
207
|
let resultCount = $state(0);
|
|
@@ -108,9 +215,32 @@
|
|
|
108
215
|
let metadataFacetError = $state<string | null>(null);
|
|
109
216
|
let metadataFacetGeneration = 0;
|
|
110
217
|
let searchRevision = 0;
|
|
218
|
+
const resultSummary = $derived(
|
|
219
|
+
searching
|
|
220
|
+
? "Searching…"
|
|
221
|
+
: scope === "files"
|
|
222
|
+
? `${resultCount} result${resultCount === 1 ? "" : "s"}`
|
|
223
|
+
: `${scope === "history" ? 0 : resultCount} current · ${historyCount} saved${historyPending ? " loaded" : ""}`,
|
|
224
|
+
);
|
|
225
|
+
const providerSearchKey = $derived(
|
|
226
|
+
JSON.stringify({
|
|
227
|
+
text: query,
|
|
228
|
+
caseSensitive: settings.view.matchCase,
|
|
229
|
+
mode: retrievalModeForQuery(),
|
|
230
|
+
limit: settings.query.resultLimit,
|
|
231
|
+
enabled: scope !== "files",
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
$effect(() => {
|
|
235
|
+
const { enabled, ...input } = JSON.parse(providerSearchKey);
|
|
236
|
+
const timer = window.setTimeout(
|
|
237
|
+
() => providerController.search(input, enabled),
|
|
238
|
+
250,
|
|
239
|
+
);
|
|
240
|
+
return () => window.clearTimeout(timer);
|
|
241
|
+
});
|
|
111
242
|
const databaseRefreshDelayMs = 75;
|
|
112
243
|
|
|
113
|
-
const parsedQuery = $derived(parseSearchQueryAst(query));
|
|
114
244
|
const diagnostic = $derived(
|
|
115
245
|
query.trim() ? (parsedQuery.diagnostics[0]?.message ?? null) : null,
|
|
116
246
|
);
|
|
@@ -124,6 +254,7 @@
|
|
|
124
254
|
const searchRequestKey = $derived(
|
|
125
255
|
JSON.stringify([
|
|
126
256
|
query,
|
|
257
|
+
scope,
|
|
127
258
|
settings.view.matchCase,
|
|
128
259
|
settings.view.showMoreContext,
|
|
129
260
|
settings.view.semanticSearchInStructuredQueries,
|
|
@@ -144,6 +275,7 @@
|
|
|
144
275
|
: "Matching filenames, paths, tags, metadata, and content.";
|
|
145
276
|
});
|
|
146
277
|
const filteredResults = $derived.by(() => {
|
|
278
|
+
if (scope === "history" || currentUnsupported) return [];
|
|
147
279
|
const facet = settings.view.resultFacet;
|
|
148
280
|
const matching = results.filter((result) => {
|
|
149
281
|
if (facet === "all") return true;
|
|
@@ -163,11 +295,41 @@
|
|
|
163
295
|
.map((file) => file.name)
|
|
164
296
|
.sort()
|
|
165
297
|
.slice(0, 100);
|
|
166
|
-
|
|
298
|
+
const syntax = createVaultSearchFilterSyntax({
|
|
167
299
|
fileNames: names,
|
|
168
300
|
paths,
|
|
169
301
|
tags: metadataTags,
|
|
170
302
|
});
|
|
303
|
+
if (scope === "files") return syntax;
|
|
304
|
+
const fields =
|
|
305
|
+
scope === "history"
|
|
306
|
+
? (syntax.fields?.filter((field) =>
|
|
307
|
+
["file", "path", "content"].includes(field.name),
|
|
308
|
+
) ?? [])
|
|
309
|
+
: (syntax.fields ?? []);
|
|
310
|
+
return {
|
|
311
|
+
...syntax,
|
|
312
|
+
title: "Search syntax",
|
|
313
|
+
fields: [
|
|
314
|
+
...fields,
|
|
315
|
+
{ name: "author", description: "Checkpoint author", operators: [":"] },
|
|
316
|
+
{
|
|
317
|
+
name: "after",
|
|
318
|
+
description: "Checkpoint date on or after (UTC)",
|
|
319
|
+
operators: [":"],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "before",
|
|
323
|
+
description: "Checkpoint date on or before (UTC)",
|
|
324
|
+
operators: [":"],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "commit",
|
|
328
|
+
description: "Checkpoint revision",
|
|
329
|
+
operators: [":"],
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
};
|
|
171
333
|
});
|
|
172
334
|
|
|
173
335
|
async function refreshMetadataFacets(): Promise<void> {
|
|
@@ -193,6 +355,10 @@
|
|
|
193
355
|
}
|
|
194
356
|
}
|
|
195
357
|
|
|
358
|
+
export function setSearchScope(next: SearchResultScope): void {
|
|
359
|
+
scope = next;
|
|
360
|
+
}
|
|
361
|
+
|
|
196
362
|
export function setSearchQuery(next: string): void {
|
|
197
363
|
query = next;
|
|
198
364
|
}
|
|
@@ -212,13 +378,28 @@
|
|
|
212
378
|
);
|
|
213
379
|
}
|
|
214
380
|
|
|
381
|
+
let openQuery = "";
|
|
382
|
+
$effect(() => {
|
|
383
|
+
if (openQuery !== query) {
|
|
384
|
+
openQuery = query;
|
|
385
|
+
resultOpenState = {};
|
|
386
|
+
}
|
|
387
|
+
});
|
|
215
388
|
function refreshOpenState(items: SearchResult[], identity: string): void {
|
|
216
389
|
if (identity === resultIdentity) return;
|
|
217
390
|
resultIdentity = identity;
|
|
218
391
|
const defaultOpen = !settings.view.collapseResults;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
392
|
+
const previous = openQuery === query ? resultOpenState : {};
|
|
393
|
+
openQuery = query;
|
|
394
|
+
resultOpenState = {
|
|
395
|
+
...previous,
|
|
396
|
+
...Object.fromEntries(
|
|
397
|
+
items.map((item) => [
|
|
398
|
+
item.file.path,
|
|
399
|
+
previous[item.file.path] ?? defaultOpen,
|
|
400
|
+
]),
|
|
401
|
+
),
|
|
402
|
+
};
|
|
222
403
|
}
|
|
223
404
|
|
|
224
405
|
function setResultOpen(path: string, open: boolean): void {
|
|
@@ -251,13 +432,19 @@
|
|
|
251
432
|
if (patch.view?.collapseResults !== undefined) {
|
|
252
433
|
const defaultOpen = !settings.view.collapseResults;
|
|
253
434
|
resultOpenState = Object.fromEntries(
|
|
254
|
-
|
|
435
|
+
displayGroups.map((group) => [group.id, defaultOpen]),
|
|
255
436
|
);
|
|
256
437
|
}
|
|
257
438
|
}
|
|
258
439
|
|
|
259
440
|
async function executeSearch(term: string, revision: number): Promise<void> {
|
|
260
|
-
if (
|
|
441
|
+
if (plugin.state !== "enabled") return;
|
|
442
|
+
if (
|
|
443
|
+
!term.trim() ||
|
|
444
|
+
diagnostic ||
|
|
445
|
+
scope === "history" ||
|
|
446
|
+
currentUnsupported
|
|
447
|
+
) {
|
|
261
448
|
results = [];
|
|
262
449
|
resultCount = 0;
|
|
263
450
|
searching = false;
|
|
@@ -272,11 +459,11 @@
|
|
|
272
459
|
snippetLength: snippetLength(),
|
|
273
460
|
mode: retrievalModeForQuery(),
|
|
274
461
|
});
|
|
275
|
-
if (revision !== searchRevision) return;
|
|
462
|
+
if (revision !== searchRevision || plugin.state !== "enabled") return;
|
|
276
463
|
const nextResults = response.hits
|
|
277
464
|
.map((hit) => searchResultFromHit(app, hit))
|
|
278
465
|
.filter((result): result is SearchResult => result !== null);
|
|
279
|
-
resultCount =
|
|
466
|
+
resultCount = nextResults.length;
|
|
280
467
|
const identity = `${term}\u0000${nextResults
|
|
281
468
|
.map(
|
|
282
469
|
(result) =>
|
|
@@ -285,7 +472,14 @@
|
|
|
285
472
|
.join("\u0000")}`;
|
|
286
473
|
if (identity !== resultIdentity) results = nextResults;
|
|
287
474
|
refreshOpenState(nextResults, identity);
|
|
288
|
-
|
|
475
|
+
await rememberSearch(term);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (revision === searchRevision && plugin.state === "enabled")
|
|
478
|
+
new Notice(
|
|
479
|
+
error instanceof Error ? error.message : String(error),
|
|
480
|
+
undefined,
|
|
481
|
+
app,
|
|
482
|
+
);
|
|
289
483
|
} finally {
|
|
290
484
|
if (revision === searchRevision) searching = false;
|
|
291
485
|
}
|
|
@@ -303,6 +497,7 @@
|
|
|
303
497
|
});
|
|
304
498
|
|
|
305
499
|
async function refreshIndex(): Promise<void> {
|
|
500
|
+
providerController.refresh();
|
|
306
501
|
if (indexing) return;
|
|
307
502
|
indexing = true;
|
|
308
503
|
try {
|
|
@@ -319,10 +514,18 @@
|
|
|
319
514
|
result.file.path,
|
|
320
515
|
...result.matches.map((match) => ` ${match.key}: ${match.text}`),
|
|
321
516
|
]);
|
|
517
|
+
lines.push(
|
|
518
|
+
...providerRows.flatMap((row) =>
|
|
519
|
+
row.hits.flatMap((hit) => [
|
|
520
|
+
hit.path ?? hit.title,
|
|
521
|
+
...hit.matches.map((match) => ` ${match.field}: ${match.text}`),
|
|
522
|
+
]),
|
|
523
|
+
),
|
|
524
|
+
);
|
|
322
525
|
const text = lines.join("\n");
|
|
323
526
|
try {
|
|
324
527
|
await navigator.clipboard.writeText(text);
|
|
325
|
-
new Notice("Search results copied");
|
|
528
|
+
new Notice("Search results copied", undefined, app);
|
|
326
529
|
} catch {
|
|
327
530
|
const textarea = document.createElement("textarea");
|
|
328
531
|
textarea.value = text;
|
|
@@ -334,6 +537,8 @@
|
|
|
334
537
|
textarea.remove();
|
|
335
538
|
new Notice(
|
|
336
539
|
copied ? "Search results copied" : "Unable to copy search results",
|
|
540
|
+
undefined,
|
|
541
|
+
app,
|
|
337
542
|
);
|
|
338
543
|
}
|
|
339
544
|
}
|
|
@@ -419,7 +624,26 @@
|
|
|
419
624
|
}
|
|
420
625
|
|
|
421
626
|
onMount(() => {
|
|
422
|
-
|
|
627
|
+
const settingsChanged = observeRecoveredSearchSettings(
|
|
628
|
+
app,
|
|
629
|
+
"search",
|
|
630
|
+
(next) => {
|
|
631
|
+
const collapseChanged =
|
|
632
|
+
settings.view.collapseResults !== next.view.collapseResults;
|
|
633
|
+
settings = next;
|
|
634
|
+
if (collapseChanged) {
|
|
635
|
+
resultOpenState = Object.fromEntries(
|
|
636
|
+
displayGroups.map((group) => [
|
|
637
|
+
group.id,
|
|
638
|
+
!next.view.collapseResults,
|
|
639
|
+
]),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
);
|
|
644
|
+
const unsubscribeProviders = providerController.subscribe(
|
|
645
|
+
(rows) => (providerRows = rows),
|
|
646
|
+
);
|
|
423
647
|
void refreshMetadataFacets();
|
|
424
648
|
let databaseRefreshTimer: number | undefined;
|
|
425
649
|
const databaseChanged = app.appDatabase.subscribeToChanges((change) => {
|
|
@@ -440,6 +664,9 @@
|
|
|
440
664
|
() => void refreshMetadataFacets(),
|
|
441
665
|
);
|
|
442
666
|
return () => {
|
|
667
|
+
settingsChanged();
|
|
668
|
+
unsubscribeProviders();
|
|
669
|
+
providerController.close();
|
|
443
670
|
searchRevision += 1;
|
|
444
671
|
metadataFacetGeneration += 1;
|
|
445
672
|
window.clearTimeout(databaseRefreshTimer);
|
|
@@ -586,19 +813,31 @@
|
|
|
586
813
|
</p>
|
|
587
814
|
{/if}
|
|
588
815
|
|
|
816
|
+
<div class="search-panel__source">
|
|
817
|
+
<FilterCommandPicker
|
|
818
|
+
label={`${scope === "files" ? "Current files" : scope === "history" ? "History" : "Everything"} search source`}
|
|
819
|
+
value={scope}
|
|
820
|
+
options={[
|
|
821
|
+
{ value: "files", label: "Current files" },
|
|
822
|
+
{ value: "history", label: "History" },
|
|
823
|
+
{ value: "all", label: "Everything" },
|
|
824
|
+
]}
|
|
825
|
+
onChange={(value: string) => {
|
|
826
|
+
scope = value as SearchResultScope;
|
|
827
|
+
}}
|
|
828
|
+
/>
|
|
829
|
+
</div>
|
|
589
830
|
<div class="search-panel__summary">
|
|
590
831
|
<Button
|
|
591
832
|
variant="ghost"
|
|
592
833
|
size="sm"
|
|
593
834
|
class="search-panel__summary-control"
|
|
594
|
-
aria-label=
|
|
595
|
-
disabled={!filteredResults.length}
|
|
835
|
+
aria-label={`Copy search results: ${resultSummary}`}
|
|
836
|
+
disabled={!filteredResults.length && !historyCount}
|
|
596
837
|
onclick={copySearchResults}
|
|
597
838
|
>
|
|
598
839
|
<Copy aria-hidden="true" />
|
|
599
|
-
{
|
|
600
|
-
? "Searching…"
|
|
601
|
-
: `${resultCount} result${resultCount === 1 ? "" : "s"}`}
|
|
840
|
+
{resultSummary}
|
|
602
841
|
</Button>
|
|
603
842
|
<Popover.Root>
|
|
604
843
|
<Popover.Trigger>
|
|
@@ -608,8 +847,11 @@
|
|
|
608
847
|
variant="ghost"
|
|
609
848
|
size="sm"
|
|
610
849
|
class="search-panel__summary-control search-panel__sort"
|
|
850
|
+
aria-label={formatSearchViewSortLabel(settings.view.sortMode)}
|
|
611
851
|
>
|
|
612
|
-
<span
|
|
852
|
+
<span class="search-panel__sort-label"
|
|
853
|
+
>{formatSearchViewSortLabel(settings.view.sortMode)}</span
|
|
854
|
+
>
|
|
613
855
|
<ChevronsUpDown aria-hidden="true" />
|
|
614
856
|
</Button>
|
|
615
857
|
{/snippet}
|
|
@@ -630,6 +872,35 @@
|
|
|
630
872
|
</Popover.Root>
|
|
631
873
|
</div>
|
|
632
874
|
|
|
875
|
+
{#if scope !== "files"}
|
|
876
|
+
{#if !providerRows.length && query.trim()}<p
|
|
877
|
+
class="search-panel__explanation"
|
|
878
|
+
>
|
|
879
|
+
History search is unavailable. Enable Repository History to search
|
|
880
|
+
saved versions.
|
|
881
|
+
</p>{/if}
|
|
882
|
+
{#each providerRows as row (row.provider.id)}
|
|
883
|
+
{#if row.unavailable}<p class="search-panel__explanation">
|
|
884
|
+
{row.provider.label}: {row.unavailable}
|
|
885
|
+
</p>{/if}
|
|
886
|
+
{#if row.status?.omitted}<p class="search-panel__explanation">
|
|
887
|
+
{row.status.omitted} file versions are searchable by path only.
|
|
888
|
+
</p>{/if}
|
|
889
|
+
{#if row.status?.complete === false}<p
|
|
890
|
+
class="search-panel__explanation"
|
|
891
|
+
role="status"
|
|
892
|
+
>
|
|
893
|
+
{row.status.description ??
|
|
894
|
+
`Indexing history · ${row.status.indexed} checkpoints ready`}
|
|
895
|
+
</p>{/if}
|
|
896
|
+
{/each}
|
|
897
|
+
{/if}
|
|
898
|
+
{#if currentUnsupported && scope !== "history"}<p
|
|
899
|
+
class="search-panel__explanation"
|
|
900
|
+
>
|
|
901
|
+
Current files do not support checkpoint author, date or revision
|
|
902
|
+
filters.
|
|
903
|
+
</p>{/if}
|
|
633
904
|
{#if explanation}
|
|
634
905
|
<p class="search-panel__explanation">{explanation}</p>
|
|
635
906
|
{/if}
|
|
@@ -639,7 +910,17 @@
|
|
|
639
910
|
<Sidebar.Content class="search-panel__results" data-ui-part="content">
|
|
640
911
|
{#if !query.trim()}
|
|
641
912
|
<p class="search-panel__empty">Type to search.</p>
|
|
642
|
-
{:else if
|
|
913
|
+
{:else if providerRows.some((row) => row.busy) && displayGroups.length === 0}
|
|
914
|
+
<div
|
|
915
|
+
class="search-panel__loading"
|
|
916
|
+
role="status"
|
|
917
|
+
aria-label="Searching saved history"
|
|
918
|
+
>
|
|
919
|
+
{#each Array(4) as _}<Skeleton
|
|
920
|
+
class="search-panel__loading-title"
|
|
921
|
+
/><Skeleton class="search-panel__loading-snippet" />{/each}
|
|
922
|
+
</div>
|
|
923
|
+
{:else if !searching && !historyPending && !providerRows.some((row) => row.unavailable) && !diagnostic && displayGroups.length === 0}
|
|
643
924
|
<p class="search-panel__empty">No matches found.</p>
|
|
644
925
|
{:else}
|
|
645
926
|
<div class="search-panel__tree-inset">
|
|
@@ -648,14 +929,16 @@
|
|
|
648
929
|
aria-label="Search results"
|
|
649
930
|
class="search-panel__tree"
|
|
650
931
|
>
|
|
651
|
-
{#each
|
|
932
|
+
{#each displayGroups as display (display.id)}
|
|
933
|
+
{@const result = display.current}
|
|
934
|
+
{@const path =
|
|
935
|
+
result?.file.path ?? display.history?.path ?? display.id}
|
|
652
936
|
{@const open =
|
|
653
|
-
resultOpenState[
|
|
654
|
-
!settings.view.collapseResults}
|
|
937
|
+
resultOpenState[display.id] ?? !settings.view.collapseResults}
|
|
655
938
|
<Sidebar.MenuItem role="none" class="search-panel__tree-item">
|
|
656
939
|
<Collapsible.Root
|
|
657
940
|
{open}
|
|
658
|
-
onOpenChange={(next) => setResultOpen(
|
|
941
|
+
onOpenChange={(next) => setResultOpen(display.id, next)}
|
|
659
942
|
class="search-panel__result"
|
|
660
943
|
>
|
|
661
944
|
<Collapsible.Trigger
|
|
@@ -664,13 +947,15 @@
|
|
|
664
947
|
aria-level={1}
|
|
665
948
|
aria-selected="false"
|
|
666
949
|
aria-expanded={open}
|
|
667
|
-
aria-label={
|
|
950
|
+
aria-label={result
|
|
951
|
+
? `${result.file.basename} ${(result?.matches.length ?? 0) + (display.history?.versions.length ?? 0)} · ${path}, ${(result?.matches.length ?? 0) + (display.history?.versions.length ?? 0)} matches`
|
|
952
|
+
: `${display.history?.title ?? path} ${display.history?.versions.length ?? 0} · ${path}, ${display.history?.versions.length ?? 0} saved versions`}
|
|
668
953
|
>
|
|
669
954
|
<ChevronRight
|
|
670
955
|
class="search-panel__disclosure"
|
|
671
956
|
aria-hidden="true"
|
|
672
957
|
/>
|
|
673
|
-
{#if result
|
|
958
|
+
{#if (result?.file.extension ?? display.history?.extension) === "canvas"}
|
|
674
959
|
<Hash
|
|
675
960
|
class="search-panel__file-icon"
|
|
676
961
|
aria-hidden="true"
|
|
@@ -683,30 +968,34 @@
|
|
|
683
968
|
{/if}
|
|
684
969
|
<span class="search-panel__file-label">
|
|
685
970
|
<HighlightedText
|
|
686
|
-
text={result
|
|
687
|
-
|
|
971
|
+
text={result?.file.basename ??
|
|
972
|
+
display.history?.title ??
|
|
973
|
+
path}
|
|
974
|
+
ranges={result?.title?.ranges ?? []}
|
|
688
975
|
/>
|
|
689
976
|
</span>
|
|
690
977
|
<Sidebar.MenuBadge class="search-panel__count-badge">
|
|
691
|
-
{result
|
|
978
|
+
{(result?.matches.length ?? 0) +
|
|
979
|
+
(display.history?.versions.length ?? 0)}
|
|
692
980
|
</Sidebar.MenuBadge>
|
|
693
981
|
</Collapsible.Trigger>
|
|
694
982
|
<Collapsible.Content class="search-panel__match-list">
|
|
695
983
|
<div class="search-panel__match-header">
|
|
696
|
-
<span
|
|
697
|
-
|
|
698
|
-
title={result.file.path}
|
|
699
|
-
>
|
|
700
|
-
{result.file.path}
|
|
984
|
+
<span class="search-panel__match-path" title={path}>
|
|
985
|
+
{path}
|
|
701
986
|
</span>
|
|
702
987
|
<Badge variant="outline" class="search-panel__mode-badge">
|
|
703
|
-
{result
|
|
988
|
+
{result?.hit.retrievalMode ?? "lexical"}
|
|
704
989
|
</Badge>
|
|
990
|
+
{#if display.history && result}<Badge variant="outline"
|
|
991
|
+
>Current</Badge
|
|
992
|
+
>{/if}
|
|
705
993
|
</div>
|
|
706
994
|
<Sidebar.MenuSub role="group">
|
|
707
|
-
{#each result
|
|
995
|
+
{#each result?.matches ?? [] as match, index (`${display.id}:${index}`)}
|
|
996
|
+
{@const currentResult = result!}
|
|
708
997
|
<Sidebar.MenuSubItem role="none">
|
|
709
|
-
{@const loadingKey = `${
|
|
998
|
+
{@const loadingKey = `${path}\u0000${match.id}`}
|
|
710
999
|
<div
|
|
711
1000
|
class="search-panel__match-shell"
|
|
712
1001
|
role="treeitem"
|
|
@@ -718,12 +1007,12 @@
|
|
|
718
1007
|
match.context &&
|
|
719
1008
|
match.context.end < match.context.sourceLength,
|
|
720
1009
|
)}
|
|
721
|
-
onclick={() => openResult(
|
|
1010
|
+
onclick={() => openResult(currentResult, match.pos)}
|
|
722
1011
|
onkeydown={(event) => {
|
|
723
1012
|
if (event.key !== "Enter" && event.key !== " ")
|
|
724
1013
|
return;
|
|
725
1014
|
event.preventDefault();
|
|
726
|
-
void openResult(
|
|
1015
|
+
void openResult(currentResult, match.pos);
|
|
727
1016
|
}}
|
|
728
1017
|
>
|
|
729
1018
|
{#if match.context?.start}
|
|
@@ -736,7 +1025,7 @@
|
|
|
736
1025
|
onclick={(event) => {
|
|
737
1026
|
event.stopPropagation();
|
|
738
1027
|
void expandMatchContext(
|
|
739
|
-
|
|
1028
|
+
currentResult,
|
|
740
1029
|
match,
|
|
741
1030
|
"before",
|
|
742
1031
|
);
|
|
@@ -771,7 +1060,7 @@
|
|
|
771
1060
|
onclick={(event) => {
|
|
772
1061
|
event.stopPropagation();
|
|
773
1062
|
void expandMatchContext(
|
|
774
|
-
|
|
1063
|
+
currentResult,
|
|
775
1064
|
match,
|
|
776
1065
|
"after",
|
|
777
1066
|
);
|
|
@@ -784,6 +1073,10 @@
|
|
|
784
1073
|
</Sidebar.MenuSubItem>
|
|
785
1074
|
{/each}
|
|
786
1075
|
</Sidebar.MenuSub>
|
|
1076
|
+
{#if display.history}<SearchHistoryVersions
|
|
1077
|
+
versions={display.history.versions}
|
|
1078
|
+
report={reportProviderError}
|
|
1079
|
+
/>{/if}
|
|
787
1080
|
</Collapsible.Content>
|
|
788
1081
|
</Collapsible.Root>
|
|
789
1082
|
</Sidebar.MenuItem>
|
|
@@ -791,6 +1084,15 @@
|
|
|
791
1084
|
</Sidebar.Menu>
|
|
792
1085
|
</div>
|
|
793
1086
|
{/if}
|
|
1087
|
+
{#each providerRows.filter((row) => row.next) as row (row.provider.id)}
|
|
1088
|
+
<Button
|
|
1089
|
+
variant="ghost"
|
|
1090
|
+
size="sm"
|
|
1091
|
+
disabled={row.busy}
|
|
1092
|
+
onclick={() => providerController.more(row.provider.id)}
|
|
1093
|
+
>{row.busy ? "Searching history…" : "Search older history"}</Button
|
|
1094
|
+
>
|
|
1095
|
+
{/each}
|
|
794
1096
|
</Sidebar.Content>
|
|
795
1097
|
</ScrollArea>
|
|
796
1098
|
</div>
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import "./search-panel.css";
|
|
2
|
-
import { type App } from "@lapis-notes/api";
|
|
2
|
+
import { type App, type SearchResultScope } from "@lapis-notes/api";
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
app: App;
|
|
5
5
|
initialQuery?: string;
|
|
6
|
+
initialScope?: SearchResultScope;
|
|
7
|
+
onStateChange?: () => void;
|
|
6
8
|
};
|
|
7
9
|
declare const SearchPanel: import("svelte").Component<$$ComponentProps, {
|
|
10
|
+
getSearchState: () => {
|
|
11
|
+
query: string;
|
|
12
|
+
scope: SearchResultScope;
|
|
13
|
+
};
|
|
14
|
+
setSearchScope: (next: SearchResultScope) => void;
|
|
8
15
|
setSearchQuery: (next: string) => void;
|
|
9
16
|
}, "">;
|
|
10
17
|
type SearchPanel = ReturnType<typeof SearchPanel>;
|
package/dist/search-plugin.d.ts
CHANGED
|
@@ -7,16 +7,22 @@ export declare class SearchPlugin extends Plugin {
|
|
|
7
7
|
private startupRefreshPromise;
|
|
8
8
|
private providerReconcileReason;
|
|
9
9
|
private providerReconcilePromise;
|
|
10
|
-
|
|
10
|
+
private managerStopped;
|
|
11
|
+
private currentSearchManager;
|
|
11
12
|
constructor(app: App, pluginManifest?: PluginManifest);
|
|
13
|
+
private showNotice;
|
|
14
|
+
get searchManager(): SearchManager;
|
|
12
15
|
getSettings(): SearchPluginSettings;
|
|
13
16
|
updateSettings(patch: SearchPluginSettingsPatch): Promise<void>;
|
|
17
|
+
private reconfigureSettings;
|
|
14
18
|
getSearchStatus(): Promise<SearchRuntimeStatus>;
|
|
15
19
|
rebuildSemanticSearch(): Promise<SearchRuntimeStatus>;
|
|
16
20
|
onload(): Promise<void>;
|
|
17
21
|
refreshIndex(reason?: string): Promise<SearchRuntimeStatus>;
|
|
22
|
+
disable(): Promise<void>;
|
|
18
23
|
onunload(): Promise<void>;
|
|
19
24
|
private startupRefresh;
|
|
20
25
|
private scheduleProviderReconciliation;
|
|
21
26
|
private openSearchInLeftSidebar;
|
|
27
|
+
private nativeObserverDetached;
|
|
22
28
|
}
|