feat(parser): v0.1.15 — parser short+medium term improvements

Short-term fixes:
- Fix ***bold italic*** / ___bold italic___ triple emphasis
- Fix link text inline formatting (recursive renderInline on [**bold**](url))
- Fix backtick inline code nesting (CommonMark-compliant extractInlineCodes)
- Fix tab indentation stripping in indented code blocks

Medium-term improvements:
- parseTokens() / renderTokens() separation API for token-level transform
- registerBlockHandler() table-driven block handler registry
- Backslash escape support (\* \_ \)

Also:
- Add 48 new test cases (583 total, 7 suites, all passing)
- Parser coverage: 95.43% stmts / 91.38% branches / 100% funcs / 98.25% lines
- Update README.md, site/, package.json to v0.1.15
This commit is contained in:
2026-07-24 21:39:11 +08:00
parent c6db68af59
commit d7cae48073
8 changed files with 968 additions and 253 deletions
+45 -4
View File
@@ -176,7 +176,7 @@
<header class="docs-header">
<h1><span class="grad">文档</span></h1>
<p class="sub">MetonaEditor v0.1.14 完整 API、配置与使用指南</p>
<p class="sub">MetonaEditor v0.1.15 完整 API、配置与使用指南</p>
</header>
<div class="container">
@@ -201,6 +201,7 @@
<a href="#static-api">顶层函数</a>
<div class="toc-group">语法参考</div>
<a href="#syntax">解析器语法</a>
<a href="#parser-api">解析器 APIv0.1.15</a>
<div class="toc-group">插件</div>
<a href="#plugins">插件约定</a>
<a href="#preset-plugins">预设插件</a>
@@ -475,6 +476,7 @@ MeEditor<span class="c-punc">.</span><span class="c-fn">destroy</span><span clas
<tr><td>标题</td><td><code># H1</code> ~ <code>###### H6</code></td><td><code>&lt;h1&gt;</code> ~ <code>&lt;h6&gt;</code></td></tr>
<tr><td>段落</td><td>纯文本</td><td><code>&lt;p&gt;</code></td></tr>
<tr><td>粗体</td><td><code>**bold**</code> <code>__bold__</code></td><td><code>&lt;strong&gt;</code></td></tr>
<tr><td>粗斜体 <span class="badge">v0.1.15</span></td><td><code>***text***</code> <code>___text___</code></td><td><code>&lt;em&gt;&lt;strong&gt;</code></td></tr>
<tr><td>斜体</td><td><code>*italic*</code> <code>_italic_</code></td><td><code>&lt;em&gt;</code></td></tr>
<tr><td>删除线</td><td><code>~~text~~</code></td><td><code>&lt;del&gt;</code></td></tr>
<tr><td>高亮标记</td><td><code>==text==</code></td><td><code>&lt;mark&gt;</code></td></tr>
@@ -488,16 +490,55 @@ MeEditor<span class="c-punc">.</span><span class="c-fn">destroy</span><span clas
<tr><td>任务列表</td><td><code>- [x] done</code></td><td><code>&lt;li class="me-task-item"&gt;</code></td></tr>
<tr><td>水平线</td><td><code>---</code> <code>***</code> <code>___</code></td><td><code>&lt;hr&gt;</code></td></tr>
<tr><td>表格</td><td><code>| a | b |</code></td><td><code>&lt;table&gt;</code>(支持 <code>:---:</code> 列对齐)</td></tr>
<tr><td>链接</td><td><code>[text](url)</code></td><td><code>&lt;a&gt;</code></td></tr>
<tr><td>链接 v0.1.15</td><td><code>[**text**](url)</code></td><td>链接文本内支持行内格式</td></tr>
<tr><td>图片</td><td><code>![alt](url)</code></td><td><code>&lt;img&gt;</code></td></tr>
<tr><td>自动链接</td><td><code>&lt;https://...&gt;</code></td><td><code>&lt;a&gt;</code></td></tr>
<tr><td>Emoji</td><td><code>:smile:</code> <code>:rocket:</code></td><td>😊 🚀(80+ 常用)</td></tr>
<tr><td>Emoji</td><td><code>:smile:</code> <code>:rocket:</code></td><td>😊 🚀(150+ 常用)</td></tr>
<tr><td>反斜杠转义 <span class="badge">v0.1.15</span></td><td><code>\*</code> <code>\_</code> <code>\\</code></td><td>取消标点特殊含义</td></tr>
</table>
<h3>XSS 防护</h3>
<p>所有文本经 HTML 转义。URL 自动过滤 <code>javascript:</code> <code>vbscript:</code> <code>file:</code> 及非图片 <code>data:</code> 协议。</p>
</section>
<!-- ======== 解析器 API v0.1.15 ======== -->
<section id="parser-api">
<h2>解析器 API <span class="badge">v0.1.15 新增</span></h2>
<h3>parseTokens / renderTokens(解析与渲染分离)</h3>
<p>将 Markdown 解析为中间 Token 数组,可在渲染前变换,再输出 HTML。</p>
<pre><span class="c-kw">import</span> <span class="c-punc">{</span> parseTokens<span class="c-punc">,</span> renderTokens <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-com">// 解析为 Token 数组</span>
<span class="c-kw">const</span> <span class="c-punc">{</span> tokens<span class="c-punc">,</span> footnotes <span class="c-punc">}</span> <span class="c-punc">=</span> <span class="c-fn">parseTokens</span><span class="c-punc">(</span><span class="c-str">'# Hello\n\n**world**'</span><span class="c-punc">);</span>
<span class="c-com">// tokens = [</span>
<span class="c-com">// { type: 'heading', level: 1, text: 'Hello' },</span>
<span class="c-com">// { type: 'paragraph', text: '**world**' }</span>
<span class="c-com">// ]</span>
<span class="c-com">// 你可以在渲染前变换 token 数组</span>
tokens<span class="c-punc">.</span><span class="c-fn">unshift</span><span class="c-punc">({</span> type<span class="c-punc">:</span> <span class="c-str">'hr'</span> <span class="c-punc">});</span>
<span class="c-com">// 渲染为 HTML</span>
<span class="c-kw">const</span> html <span class="c-punc">=</span> <span class="c-fn">renderTokens</span><span class="c-punc">(</span>tokens<span class="c-punc">,</span> env<span class="c-punc">,</span> footnotes<span class="c-punc">);</span></pre>
<h3>registerBlockHandler(注册自定义块级语法)</h3>
<p>无需替换整个解析器,按优先级注册自定义块级语法处理器。</p>
<pre><span class="c-kw">import</span> <span class="c-punc">{</span> registerBlockHandler <span class="c-punc">}</span> <span class="c-kw">from</span> <span class="c-str">'@metona-team/metona-editor'</span><span class="c-punc">;</span>
<span class="c-fn">registerBlockHandler</span><span class="c-punc">({</span>
name<span class="c-punc">:</span> <span class="c-str">'customAlert'</span><span class="c-punc">,</span>
priority<span class="c-punc">:</span> <span class="c-num">8.5</span><span class="c-punc">,</span> <span class="c-com">// 越小越先尝试</span>
test<span class="c-punc">:</span> <span class="c-punc">(</span>line<span class="c-punc">)</span> <span class="c-punc">=></span> line<span class="c-punc">.</span><span class="c-fn">match</span><span class="c-punc">(</span><span class="c-re">/^:::(\w+)/</span><span class="c-punc">),</span>
parse<span class="c-punc">:</span> <span class="c-punc">(</span>lines<span class="c-punc">,</span> i<span class="c-punc">,</span> match<span class="c-punc">)</span> <span class="c-punc">=></span> <span class="c-punc">{</span>
<span class="c-com">// 返回 { token: {...}, newIndex }</span>
<span class="c-kw">return</span> <span class="c-punc">{</span> token<span class="c-punc">:</span> <span class="c-punc">{</span> type<span class="c-punc">:</span> <span class="c-str">'alert'</span><span class="c-punc">,</span> alertType<span class="c-punc">:</span> match<span class="c-punc">[</span><span class="c-num">1</span><span class="c-punc">]</span> <span class="c-punc">},</span> newIndex<span class="c-punc">:</span> j <span class="c-punc">};</span>
<span class="c-punc">},</span>
<span class="c-punc">});</span></pre>
<p>TIP:如果自定义 token 的 <code>type</code> 不被 <code>renderTokens</code> 识别,可以在渲染前通过 <code>tokens.map()</code> 转换为已知 type,或自行实现渲染逻辑。</p>
</section>
<!-- ======== 插件 ======== -->
<section id="plugins">
<h2>插件约定</h2>
@@ -619,7 +660,7 @@ i18nUtils<span class="c-punc">.</span><span class="c-fn">formatDate</span><span
<footer>
<div class="container">
MetonaEditor v0.1.14 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
MetonaEditor v0.1.15 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
</div>
</footer>