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:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user