'use strict'; // ---------- markdown ---------- // A small, safe renderer. It builds elements (never HTML strings), and only http(s) links are links. // [[Page title]] and [[Page title|text]] link to other pages; pageByTitle (app.js) says which exist. const INLINE_SRC = '`([^`\\n]+)`|\\*\\*([^*\\n]+)\\*\\*|(? h('a', { class: 'chip', href: '#/tag/' + encodeURIComponent(t) }, '#' + t); const extLink = (url, label) => h('a', { href: url, target: '_blank', rel: 'noopener noreferrer' }, label); function wikiLink(target, text) { const title = target.trim(), page = pageByTitle(title); if (page) return h('a', { class: 'wikilink', href: '#/p/' + page.id }, text || title); return h('a', { class: 'wikilink missing', href: '#/link/' + encodeURIComponent(title), title: 'No page called "' + title + '" yet. Click to create it.' }, text || title); } function inline(text) { const re = new RegExp(INLINE_SRC, 'gu'), out = []; let last = 0, m; while ((m = re.exec(text))) { if (m.index > last) out.push(text.slice(last, m.index)); last = re.lastIndex; if (m[1] != null) out.push(h('code', {}, m[1])); else if (m[2] != null) out.push(h('strong', {}, inline(m[2]))); else if (m[3] != null) out.push(h('em', {}, inline(m[3]))); else if (m[4] != null) out.push(wikiLink(m[4], m[5])); else if (m[6] != null) out.push(extLink(m[7], m[6])); else if (m[8] != null) out.push(extLink(m[8], m[8])); else { const t = m[9].replace(/[/-]+$/, ''); if (!t || /^\d+$/.test(t)) out.push('#' + m[9]); else out.push(tagChip(t.toLowerCase()), m[9].slice(t.length)); } } if (last < text.length) out.push(text.slice(last)); return out; } // md renders a page. onTask(lineIndex, checked) makes the checkboxes work; without it they are read-only. function md(src, onTask, big) { const root = h('div', { class: 'md' + (big ? ' md-big' : '') }), lines = src.split('\n'); let para = [], i = 0, m; const flush = () => { if (!para.length) return; const p = h('p'); para.forEach((t, k) => { if (k) p.append(h('br')); p.append(...inline(t)); }); root.append(p); para = []; }; while (i < lines.length) { const line = lines[i]; if (/^\s*```/.test(line)) { flush(); const code = []; i++; while (i < lines.length && !/^\s*```/.test(lines[i])) code.push(lines[i++]); i++; root.append(h('pre', {}, h('code', {}, code.join('\n')))); continue; } if (!line.trim()) { flush(); i++; continue; } if ((m = /^(#{1,3})\s+(.+)$/.exec(line))) { flush(); root.append(h('h' + (m[1].length + 2), { class: 'md-h' }, inline(m[2]))); i++; continue; } if (/^\s*(-{3,}|\*{3,})\s*$/.test(line)) { flush(); root.append(h('hr')); i++; continue; } if (/^>/.test(line)) { flush(); const q = []; while (i < lines.length && /^>/.test(lines[i])) q.push(lines[i++].replace(/^>\s?/, '')); const bq = h('blockquote'); q.forEach((t, k) => { if (k) bq.append(h('br')); bq.append(...inline(t)); }); root.append(bq); continue; } if (/^(\s*)([-*+]|\d+[.)])\s+/.test(line)) { flush(); const ordered = /^\s*\d/.test(line), list = h(ordered ? 'ol' : 'ul'); while (i < lines.length && (m = /^(\s*)([-*+]|\d+[.)])\s+(?:\[([ xX])\]\s+)?(.*)$/.exec(lines[i]))) { const lvl = Math.min(3, Math.floor(m[1].replace(/\t/g, ' ').length / 2)), at = i; const li = h('li', { class: 'ind-' + lvl + (m[3] != null ? ' task' : '') }); if (m[3] != null) { const box = h('input', { type: 'checkbox', checked: m[3] !== ' ', disabled: !onTask, 'aria-label': m[4] || 'Task' }); if (onTask) box.addEventListener('change', () => onTask(at, box.checked)); li.append(box, h('span', { class: m[3] !== ' ' ? 'done' : '' }, inline(m[4]))); } else li.append(...inline(m[4])); list.append(li); i++; } root.append(list); continue; } para.push(line); i++; } flush(); return root; }