opt(v0.1.14): enhance syntax highlight — rAF throttle, regex order, large-doc skip
opt(core): _highlightEditor rAF throttle to avoid per-keystroke reflow opt(core): fix regex order — image before link, code before bold, italic last opt(core): skip highlighting for documents >50KB to prevent lag opt(core): add toggleSyntaxHighlight() API method fix(core): clear _hlRaf in destroy to prevent post-mortem callbacks fix(core): initialize _hlRaf = null in constructor
This commit is contained in:
+45
-18
@@ -115,6 +115,7 @@ class MarkdownEditor {
|
|||||||
this._zenMode = false; // v0.1.12: Zen 模式
|
this._zenMode = false; // v0.1.12: Zen 模式
|
||||||
this._wordWrap = true; // v0.1.12: 自动换行
|
this._wordWrap = true; // v0.1.12: 自动换行
|
||||||
this._syncing = false; // 滚动同步锁
|
this._syncing = false; // 滚动同步锁
|
||||||
|
this._hlRaf = null; // v0.1.14: 高亮节流
|
||||||
|
|
||||||
// 渲染函数:自定义覆盖内置解析器
|
// 渲染函数:自定义覆盖内置解析器
|
||||||
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
|
||||||
@@ -1318,32 +1319,44 @@ class MarkdownEditor {
|
|||||||
// ============ 语法高亮编辑区(v0.1.14) ============
|
// ============ 语法高亮编辑区(v0.1.14) ============
|
||||||
|
|
||||||
_highlightEditor() {
|
_highlightEditor() {
|
||||||
if (!this.highlightLayer) return;
|
if (!this.highlightLayer || !this.config.syntaxHighlight) return;
|
||||||
|
// rAF 节流,避免高频更新
|
||||||
|
if (this._hlRaf) return;
|
||||||
|
this._hlRaf = requestAnimationFrame(() => {
|
||||||
|
this._hlRaf = null;
|
||||||
|
this._doHighlight();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_doHighlight() {
|
||||||
const text = this._value || '';
|
const text = this._value || '';
|
||||||
if (!text) { this.highlightLayer.innerHTML = ''; return; }
|
if (!text) { this.highlightLayer.innerHTML = ''; return; }
|
||||||
|
// 超过 50KB 跳过,避免大文档卡顿
|
||||||
|
if (text.length > 50000) { this.highlightLayer.innerHTML = escapeHTML(text) + '\n'; return; }
|
||||||
|
|
||||||
// 轻量级 tokenizer:正则匹配所有语法模式,包裹为 span
|
|
||||||
let html = escapeHTML(text);
|
let html = escapeHTML(text);
|
||||||
|
|
||||||
// 标题
|
// 正则按优先级:先匹配更具体的模式,避免被泛模式吞掉
|
||||||
html = html.replace(/^(#{1,6})\s+(.+)$/gm, '<span class="mh-heading">$&</span>');
|
// 1. 图片(先于链接)
|
||||||
// 粗体
|
|
||||||
html = html.replace(/(\*\*|__)(.+?)\1/g, '<span class="mh-bold">$&</span>');
|
|
||||||
// 斜体
|
|
||||||
html = html.replace(/(?<!\*)\*(?!\*)(.+?)\*(?!\*)/g, '<span class="mh-italic">$&</span>');
|
|
||||||
// 删除线
|
|
||||||
html = html.replace(/~~(.+?)~~/g, '<span class="mh-strike">$&</span>');
|
|
||||||
// 行内代码
|
|
||||||
html = html.replace(/`([^`]+)`/g, '<span class="mh-code">$&</span>');
|
|
||||||
// 链接
|
|
||||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<span class="mh-link">$&</span>');
|
|
||||||
// 图片
|
|
||||||
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<span class="mh-image">$&</span>');
|
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<span class="mh-image">$&</span>');
|
||||||
// 列表标记
|
// 2. 链接
|
||||||
|
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<span class="mh-link">$&</span>');
|
||||||
|
// 3. 行内代码(先于粗体/斜体,避免 `**` 被误匹配)
|
||||||
|
html = html.replace(/`([^`]+)`/g, '<span class="mh-code">$&</span>');
|
||||||
|
// 4. 粗体 **text** / __text__
|
||||||
|
html = html.replace(/(\*\*|__)(.+?)\1/g, '<span class="mh-bold">$&</span>');
|
||||||
|
// 5. 删除线
|
||||||
|
html = html.replace(/~~(.+?)~~/g, '<span class="mh-strike">$&</span>');
|
||||||
|
// 6. 斜体 *text* / _text_(在粗体之后,避免 ** 被拆成两个斜体)
|
||||||
|
html = html.replace(/(?<!\*)\*(?!\*)([^*\n]+?)\*(?!\*)/g, '<span class="mh-italic">$&</span>');
|
||||||
|
html = html.replace(/(?<!_)_(?!_)([^_\n]+?)_(?!_)/g, '<span class="mh-italic">$&</span>');
|
||||||
|
// 7. 标题(行级)
|
||||||
|
html = html.replace(/^(#{1,6})\s+(.+)$/gm, '<span class="mh-heading">$&</span>');
|
||||||
|
// 8. 列表标记
|
||||||
html = html.replace(/^(\s*[-*+]|\d+\.)\s/gm, '<span class="mh-list">$&</span>');
|
html = html.replace(/^(\s*[-*+]|\d+\.)\s/gm, '<span class="mh-list">$&</span>');
|
||||||
// 引用
|
// 9. 引用
|
||||||
html = html.replace(/^(>)\s?/gm, '<span class="mh-quote">$&</span>');
|
html = html.replace(/^(>)\s?/gm, '<span class="mh-quote">$&</span>');
|
||||||
// 水平线
|
// 10. 水平线
|
||||||
html = html.replace(/^([-*_]{3,})\s*$/gm, '<span class="mh-hr">$&</span>');
|
html = html.replace(/^([-*_]{3,})\s*$/gm, '<span class="mh-hr">$&</span>');
|
||||||
|
|
||||||
this.highlightLayer.innerHTML = html + '\n';
|
this.highlightLayer.innerHTML = html + '\n';
|
||||||
@@ -1351,6 +1364,19 @@ class MarkdownEditor {
|
|||||||
if (this.textarea) this.highlightLayer.scrollTop = this.textarea.scrollTop;
|
if (this.textarea) this.highlightLayer.scrollTop = this.textarea.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 切换语法高亮(v0.1.14) */
|
||||||
|
toggleSyntaxHighlight() {
|
||||||
|
this.config.syntaxHighlight = !this.config.syntaxHighlight;
|
||||||
|
if (this.textarea) {
|
||||||
|
this.textarea.classList.toggle('me-textarea-highlight', this.config.syntaxHighlight);
|
||||||
|
}
|
||||||
|
if (!this.config.syntaxHighlight && this.highlightLayer) {
|
||||||
|
this.highlightLayer.innerHTML = '';
|
||||||
|
}
|
||||||
|
if (this.config.syntaxHighlight) this._doHighlight();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
_updateWordCount() {
|
_updateWordCount() {
|
||||||
if (!this.config.wordCount || !this.statusEl) return;
|
if (!this.config.wordCount || !this.statusEl) return;
|
||||||
const text = this._value || '';
|
const text = this._value || '';
|
||||||
@@ -1737,6 +1763,7 @@ class MarkdownEditor {
|
|||||||
if (this._renderRaf) cancelAnimationFrame(this._renderRaf);
|
if (this._renderRaf) cancelAnimationFrame(this._renderRaf);
|
||||||
if (this._historyTimer) clearTimeout(this._historyTimer);
|
if (this._historyTimer) clearTimeout(this._historyTimer);
|
||||||
if (this._outlineTimer) clearTimeout(this._outlineTimer);
|
if (this._outlineTimer) clearTimeout(this._outlineTimer);
|
||||||
|
if (this._hlRaf) cancelAnimationFrame(this._hlRaf);
|
||||||
|
|
||||||
this._cleanups.forEach((fn) => { try { fn(); } catch (_) {} });
|
this._cleanups.forEach((fn) => { try { fn(); } catch (_) {} });
|
||||||
this._cleanups = [];
|
this._cleanups = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user