Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f794f8f
Add live possible-duplicates check to the new-entry dialog
myieye Jul 4, 2026
ad8bfa5
Guard against concurrent duplicate-row actions (CodeRabbit review)
myieye Jul 6, 2026
96f287f
Align duplicate classification with backend match semantics
myieye Jul 6, 2026
92321dd
Expand duplicate rows in place and add an out-of-view jump pill
myieye Jul 7, 2026
2dc41fb
Merge remote-tracking branch 'origin/develop' into feat/possible-dupl…
myieye Jul 7, 2026
5dc782b
Merge remote-tracking branch 'origin/develop' into claude/duplicate-d…
claude Jul 12, 2026
8d2f410
Adapt duplicates test to new UI-test structure, cap jump pill width, …
claude Jul 12, 2026
92c3d0a
Surface duplicate-search failures inline and pluralize Show-more label
claude Jul 12, 2026
d267bd1
Add PR screenshots (reverted in next commit)
claude Jul 12, 2026
db41ea2
Revert "Add PR screenshots (reverted in next commit)"
claude Jul 12, 2026
ab6a1fa
Merge remote-tracking branch 'origin/develop' into claude/duplicate-d…
claude Jul 15, 2026
1cd120a
Merge remote-tracking branch 'origin/develop' into claude/duplicate-e…
myieye Jul 17, 2026
35544e9
Query duplicates diacritic-insensitively; classify exact vs accent match
myieye Jul 17, 2026
e5b1028
Add missing msgstr for two new msgids in non-English catalogs
myieye Jul 17, 2026
699340f
Polish
myieye Jul 20, 2026
dfdf2ea
Fix missing function
myieye Jul 20, 2026
143c5e5
Add button group
myieye Jul 20, 2026
ad093ac
Fix jump pill collapsing the duplicate strip instead of expanding it
myieye Jul 20, 2026
81549b4
Remove button group and add sense buttons
myieye Jul 20, 2026
edb48f5
Move duplicate-check code to subfolder
myieye Jul 20, 2026
e11f04a
Fix scrolls to top of dialog when clicking 'Show more'
myieye Jul 20, 2026
0782d4c
Test normalizeForExactCompare in its own missing-value test
myieye Jul 20, 2026
e3c2b56
Merge branch 'develop' into claude/duplicate-detection-review-ny053c
myieye Jul 20, 2026
ba81d24
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Jul 21, 2026
257a347
add a simple fly transition on the duplicate check pill
hahn-kev Jul 22, 2026
60c1d0c
set an observe threshold of 50% so when the duplicate check is half h…
hahn-kev Jul 22, 2026
70753a7
Address duplicate-check PR review feedback
claude Jul 23, 2026
3b7b27e
Drop over-specific auto-expand UI test
claude Jul 23, 2026
9ba8e1e
Improve and add animations to minimize jank
myieye Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
<DropdownMenu.Item class="cursor-pointer" onclick={e => e.preventDefault()}>
{#snippet child({props})}
<Link {...props} to="./browse?{entryBrowseParams(entry.id)}">
<Icon icon="i-mdi-link" />
<Icon icon="i-mdi-book-arrow-right-outline" />
{$t`Go to ${pt($t`Entry`, $t`Word`, viewService.currentView)}`}
</Link>
{/snippet}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
headwordClass = '',
highlightSenseId = undefined,
hideExamples = false,
inline = false,
...restProps
}: HTMLAttributes<HTMLDivElement> & {
entry: IEntry;
Expand All @@ -24,6 +25,8 @@
headwordClass?: string;
highlightSenseId?: string;
hideExamples?: boolean;
/** Render senses as one flowing line (no line break per sense) — for compact previews */
inline?: boolean;
} = $props();

$effect(() => {
Expand Down Expand Up @@ -104,7 +107,12 @@
<Headwords {entry} class={cn('mr-1', headwordClass)} />
{#each senses as sense, i (sense.id)}
{#if senses.length > 1}
<br />
{#if inline}
<!-- eslint-disable-next-line svelte/no-useless-mustaches This mustache is not useless, it preserves whitespace -->
{' '}
{:else}
<br />
{/if}
{/if}
<span class={cn(highlightSenseId === sense.id && 'rounded bg-secondary')}>
{#if senses.length > 1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<DropdownMenu.Item class="cursor-pointer">
{#snippet child({props})}
<Link {...props} to="browse?{entryBrowseParams(entryId)}">
<Icon icon="i-mdi-book-outline" />
<Icon icon="i-mdi-book-arrow-right-outline" />
{$t`Go to ${pt($t`Entry`, $t`Word`, viewService.currentView)}`}
</Link>
{/snippet}
Expand Down
43 changes: 28 additions & 15 deletions frontend/viewer/src/lib/entry-editor/NewEntryDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import {pt} from '$lib/views/view-text';
import * as Editor from '$lib/components/editor';
import Icon from '$lib/components/ui/icon/icon.svelte';
import DuplicateCheckSection from './duplicate-check/DuplicateCheckSection.svelte';
import EntryEditorPrimitive from './object-editors/EntryEditorPrimitive.svelte';
import ObjectHeader from './object-editors/ObjectHeader.svelte';
import SenseEditorPrimitive from './object-editors/SenseEditorPrimitive.svelte';
Expand Down Expand Up @@ -59,21 +60,26 @@
async function createEntry(e: Event) {
e.preventDefault();
e.stopPropagation();
// we might already be creating something
if (loading) return;
if (!requester) throw new Error('No requester');

await editor?.commit();
await addMainPublicationPromise; // make sure the main publication landed before we snapshot the entry
entry.senses = sense ? [sense] : [];
if (!validateEntry()) return;

loading = true;
const entrySnapshot = $state.snapshot(entry);
// The dialog pre-populates publishIn (main publication + any active filter), so always create the entry as-is.
await saveHandler.handleSave(() => lexboxApi.createEntry(entrySnapshot, createEntryOptions.asIs));
requester.resolve(entry);
requester = undefined;
loading = false;
open = false;
try {
await editor?.commit();
await addMainPublicationPromise; // make sure the main publication landed before we snapshot the entry
entry.senses = sense ? [sense] : [];
if (!validateEntry()) return;

const entrySnapshot = $state.snapshot(entry);
// The dialog pre-populates publishIn (main publication + any active filter), so always create the entry as-is.
await saveHandler.handleSave(() => lexboxApi.createEntry(entrySnapshot, createEntryOptions.asIs));
requester.resolve(entry);
requester = undefined;
open = false;
} finally {
loading = false;
}
}

let errors: string[] = $state([]);
Expand Down Expand Up @@ -178,11 +184,16 @@
{/snippet}

<Dialog.Root bind:open={open}>
<Dialog.DialogContent onkeydown={handleKeydown} class="sm:min-h-[min(calc(100%-16px),30rem)] max-md:px-2">
<!-- Fixed width (not min/max): the duplicate check adds and reshapes content while the
dialog is open, and a content-sized dialog jumps around with every keystroke -->
<Dialog.DialogContent onkeydown={handleKeydown}
class="sm:min-h-[min(calc(100%-16px),30rem)] sm:w-[min(calc(100%-32px),50rem)] max-md:px-2">
<Dialog.DialogHeader>
<Dialog.DialogTitle>{pt($t`New Entry`, $t`New Word`, viewService.currentView)}</Dialog.DialogTitle>
</Dialog.DialogHeader>
<div>
<!-- min-w-0: as a grid item this div defaults to min-width:auto, letting long duplicate
headword lists widen the dialog instead of truncating -->
<div class="min-w-0">
<OverrideFields shownFields={[
'lexemeForm', 'citationForm',
'gloss', 'definition', 'partOfSpeechId',
Expand Down Expand Up @@ -211,6 +222,8 @@
</Editor.Grid>
</Editor.Root>
</OverrideFields>
<DuplicateCheckSection {entry} {sense}
onNavigateToEntry={() => open = false} />
</div>
{#if errors.length}
<div class="text-end space-y-2">
Expand All @@ -221,7 +234,7 @@
{/if}
<Dialog.DialogFooter>
<Button onclick={() => open = false} variant="secondary">{$t`Cancel`}</Button>
<Button onclick={e => createEntry(e)} disabled={loading} {loading}>
<Button onclick={e => createEntry(e)} {loading}>
{pt($t`Create Entry`, $t`Add Word`, viewService.currentView)}
</Button>
</Dialog.DialogFooter>
Expand Down
Loading
Loading