Files
familienarchiv/frontend/src/lib/utils/deepLinkScroll.ts
Marcel 251eb9c3fc feat(frontend): add scrollToCommentFromQuery helper for notification deep-link
Pure function that reads commentId + annotationId from the page URL,
enters transcribe mode if needed, activates the block's annotation,
scrolls the target comment into view, focuses it for screen readers,
fires the existing annotation flash, and strips the params via the
injected callback.

All side effects go through callbacks so the helper is unit-testable
without mounting the page or a DOM (only scrollIntoView/focus are
called on the injected element). Eight tests cover both absent params,
happy path, transcribe-mode activation, missing DOM target, reduced
motion, flash trigger, and URL strip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:29:52 +02:00

41 lines
1.1 KiB
TypeScript

export type DeepLinkScrollOptions = {
transcribeMode: boolean;
setTranscribeMode: (value: boolean) => void;
loadBlocks: () => Promise<void>;
setActiveAnnotationId: (id: string) => void;
flashAnnotation: (annotationId: string) => void;
prefersReducedMotion: boolean;
afterTick: () => Promise<void>;
getElement: (id: string) => HTMLElement | null;
onStripUrl: () => void;
};
export async function scrollToCommentFromQuery(
url: URL,
opts: DeepLinkScrollOptions
): Promise<void> {
const commentId = url.searchParams.get('commentId');
if (!commentId) return;
const annotationId = url.searchParams.get('annotationId');
if (!annotationId) return;
if (!opts.transcribeMode) {
opts.setTranscribeMode(true);
await opts.loadBlocks();
}
opts.setActiveAnnotationId(annotationId);
await opts.afterTick();
const el = opts.getElement(`comment-${commentId}`);
if (el) {
const behavior: ScrollBehavior = opts.prefersReducedMotion ? 'instant' : 'smooth';
el.scrollIntoView({ behavior, block: 'center' });
el.focus({ preventScroll: true });
opts.flashAnnotation(annotationId);
}
opts.onStripUrl();
}