Commit 122b780
authored
feat(export): add Notion, Bilibili, Mastodon, Bluesky, Markdown round-trip targets (#19)
* feat(export): add notion, bilibili, mastodon, bluesky, markdown-roundtrip targets
- notion.ts: juice-inline body + drop section wrappers and class/data-* noise;
keep `language-*` on <code> so Notion picks the right code mode.
- bilibili.ts: walk DOM through a 专栏 tag whitelist; replace non-bili-CDN
images with a "re-upload to bilibili" placeholder, stash the original src
on data-original-src so the user can recover it.
- mastodon.ts / bluesky.ts: build {blob, text, filename} share bundles —
screenshot via the existing iframeToBlob, caption trimmed by code-point
count to 500 / 300 chars (so emoji and surrogate pairs don't get sliced).
Image and text go on the clipboard via separate calls — browsers refuse
mixed-mime writes.
- markdown-roundtrip.ts: focused HTML → GFM walker covering the subset the
editor renders (headings, lists with nesting, fenced code with language,
tables, links, images, blockquotes). Skipped turndown to avoid pulling a
large dep for a narrow input.
Wire-up into the export menu is left for a follow-up so this PR stays
mechanical.
* fix(export): tighten markdown emitter, bilibili code class, mastodon comment
Addresses review feedback on the new export targets:
- markdown-roundtrip: use longest-backtick-run fences for inline + fenced
code so embedded backticks don't break the block; URL-escape link/image
hrefs with whitespace or parens via the angle-bracket form, and escape
quotes/backslashes in titles; backslash-escape brackets inside link
text and img alt; new escapeBlockStarts pass keeps a paragraph that
starts with "# ", "> ", "- ", "1. ", etc. from being parsed as a heading
or list; renderList now partitions <li> children into inline / nested-
list / other-block buckets so paragraphs and code blocks inside list
items render with a proper continuation indent.
- bilibili: filter <code class> to language-* only so arbitrary highlight
classes don't leak through; drop dead "caller decides" comment.
- mastodon: clarify that truncateForPost counts code points, not graphemes
— ZWJ emoji sequences are conservatively counted as multiple units.
* test(export): vitest coverage for notion/bilibili/mastodon/markdown emitters
Adds vitest + happy-dom and 50 tests covering the new export targets.
Each test runs in a real DOM environment; the markdown emitter tests
additionally pipe the output through `marked` (already a runtime dep)
to verify the produced Markdown round-trips into the same structural
shape — closest "simulate real" check we can do without spinning up
Hugo / 11ty / Obsidian.
- markdown-roundtrip.test.ts (25): headings, inline marks (with literal
*, _, ~ kept literal), inline code with backticks (longest-fence
rule), fenced code with embedded triple backticks, links/images
(angle-bracket URL form for spaces+parens, title quote escape,
bracket escape in link text), line-start block-marker escaping
(paragraph starting with `1. `, `# `, `- `, `> ` stays a paragraph),
nested lists, list item with paragraph + code block, blockquote,
table with pipe escape, hr, full-document smoke.
- bilibili.test.ts (9): whitelist enforcement, attribute filtering,
language-* class retention on <code>, CDN vs. non-CDN image rewrite.
- notion.test.ts (8): section/article/header/footer/main unwrapping,
data-* + class stripping, style attribute preserved, code language-*
class retention, language-plaintext fallback, <pre> without <code>
gets wrapped.
- mastodon.test.ts (8): whitespace collapse, surrogate-pair safety,
ZWJ-emoji conservative behavior, boundary cases.
While writing these:
- Hoisted the first <p>'s inline content onto the list-marker line when
an <li> has no inline children; otherwise marked saw "1. \n\n text"
and started a new list at the next marker.
- Tightened notion.ts <code class> filter to keep only language-*
tokens (matches bilibili.ts; arbitrary highlighter classes no longer
leak through).
`pnpm test` (now wired in package.json) runs the full suite; `tsc
--noEmit` clean.
* fix(export): CDN lookalike, nested-in-link mangle, table cell <br>
Addresses the three non-blocking findings from PerishCode's review:
- bilibili `isBilibiliCdn`: tighten the regex so `evilhdslb.com` /
`notbilibili.com` no longer pass as valid CDNs. Require either an
exact host match or a subdomain dot before the literal domain.
Without this, the placeholder swap was skipped for lookalike URLs
and the user would silently lose the image after bilibili's
publish-time scrub.
- markdown emitter, nested elements inside <a>: move bracket escape
into `escapeMd` at the text-node level instead of wrapping the
fully-composed `inner()` string. The old approach mangled
<a><img src=y alt=a></a> to `[!\[a\](y)](href)` so the inner
image no longer round-tripped; same for `<a><code>x</code></a>`
where literal backslashes appeared inside the code span.
- renderTable: GFM tables require one row per source line, so a
literal \n inside a cell (from <br>, <p>, etc.) terminates the
row early and collapses every downstream row. Collapse intra-cell
\r?\n+ to literal `<br>` (inline HTML is permitted inside GFM
cells) before pipe-escaping.
Tests added: bilibili lookalike + subdomain matrix; markdown emitter
`<a><img>` (badge link), `<a><code>` (no stray backslashes), and
`<td>line1<br>line2</td>` (table stays intact). 55/55 green.1 parent b799c28 commit 122b780
9 files changed
Lines changed: 1330 additions & 0 deletions
File tree
- next/src/lib/export
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
0 commit comments