From d3b98fddf2b4cc781fa2bb013bff0925f5745456 Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Wed, 8 Apr 2026 22:32:22 +0200 Subject: [PATCH] fix: address PR review feedback - Add noreferrer to rel attribute to prevent Referer header leaks - Strip newlines from pasted text to prevent checklist corruption - Add aria-placeholder for screen reader accessibility - Dismiss LinkPopover on scroll/resize - Stop keydown propagation on links in NoteCard preview --- src/lib/components/Checklist.svelte | 3 ++- src/lib/components/LinkPopover.svelte | 2 +- src/lib/components/NoteCard.svelte | 3 ++- src/lib/utils/checklist.test.ts | 8 ++++---- src/lib/utils/checklist.ts | 2 +- static/api/openapi.json | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/components/Checklist.svelte b/src/lib/components/Checklist.svelte index 070487f..5021589 100644 --- a/src/lib/components/Checklist.svelte +++ b/src/lib/components/Checklist.svelte @@ -183,7 +183,7 @@ function handlePaste(e: ClipboardEvent) { e.preventDefault(); - const text = e.clipboardData?.getData('text/plain') ?? ''; + const text = (e.clipboardData?.getData('text/plain') ?? '').replace(/[\r\n]+/g, ' '); const selection = window.getSelection(); if (!selection?.rangeCount) return; const range = selection.getRangeAt(0); @@ -327,6 +327,7 @@ onblur={(e) => { (e.target as HTMLElement).innerHTML = linkifyText(item.text); }} class="flex-1 min-w-0 bg-transparent text-sm outline-none break-words {item.checked ? 'text-[var(--text-muted)] line-through' : 'text-[var(--text)]'}" data-placeholder="List item" + aria-placeholder="List item" data-testid="checklist-input" data-item-id={item.id} role="textbox" diff --git a/src/lib/components/LinkPopover.svelte b/src/lib/components/LinkPopover.svelte index 88fd46e..9ee5063 100644 --- a/src/lib/components/LinkPopover.svelte +++ b/src/lib/components/LinkPopover.svelte @@ -52,7 +52,7 @@ } - +
0}
    { if ((e.target as HTMLElement).closest('a')) e.stopPropagation(); }}> + onclick={(e) => { if ((e.target as HTMLElement).closest('a')) e.stopPropagation(); }} + onkeydown={(e) => { if ((e.target as HTMLElement).closest('a')) e.stopPropagation(); }}> {#each sortedChecklistItems.slice(0, 8) as item}
  • diff --git a/src/lib/utils/checklist.test.ts b/src/lib/utils/checklist.test.ts index d4d5e14..f60fbec 100644 --- a/src/lib/utils/checklist.test.ts +++ b/src/lib/utils/checklist.test.ts @@ -224,19 +224,19 @@ describe('outdentItem', () => { describe('linkifyText', () => { it('converts https URLs to anchor tags', () => { expect(linkifyText('visit https://example.com today')).toBe( - 'visit https://example.com today' + 'visit https://example.com today' ); }); it('converts http URLs to anchor tags', () => { expect(linkifyText('go to http://example.com')).toBe( - 'go to http://example.com' + 'go to http://example.com' ); }); it('converts bare www URLs with https href', () => { expect(linkifyText('check www.example.com')).toBe( - 'check www.example.com' + 'check www.example.com' ); }); @@ -281,7 +281,7 @@ describe('linkifyText', () => { it('strips trailing punctuation from URLs', () => { expect(linkifyText('see https://example.com.')).toBe( - 'see https://example.com.' + 'see https://example.com.' ); }); diff --git a/src/lib/utils/checklist.ts b/src/lib/utils/checklist.ts index 2f51a48..a10eddc 100644 --- a/src/lib/utils/checklist.ts +++ b/src/lib/utils/checklist.ts @@ -180,7 +180,7 @@ export function linkifyText(text: string): string { let lastIndex = 0; for (const { index, match, href } of matches) { result += escapeHtml(text.slice(lastIndex, index)); - result += `${escapeHtml(match)}`; + result += `${escapeHtml(match)}`; lastIndex = index + match.length; } result += escapeHtml(text.slice(lastIndex)); diff --git a/static/api/openapi.json b/static/api/openapi.json index 55bf71a..7f8e5d1 100644 --- a/static/api/openapi.json +++ b/static/api/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "Crumbs API", "description": "Auto-generated API spec for crumbs", - "version": "0.24.0", + "version": "0.27.1", "contact": { "name": "Bretzel", "url": "https://bretzel.app"