feat(#221): change TagInput binding to Tag[], add color dots and hierarchy grouping
Backend:
- TagRepository: add findDescendantIdsByName() recursive CTE query
- TagService: add expandTagNamesToDescendantIdSets() for document search
Frontend:
- TagInput: accept Tag[] (id, name, color, parentId) instead of string[]
- Chips show color dot via var(--c-tag-{color}) when tag has color
- Suggestions grouped hierarchically: children indented under their parents
- Update DescriptionSection, edit/new pages, SearchFilterBar, +page.svelte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,9 @@ let from = $state(untrack(() => data.filters?.from || ''));
|
||||
let to = $state(untrack(() => data.filters?.to || ''));
|
||||
let senderId = $state(untrack(() => data.filters?.senderId || ''));
|
||||
let receiverId = $state(untrack(() => data.filters?.receiverId || ''));
|
||||
let tagNames = $state<string[]>(untrack(() => data.filters?.tags || []));
|
||||
let tagNames = $state<{ name: string; id?: string; color?: string; parentId?: string }[]>(
|
||||
untrack(() => (data.filters?.tags || []).map((name: string) => ({ name })))
|
||||
);
|
||||
let sort = $state(untrack(() => data.filters?.sort || 'DATE'));
|
||||
let dir = $state(untrack(() => data.filters?.dir || 'desc'));
|
||||
let tagQ = $state(untrack(() => data.filters?.tagQ || ''));
|
||||
@@ -43,7 +45,7 @@ function triggerSearch() {
|
||||
if (to) params.set('to', to);
|
||||
if (senderId) params.set('senderId', senderId);
|
||||
if (receiverId) params.set('receiverId', receiverId);
|
||||
if (tagNames) tagNames.forEach((tag) => params.append('tag', tag));
|
||||
if (tagNames) tagNames.forEach((tag) => params.append('tag', tag.name));
|
||||
if (sort) params.set('sort', sort);
|
||||
if (dir) params.set('dir', dir);
|
||||
if (tagQ) params.set('tagQ', tagQ);
|
||||
@@ -56,9 +58,9 @@ function handleTextSearch() {
|
||||
}
|
||||
|
||||
// Trigger search when tags change
|
||||
let prevTagStr = untrack(() => tagNames.join(','));
|
||||
let prevTagStr = untrack(() => tagNames.map((t) => t.name).join(','));
|
||||
$effect(() => {
|
||||
const cur = tagNames.join(',');
|
||||
const cur = tagNames.map((t) => t.name).join(',');
|
||||
if (cur !== prevTagStr) {
|
||||
prevTagStr = cur;
|
||||
triggerSearch();
|
||||
@@ -73,7 +75,7 @@ $effect(() => {
|
||||
to = data.filters?.to || '';
|
||||
senderId = data.filters?.senderId || '';
|
||||
receiverId = data.filters?.receiverId || '';
|
||||
tagNames = data.filters?.tags || [];
|
||||
tagNames = (data.filters?.tags || []).map((name: string) => ({ name }));
|
||||
sort = data.filters?.sort || 'DATE';
|
||||
dir = data.filters?.dir || 'desc';
|
||||
tagQ = data.filters?.tagQ || '';
|
||||
|
||||
@@ -12,7 +12,7 @@ let {
|
||||
to = $bindable(''),
|
||||
senderId = $bindable(''),
|
||||
receiverId = $bindable(''),
|
||||
tagNames = $bindable<string[]>([]),
|
||||
tagNames = $bindable<{ name: string; id?: string; color?: string; parentId?: string }[]>([]),
|
||||
tagQ = $bindable(''),
|
||||
sort = $bindable('DATE'),
|
||||
dir = $bindable('desc'),
|
||||
@@ -29,7 +29,7 @@ let {
|
||||
to?: string;
|
||||
senderId?: string;
|
||||
receiverId?: string;
|
||||
tagNames?: string[];
|
||||
tagNames?: { name: string; id?: string; color?: string; parentId?: string }[];
|
||||
tagQ?: string;
|
||||
sort?: string;
|
||||
dir?: string;
|
||||
|
||||
@@ -11,7 +11,7 @@ import SaveBar from './SaveBar.svelte';
|
||||
let { data, form } = $props();
|
||||
|
||||
let { document: doc } = untrack(() => data);
|
||||
let tags = $state(doc.tags ? doc.tags.map((t: { name: string }) => t.name) : []);
|
||||
let tags = $state(doc.tags ?? []);
|
||||
let senderId = $state(doc.sender?.id ?? '');
|
||||
let selectedReceivers = $state(doc.receivers ?? []);
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { type FilenameParseResult } from '$lib/utils/filename';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
let tags: string[] = $state([]);
|
||||
let tags: { name: string; id?: string; color?: string; parentId?: string }[] = $state([]);
|
||||
let senderId = $state(untrack(() => data.initialSenderId));
|
||||
let selectedReceivers: { id: string; firstName?: string; lastName: string; displayName: string }[] =
|
||||
$state(untrack(() => data.initialReceivers));
|
||||
|
||||
@@ -33,7 +33,7 @@ $effect(() => {
|
||||
onDestroy(() => fileLoader.destroy());
|
||||
|
||||
// Form state
|
||||
let tags = $state(untrack(() => doc.tags?.map((t: { name: string }) => t.name) ?? []));
|
||||
let tags = $state(untrack(() => doc.tags ?? []));
|
||||
let senderId = $state(untrack(() => doc.sender?.id ?? ''));
|
||||
let selectedReceivers = $state(untrack(() => doc.receivers ?? []));
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user