From 1fd38830fe5ce6b4269f79cba89fd8e3d4792ba9 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 29 Apr 2026 08:21:35 +0200 Subject: [PATCH] feat(person-mention): TranscriptionReadView wires hover card and click nav MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Composes splitByMarkers + renderTranscriptionBody so [unleserlich] markers render as siblings of the mention anchor — neither nested inside the other (B19b). Hover card lifecycle on each .person-mention anchor: mouseenter → set aria-describedby, place card via getBoundingClientRect (default below-right; flip up if <200px from bottom or mention is in bottom 30% of viewport; flip left if <300px from right), fire fetch, mount card with skeleton state resolved → swap card to loaded state with person + family relationships (PARENT_OF / SPOUSE_OF / SIBLING_OF only) 404 → degrade: mark anchor with data-person-deleted="true", unmount card, suppress future hovers/clicks network → swap card to error state — link still navigates mouseleave → drop aria-describedby, unmount card Per-page SvelteMap cache (B15.5) so a sweep across N mentions of the same person fires the backend once. Click handler calls goto() so SvelteKit handles routing without a full reload. Event listeners are attached once per article via a Svelte action because the anchor HTML is injected via {@html ...} and would not receive declarative bindings. The eslint-disable comment mirrors the rationale on CommentMessage.svelte:88-89. Co-Authored-By: Claude Sonnet 4.6 --- .../components/TranscriptionReadView.svelte | 197 +++++++++++++- .../TranscriptionReadView.svelte.test.ts | 242 +++++++++++++++++- 2 files changed, 428 insertions(+), 11 deletions(-) diff --git a/frontend/src/lib/components/TranscriptionReadView.svelte b/frontend/src/lib/components/TranscriptionReadView.svelte index f1586e6c..80829678 100644 --- a/frontend/src/lib/components/TranscriptionReadView.svelte +++ b/frontend/src/lib/components/TranscriptionReadView.svelte @@ -1,6 +1,16 @@ -
+
{#each sorted as block (block.id)}
a.sortOrder - b.sortOrder)); onclick={() => onParagraphClick(block.annotationId)} role="button" tabindex="0" - onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') onParagraphClick(block.annotationId); }} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') onParagraphClick(block.annotationId); + }} > - {#each splitByMarkers(block.text) as segment, i (i)} - {#if segment.type === 'marker'} - {segment.text} - {:else} - {segment.text} - {/if} - {/each} + + {@html renderBlockHtml(block)}
{/each}
+{#if activeCard} + +{/if} +