import { createEffect, on, onMount } from "solid-js"; // M15/THREAT-006: allow-list sanitizer for note bodies. The editor writes // arbitrary HTML directly to innerHTML; without this any note body could // inject iframe/base/meta/form/script. Allow-list is intentional — adding // to it requires reviewing the attack surface of the new tag. const ALLOWED_NOTE_TAGS = new Set([ "b", "i", "u", "s", "strong", "em", "h2", "h3", "blockquote", "ul", "ol", "li", "br", "hr", "p", ]); function sanitizeNoteElement(el: Element): void { for (const child of Array.from(el.children)) { if (!ALLOWED_NOTE_TAGS.has(child.tagName.toLowerCase())) { child.replaceWith(document.createTextNode(child.textContent ?? "")); } else { while (child.attributes.length > 0) { child.removeAttribute(child.attributes[0]!.name); } sanitizeNoteElement(child); } } } export function toHtml(raw: string): string { if (!/<\/?[a-z]/i.test(raw)) { return raw .replace(/&/g, "&") .replace(//g, ">") .replace(/\n/g, "
"); } const doc = new DOMParser().parseFromString(raw, "text/html"); sanitizeNoteElement(doc.body); return doc.body.innerHTML; } const BoldIcon = () => B; const ItalicIcon = () => I; const UnderlineIcon = () => U; const StrikeIcon = () => S; const HeadingIcon = () => H; const BulletIcon = () => ( ); const NumberedIcon = () => ( 1. 2. 3. ); const RuleIcon = () => ( ); export type ExecFn = (cmd: string, val?: string) => void; export function Toolbar(props: { exec: ExecFn; class?: string }) { const e = props.exec; function toggleHeading() { const val = document.queryCommandValue("formatBlock").toLowerCase(); e("formatBlock", val === "h2" ? "p" : "h2"); } return (
); } /* ── Inline editor (Add.tsx, Entry.tsx note view) ── */ interface NoteEditorInlineProps { initialContent: string; onContentChange?: (html: string) => void; onBlur?: (html: string) => void; onReady?: (el: HTMLDivElement) => void; autoFocus?: boolean; /** When this key changes, the editor re-loads initialContent (e.g., switching between vault entries in-place). */ entryKey?: string; } // PB-26-009: shared by the inline editor and the standalone notes editor — // pasted rich HTML must never enter the extension-origin DOM live. CSP blocks // inline script execution, but outbound /