chore(export-viewer): rebuild the embedded bundle, and fix the lint ignores
The offline keepsake viewer had the same two filter defects as the app: typed suggestions were capped so a matching tag could be unselectable, and the dropdown used `onmousedown` with a backdrop that swallowed the selection. Rebuilt into `backend/static/export-viewer/index.html`, which `include_dir!` embeds in the binary. The eslint ignores were unanchored, so once the export-viewer's dependencies were installed its nested `.svelte-kit` output was linted as source. Anchored with `**/`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -58,10 +58,16 @@ export default ts.config(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
'.svelte-kit/',
|
// `**/` matters: these patterns are relative to this config's directory, so a bare
|
||||||
'build/',
|
// `.svelte-kit/` matched only the top-level one and left `export-viewer/.svelte-kit/`
|
||||||
'dist/',
|
// — SvelteKit's GENERATED types, gitignored but present after a viewer build — being
|
||||||
'node_modules/',
|
// linted, for 10 errors in code nobody wrote. Rebuilding the viewer is a required
|
||||||
|
// step whenever its source changes (it is embedded in the binary via include_dir!),
|
||||||
|
// so that directory exists in any normal working tree.
|
||||||
|
'**/.svelte-kit/',
|
||||||
|
'**/build/',
|
||||||
|
'**/dist/',
|
||||||
|
'**/node_modules/',
|
||||||
'static/export-viewer/',
|
'static/export-viewer/',
|
||||||
'*.config.js',
|
'*.config.js',
|
||||||
'*.config.ts'
|
'*.config.ts'
|
||||||
|
|||||||
7
frontend/export-viewer/package-lock.json
generated
7
frontend/export-viewer/package-lock.json
generated
@@ -901,7 +901,6 @@
|
|||||||
"integrity": "sha512-9hDOl3yUh8UXWt+mN29dbcdrW0vNwPvMqi01y2Mw+ceErNIISh8MeEY7fXT2Dx1CjC/kfsVqrbxw7DifYr4hsg==",
|
"integrity": "sha512-9hDOl3yUh8UXWt+mN29dbcdrW0vNwPvMqi01y2Mw+ceErNIISh8MeEY7fXT2Dx1CjC/kfsVqrbxw7DifYr4hsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||||
@@ -944,7 +943,6 @@
|
|||||||
"integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
|
"integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
@@ -1291,7 +1289,6 @@
|
|||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -1904,7 +1901,6 @@
|
|||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -2024,7 +2020,6 @@
|
|||||||
"integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==",
|
"integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
"@jridgewell/remapping": "^2.3.4",
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -2114,7 +2109,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -2129,7 +2123,6 @@
|
|||||||
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
|
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
|
|||||||
@@ -57,22 +57,25 @@
|
|||||||
...allTags.slice(0, 3).map(([t]) => ({ type: 'tag' as const, value: t }))
|
...allTags.slice(0, 3).map(([t]) => ({ type: 'tag' as const, value: t }))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
// Once the user has TYPED, every match is offered — no `.slice()`. Capping matters
|
||||||
|
// more here than in the live app, for two reasons: this viewer holds the WHOLE event
|
||||||
|
// (the export queries are uncapped), so the tag set is far larger than any one feed
|
||||||
|
// page; and the keepsake is a frozen artifact — a guest opening it years later has no
|
||||||
|
// server to re-query and no "scroll to load more" escape hatch. A silently dropped
|
||||||
|
// match would simply be unreachable forever. The dropdown scrolls instead.
|
||||||
if (q.startsWith('#')) {
|
if (q.startsWith('#')) {
|
||||||
const prefix = q.slice(1).toLowerCase();
|
const prefix = q.slice(1).toLowerCase();
|
||||||
return allTags
|
return allTags
|
||||||
.filter(([t]) => t.startsWith(prefix))
|
.filter(([t]) => t.startsWith(prefix))
|
||||||
.slice(0, 8)
|
|
||||||
.map(([t]) => ({ type: 'tag' as const, value: t }));
|
.map(([t]) => ({ type: 'tag' as const, value: t }));
|
||||||
}
|
}
|
||||||
const lower = q.toLowerCase();
|
const lower = q.toLowerCase();
|
||||||
return [
|
return [
|
||||||
...allUploaders
|
...allUploaders
|
||||||
.filter((u) => u.toLowerCase().includes(lower))
|
.filter((u) => u.toLowerCase().includes(lower))
|
||||||
.slice(0, 4)
|
|
||||||
.map((u) => ({ type: 'user' as const, value: u })),
|
.map((u) => ({ type: 'user' as const, value: u })),
|
||||||
...allTags
|
...allTags
|
||||||
.filter(([t]) => t.includes(lower))
|
.filter(([t]) => t.includes(lower))
|
||||||
.slice(0, 4)
|
|
||||||
.map(([t]) => ({ type: 'tag' as const, value: t }))
|
.map(([t]) => ({ type: 'tag' as const, value: t }))
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
@@ -381,13 +384,24 @@
|
|||||||
|
|
||||||
<!-- Autocomplete dropdown -->
|
<!-- Autocomplete dropdown -->
|
||||||
{#if showAutocomplete && suggestions.length > 0}
|
{#if showAutocomplete && suggestions.length > 0}
|
||||||
|
<!-- `preventDefault` on mousedown suppresses the input's blur, which would
|
||||||
|
otherwise close this dropdown (see the input's `onblur`) before a click
|
||||||
|
could land. That is what lets the commit live on `onclick` — the LAST
|
||||||
|
event — instead of `onmousedown`. Committing on press meant sliding off
|
||||||
|
a suggestion you didn't mean to hit still applied the filter, and
|
||||||
|
`selectSuggestion` sets `showAutocomplete = false`, so the button
|
||||||
|
destroyed itself on the first event of the click sequence. Ported from
|
||||||
|
the live feed page, which fixed this; the viewer never got it. -->
|
||||||
<div
|
<div
|
||||||
class="absolute left-0 right-0 top-full z-50 mt-1 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg"
|
class="absolute left-0 right-0 top-full z-50 mt-1 max-h-72 overflow-y-auto overscroll-contain rounded-xl border border-gray-200 bg-white shadow-lg"
|
||||||
|
onmousedown={(e) => e.preventDefault()}
|
||||||
|
role="listbox"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
{#each suggestions as item (item.type + ':' + item.value)}
|
{#each suggestions as item (item.type + ':' + item.value)}
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center gap-3 px-4 py-2.5 text-left text-sm hover:bg-gray-50"
|
class="flex w-full items-center gap-3 px-4 py-2.5 text-left text-sm hover:bg-gray-50"
|
||||||
onmousedown={() => selectSuggestion(item)}
|
onclick={() => selectSuggestion(item)}
|
||||||
>
|
>
|
||||||
{#if item.type === 'user'}
|
{#if item.type === 'user'}
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
Reference in New Issue
Block a user