feat(frontend): add MIME filter controls to FilterBar
deploy / deploy (push) Successful in 24s

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~<pattern> tokens (LIKE on the type name), plus friendly token
labels in dsl.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 22:52:12 +03:00
parent a864ca4f7b
commit 95db88388b
2 changed files with 81 additions and 2 deletions
@@ -39,6 +39,18 @@
tokens = [...tokens, t]; 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=<id> 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) { function removeToken(i: number) {
tokens = tokens.filter((_, idx) => idx !== i); tokens = tokens.filter((_, idx) => idx !== i);
} }
@@ -197,6 +209,28 @@
{/each} {/each}
</div> </div>
<!-- MIME / media type — appends an m~ token like a tag/operator -->
<div class="mime">
<button class="token mime-token" onclick={() => addToken('m~image/%')}>Images</button>
<button class="token mime-token" onclick={() => addToken('m~video/%')}>Video</button>
<input
class="mime-input"
type="text"
placeholder="MIME, e.g. image/png"
bind:value={mimeInput}
onkeydown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
addMime();
}
}}
autocomplete="off"
/>
<button class="token op-token mime-add" onclick={addMime} disabled={!mimeInput.trim()}>
+ MIME
</button>
</div>
<!-- Review status (mutually-exclusive r=1 / r=0) --> <!-- Review status (mutually-exclusive r=1 / r=0) -->
<div class="review-seg" role="group" aria-label="Review status"> <div class="review-seg" role="group" aria-label="Review status">
<button class="seg" class:on={reviewToken === null} onclick={() => setReview(null)}>Any</button> <button class="seg" class:on={reviewToken === null} onclick={() => setReview(null)}>Any</button>
@@ -281,6 +315,47 @@
gap: 4px; 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 { .review-seg {
display: flex; display: flex;
gap: 2px; gap: 2px;
+6 -2
View File
@@ -3,8 +3,8 @@
* *
* Token format (comma-separated inside braces): * Token format (comma-separated inside braces):
* t=<uuid> — has tag * t=<uuid> — has tag
* m=<mime> — exact MIME * m=<id> — exact MIME by numeric mime_id
* m~<pattern> — MIME LIKE pattern * m~<pattern> — MIME type-name LIKE pattern (e.g. image/%, image/png)
* r=1 / r=0 — needs review / review done * r=1 / r=0 — needs review / review done
* ( ) & | ! — grouping / boolean operators * ( ) & | ! — grouping / boolean operators
* *
@@ -34,6 +34,10 @@ export function tokenLabel(token: string, tagNames: Map<string, string>): string
if (token === ')') return ')'; if (token === ')') return ')';
if (token === 'r=1') return 'Needs review'; if (token === 'r=1') return 'Needs review';
if (token === 'r=0') return 'Reviewed'; 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=')) { if (token.startsWith('t=')) {
const id = token.slice(2); const id = token.slice(2);
return tagNames.get(id) ?? token; return tagNames.get(id) ?? token;