// ── Markdown rendering ── import { esc } from './util.js'; import { state } from './state.js'; marked.use({ breaks: true, gfm: true }); const purifyConfig = { ALLOWED_TAGS: ['h1','h2','h3','h4','h5','h6','p','br','hr','ul','ol','li','a','strong','em','del','code','pre','blockquote','table','thead','tbody','tr','th','td','img','input','span','div','audio','source'], ALLOWED_ATTR: ['href','src','alt','width','height','type','checked','disabled','class','style','data-card-id','title','controls','preload'], ALLOW_DATA_ATTR: true, }; export function renderMd(src) { const linked = (src || '').replace(/\[\[([^\]]+)\]\]/g, (_, title) => { const card = state.cards.find(c => c.title.toLowerCase() === title.toLowerCase()); if (card) return `${esc(title)}`; return `${esc(title)}`; }); const raw = marked.parse(linked); return typeof DOMPurify !== 'undefined' ? DOMPurify.sanitize(raw, purifyConfig) : raw; } export function stripMd(src) { return (src || '') .replace(/]*>/gi, '') .replace(/<[^>]+>/g, '') .replace(/!\[[^\]]*\]\([^)]*\)/g, '') .replace(/\[\[([^\]]+)\]\]/g, '$1') .replace(/^#{1,6}\s+/gm, '') .replace(/(\*{1,3}|_{1,3}|~~)(.*?)\1/g, '$2') .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') .replace(/`{1,3}[^`]*`{1,3}/g, '') .replace(/^[-*>]\s+/gm, '') .replace(/^\d+\.\s+/gm, '') .replace(/\n{2,}/g, '\n') .trim(); } export function getTodos(src) { const s = src || ''; const total = (s.match(/- \[([ x])\]/g) || []).length; if (!total) return null; return { total, done: (s.match(/- \[x\]/g) || []).length }; } export function firstImage(src) { const s = src || ''; const md = s.match(/!\[[^\]]*\]\(([^)]+)\)/); if (md) return md[1]; const html = s.match(/]+src="([^"]+)"/i); return html ? html[1] : null; } export function leadingImage(src) { const s = (src || '').trimStart(); if (!s) return null; const md = s.match(/^!\[[^\]]*\]\(([^)]+)\)/); if (md) return md[1].split(/\s+/)[0].replace(/^<(.+)>$/, '$1') || null; const html = s.match(/^]+src=(['"])(.*?)\1/i); return html ? html[2] : null; }