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
This commit is contained in:
Frederic Cabassut
2026-04-08 22:36:19 +02:00
committed by Fred Cabassut
parent 6a3b2e7de8
commit e07b915c80
6 changed files with 11 additions and 9 deletions
+2 -1
View File
@@ -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"
+1 -1
View File
@@ -52,7 +52,7 @@
}
</script>
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} onscroll={onClose} onresize={onClose} />
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
+2 -1
View File
@@ -134,7 +134,8 @@
{#if note.checklistMode && checklistItems.length > 0}
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<ul class="space-y-2 mb-6" data-testid="note-checklist-preview"
onclick={(e) => { 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}
<li class="flex items-start gap-2 text-sm {item.indented ? 'pl-4 ' : ''}{item.checked ? 'text-[var(--text-muted)] line-through' : 'text-[var(--text)]'}"
data-testid={item.indented ? 'card-checklist-child' : undefined}>
+4 -4
View File
@@ -224,19 +224,19 @@ describe('outdentItem', () => {
describe('linkifyText', () => {
it('converts https URLs to anchor tags', () => {
expect(linkifyText('visit https://example.com today')).toBe(
'visit <a href="https://example.com" target="_blank" rel="noopener" class="checklist-link">https://example.com</a> today'
'visit <a href="https://example.com" target="_blank" rel="noopener noreferrer" class="checklist-link">https://example.com</a> today'
);
});
it('converts http URLs to anchor tags', () => {
expect(linkifyText('go to http://example.com')).toBe(
'go to <a href="http://example.com" target="_blank" rel="noopener" class="checklist-link">http://example.com</a>'
'go to <a href="http://example.com" target="_blank" rel="noopener noreferrer" class="checklist-link">http://example.com</a>'
);
});
it('converts bare www URLs with https href', () => {
expect(linkifyText('check www.example.com')).toBe(
'check <a href="https://www.example.com" target="_blank" rel="noopener" class="checklist-link">www.example.com</a>'
'check <a href="https://www.example.com" target="_blank" rel="noopener noreferrer" class="checklist-link">www.example.com</a>'
);
});
@@ -281,7 +281,7 @@ describe('linkifyText', () => {
it('strips trailing punctuation from URLs', () => {
expect(linkifyText('see https://example.com.')).toBe(
'see <a href="https://example.com" target="_blank" rel="noopener" class="checklist-link">https://example.com</a>.'
'see <a href="https://example.com" target="_blank" rel="noopener noreferrer" class="checklist-link">https://example.com</a>.'
);
});
+1 -1
View File
@@ -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 += `<a href="${escapeHtml(href)}" target="_blank" rel="noopener" class="checklist-link">${escapeHtml(match)}</a>`;
result += `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer" class="checklist-link">${escapeHtml(match)}</a>`;
lastIndex = index + match.length;
}
result += escapeHtml(text.slice(lastIndex));
+1 -1
View File
@@ -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"