release: v0.1.4 — parser enhancement, new syntax, performance optimization

feat(parser): comprehensive Markdown parser enhancement
- Add nested list support (multi-level unordered/ordered, mixed nesting)
- Add Setext headings (=== / ---)
- Add indented code blocks (4-space indent)
- Add HTML comment passthrough (<!-- -->)
- Add entity reference protection (&amp; &#169;)
- Add ordered list start attribute
- Add link title single-quote support
- Add LaTeX math formulas ($inline$ / $$block$$)
- Add footnote support ([^1] ref + [^1]: definition)
- Add definition lists (term\n: definition)
- Extend emoji map from 85 to 150+ common emojis
- Fix backtick code block matching (CommonMark spec)
- Fix italic regex character truncation (lookbehind assertions)
- Fix superscript/subscript/strikethrough ambiguity
- Fix blockquote prefix handling (>text without space)
- Enhance safeUrl filtering (additional protocol checks)
- Enhance slugify (Unicode NFKC normalization)

perf(parser): single-pass inline scanning, render cache, pre-compiled regex
- Replace 14-step chained regex with unified single-pass scanInline()
- Add cachedRenderInline() with FIFO LRU eviction (300-entry cap)
- Pre-compile 15+ static regex constants for block boundary detection
- Skip redundant style='text-align:left' on default-aligned table cells

style(parser): add CSS for footnotes, definition lists, math formulas, sup/sub

test(parser): 44 new test cases covering all v0.1.4 syntax additions (469 total)

chore: bump version 0.1.3 → 0.1.4 across all source files, docs, site pages
This commit is contained in:
2026-07-24 09:35:51 +08:00
parent 2eb86d29b6
commit f8b9f4a761
19 changed files with 1128 additions and 198 deletions
+16 -8
View File
@@ -2,9 +2,9 @@
> 轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用,中文优先。
[![npm version](https://img.shields.io/badge/version-0.1.3-blue.svg)](https://www.npmjs.com/package/@metona-team/metona-editor)
[![npm version](https://img.shields.io/badge/version-0.1.4-blue.svg)](https://www.npmjs.com/package/@metona-team/metona-editor)
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
[![tests](https://img.shields.io/badge/tests-425%20passed-brightgreen.svg)](./tests)
[![tests](https://img.shields.io/badge/tests-469%20passed-brightgreen.svg)](./tests)
[![coverage](https://img.shields.io/badge/coverage-97%25-brightgreen.svg)](./tests)
---
@@ -17,7 +17,7 @@
- **易维护** — 模块化源码,JSDoc 注释完整,TypeScript 类型声明,425 个单元测试覆盖
- **易使用** — 工厂函数 `create()` 一行接入,链式 API,中文优先文档与翻译
- **安全** — 内置 XSS 防护(`javascript:`/`vbscript:`/`file:` 协议过滤),HTML 转义,`safeUrl` 净化
- **内置解析器** — 自研 CommonMark 子集 + GFM 扩展(表格含列对齐、任务列表、emoji 短码、上下标),可整体替换
- **内置解析器** — 自研 CommonMark 子集 + GFM 扩展(表格含列对齐、任务列表、上下标、数学公式、脚注、嵌套列表、emoji 短码),可整体替换
- **三模式视图** — edit / split / preview 自由切换,分屏拖拽调整比例,滚动同步,模式切换保持滚动位置
- **主题系统** — light / dark / auto / warm 四套预设,CSS 变量定制,跟随系统主题,@media print 打印样式
- **国际化** — zh-CN / en-US 完整翻译,RTL 支持,`Intl` 数字/货币/日期格式化
@@ -680,6 +680,7 @@ MetonaEditor 内置自研轻量解析器,支持 CommonMark 子集 + GFM 扩展
| 语法 | 示例 | 输出 |
|------|------|------|
| 标题 | `# H1` `## H2` | `<h1>` ~ `<h6>` |
| Setext 标题 | `H1\n===` | `<h1>` / `<h2>` |
| 段落 | 纯文本 | `<p>` |
| 粗体 | `**bold**` `__bold__` | `<strong>` |
| 斜体 | `*italic*` `_italic_` | `<em>` |
@@ -689,16 +690,23 @@ MetonaEditor 内置自研轻量解析器,支持 CommonMark 子集 + GFM 扩展
| 下标 | `H~2~O` | `<sub>` |
| 行内代码 | `` `code` `` | `<code>` |
| 代码块 | ` ```lang ` | `<pre><code class="language-lang">` |
| 缩进代码块 | ` code` | `<pre><code>` |
| 引用 | `> quote` | `<blockquote>` |
| 无序列表 | `- item` / `* item` / `+ item` | `<ul><li>` |
| 有序列表 | `1. item` | `<ol><li>` |
| 嵌套列表 | `- p\n - sub` | 多级 `<ul>/<ol>` |
| 有序列表 | `1. item` / `3. start` | `<ol start="3">` |
| 任务列表 | `- [x] done` | `<li class="me-task-item">` |
| 水平线 | `---` `***` `___` | `<hr>` |
| 表格 | `\| a \| b \|` | `<table>`(含 `:---:` 列对齐) |
| 链接 | `[text](url)` | `<a>` |
| 定义列表 | `Term\n: def` | `<dl><dt><dd>` |
| 链接 | `[text](url 'title')` | `<a>` |
| 图片 | `![alt](url)` | `<img>` |
| 自动链接 | `<https://...>` | `<a>` |
| Emoji 短码 | `:smile:` `:rocket:` | 😊 🚀(80+ 常用) |
| 数学公式 | `$E=mc^2$` `$$\int$$` | `<span>/<div>` |
| 脚注 | `text[^1]` | `<sup>` + 底部定义 |
| Emoji 短码 | `:smile:` `:rainbow:` | 😊 🌈(150+ 常用) |
| HTML 注释 | `<!-- note -->` | 透传 |
| 实体引用保护 | `&amp;` `&#169;` | 不被二次转义 |
### XSS 防护
@@ -806,9 +814,9 @@ npm run test:coverage
| core.js | 99.5% | 96.6% | 98.2% |
| **总体** | **96.9%** | **80.3%** | **94.6%** |
测试文件位于 [tests/](./tests) 目录,共 **425 个测试用例**,覆盖:
测试文件位于 [tests/](./tests) 目录,共 **469 个测试用例**,覆盖:
- `parser.test.js` — Markdown 解析器全部语法(含新增上下标/高亮/emoji/表格对齐 + XSS 防护
- `parser.test.js` — Markdown 解析器全部语法(含 v0.1.4 新增:嵌套列表、Setext标题、缩进代码块、HTML注释、实体引用保护、链接单引号title、LaTeX数学公式、脚注、定义列表、反引号精确匹配、130+ 表情扩展 + XSS 防护
- `utils.test.js` — 工具函数(generateId / escapeHTML / debounce / throttle / deepMerge 等)
- `core.test.js` — MarkdownEditor 构造、内容 API、exec 命令(含自定义动作)、历史栈、模式、事件、插件、销毁、readOnly
- `plugins.test.js` — 预设插件(autoSave / exportTool / searchReplace / imagePaste+ PluginManager + pluginUtils
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@metona-team/metona-editor",
"version": "0.1.3",
"version": "0.1.4",
"description": "轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用。",
"type": "module",
"main": "dist/metona-editor.js",
+27 -2
View File
@@ -313,9 +313,34 @@
'',
'| 名称 | 值 |',
'| --- | --- |',
'| 版本 | 0.1.3 |',
'| 版本 | 0.1.4 |',
'| 依赖 | 零 |',
'| 测试 | 390+ |',
'| 测试 | 469+ |',
'',
'## 新增语法',
'',
'### 数学公式',
'',
'- 行内:$E=mc^2$',
'- 块级:$$\\int_0^\\infty e^{-x}dx$$',
'',
'### 嵌套列表',
'',
'- 一级',
' - 二级',
' - 三级',
'',
'### 脚注',
'',
'这是带脚注的文字[^1]',
'',
'[^1]: 这是脚注内容',
'',
'### 定义列表',
'',
'Markdown',
': 一种轻量级标记语言。',
': 由 John Gruber 创建。',
'',
'## 任务列表',
'',
+2 -2
View File
@@ -175,7 +175,7 @@
<header class="docs-header">
<h1><span class="grad">文档</span></h1>
<p class="sub">MetonaEditor v0.1.3 完整 API、配置与使用指南</p>
<p class="sub">MetonaEditor v0.1.4 完整 API、配置与使用指南</p>
</header>
<div class="container">
@@ -598,7 +598,7 @@ i18nUtils<span class="c-punc">.</span><span class="c-fn">formatDate</span><span
<footer>
<div class="container">
MetonaEditor v0.1.3 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
MetonaEditor v0.1.4 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
</div>
</footer>
+2 -2
View File
@@ -467,8 +467,8 @@
<div class="feature-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
</div>
<h3>内置轻量解析器</h3>
<p>自研 CommonMark 子集 + GFM 扩展解析器,覆盖标题、表格、任务列表、代码块等常用语法,也可整体替换。</p>
<h3>内置增强解析器</h3>
<p>自研 CommonMark + GFM 扩展解析器,覆盖数学公式、脚注、嵌套列表、定义列表、表格对齐、任务列表等丰富语法,也可整体替换。</p>
</div>
<div class="feature">
<div class="feature-icon">
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Animations - 动画管理
* @module animations
* @version 0.1.3
* @version 0.1.4
* @description 动画注册与管理(当前为 CSS 动画元数据管理,供未来扩展如 Toast/通知组件的进出场动画使用;
* 内置 7 种预设动画曲线配置;编辑器核心当前未直接调用此模块)
*/
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Constants - 常量定义
* @module constants
* @version 0.1.3
* @version 0.1.4
* @description 默认配置、主题、动画、工具栏等常量
*/
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Core - 编辑器核心
* @module core
* @version 0.1.3
* @version 0.1.4
* @description MarkdownEditor 类实现:DOM 构建、事件、渲染、历史栈、选区操作、模式切换
*/
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor i18n - 国际化管理
* @module i18n
* @version 0.1.3
* @version 0.1.4
* @description 多语言支持、语言切换和翻译管理
*/
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Icons — 工具栏图标SVG定义
* @module icons
* @version 0.1.3
* @version 0.1.4
* @description 工具栏格式化按钮 SVG 图标
*/
+5 -3
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor - 轻量级 Markdown Editor 库
* @module metona-editor
* @version 0.1.3
* @version 0.1.4
* @author thzxx
* @description 现代化、零依赖、易扩展、易维护、易使用的 Markdown 编辑器库。单文件,开箱即用。
* @license MIT
@@ -21,7 +21,7 @@
*/
import { MarkdownEditor } from './core.js';
import { parseMarkdown, safeUrl, slugify } from './parser.js';
import { parseMarkdown, safeUrl, slugify, clearRenderCache } from './parser.js';
import { themeUtils } from './themes.js';
import { i18nUtils } from './i18n.js';
import { pluginUtils, presetPlugins } from './plugins.js';
@@ -29,7 +29,7 @@ import { animationUtils } from './animations.js';
import { DEFAULTS, ICONS, THEMES, EDIT_MODES, DEFAULT_TOOLBAR, TOOLBAR_ACTIONS } from './constants.js';
// 版本信息
const VERSION = '0.1.3';
const VERSION = '0.1.4';
/**
* 全局默认插件队列
@@ -193,6 +193,7 @@ const api = {
parseMarkdown,
safeUrl,
slugify,
clearRenderCache,
// 工具集
themes: themeUtils,
@@ -233,6 +234,7 @@ export {
parseMarkdown,
safeUrl,
slugify,
clearRenderCache,
themeUtils,
i18nUtils,
pluginUtils,
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Locales — 国际化翻译数据
* @module locales
* @version 0.1.3
* @version 0.1.4
* @description 内置 zh-CN / en-US 完整翻译
*/
+696 -159
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Plugins - 插件系统
* @module plugins
* @version 0.1.3
* @version 0.1.4
* @description 插件管理器 + 预设插件(autoSave / exportTool / searchReplace
*
* 插件约定:
+51 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Styles - 编辑器样式
* @module styles
* @version 0.1.3
* @version 0.1.4
* @description 编辑器 UI 样式注入与管理
*/
@@ -342,6 +342,56 @@ const generateCSS = () => {
border-radius: 3px;
}
/* ============ 上标/下标 ============ */
.me-preview sup { font-size: 0.75em; vertical-align: super; line-height: 1; }
.me-preview sub { font-size: 0.75em; vertical-align: sub; line-height: 1; }
/* ============ 数学公式 ============ */
.me-preview .me-math-block {
display: block;
margin: 1.2em 0;
padding: 12px 16px;
background: var(--md-code-bg);
border-radius: 8px;
overflow-x: auto;
font-family: var(--md-mono);
font-size: 0.95em;
text-align: center;
}
.me-preview .me-math-inline {
font-family: var(--md-mono);
font-size: 0.95em;
padding: 0.05em 0.2em;
}
/* ============ 定义列表 ============ */
.me-preview dl { margin: 0.8em 0; }
.me-preview dt { font-weight: 650; margin: 0.6em 0 0.2em; }
.me-preview dd { margin: 0 0 0.3em 1.6em; color: var(--md-text); }
/* ============ 脚注 ============ */
.me-preview .me-footnote-ref a {
font-size: 0.75em;
vertical-align: super;
text-decoration: none;
color: var(--md-accent);
}
.me-preview .me-footnotes {
margin-top: 2em;
border-top: 1px solid var(--md-border);
padding-top: 0.8em;
font-size: 0.9em;
color: var(--md-muted);
}
.me-preview .me-footnotes hr { display: none; }
.me-preview .me-footnotes ol { padding-left: 1.2em; }
.me-preview .me-footnote-item { margin: 0.3em 0; }
.me-preview .me-footnote-backref {
text-decoration: none;
color: var(--md-accent);
margin-right: 0.4em;
}
/* ============ 打印样式 ============ */
@media print {
.me-wrapper { border: 0 !important; box-shadow: none !important; }
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Themes - 主题管理
* @module themes
* @version 0.1.3
* @version 0.1.4
* @description 主题系统、自定义主题和主题切换
*/
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* MetonaEditor Utils - 工具函数
* @module utils
* @version 0.1.3
* @version 0.1.4
* @description 通用工具函数集合
*/
+313 -7
View File
@@ -185,9 +185,15 @@ describe('parseMarkdown - 列表', () => {
test('有序列表', () => {
const html = parseMarkdown('1. one\n2. two');
expect(html).toContain('<ol>');
expect(html).toContain('<li>one</li>');
expect(html).toContain('<li>two</li>');
expect(html).toContain('<ol');
expect(html).toContain('>one</li>');
expect(html).toContain('>two</li>');
});
test('有序列表自定义起始编号', () => {
const html = parseMarkdown('3. three\n4. four');
expect(html).toContain('<ol');
expect(html).toContain('value="3"');
});
test('任务列表未完成', () => {
@@ -228,10 +234,10 @@ describe('parseMarkdown - 表格', () => {
const html = parseMarkdown(md);
expect(html).toContain('<table>');
expect(html).toContain('<thead>');
expect(html).toContain('<th style="text-align:left">a</th>');
expect(html).toContain('<th style="text-align:left">b</th>');
expect(html).toContain('<td style="text-align:left">1</td>');
expect(html).toContain('<td style="text-align:left">2</td>');
expect(html).toContain('<th>a</th>');
expect(html).toContain('<th>b</th>');
expect(html).toContain('<td>1</td>');
expect(html).toContain('<td>2</td>');
});
test('表格被 me-table-wrap 包裹', () => {
@@ -391,3 +397,303 @@ describe('parseMarkdown - v0.1.2 新语法', () => {
expect(html).toContain('text-align:right');
});
});
// ============ v0.1.4 新增语法测试 ============
describe('parseMarkdown - v0.1.4 Setext 标题', () => {
test('Setext h1 ===', () => {
const html = parseMarkdown('Heading 1\n=======');
expect(html).toContain('<h1');
expect(html).toContain('Heading 1');
});
test('Setext h2 ---', () => {
const html = parseMarkdown('Heading 2\n-------');
expect(html).toContain('<h2');
expect(html).toContain('Heading 2');
});
test('Setext 标题生成 id', () => {
const html = parseMarkdown('My Title\n===');
expect(html).toContain('id="my-title"');
});
test('Setext 与 ATX 共存正确', () => {
const html = parseMarkdown('ATX\n===\n\n# Another');
expect(html).toContain('<h1');
expect(html).toContain('ATX');
expect(html).toContain('Another');
});
});
describe('parseMarkdown - v0.1.4 嵌套列表', () => {
test('两层无序列表嵌套', () => {
const html = parseMarkdown('- parent\n - child');
expect(html).toContain('<ul>');
expect(html).toContain('parent');
expect(html).toContain('child');
});
test('两层有序列表嵌套', () => {
const html = parseMarkdown('1. first\n 1. sub');
expect(html).toContain('<ol');
expect(html).toContain('first');
expect(html).toContain('sub');
});
test('无序列表内嵌套有序列表', () => {
const html = parseMarkdown('- item\n 1. sub1\n 2. sub2');
expect(html).toContain('<ul>');
expect(html).toContain('<ol');
expect(html).toContain('sub1');
expect(html).toContain('sub2');
});
test('三级嵌套', () => {
const html = parseMarkdown('- l1\n - l2\n - l3');
expect(html).toContain('l1');
expect(html).toContain('l2');
expect(html).toContain('l3');
});
test('嵌套任务列表', () => {
const html = parseMarkdown('- parent\n - [x] done');
expect(html).toContain('me-task-item');
expect(html).toContain('checked');
});
});
describe('parseMarkdown - v0.1.4 缩进代码块', () => {
test('4空格缩进代码块', () => {
const html = parseMarkdown(' code line 1\n code line 2');
expect(html).toContain('<pre><code>');
expect(html).toContain('code line 1');
expect(html).toContain('code line 2');
});
test('缩进代码块内容被转义', () => {
const html = parseMarkdown(' <div>');
expect(html).toContain('<pre><code>');
expect(html).not.toContain('<div>');
});
});
describe('parseMarkdown - v0.1.4 HTML 注释', () => {
test('HTML 注释被透传', () => {
const html = parseMarkdown('<!-- comment -->');
expect(html).toContain('<!--');
expect(html).toContain('comment');
expect(html).toContain('-->');
});
test('HTML 注释内内容不被转义', () => {
const html = parseMarkdown('<!-- <b>bold</b> -->');
expect(html).toContain('<!-- <b>bold</b> -->');
});
});
describe('parseMarkdown - v0.1.4 实体引用保护', () => {
test('已有实体引用不被二次转义', () => {
const html = parseMarkdown('AT&amp;T');
expect(html).toContain('&amp;');
// &amp; 应保持为 &amp; 而非 &amp;amp;
expect(html).not.toContain('&amp;amp;');
});
test('数字实体引用被保护', () => {
const html = parseMarkdown('&#169; 2024');
expect(html).toContain('&#169;');
});
test('十六进制实体引用被保护', () => {
const html = parseMarkdown('&#x3C;');
expect(html).toContain('&#x3C;');
});
});
describe('parseMarkdown - v0.1.4 硬换行', () => {
test('行尾2空格产生硬换行', () => {
const html = parseMarkdown('line1 \nline2');
// 硬换行不应产生单独的 <br/>,而是保持为真正的 <br/>
// 软换行 \n 产生 <br/>,硬换行(2空格+\n)也应产生 <br/>
// 当前实现:2空格换行和普通换行都生成 <br/>
expect(html).toContain('line1');
expect(html).toContain('line2');
});
});
describe('parseMarkdown - v0.1.4 链接标题单引号', () => {
test('链接 title 使用单引号', () => {
const html = parseMarkdown('[text](https://example.com \'title\')');
expect(html).toContain('href="https://example.com"');
expect(html).toContain('title="title"');
});
test('链接 title 使用双引号(兼容)', () => {
const html = parseMarkdown('[text](https://example.com "title")');
expect(html).toContain('title="title"');
});
});
describe('parseMarkdown - v0.1.4 LaTeX 数学公式', () => {
test('行内公式 $...$', () => {
const html = parseMarkdown('Euler: $e^{i\\pi} = -1$');
expect(html).toContain('me-math-inline');
expect(html).toContain('e^{i\\pi} = -1');
});
test('块级公式 $$...$$(单行)', () => {
const html = parseMarkdown('$$\\int_0^\\infty e^{-x} dx = 1$$');
expect(html).toContain('me-math-block');
});
test('块级公式 $$...$$(多行)', () => {
const md = '$$\n\\begin{aligned}\nx &= 1 + 2 \\\\\ny &= 3 + 4\n\\end{aligned}\n$$';
const html = parseMarkdown(md);
expect(html).toContain('me-math-block');
expect(html).toContain('\\begin{aligned}');
});
});
describe('parseMarkdown - v0.1.4 脚注', () => {
test('脚注引用与定义', () => {
const md = 'Text with footnote[^1].\n\n[^1]: This is the footnote.';
const html = parseMarkdown(md);
expect(html).toContain('me-footnote-ref');
expect(html).toContain('fnref-1');
expect(html).toContain('me-footnotes');
expect(html).toContain('fn-1');
expect(html).toContain('This is the footnote.');
});
test('多个脚注', () => {
const md = 'First[^a] and second[^b].\n\n[^a]: Note A.\n[^b]: Note B.';
const html = parseMarkdown(md);
expect(html).toContain('me-footnotes');
expect(html).toContain('Note A.');
expect(html).toContain('Note B.');
});
});
describe('parseMarkdown - v0.1.4 定义列表', () => {
test('基本定义列表', () => {
const md = 'Term\n: Definition of the term.';
const html = parseMarkdown(md);
expect(html).toContain('<dl>');
expect(html).toContain('<dt>');
expect(html).toContain('Term');
expect(html).toContain('<dd>');
expect(html).toContain('Definition of the term.');
});
test('多条定义', () => {
const md = 'Term\n: Definition 1.\n: Definition 2.';
const html = parseMarkdown(md);
expect(html).toContain('Definition 1.');
expect(html).toContain('Definition 2.');
});
});
describe('parseMarkdown - v0.1.4 反引号精确匹配', () => {
test('双反引号包裹单反引号', () => {
const html = parseMarkdown('`` ` ``');
expect(html).toContain('<code>');
expect(html).toContain('`');
});
test('不同长度反引号串', () => {
const html = parseMarkdown('``` `code` ```');
expect(html).toContain('<code>');
// 应包含原始反引号内容
expect(html).toContain('`code`');
});
});
describe('parseMarkdown - v0.1.4 斜体优化', () => {
test('*text* 正常斜体', () => {
const html = parseMarkdown('a *italic* b');
expect(html).toContain('<em>italic</em>');
});
test('*** 不作为斜体', () => {
const html = parseMarkdown('***');
expect(html).not.toContain('<em>');
});
test('__ 不作为粗体误判', () => {
const html = parseMarkdown('__bold__');
expect(html).toContain('<strong>bold</strong>');
});
});
describe('parseMarkdown - v0.1.4 引用块优化', () => {
test('引用块 >text 无空格', () => {
const html = parseMarkdown('>quote text');
expect(html).toContain('<blockquote>');
expect(html).toContain('quote text');
});
test('嵌套引用', () => {
const html = parseMarkdown('> outer\n>> nested');
expect(html).toContain('<blockquote>');
expect(html).toContain('outer');
});
});
describe('parseMarkdown - v0.1.4 safeUrl 增强', () => {
test('正常 http/https url 通过', () => {
expect(safeUrl('http://example.com')).toBe('http://example.com');
expect(safeUrl('https://example.com')).toBe('https://example.com');
});
test('mailto: 协议通过', () => {
expect(safeUrl('mailto:test@example.com')).toBe('mailto:test@example.com');
});
});
describe('parseMarkdown - v0.1.4 slugify 增强', () => {
test('全角字符处理', () => {
expect(slugify('你好世界')).toBe('你好世界');
});
test('Unicode NFKC 规范化', () => {
// 全角英文字母转半角
const slug = slugify('Cat');
// NFKC 将全角 C/a/t 转为半角
expect(slug).toContain('cat');
});
});
describe('parseMarkdown - v0.1.4 表情扩展', () => {
test('新增表情 :confetti:', () => {
const html = parseMarkdown(':confetti:');
expect(html).toContain('🎊');
});
test('新增表情 :rainbow:', () => {
const html = parseMarkdown(':rainbow:');
expect(html).toContain('🌈');
});
test('新增表情 :hamburger:', () => {
const html = parseMarkdown(':hamburger:');
expect(html).toContain('🍔');
});
test('新增表情 :earth:', () => {
const html = parseMarkdown(':earth:');
expect(html).toContain('🌍');
});
});
describe('parseMarkdown - v0.1.4 代码块语言扩展', () => {
test('语言标记支持 # + . 等字符', () => {
const html = parseMarkdown('```c++\nint main() {}\n```');
expect(html).toContain('language-c++');
});
test('语言标记支持点号', () => {
const html = parseMarkdown('```file.txt\ncontent\n```');
expect(html).toContain('language-file.txt');
});
});
+5 -3
View File
@@ -1,4 +1,4 @@
// Type definitions for MetonaEditor
// Type definitions for MetonaEditor v0.1.4
// Project: https://git.metona.cn/MetonaTeam/MetonaEditor
// Author: thzxx
@@ -243,10 +243,12 @@ export function getStatus(): {
// ============ 解析器 ============
/** Markdown 解析函数 */
export function parseMarkdown(markdown: string, env?: RenderEnv): string;
/** URL 安全过滤 */
/** URL 安全过滤(增强版) */
export function safeUrl(url: string): string;
/** 生成标题锚点 id */
/** 生成标题锚点 idUnicode NFKC 规范化) */
export function slugify(text: string): string;
/** 清除渲染缓存(主题切换等场景) */
export function clearRenderCache(): void;
// ============ 插件 ============
/** 预设插件表 */