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 (& ©) - 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:
@@ -2,9 +2,9 @@
|
||||
|
||||
> 轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用,中文优先。
|
||||
|
||||
[](https://www.npmjs.com/package/@metona-team/metona-editor)
|
||||
[](https://www.npmjs.com/package/@metona-team/metona-editor)
|
||||
[](./LICENSE)
|
||||
[](./tests)
|
||||
[](./tests)
|
||||
[](./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>` |
|
||||
| 图片 | `` | `<img>` |
|
||||
| 自动链接 | `<https://...>` | `<a>` |
|
||||
| Emoji 短码 | `:smile:` `:rocket:` | 😊 🚀(80+ 常用) |
|
||||
| 数学公式 | `$E=mc^2$` `$$\int$$` | `<span>/<div>` |
|
||||
| 脚注 | `text[^1]` | `<sup>` + 底部定义 |
|
||||
| Emoji 短码 | `:smile:` `:rainbow:` | 😊 🌈(150+ 常用) |
|
||||
| HTML 注释 | `<!-- note -->` | 透传 |
|
||||
| 实体引用保护 | `&` `©` | 不被二次转义 |
|
||||
|
||||
### 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Animations - 动画管理
|
||||
* @module animations
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 动画注册与管理(当前为 CSS 动画元数据管理,供未来扩展如 Toast/通知组件的进出场动画使用;
|
||||
* 内置 7 种预设动画曲线配置;编辑器核心当前未直接调用此模块)
|
||||
*/
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Constants - 常量定义
|
||||
* @module constants
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 默认配置、主题、动画、工具栏等常量
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Core - 编辑器核心
|
||||
* @module core
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description MarkdownEditor 类实现:DOM 构建、事件、渲染、历史栈、选区操作、模式切换
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor i18n - 国际化管理
|
||||
* @module i18n
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 多语言支持、语言切换和翻译管理
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Icons — 工具栏图标SVG定义
|
||||
* @module icons
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 工具栏格式化按钮 SVG 图标
|
||||
*/
|
||||
|
||||
|
||||
+5
-3
@@ -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
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Locales — 国际化翻译数据
|
||||
* @module locales
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 内置 zh-CN / en-US 完整翻译
|
||||
*/
|
||||
|
||||
|
||||
+696
-159
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Plugins - 插件系统
|
||||
* @module plugins
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 插件管理器 + 预设插件(autoSave / exportTool / searchReplace)
|
||||
*
|
||||
* 插件约定:
|
||||
|
||||
+51
-1
@@ -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
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Themes - 主题管理
|
||||
* @module themes
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 主题系统、自定义主题和主题切换
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaEditor Utils - 工具函数
|
||||
* @module utils
|
||||
* @version 0.1.3
|
||||
* @version 0.1.4
|
||||
* @description 通用工具函数集合
|
||||
*/
|
||||
|
||||
|
||||
+313
-7
@@ -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&T');
|
||||
expect(html).toContain('&');
|
||||
// & 应保持为 & 而非 &amp;
|
||||
expect(html).not.toContain('&amp;');
|
||||
});
|
||||
|
||||
test('数字实体引用被保护', () => {
|
||||
const html = parseMarkdown('© 2024');
|
||||
expect(html).toContain('©');
|
||||
});
|
||||
|
||||
test('十六进制实体引用被保护', () => {
|
||||
const html = parseMarkdown('<');
|
||||
expect(html).toContain('<');
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+5
-3
@@ -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 */
|
||||
/** 生成标题锚点 id(Unicode NFKC 规范化) */
|
||||
export function slugify(text: string): string;
|
||||
/** 清除渲染缓存(主题切换等场景) */
|
||||
export function clearRenderCache(): void;
|
||||
|
||||
// ============ 插件 ============
|
||||
/** 预设插件表 */
|
||||
|
||||
Reference in New Issue
Block a user