feat(person-mention): PR-B2 — read-mode rendering + hover card (issue #362) #371

Merged
marcel merged 18 commits from feat/person-mentions-issue-362-frontend-b2 into main 2026-04-29 13:37:06 +02:00
2 changed files with 18 additions and 4 deletions
Showing only changes of commit 1842e23c81 - Show all commits

View File

@@ -2,7 +2,11 @@
import type { TranscriptionBlockData } from '$lib/types'; import type { TranscriptionBlockData } from '$lib/types';
import type { components } from '$lib/generated/api'; import type { components } from '$lib/generated/api';
import { splitByMarkers } from '$lib/utils/transcriptionMarkers'; import { splitByMarkers } from '$lib/utils/transcriptionMarkers';
import { renderTranscriptionBody, type SafeHtml } from '$lib/utils/mention'; import {
renderTranscriptionBody,
type SafeHtml,
PERSON_MENTION_SELECTOR
} from '$lib/utils/mention';
import PersonHoverCard from './PersonHoverCard.svelte'; import PersonHoverCard from './PersonHoverCard.svelte';
import type { HoverData, LoadState } from '$lib/types/personHoverCard'; import type { HoverData, LoadState } from '$lib/types/personHoverCard';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
@@ -163,15 +167,15 @@ async function handleMentionClick(event: MouseEvent) {
function attachMentionHandlers(node: HTMLElement) { function attachMentionHandlers(node: HTMLElement) {
function onEnter(e: Event) { function onEnter(e: Event) {
const t = e.target as HTMLElement; const t = e.target as HTMLElement;
if (t.matches?.('a.person-mention')) handleMentionEnter(e); if (t.matches?.(PERSON_MENTION_SELECTOR)) handleMentionEnter(e);
} }
function onLeave(e: Event) { function onLeave(e: Event) {
const t = e.target as HTMLElement; const t = e.target as HTMLElement;
if (t.matches?.('a.person-mention')) handleMentionLeave(e); if (t.matches?.(PERSON_MENTION_SELECTOR)) handleMentionLeave(e);
} }
function onClick(e: MouseEvent) { function onClick(e: MouseEvent) {
const t = e.target as HTMLElement; const t = e.target as HTMLElement;
if (t.matches?.('a.person-mention')) handleMentionClick(e); if (t.matches?.(PERSON_MENTION_SELECTOR)) handleMentionClick(e);
} }
// mouseenter does not bubble — capture it. // mouseenter does not bubble — capture it.
node.addEventListener('mouseenter', onEnter, true); node.addEventListener('mouseenter', onEnter, true);

View File

@@ -1,5 +1,15 @@
import type { MentionDTO, PersonMention } from '$lib/types'; import type { MentionDTO, PersonMention } from '$lib/types';
/**
* Single-source CSS selector for rendered person-mention anchors. Used by:
* - layout.css (.person-mention rule, focus ring, underline)
* - TranscriptionReadView (delegated mouseenter/leave/click handlers)
* - unit + e2e tests
*
* Keep these in sync — the renderer template below emits exactly this class.
*/
export const PERSON_MENTION_SELECTOR = 'a.person-mention';
/** /**
* Branded string type for HTML that has been pre-escaped and assembled by * Branded string type for HTML that has been pre-escaped and assembled by
* one of the trusted renderers in this module. The brand exists so that * one of the trusted renderers in this module. The brand exists so that