diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 9f4dc6ba..427baf85 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -604,5 +604,9 @@ "admin_tag_delete_only_this_sub_root": "Untergeordnete werden zu Root-Schlagwörtern", "admin_tag_delete_subtree": "Gesamten Teilbaum löschen", "admin_tag_delete_subtree_warn": "Löscht auch {count} untergeordnete Schlagwörter", - "admin_tag_delete_confirm_heading": "Gib «{name}» zur Bestätigung ein:" + "admin_tag_delete_confirm_heading": "Gib «{name}» zur Bestätigung ein:", + "filter_operator_and": "UND", + "filter_operator_or": "ODER", + "filter_operator_and_label": "Alle gewählten Schlagworte müssen zutreffen (UND)", + "filter_operator_or_label": "Mindestens ein Schlagwort muss zutreffen (ODER)" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 633835f6..fdc835e2 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -604,5 +604,9 @@ "admin_tag_delete_only_this_sub_root": "Children will become root tags", "admin_tag_delete_subtree": "Delete entire subtree", "admin_tag_delete_subtree_warn": "Also deletes {count} child tags", - "admin_tag_delete_confirm_heading": "Type «{name}» to confirm:" + "admin_tag_delete_confirm_heading": "Type «{name}» to confirm:", + "filter_operator_and": "AND", + "filter_operator_or": "OR", + "filter_operator_and_label": "All selected tags must match (AND)", + "filter_operator_or_label": "At least one tag must match (OR)" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index fbd52839..04180d98 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -604,5 +604,9 @@ "admin_tag_delete_only_this_sub_root": "Las subordinadas se convertirán en etiquetas raíz", "admin_tag_delete_subtree": "Eliminar todo el subárbol", "admin_tag_delete_subtree_warn": "También elimina {count} etiquetas subordinadas", - "admin_tag_delete_confirm_heading": "Escribe «{name}» para confirmar:" + "admin_tag_delete_confirm_heading": "Escribe «{name}» para confirmar:", + "filter_operator_and": "Y", + "filter_operator_or": "O", + "filter_operator_and_label": "Todas las etiquetas seleccionadas deben coincidir (Y)", + "filter_operator_or_label": "Al menos una etiqueta debe coincidir (O)" } diff --git a/frontend/src/lib/components/TagInput.svelte b/frontend/src/lib/components/TagInput.svelte index 7a2decc8..2dc13e6c 100644 --- a/frontend/src/lib/components/TagInput.svelte +++ b/frontend/src/lib/components/TagInput.svelte @@ -119,7 +119,7 @@ function handleKeydown(e: KeyboardEvent) { class="flex min-h-[42px] flex-wrap gap-2 rounded border border-line bg-surface p-2 focus-within:border-ink focus-within:ring-1 focus-within:ring-ink" > - {#each tags as tag, i (i)} + {#each tags as tag, i (tag.id ?? tag.name)} {#if tag.color} - {#each orderedSuggestions as suggestion, i (i)} + {#each orderedSuggestions as suggestion, i (suggestion.id ?? suggestion.name)}
  • {
    {m.filter_operator_and()} {m.filter_operator_or()}
    {/if} diff --git a/frontend/src/routes/SearchFilterBar.svelte.spec.ts b/frontend/src/routes/SearchFilterBar.svelte.spec.ts index 065f1721..26d1d333 100644 --- a/frontend/src/routes/SearchFilterBar.svelte.spec.ts +++ b/frontend/src/routes/SearchFilterBar.svelte.spec.ts @@ -61,7 +61,7 @@ describe('SearchFilterBar – AND/OR tag operator toggle', () => { tagNames: [{ name: 'Tag1' }] }); await openAdvanced(); - await expect.element(page.getByRole('button', { name: 'AND' })).not.toBeInTheDocument(); + await expect.element(page.getByTestId('operator-and')).not.toBeInTheDocument(); vi.unstubAllGlobals(); }); @@ -79,8 +79,8 @@ describe('SearchFilterBar – AND/OR tag operator toggle', () => { await openAdvanced(); const toggle = page.getByTestId('and-or-toggle'); await expect.element(toggle).toBeInTheDocument(); - await expect.element(toggle.getByRole('button', { name: 'AND' })).toBeInTheDocument(); - await expect.element(toggle.getByRole('button', { name: 'OR' })).toBeInTheDocument(); + await expect.element(toggle.getByTestId('operator-and')).toBeInTheDocument(); + await expect.element(toggle.getByTestId('operator-or')).toBeInTheDocument(); vi.unstubAllGlobals(); }); @@ -99,7 +99,7 @@ describe('SearchFilterBar – AND/OR tag operator toggle', () => { }); await openAdvanced(); const toggle = page.getByTestId('and-or-toggle'); - await toggle.getByRole('button', { name: 'OR' }).click(); + await toggle.getByTestId('operator-or').click(); await expect.poll(() => onSearch.mock.calls.length).toBeGreaterThan(0); vi.unstubAllGlobals(); }); @@ -121,7 +121,7 @@ describe('SearchFilterBar – AND/OR tag operator toggle', () => { }); await openAdvanced(); const toggle = page.getByTestId('and-or-toggle'); - await toggle.getByRole('button', { name: 'OR' }).click(); + await toggle.getByTestId('operator-or').click(); await expect.poll(() => onSearchImmediate.mock.calls.length).toBeGreaterThan(0); expect(onSearch).not.toHaveBeenCalled(); vi.unstubAllGlobals(); diff --git a/frontend/src/routes/admin/tags/[id]/TagMergeZone.svelte b/frontend/src/routes/admin/tags/[id]/TagMergeZone.svelte index 29959878..7ebebe34 100644 --- a/frontend/src/routes/admin/tags/[id]/TagMergeZone.svelte +++ b/frontend/src/routes/admin/tags/[id]/TagMergeZone.svelte @@ -50,8 +50,8 @@ const targetTag = $derived(allTags.find((t) => t.id === targetId));

    {m.admin_tag_merge_description()}

    - -

    + +

    {step === 1 ? m.admin_tag_merge_step1() : m.admin_tag_merge_step2()}