// export.jsx — 把一页打成「自带样式、可独立打开、可分享」的网页 / 幻灯片 / Markdown // 依赖 engine.jsx 的 downloadFile / safeName / contentHash function escHtml(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ( { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] )); } // 媒体标记 [名称:完成态] → 识别成图 / 视频 / 音乐 / 配音 / 链接 function mediaKind(name) { const n = String(name || ''); if (/视频|短片|youtube|影/i.test(n)) return 'video'; if (/配音|语音|tts|朗读/i.test(n)) return 'voice'; if (/音乐|配乐|谱|声/i.test(n)) return 'audio'; if (/连接|链接|link/i.test(n)) return 'link'; if (/图|画|插画|image/i.test(n)) return 'image'; return 'image'; } const MEDIA_GLYPH = { image: '', video: '', audio: '', voice: '', link: '', }; const MEDIA_LABEL = { image: '图像', video: '影像', audio: '配乐', voice: '配音', link: '连接' }; function mediaCardHtml(name, done) { const k = mediaKind(name); return '
' + '' + MEDIA_GLYPH[k] + '' + '' + escHtml(name) + '' + escHtml(done || MEDIA_LABEL[k]) + '' + '
'; } // 真实媒体内联:图片 / 视频 / 音频·配乐 / 作者朗读 / YouTube → 可直接播放、随网页自包含 function realMediaHtml(m) { if (!m || m.placeholder || !(m.src || m.embed)) return null; if (m.kind === 'image') { return '
' + escHtml(m.name || '') + '' + (m.name ? '
' + escHtml(m.name) + '
' : '') + '
'; } if (m.kind === 'video') { return '
' + (m.name ? '
' + escHtml(m.name) + '
' : '') + '
'; } if (m.kind === 'youtube' && m.embed) { return '
' + '
YouTube 片段' + (m.span ? ' · ' + escHtml(m.span) : '') + '
'; } if (m.kind === 'voice' || m.kind === 'audio') { const isAuthor = m.kind === 'voice' && m.author; const label = m.kind === 'voice' ? (m.label || '配音') : '配乐'; const sub = isAuthor ? '作者的声音 · 比文字更近一层' : (m.name || label); return '
' + '
' + (m.kind === 'voice' ? MEDIA_GLYPH.voice : MEDIA_GLYPH.audio) + '' + '' + escHtml(label) + '' + escHtml(sub) + '
' + '
'; } return null; } // 行内 markdown:**粗** *斜* `码` [链](url) function mdInlineExport(text) { let s = escHtml(text); s = s.replace(/`([^`]+)`/g, '$1'); s = s.replace(/\*\*([^*]+)\*\*/g, '$1'); s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1$2'); s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); return s; } // 正文(markdown 文本 + [媒体标记])→ HTML;media 为按序的真实媒体(内联用) function exportBodyToHtml(body, media) { const lines = String(body || '').split('\n'); let html = '', para = [], mi = 0; const flush = () => { if (para.length) { html += '

' + para.map(mdInlineExport).join('
') + '

'; para = []; } }; for (let i = 0; i < lines.length; i++) { const line = lines[i].replace(/\u00a0/g, ' '); const t = line.trim(); if (!t) { flush(); continue; } // 围栏代码块 ``` if (t.startsWith('```')) { flush(); const buf = []; i++; while (i < lines.length && !lines[i].trim().startsWith('```')) { buf.push(lines[i]); i++; } html += '
' + escHtml(buf.join('\n')) + '
'; continue; } // 媒体标记 [名称:完成态] —— 优先内联真实媒体,缺失时回落为占位卡 const m = t.match(/^\[([^::]+)[::]\s*(.*?)\]$/); if (m) { flush(); const payload = media && media[mi++]; html += (payload && realMediaHtml(payload)) || mediaCardHtml(m[1].trim(), m[2].trim()); continue; } // 列表 if (/^[-*]\s/.test(t)) { flush(); const buf = []; while (i < lines.length && /^\s*[-*]\s/.test(lines[i])) { buf.push(lines[i].trim().replace(/^[-*]\s/, '')); i++; } i--; html += ''; continue; } if (/^>\s?/.test(t)) { flush(); const buf = []; while (i < lines.length && /^>/.test(lines[i].trim())) { buf.push(lines[i].trim().replace(/^>\s?/, '')); i++; } i--; html += '
' + buf.filter(Boolean).map(b => '

' + mdInlineExport(b) + '

').join('') + '
'; continue; } if (/^#{1,3}\s/.test(t)) { flush(); const lv = t.match(/^#+/)[0].length; html += '' + mdInlineExport(t.replace(/^#+\s/, '')) + ''; continue; } if (/^(-{3,}|\*{3,})$/.test(t)) { flush(); html += '
'; continue; } para.push(t); } flush(); return html || '

这一页还没有正文。

'; } const SHOWCASE_CSS = ` :root{--paper:#FAF6EE;--paper2:#F3EDE0;--ink:#2a2a2a;--ink2:#6a6a6a;--ink3:#a6a097;--sea:#1a2840;--flame:#8B2020;--gold:#E8C840;--rule:#E4DBCB;--mono:'JetBrains Mono','SF Mono',Menlo,monospace;--serif:'EB Garamond',Georgia,serif;--cjk:'Noto Serif SC','Songti SC',serif;} *{box-sizing:border-box;} html,body{margin:0;padding:0;} body{background:var(--paper);color:var(--ink);font-family:var(--cjk);-webkit-font-smoothing:antialiased;line-height:1.95;} body::before{content:'';position:fixed;inset:0;pointer-events:none;background:radial-gradient(1200px 600px at 50% -10%,rgba(26,40,64,.05),transparent 60%);} .pp-wrap{max-width:680px;margin:0 auto;padding:0 28px 96px;} .pp-mast{display:flex;align-items:center;gap:10px;padding:30px 0 0;} .pp-mark{font-family:var(--mono);font-size:11px;letter-spacing:.46em;color:var(--sea);text-transform:uppercase;} .pp-rule{flex:1;height:1px;background:var(--rule);} .pp-flame{width:26px;height:3px;background:var(--flame);border-radius:2px;} .pp-hero{padding:54px 0 30px;border-bottom:1px solid var(--rule);} .pp-kicker{font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink3);margin-bottom:18px;} .pp-title{font-family:var(--cjk);font-weight:500;font-size:39px;line-height:1.28;margin:0;letter-spacing:.01em;} .pp-en{font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--ink2);margin:10px 0 0;} .pp-coords{display:flex;flex-wrap:wrap;gap:8px;margin-top:24px;} .pp-coord{font-family:var(--mono);font-size:11px;color:var(--ink2);background:var(--paper2);border:1px solid var(--rule);border-radius:6px;padding:4px 10px;} .pp-coord b{color:var(--ink3);font-weight:400;margin-right:6px;} .pp-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:14px;} .pp-tag{font-size:11px;padding:3px 9px;border-radius:5px;border:1px solid;} .pp-tag.cat{color:#534AB7;background:#EEEDFE;border-color:#cfccf5;} .pp-tag.note{color:#085041;background:#E1F5EE;border-color:#bfe6d8;} .pp-tag.loc{color:#7a5b00;background:#FDF6E0;border-color:#ecdca0;} .pp-body{padding:34px 0 0;font-family:var(--cjk);font-size:17px;line-height:1.98;color:#222;} .pp-body p{margin:0 0 18px;text-wrap:pretty;} .pp-body .pp-sub{font-family:var(--cjk);font-weight:500;margin:30px 0 12px;line-height:1.4;} .pp-body h2.pp-sub{font-size:21px;}.pp-body h3.pp-sub{font-size:18px;}.pp-body h4.pp-sub{font-size:16px;color:var(--ink2);} .pp-body blockquote{margin:0 0 18px;padding:6px 0 6px 20px;border-left:2px solid var(--gold);background:linear-gradient(90deg,#FDF6E0,transparent 80%);font-style:italic;color:#4a4636;} .pp-body blockquote p{margin:0 0 6px;}.pp-body blockquote p:last-child{margin:0;} .pp-body ul{margin:0 0 18px;padding-left:22px;}.pp-body li{margin:0 0 7px;} .pp-body strong{font-weight:600;color:#1c1c1c;}.pp-body em{font-style:italic;} .pp-body code{font-family:var(--mono);font-size:14px;background:var(--paper2);padding:1px 6px;border-radius:4px;} .pp-body pre{font-family:var(--mono);font-size:13.5px;line-height:1.7;background:var(--paper2);border:1px solid var(--rule);padding:14px 16px;border-radius:10px;overflow-x:auto;margin:0 0 18px;} .pp-body pre code{background:none;padding:0;font-size:13.5px;} .pp-body a{color:var(--flame);text-decoration:none;border-bottom:1px solid rgba(139,32,32,.32);} .pp-body hr{border:0;border-top:1px solid var(--rule);margin:24px 0;} .pp-empty{color:var(--ink3);font-style:italic;} .pp-media{display:flex;align-items:center;gap:14px;margin:0 0 18px;padding:16px 18px;border:1px solid var(--rule);border-radius:12px;background:repeating-linear-gradient(135deg,#fff,#fff 9px,#FBF7EF 9px,#FBF7EF 18px);} .pp-media-ico{flex:none;width:38px;height:38px;border-radius:9px;background:#fff;border:1px solid var(--rule);display:grid;place-items:center;color:var(--sea);} .pp-media-ico svg{width:21px;height:21px;} .pp-media-meta{display:flex;flex-direction:column;gap:3px;} .pp-media-meta b{font-family:var(--cjk);font-weight:500;font-size:14px;color:var(--ink);} .pp-media-meta i{font-family:var(--mono);font-style:normal;font-size:11px;color:var(--ink3);} .pp-video .pp-media-ico{color:var(--flame);}.pp-audio .pp-media-ico,.pp-voice .pp-media-ico{color:#085041;}.pp-link .pp-media-ico{color:#534AB7;} .pp-fig{margin:0 0 22px;} .pp-fig img{display:block;width:100%;height:auto;border-radius:12px;border:1px solid var(--rule);} .pp-fig-video video{display:block;width:100%;border-radius:12px;border:1px solid var(--rule);background:#000;} .pp-fig figcaption{font-family:var(--mono);font-size:11px;color:var(--ink3);margin-top:8px;text-align:center;} .pp-embed-frame{position:relative;width:100%;aspect-ratio:16/9;border-radius:12px;overflow:hidden;background:#000;border:1px solid var(--rule);} .pp-embed-frame iframe{position:absolute;inset:0;width:100%;height:100%;} .pp-audio{margin:0 0 22px;padding:16px 18px;border:1px solid var(--rule);border-radius:12px;background:var(--paper2);} .pp-audio .pp-audio-head{display:flex;align-items:center;gap:13px;margin-bottom:12px;} .pp-audio-ico{flex:none;width:38px;height:38px;border-radius:9px;background:#fff;border:1px solid var(--rule);display:grid;place-items:center;color:#085041;} .pp-audio-ico svg{width:21px;height:21px;} .pp-audio-meta{display:flex;flex-direction:column;gap:3px;} .pp-audio-meta b{font-family:var(--cjk);font-weight:500;font-size:14px;color:var(--ink);} .pp-audio-meta i{font-family:var(--mono);font-style:normal;font-size:11px;color:var(--ink3);} .pp-audio audio{width:100%;display:block;} .pp-voice{background:linear-gradient(180deg,#FBF3EE,var(--paper2));border-color:#E8C9BC;} .pp-voice .pp-audio-ico{color:var(--flame);border-color:#E8C9BC;} .pp-seal{display:flex;gap:16px;margin:46px 0 0;padding:20px 22px;background:var(--paper2);border:1px solid var(--rule);border-radius:14px;position:relative;} .pp-seal-fp{flex:none;width:40px;height:40px;border-radius:50%;background:#fff;border:1px solid var(--rule);display:grid;place-items:center;color:var(--sea);} .pp-seal-fp svg{width:23px;height:23px;} .pp-seal-body{flex:1;min-width:0;} .pp-seal-h{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink3);font-weight:600;margin-bottom:11px;} .pp-row{display:flex;gap:12px;font-size:12px;margin-bottom:7px;align-items:baseline;} .pp-row .k{flex:none;width:64px;color:var(--ink3);font-family:var(--cjk);} .pp-row .v{font-family:var(--mono);color:var(--ink2);word-break:break-all;} .pp-row .v.hash{color:var(--sea);} .pp-row .v.link{color:var(--flame);} .pp-seal-note{font-family:var(--cjk);font-size:11px;color:var(--ink3);line-height:1.7;margin-top:10px;padding-top:10px;border-top:1px solid var(--rule);} .pp-chop{flex:none;align-self:flex-start;width:32px;height:32px;background:var(--flame);color:#F6E4DC;font-family:var(--cjk);font-size:11px;line-height:1.12;text-align:center;padding-top:4px;border-radius:5px;letter-spacing:.04em;} .pp-foot{margin:40px 0 0;padding-top:22px;border-top:1px solid var(--rule);display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;} .pp-foot-l{font-family:var(--cjk);font-size:12px;color:var(--ink2);} .pp-foot-l b{font-family:var(--mono);letter-spacing:.3em;color:var(--sea);font-weight:400;text-transform:uppercase;font-size:11px;} .pp-foot-r{font-family:var(--cjk);font-size:11px;color:var(--ink3);font-style:italic;} @media(max-width:560px){.pp-wrap{padding:0 18px 64px;}.pp-title{font-size:30px;}} `; const FP_SVG = ''; // 完整自带样式的网页 function buildShowcaseHTML(o) { o = o || {}; const title = o.title || '未命名'; const coords = []; if (o.date) coords.push('制作' + escHtml(o.date) + ''); if (o.city) coords.push('地点' + escHtml(o.city) + ''); if (o.scene) coords.push('场景' + escHtml(o.scene) + ''); if (o.gps) coords.push('GPS' + escHtml(o.gps) + ''); const tags = [] .concat(o.cat ? ['' + escHtml(o.cat) + ''] : []) .concat((o.notes || []).map(t => '' + escHtml(t) + '')) .concat((o.locs || []).map(t => '' + escHtml(t) + '')); const kicker = [o.date, o.city].filter(Boolean).join(' · '); return '' + '' + '' + escHtml(title) + ' · popor' + '' + '' + '' + '' + '
' + '
popor
' + '
' + (kicker ? '
' + escHtml(kicker) + '
' : '') + '

' + escHtml(title) + '

' + (o.en ? '
' + escHtml(o.en) + '
' : '') + (coords.length ? '
' + coords.join('') + '
' : '') + (tags.length ? '
' + tags.join('') + '
' : '') + '
' + '
' + exportBodyToHtml(o.body, o.media) + '
' + '
' + FP_SVG + '' + '
数字凭证 · Proof of Authorship
' + '
确权时间' + escHtml(o.stamp || '') + '
' + '
内容指纹' + escHtml(o.hash || '') + '
' + (o.shareId ? '
分享链接popor.ink/p/' + escHtml(o.shareId) + '
' : '') + '
指纹由内容经 SHA-256 计算(此处为演示哈希),可锚定到公链时间戳(OpenTimestamps),作为"这页文字在此刻已存在且未被篡改"的证据——原文不上链,隐私保留。
' + '

' + '' + '
'; } // 网页幻灯片(内测):封面 + 按段落分页,← → 翻页,可打印成 PDF/PPT function buildSlidesHTML(o) { o = o || {}; const title = o.title || '未命名'; const blocks = exportBodyToHtml(o.body, o.media) .split(/(?= s.trim()).filter(Boolean); // 把内容聚成每页 1–2 块 const slides = []; for (let i = 0; i < blocks.length; i += 2) slides.push(blocks.slice(i, i + 2).join('')); const kicker = [o.date, o.city].filter(Boolean).join(' · '); const cover = '
' + (kicker ? '
' + escHtml(kicker) + '
' : '') + '

' + escHtml(title) + '

' + (o.en ? '
' + escHtml(o.en) + '
' : '') + '
用 popor 书写
'; const content = slides.map((s, i) => '
' + String(i + 1).padStart(2, '0') + '
' + s + '
' ).join(''); const sealSlide = '
数字凭证
' + '
' + escHtml(o.stamp || '') + '
' + '
' + escHtml(o.hash || '') + '
' + (o.shareId ? '' : '') + '
'; const css = SHOWCASE_CSS + ` body{overflow:hidden;} .sl{position:fixed;inset:0;display:none;place-items:center;padding:7vmin;} .sl.on{display:grid;} .sl-in{width:min(900px,92vw);} .sl.cover .sl-in{text-align:center;} .sl-kick{font-family:var(--mono);font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink3);margin-bottom:24px;} .sl.cover h1{font-family:var(--cjk);font-weight:500;font-size:6vmin;line-height:1.25;margin:0;} .sl-en{font-family:var(--serif);font-style:italic;font-size:3vmin;color:var(--ink2);margin-top:14px;} .sl-mark{font-family:var(--mono);font-size:11px;letter-spacing:.34em;text-transform:uppercase;color:var(--sea);margin-top:42px;} .sl-no{font-family:var(--mono);font-size:13px;color:var(--flame);letter-spacing:.1em;margin-bottom:22px;} .sl-body{font-size:2.7vmin;line-height:1.85;} .sl-body p{margin:0 0 16px;} .sl-body .pp-media{font-size:14px;} .sl-body .pp-fig{margin:0;} .sl-body .pp-fig img,.sl-body .pp-fig-video video{max-height:60vh;width:auto;max-width:100%;margin:0 auto;} .sl-body .pp-embed-frame{max-width:78vh;margin:0 auto;} .sl-body .pp-audio{font-size:14px;} .sl.seal{background:var(--sea);} .sl.seal .sl-in{text-align:center;color:var(--sea-text,#C8DDF0);} .sl-seal-h{font-family:var(--mono);font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:#8aa3c8;margin-bottom:24px;} .sl-seal-row{font-family:var(--cjk);font-size:2.4vmin;color:#C8DDF0;margin-bottom:12px;} .sl-seal-row.mono{font-family:var(--mono);font-size:1.8vmin;color:#9fb6d8;word-break:break-all;} .sl-seal-row.link{color:#E8A28C;font-family:var(--mono);} .sl-nav{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:16px;font-family:var(--mono);font-size:12px;color:var(--ink3);z-index:5;} .sl-nav button{font:inherit;color:var(--ink2);background:#fff;border:1px solid var(--rule);border-radius:8px;width:34px;height:30px;cursor:pointer;} .sl-nav button:hover{border-color:var(--ink);} @media print{.sl{display:grid!important;position:relative;page-break-after:always;height:100vh;}.sl-nav{display:none;}} `; const all = cover + content + sealSlide; const n = slides.length + 2; return '' + '' + '' + escHtml(title) + ' · 幻灯片' + '' + '' + '' + all + '
1 / ' + n + '← → 翻页 · Ctrl/⌘+P 存 PDF
' + '