From 95db88388bfd178b3e0508ff8452a1e0d8ba5133 Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Mon, 15 Jun 2026 22:52:12 +0300 Subject: [PATCH] feat(frontend): add MIME filter controls to FilterBar The DSL already supported m~/m= tokens but the filter UI had no way to add them. Add Images/Video quick buttons and a free-text MIME input that append m~ tokens (LIKE on the type name), plus friendly token labels in dsl.ts. Co-Authored-By: Claude Opus 4.8 --- .../src/lib/components/file/FilterBar.svelte | 75 +++++++++++++++++++ frontend/src/lib/utils/dsl.ts | 8 +- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/file/FilterBar.svelte b/frontend/src/lib/components/file/FilterBar.svelte index ce2bd59..9d3addf 100644 --- a/frontend/src/lib/components/file/FilterBar.svelte +++ b/frontend/src/lib/components/file/FilterBar.svelte @@ -39,6 +39,18 @@ tokens = [...tokens, t]; } + // Free-text MIME filter. Matches against the type name (mt.name) via LIKE, so + // "image/png" is an exact-ish match and "image/%" / "%mp4" act as patterns. + // (m= targets the numeric mime_id, which the UI doesn't expose.) + let mimeInput = $state(''); + + function addMime() { + const v = mimeInput.trim(); + if (!v) return; + addToken(`m~${v}`); + mimeInput = ''; + } + function removeToken(i: number) { tokens = tokens.filter((_, idx) => idx !== i); } @@ -197,6 +209,28 @@ {/each} + +
+ + + { + if (e.key === 'Enter') { + e.preventDefault(); + addMime(); + } + }} + autocomplete="off" + /> + +
+
@@ -281,6 +315,47 @@ gap: 4px; } + .mime { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; + } + + .mime-token { + background-color: color-mix(in srgb, var(--color-accent) 18%, var(--color-bg-elevated)); + color: var(--color-text-primary); + font-weight: 600; + } + + .mime-token:hover { + background-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-bg-elevated)); + } + + .mime-input { + flex: 1; + min-width: 120px; + box-sizing: border-box; + height: 26px; + padding: 0 8px; + border-radius: 5px; + border: 1px solid color-mix(in srgb, var(--color-accent) 30%, transparent); + background-color: var(--color-bg-primary); + color: var(--color-text-primary); + font-size: 0.8rem; + font-family: inherit; + outline: none; + } + + .mime-input:focus { + border-color: var(--color-accent); + } + + .mime-add:disabled { + opacity: 0.4; + cursor: default; + } + .review-seg { display: flex; gap: 2px; diff --git a/frontend/src/lib/utils/dsl.ts b/frontend/src/lib/utils/dsl.ts index 6966389..1dcfcd7 100644 --- a/frontend/src/lib/utils/dsl.ts +++ b/frontend/src/lib/utils/dsl.ts @@ -3,8 +3,8 @@ * * Token format (comma-separated inside braces): * t= — has tag - * m= — exact MIME - * m~ — MIME LIKE pattern + * m= — exact MIME by numeric mime_id + * m~ — MIME type-name LIKE pattern (e.g. image/%, image/png) * r=1 / r=0 — needs review / review done * ( ) & | ! — grouping / boolean operators * @@ -34,6 +34,10 @@ export function tokenLabel(token: string, tagNames: Map): string if (token === ')') return ')'; if (token === 'r=1') return 'Needs review'; if (token === 'r=0') return 'Reviewed'; + if (token === 'm~image/%') return 'Images'; + if (token === 'm~video/%') return 'Video'; + if (token.startsWith('m~')) return token.slice(2); + if (token.startsWith('m=')) return `mime #${token.slice(2)}`; if (token.startsWith('t=')) { const id = token.slice(2); return tagNames.get(id) ?? token;