69 lines
2.9 KiB
JavaScript
69 lines
2.9 KiB
JavaScript
// Export dropdown — Markdown / Plain text / Web page / PDF. Client-side, no deps
|
|
// beyond the bundled marked + DOMPurify. getContent() → { title, md }.
|
|
import { el } from '../dom.js';
|
|
import { marked } from '../vendor/marked.esm.js';
|
|
import DOMPurify from '../vendor/purify.es.mjs';
|
|
|
|
function download(name, text, mime) {
|
|
const url = URL.createObjectURL(new Blob([text], { type: mime }));
|
|
const a = el('a', { href: url, download: name });
|
|
document.body.appendChild(a); a.click(); a.remove();
|
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
}
|
|
|
|
function toPlain(md) {
|
|
return md.replace(/```[\s\S]*?```/g, m => m.replace(/```/g, ''))
|
|
.replace(/^#{1,6}\s+/gm, '').replace(/\*\*|__|\*|_|`|~~/g, '')
|
|
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1').replace(/^>\s?/gm, '');
|
|
}
|
|
|
|
function htmlDoc(title, md) {
|
|
const body = DOMPurify.sanitize(marked.parse(md));
|
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
<title>${title}</title>
|
|
<style>
|
|
body{max-width:820px;margin:40px auto;padding:0 20px;font:15px/1.6 -apple-system,Segoe UI,Roboto,sans-serif;color:#1a1a1a}
|
|
h1,h2,h3{font-family:Georgia,serif} h1{font-size:26px} h2{font-size:20px} h3{font-size:16px}
|
|
code{background:#f4f4f6;padding:1px 5px;border-radius:3px;font-size:13px}
|
|
pre{background:#f4f4f6;padding:12px;border-radius:5px;overflow:auto}
|
|
table{border-collapse:collapse;width:100%;margin:12px 0} th,td{border:1px solid #ccc;padding:6px 10px;text-align:left}
|
|
th{background:#f0f0f2} blockquote{border-left:3px solid #ccc;margin:8px 0;padding:2px 12px;color:#555}
|
|
a{color:#b8431f}
|
|
</style></head><body>${body}</body></html>`;
|
|
}
|
|
|
|
export function exportMenu({ getContent, filenameBase }) {
|
|
const wrap = el('div', { class: 'exp-menu' });
|
|
|
|
async function run(kind) {
|
|
wrap.classList.remove('open');
|
|
const { title, md } = await getContent();
|
|
const base = (filenameBase || title || 'export').replace(/[^\w.-]+/g, '-').replace(/(^-|-$)/g, '') || 'export';
|
|
if (kind === 'md') download(base + '.md', md, 'text/markdown');
|
|
else if (kind === 'txt') download(base + '.txt', toPlain(md), 'text/plain');
|
|
else if (kind === 'html') download(base + '.html', htmlDoc(title, md), 'text/html');
|
|
else if (kind === 'pdf') {
|
|
const w = window.open('', '_blank');
|
|
if (!w) return;
|
|
w.document.write(htmlDoc(title, md)); w.document.close(); w.focus();
|
|
setTimeout(() => w.print(), 300);
|
|
}
|
|
}
|
|
|
|
const btn = el('button', {
|
|
class: 'ghost',
|
|
onclick: (e) => { e.stopPropagation(); wrap.classList.toggle('open'); }
|
|
}, 'Export ▾');
|
|
|
|
const list = el('div', { class: 'exp-list' },
|
|
el('button', { onclick: () => run('md') }, 'Markdown (.md)'),
|
|
el('button', { onclick: () => run('txt') }, 'Plain text (.txt)'),
|
|
el('button', { onclick: () => run('html') }, 'Web page (.html)'),
|
|
el('button', { onclick: () => run('pdf') }, 'PDF (print)')
|
|
);
|
|
|
|
wrap.append(btn, list);
|
|
document.addEventListener('click', () => wrap.classList.remove('open'));
|
|
return wrap;
|
|
}
|