"use client"; import React, { useState, useRef, useEffect } from "react"; import { marked } from "marked"; import DOMPurify from "dompurify"; import { useI18n } from "@/lib/i18n"; interface MarkdownNoteEditorProps { value: string; onChange: (newValue: string) => void; onSave?: () => void; } export function MarkdownNoteEditor({ value, onChange, onSave }: MarkdownNoteEditorProps) { const { t } = useI18n(); // Default to preview mode as requested const [mode, setMode] = useState<"edit" | "preview">("preview"); const textareaRef = useRef(null); const previewRef = useRef(null); // Reset to preview whenever value or active task changes useEffect(() => { // Keep preview mode as primary }, []); // Configure marked to open links in new tabs safely useEffect(() => { const renderer = new marked.Renderer(); renderer.link = ({ href, title, text }: { href: string; title?: string | null; text: string }) => { const titleAttr = title ? ` title="${title}"` : ""; return `${text}`; }; marked.use({ renderer, breaks: true, gfm: true }); }, []); // Keyboard shortcuts const handleKeyDown = (e: React.KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); if (onSave) onSave(); } else if (e.key === "Tab") { e.preventDefault(); const ta = textareaRef.current; if (!ta) return; const start = ta.selectionStart; const end = ta.selectionEnd; const newVal = value.slice(0, start) + " " + value.slice(end); onChange(newVal); requestAnimationFrame(() => { ta.selectionStart = ta.selectionEnd = start + 2; }); } }; // Convert markdown to sanitized HTML with safe links const renderMarkdownHtml = () => { if (!value || !value.trim()) { return `
${t("notesPlaceholder")}
`; } try { const textWithLinks = value.replace( /(^|[^"'])(https?:\/\/[^\s<]+)/g, (match, prefix, url) => { if (match.includes("](") || match.includes('href="')) return match; return `${prefix}[${url}](${url})`; } ); const rawHtml = marked.parse(textWithLinks, { breaks: true, gfm: true }) as string; return DOMPurify.sanitize(rawHtml, { ALLOWED_TAGS: [ "h1", "h2", "h3", "h4", "h5", "h6", "p", "a", "span", "strong", "em", "del", "s", "ul", "ol", "li", "code", "pre", "blockquote", "hr", "br", "table", "thead", "tbody", "tr", "th", "td" ], ALLOWED_ATTR: ["href", "title", "target", "rel", "class", "style"], ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, ADD_ATTR: ["target", "rel", "class"], }); } catch { return value; } }; return (
{/* Minimal Header with Mode Switcher */}
{t("notes").toUpperCase()} {/* Minimal Matte One-touch Mode Toggle */}
{/* Editor / Preview Area */}
{mode === "edit" ? (