@farming-labs/svelte-theme 0.2.37 → 0.2.39
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/package.json +3 -3
- package/src/components/DocsLayout.svelte +50 -0
- package/src/components/SearchDialog.svelte +239 -77
- package/src/lib/clientAnalytics.js +314 -0
- package/styles/command-grid-bundle.css +1 -0
- package/styles/command-grid.css +1 -0
- package/styles/omni.css +240 -148
- package/styles/pixel-border.css +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/svelte-theme",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.39",
|
|
4
4
|
"description": "Svelte UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -122,8 +122,8 @@
|
|
|
122
122
|
"dependencies": {
|
|
123
123
|
"gray-matter": "^4.0.3",
|
|
124
124
|
"sugar-high": "^0.9.5",
|
|
125
|
-
"@farming-labs/svelte": "0.2.
|
|
126
|
-
"@farming-labs/docs": "0.2.
|
|
125
|
+
"@farming-labs/svelte": "0.2.39",
|
|
126
|
+
"@farming-labs/docs": "0.2.39"
|
|
127
127
|
},
|
|
128
128
|
"peerDependencies": {
|
|
129
129
|
"svelte": ">=5.0.0"
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
import SearchDialog from "./SearchDialog.svelte";
|
|
4
4
|
import AskAIDialog from "./AskAIDialog.svelte";
|
|
5
5
|
import FloatingAIChat from "./FloatingAIChat.svelte";
|
|
6
|
+
import { afterNavigate } from "$app/navigation";
|
|
6
7
|
import { page } from "$app/stores";
|
|
8
|
+
import { env as publicEnv } from "$env/dynamic/public";
|
|
7
9
|
import { onMount } from "svelte";
|
|
10
|
+
import {
|
|
11
|
+
emitSvelteDocsClientAnalyticsEvent,
|
|
12
|
+
installSvelteDocsAnalytics,
|
|
13
|
+
} from "../lib/clientAnalytics.js";
|
|
8
14
|
|
|
9
15
|
let {
|
|
10
16
|
tree,
|
|
@@ -79,11 +85,55 @@
|
|
|
79
85
|
].join("");
|
|
80
86
|
});
|
|
81
87
|
|
|
88
|
+
let lastAnalyticsPageKey = "";
|
|
89
|
+
|
|
90
|
+
function emitPageView(url) {
|
|
91
|
+
if (typeof window === "undefined") return;
|
|
92
|
+
if (!url) return;
|
|
93
|
+
|
|
94
|
+
window.setTimeout(() => {
|
|
95
|
+
const pathname = url.pathname.replace(/\/$/, "") || "/";
|
|
96
|
+
const search = url.search;
|
|
97
|
+
const locale =
|
|
98
|
+
url.searchParams.get("lang") ?? url.searchParams.get("locale") ?? activeLocale ?? null;
|
|
99
|
+
const pageKey = `${pathname}${search}|${locale ?? ""}`;
|
|
100
|
+
if (pageKey === lastAnalyticsPageKey) return;
|
|
101
|
+
|
|
102
|
+
lastAnalyticsPageKey = pageKey;
|
|
103
|
+
emitSvelteDocsClientAnalyticsEvent({
|
|
104
|
+
type: "page_view",
|
|
105
|
+
locale,
|
|
106
|
+
path: pathname,
|
|
107
|
+
url: url.href,
|
|
108
|
+
properties: {
|
|
109
|
+
entry: config?.entry,
|
|
110
|
+
framework: "sveltekit",
|
|
111
|
+
pathname,
|
|
112
|
+
...(search ? { search } : {}),
|
|
113
|
+
...(document.title ? { title: document.title } : {}),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}, 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
afterNavigate(({ to }) => {
|
|
120
|
+
emitPageView(to?.url ?? $page.url);
|
|
121
|
+
});
|
|
122
|
+
|
|
82
123
|
onMount(() => {
|
|
124
|
+
const cleanupAnalytics = installSvelteDocsAnalytics({
|
|
125
|
+
analytics: config?.analytics,
|
|
126
|
+
env: publicEnv,
|
|
127
|
+
});
|
|
128
|
+
|
|
83
129
|
if (forcedTheme) {
|
|
84
130
|
document.documentElement.classList.remove("light", "dark");
|
|
85
131
|
document.documentElement.classList.add(forcedTheme);
|
|
86
132
|
}
|
|
133
|
+
|
|
134
|
+
return () => {
|
|
135
|
+
cleanupAnalytics();
|
|
136
|
+
};
|
|
87
137
|
});
|
|
88
138
|
|
|
89
139
|
let sidebarOpen = $state(false);
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
const STORAGE_KEY = "fd:omni:recents";
|
|
12
12
|
const MAX_RECENTS = 8;
|
|
13
13
|
const DEBOUNCE_MS = 120;
|
|
14
|
-
const
|
|
14
|
+
const BREADCRUMB_SEPARATOR = "\u00a0\u00a0>\u00a0\u00a0";
|
|
15
|
+
const FILTER_LABELS = {
|
|
16
|
+
all: "All",
|
|
17
|
+
pages: "Pages",
|
|
18
|
+
inside: "Inside pages",
|
|
19
|
+
};
|
|
20
|
+
const FILTER_OPTIONS = ["all", "pages", "inside"];
|
|
15
21
|
|
|
16
22
|
let { onclose } = $props();
|
|
17
23
|
|
|
@@ -20,9 +26,13 @@
|
|
|
20
26
|
let loading = $state(false);
|
|
21
27
|
let activeId = $state(null);
|
|
22
28
|
let recentsList = $state([]);
|
|
29
|
+
let filter = $state("all");
|
|
30
|
+
let filterOpen = $state(false);
|
|
23
31
|
let inputEl = $state(null);
|
|
24
32
|
let listRef = $state(null);
|
|
25
33
|
let debounceTimer = null;
|
|
34
|
+
let abortController = null;
|
|
35
|
+
const searchCache = new Map();
|
|
26
36
|
|
|
27
37
|
function withLang(url) {
|
|
28
38
|
if (!url || url.startsWith("#")) return url;
|
|
@@ -37,14 +47,149 @@
|
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
|
|
50
|
+
function breadcrumbForUrl(url) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = new URL(url, "https://farming-labs.local");
|
|
53
|
+
const parts = parsed.pathname
|
|
54
|
+
.split("/")
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.map((part) =>
|
|
57
|
+
decodeURIComponent(part)
|
|
58
|
+
.replace(/[-_]+/g, " ")
|
|
59
|
+
.replace(/\b\w/g, (char) => char.toUpperCase())
|
|
60
|
+
);
|
|
61
|
+
return parts.length ? parts.join(BREADCRUMB_SEPARATOR) : "Docs";
|
|
62
|
+
} catch {
|
|
63
|
+
return "Docs";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function displayLabelForResult(result) {
|
|
68
|
+
if (result.section) return result.section;
|
|
69
|
+
const label = result.content ?? "";
|
|
70
|
+
const parts = label.split(/\s+[—–]\s+/).map((part) => part.trim()).filter(Boolean);
|
|
71
|
+
return result.type === "heading" && parts.length > 1 ? parts[parts.length - 1] : label;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeSearchPhrase(value) {
|
|
75
|
+
return (value ?? "").toLowerCase().replace(/[?!.,;:]+$/g, "").replace(/\s+/g, " ").trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function escapeRegExp(value) {
|
|
79
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function literalMatchPriority(searchQuery, value) {
|
|
83
|
+
const q = normalizeSearchPhrase(searchQuery);
|
|
84
|
+
const text = normalizeSearchPhrase(value);
|
|
85
|
+
if (!q || !text) return 0;
|
|
86
|
+
if (text === q) return 2;
|
|
87
|
+
|
|
88
|
+
const boundary = "[^\\p{L}\\p{N}]";
|
|
89
|
+
return new RegExp(`(^|${boundary})${escapeRegExp(q)}(?=$|${boundary})`, "u").test(text)
|
|
90
|
+
? 1
|
|
91
|
+
: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function tokenizeLiteralQuery(searchQuery) {
|
|
95
|
+
return Array.from(
|
|
96
|
+
new Set(
|
|
97
|
+
searchQuery
|
|
98
|
+
.toLowerCase()
|
|
99
|
+
.replace(/[^\p{L}\p{N}@/_:.-]+/gu, " ")
|
|
100
|
+
.split(/\s+/)
|
|
101
|
+
.map((word) => word.replace(/^[^\p{L}\p{N}@]+|[^\p{L}\p{N}]+$/gu, ""))
|
|
102
|
+
.filter((word) => word.length > 1)
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isLiteralLookupQuery(searchQuery) {
|
|
108
|
+
const q = normalizeSearchPhrase(searchQuery);
|
|
109
|
+
const words = tokenizeLiteralQuery(q);
|
|
110
|
+
return words.length > 0 && words.length <= 3 && words.join(" ") === q;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hasDistinctSearchSection(result) {
|
|
114
|
+
if (result.type === "page") return false;
|
|
115
|
+
if (!result.section) return true;
|
|
116
|
+
const title = (result.content ?? "").split(/\s+[—–]\s+/)[0] ?? "";
|
|
117
|
+
return normalizeSearchPhrase(result.section) !== normalizeSearchPhrase(title);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function insideLiteralPriority(result, searchQuery) {
|
|
121
|
+
if (!hasDistinctSearchSection(result) || !isLiteralLookupQuery(searchQuery)) return 0;
|
|
122
|
+
return Math.max(
|
|
123
|
+
literalMatchPriority(searchQuery, result.section),
|
|
124
|
+
literalMatchPriority(searchQuery, result.description)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sortResultsForFilter(results) {
|
|
129
|
+
const q = query.trim();
|
|
130
|
+
if (!q) return results;
|
|
131
|
+
return results
|
|
132
|
+
.map((result, index) => ({ result, index }))
|
|
133
|
+
.sort((a, b) => {
|
|
134
|
+
const aLiteralPriority = insideLiteralPriority(a.result, q);
|
|
135
|
+
const bLiteralPriority = insideLiteralPriority(b.result, q);
|
|
136
|
+
const literalDelta = bLiteralPriority - aLiteralPriority;
|
|
137
|
+
if (literalDelta) return literalDelta;
|
|
138
|
+
if (aLiteralPriority > 0 && bLiteralPriority > 0) return a.index - b.index;
|
|
139
|
+
|
|
140
|
+
const scoreDelta = (b.result.score ?? 0) - (a.result.score ?? 0);
|
|
141
|
+
if (scoreDelta) return scoreDelta;
|
|
142
|
+
|
|
143
|
+
return a.index - b.index;
|
|
144
|
+
})
|
|
145
|
+
.map(({ result }) => result);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function escapeHtml(value) {
|
|
149
|
+
return (value ?? "").replace(/[&<>"']/g, (char) => {
|
|
150
|
+
if (char === "&") return "&";
|
|
151
|
+
if (char === "<") return "<";
|
|
152
|
+
if (char === ">") return ">";
|
|
153
|
+
if (char === '"') return """;
|
|
154
|
+
return "'";
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function highlightSnippet(text) {
|
|
159
|
+
const q = query.trim();
|
|
160
|
+
if (!q) return escapeHtml(text);
|
|
161
|
+
let html = "";
|
|
162
|
+
let lastIndex = 0;
|
|
163
|
+
const regex = new RegExp(escapeRegExp(q), "gi");
|
|
164
|
+
let match;
|
|
165
|
+
|
|
166
|
+
while ((match = regex.exec(text)) !== null) {
|
|
167
|
+
html += escapeHtml(text.slice(lastIndex, match.index));
|
|
168
|
+
html += `<mark class="omni-highlight">${escapeHtml(match[0])}</mark>`;
|
|
169
|
+
lastIndex = match.index + match[0].length;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return html + escapeHtml(text.slice(lastIndex));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let visibleResults = $derived.by(() => {
|
|
176
|
+
if (filter === "pages") return currentResults.filter((result) => result.type === "page");
|
|
177
|
+
if (filter === "inside") {
|
|
178
|
+
return sortResultsForFilter(currentResults.filter((result) => result.type !== "page"));
|
|
179
|
+
}
|
|
180
|
+
return sortResultsForFilter(currentResults);
|
|
181
|
+
});
|
|
182
|
+
|
|
40
183
|
let flatItems = $derived.by(() => {
|
|
41
184
|
const q = query.trim();
|
|
42
|
-
if (q &&
|
|
43
|
-
return
|
|
185
|
+
if (q && visibleResults.length) {
|
|
186
|
+
return visibleResults.map((r) => ({
|
|
44
187
|
id: r.url,
|
|
45
|
-
label: r
|
|
188
|
+
label: displayLabelForResult(r),
|
|
46
189
|
url: r.url,
|
|
47
|
-
subtitle: r.
|
|
190
|
+
subtitle: breadcrumbForUrl(r.url),
|
|
191
|
+
description: r.description,
|
|
192
|
+
descriptionHtml: r.description ? highlightSnippet(r.description) : "",
|
|
48
193
|
}));
|
|
49
194
|
}
|
|
50
195
|
return recentsList.map((r) => ({
|
|
@@ -56,13 +201,15 @@
|
|
|
56
201
|
});
|
|
57
202
|
|
|
58
203
|
let showRecents = $derived(!query.trim());
|
|
59
|
-
let showDocs = $derived(!!query.trim() &&
|
|
204
|
+
let showDocs = $derived(!!query.trim() && visibleResults.length > 0);
|
|
60
205
|
let showEmpty = $derived(
|
|
61
|
-
query.trim() ?
|
|
206
|
+
query.trim() ? visibleResults.length === 0 && !loading : recentsList.length === 0
|
|
62
207
|
);
|
|
63
208
|
let emptyText = $derived(
|
|
64
209
|
query.trim()
|
|
65
|
-
?
|
|
210
|
+
? currentResults.length > 0
|
|
211
|
+
? `No ${FILTER_LABELS[filter].toLowerCase()} results found.`
|
|
212
|
+
: "No results found. Try a different query."
|
|
66
213
|
: "Type to search the docs, or browse recent items."
|
|
67
214
|
);
|
|
68
215
|
|
|
@@ -94,6 +241,12 @@
|
|
|
94
241
|
onclose?.();
|
|
95
242
|
}
|
|
96
243
|
|
|
244
|
+
function updateFilter(nextFilter) {
|
|
245
|
+
filter = nextFilter;
|
|
246
|
+
filterOpen = false;
|
|
247
|
+
activeId = flatItems[0]?.id ?? null;
|
|
248
|
+
}
|
|
249
|
+
|
|
97
250
|
function executeItem(item) {
|
|
98
251
|
const localizedUrl = withLang(item.url);
|
|
99
252
|
saveRecent({ id: localizedUrl, label: item.label ?? item.content ?? item.url, url: localizedUrl });
|
|
@@ -133,19 +286,41 @@
|
|
|
133
286
|
currentResults = [];
|
|
134
287
|
activeId = null;
|
|
135
288
|
const q = query.trim();
|
|
289
|
+
filterOpen = false;
|
|
290
|
+
if (abortController) abortController.abort();
|
|
136
291
|
if (!q) return;
|
|
137
292
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
293
|
+
const requestUrl = withLang(`/api/docs?query=${encodeURIComponent(q)}`);
|
|
294
|
+
const cached = searchCache.get(requestUrl);
|
|
295
|
+
if (cached) {
|
|
296
|
+
currentResults = cached;
|
|
297
|
+
activeId = cached[0]?.url ?? null;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
138
300
|
debounceTimer = setTimeout(async () => {
|
|
301
|
+
const controller = new AbortController();
|
|
302
|
+
abortController = controller;
|
|
139
303
|
loading = true;
|
|
140
304
|
try {
|
|
141
|
-
const res = await fetch(
|
|
305
|
+
const res = await fetch(requestUrl, { signal: controller.signal });
|
|
142
306
|
const data = res.ok ? await res.json() : [];
|
|
143
|
-
|
|
307
|
+
if (controller.signal.aborted) return;
|
|
308
|
+
const nextResults = Array.isArray(data) ? data : [];
|
|
309
|
+
if (searchCache.size >= 20) {
|
|
310
|
+
const firstKey = searchCache.keys().next().value;
|
|
311
|
+
if (firstKey) searchCache.delete(firstKey);
|
|
312
|
+
}
|
|
313
|
+
searchCache.set(requestUrl, nextResults);
|
|
314
|
+
currentResults = nextResults;
|
|
144
315
|
activeId = currentResults[0]?.url ?? null;
|
|
145
|
-
} catch {
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (controller.signal.aborted) return;
|
|
146
318
|
currentResults = [];
|
|
147
319
|
} finally {
|
|
148
|
-
|
|
320
|
+
if (abortController === controller) {
|
|
321
|
+
abortController = null;
|
|
322
|
+
loading = false;
|
|
323
|
+
}
|
|
149
324
|
}
|
|
150
325
|
}, DEBOUNCE_MS);
|
|
151
326
|
}
|
|
@@ -155,6 +330,11 @@
|
|
|
155
330
|
onInput();
|
|
156
331
|
});
|
|
157
332
|
|
|
333
|
+
$effect(() => {
|
|
334
|
+
void filter;
|
|
335
|
+
activeId = flatItems[0]?.id ?? null;
|
|
336
|
+
});
|
|
337
|
+
|
|
158
338
|
function handleKeydown(e) {
|
|
159
339
|
if (e.key === "Escape") {
|
|
160
340
|
e.preventDefault();
|
|
@@ -185,19 +365,6 @@
|
|
|
185
365
|
e.stopPropagation();
|
|
186
366
|
}
|
|
187
367
|
|
|
188
|
-
function onExternalClick(e, url) {
|
|
189
|
-
e.preventDefault();
|
|
190
|
-
e.stopPropagation();
|
|
191
|
-
try {
|
|
192
|
-
if (typeof window !== "undefined") window.open(url, "_blank", "noopener,noreferrer");
|
|
193
|
-
} catch {
|
|
194
|
-
if (typeof window !== "undefined") window.location.href = url;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const FileIcon = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>`;
|
|
199
|
-
const ExternalIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
|
|
200
|
-
const ChevronIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>`;
|
|
201
368
|
const EmptyIcon = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>`;
|
|
202
369
|
|
|
203
370
|
onMount(() => {
|
|
@@ -209,6 +376,7 @@
|
|
|
209
376
|
onDestroy(() => {
|
|
210
377
|
if (typeof document !== "undefined") document.body.style.overflow = "";
|
|
211
378
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
379
|
+
if (abortController) abortController.abort();
|
|
212
380
|
});
|
|
213
381
|
</script>
|
|
214
382
|
|
|
@@ -247,16 +415,12 @@
|
|
|
247
415
|
aria-expanded="true"
|
|
248
416
|
aria-controls="omni-listbox"
|
|
249
417
|
aria-activedescendant={activeId ? `omni-item-${activeId}` : undefined}
|
|
250
|
-
placeholder=
|
|
418
|
+
placeholder="Search"
|
|
251
419
|
autocomplete="off"
|
|
252
420
|
onkeydown={handleKeydown}
|
|
253
421
|
/>
|
|
254
|
-
<kbd class="omni-kbd">⌘ K</kbd>
|
|
255
422
|
<button type="button" aria-label="Close" class="omni-close-btn" onclick={close}>
|
|
256
|
-
|
|
257
|
-
<path d="M18 6 6 18" />
|
|
258
|
-
<path d="m6 6 12 12" />
|
|
259
|
-
</svg>
|
|
423
|
+
ESC
|
|
260
424
|
</button>
|
|
261
425
|
</div>
|
|
262
426
|
</div>
|
|
@@ -293,27 +457,10 @@
|
|
|
293
457
|
onmouseenter={() => (activeId = item.id)}
|
|
294
458
|
onclick={() => executeItem(item)}
|
|
295
459
|
>
|
|
296
|
-
<div class="omni-item-icon">
|
|
297
|
-
{@html FileIcon}
|
|
298
|
-
</div>
|
|
299
460
|
<div class="omni-item-text">
|
|
300
|
-
<div class="omni-item-label">{item.label}</div>
|
|
301
461
|
<div class="omni-item-subtitle">{item.subtitle}</div>
|
|
462
|
+
<div class="omni-item-label">{item.label}</div>
|
|
302
463
|
</div>
|
|
303
|
-
<a
|
|
304
|
-
href={item.url}
|
|
305
|
-
class="omni-item-badge"
|
|
306
|
-
title="Open in new tab"
|
|
307
|
-
target="_blank"
|
|
308
|
-
rel="noopener noreferrer"
|
|
309
|
-
aria-hidden="true"
|
|
310
|
-
onclick={(e) => onExternalClick(e, item.url)}
|
|
311
|
-
>
|
|
312
|
-
{@html ExternalIcon}
|
|
313
|
-
</a>
|
|
314
|
-
<span class="omni-item-chevron" aria-hidden="true">
|
|
315
|
-
{@html ChevronIcon}
|
|
316
|
-
</span>
|
|
317
464
|
</button>
|
|
318
465
|
{/each}
|
|
319
466
|
</div>
|
|
@@ -336,27 +483,15 @@
|
|
|
336
483
|
onmouseenter={() => (activeId = item.id)}
|
|
337
484
|
onclick={() => executeItem(item)}
|
|
338
485
|
>
|
|
339
|
-
<div class="omni-item-icon">
|
|
340
|
-
{@html FileIcon}
|
|
341
|
-
</div>
|
|
342
486
|
<div class="omni-item-text">
|
|
343
|
-
<div class="omni-item-label">{item.label}</div>
|
|
344
487
|
<div class="omni-item-subtitle">{item.subtitle}</div>
|
|
488
|
+
<div class="omni-item-label">{item.label}</div>
|
|
489
|
+
{#if item.description}
|
|
490
|
+
<div class="omni-item-description">
|
|
491
|
+
{@html item.descriptionHtml}
|
|
492
|
+
</div>
|
|
493
|
+
{/if}
|
|
345
494
|
</div>
|
|
346
|
-
<a
|
|
347
|
-
href={item.url}
|
|
348
|
-
class="omni-item-badge"
|
|
349
|
-
title="Open in new tab"
|
|
350
|
-
target="_blank"
|
|
351
|
-
rel="noopener noreferrer"
|
|
352
|
-
aria-hidden="true"
|
|
353
|
-
onclick={(e) => onExternalClick(e, item.url)}
|
|
354
|
-
>
|
|
355
|
-
{@html ExternalIcon}
|
|
356
|
-
</a>
|
|
357
|
-
<span class="omni-item-chevron" aria-hidden="true">
|
|
358
|
-
{@html ChevronIcon}
|
|
359
|
-
</span>
|
|
360
495
|
</button>
|
|
361
496
|
{/each}
|
|
362
497
|
</div>
|
|
@@ -377,28 +512,55 @@
|
|
|
377
512
|
<div class="omni-footer-inner">
|
|
378
513
|
<div class="omni-footer-hints">
|
|
379
514
|
<span class="omni-footer-hint">
|
|
380
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
381
|
-
<
|
|
515
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
516
|
+
<path d="m9 10-5 5 5 5" />
|
|
517
|
+
<path d="M20 4v7a4 4 0 0 1-4 4H4" />
|
|
382
518
|
</svg>
|
|
383
519
|
to select
|
|
384
520
|
</span>
|
|
385
521
|
<span class="omni-footer-hint">
|
|
386
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
387
|
-
<path d="
|
|
522
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
523
|
+
<path d="m18 15-6-6-6 6" />
|
|
388
524
|
</svg>
|
|
389
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
390
|
-
<path d="
|
|
525
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
526
|
+
<path d="m6 9 6 6 6-6" />
|
|
391
527
|
</svg>
|
|
392
528
|
to navigate
|
|
393
529
|
</span>
|
|
394
530
|
<span class="omni-footer-hint omni-footer-hint-desktop">
|
|
395
|
-
<
|
|
396
|
-
<path d="M18 6 6 18" />
|
|
397
|
-
<path d="m6 6 12 12" />
|
|
398
|
-
</svg>
|
|
531
|
+
<span class="omni-kbd-sm">ESC</span>
|
|
399
532
|
to close
|
|
400
533
|
</span>
|
|
401
534
|
</div>
|
|
535
|
+
<div class="omni-footer-filter">
|
|
536
|
+
<span class="omni-filter-label">Filter</span>
|
|
537
|
+
<button
|
|
538
|
+
type="button"
|
|
539
|
+
class="omni-filter-button"
|
|
540
|
+
aria-expanded={filterOpen}
|
|
541
|
+
onclick={() => (filterOpen = !filterOpen)}
|
|
542
|
+
>
|
|
543
|
+
{FILTER_LABELS[filter]}
|
|
544
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
545
|
+
<path d="M6 9l6 6 6-6" />
|
|
546
|
+
</svg>
|
|
547
|
+
</button>
|
|
548
|
+
{#if filterOpen}
|
|
549
|
+
<div class="omni-filter-menu" role="group" aria-label="Search filter">
|
|
550
|
+
{#each FILTER_OPTIONS as option}
|
|
551
|
+
<button
|
|
552
|
+
type="button"
|
|
553
|
+
aria-pressed={filter === option}
|
|
554
|
+
class="omni-filter-option"
|
|
555
|
+
class:omni-filter-option-active={filter === option}
|
|
556
|
+
onclick={() => updateFilter(option)}
|
|
557
|
+
>
|
|
558
|
+
{FILTER_LABELS[option]}
|
|
559
|
+
</button>
|
|
560
|
+
{/each}
|
|
561
|
+
</div>
|
|
562
|
+
{/if}
|
|
563
|
+
</div>
|
|
402
564
|
</div>
|
|
403
565
|
</div>
|
|
404
566
|
</div>
|