revert: remove syntax highlighting feature entirely

Syntax highlighting overlay approach proved too fragile:
- pixel alignment between textarea and overlay unreliable
- transparent textarea caused invisible content bugs
- dynamic gutter width positioning added complexity

Removed:
- _highlightEditor, _doHighlight, toggleSyntaxHighlight, _positionHighlightLayer
- highlightLayer DOM element creation
- .me-highlight-layer, .mh-*, .me-textarea-highlight CSS
- syntaxHighlight config option from DEFAULTS
- highlight toggle button from demo.html

Kept: Mermaid, fileSystem, preview links, all other v0.1.14 features
This commit is contained in:
2026-07-24 18:10:27 +08:00
parent f7752ee02c
commit c6db68af59
4 changed files with 1 additions and 123 deletions
-3
View File
@@ -68,7 +68,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Micr
<div class="spacer"></div>
<button data-cmd="zen" title="Zen 专注模式">🧘 Zen</button>
<button data-cmd="outline" title="切换大纲面板">📋 大纲</button>
<button data-cmd="highlight" title="切换语法高亮">🎨 高亮</button>
<button data-cmd="wrap" title="切换自动换行">↩ 换行</button>
<button data-cmd="toast" title="Toast 通知演示">💬 Toast</button>
<button data-cmd="openFile" title="打开磁盘文件">📂 打开</button>
@@ -199,7 +198,6 @@ var editor=MeEditor.create('#editor',{
value:demoMd,
mode:'split',height:620,
lineNumbers:true,autoBrackets:true,wordCount:true,
syntaxHighlight:true,
theme:'auto',locale:'zh-CN',
outline:false,
plugins:['autoSave','exportTool','searchReplace','imagePaste','shortcutHelp','fileSystem'],
@@ -229,7 +227,6 @@ bar.addEventListener('click',function(e){
if(cmd==='mode'){editor.setMode(btn.dataset.arg);updateModeBtns(btn.dataset.arg);}
else if(cmd==='zen'){editor.toggleZen();btn.classList.toggle('on',editor.isZen());}
else if(cmd==='outline'){toggleOutline(btn);}
else if(cmd==='highlight'){editor.toggleSyntaxHighlight();btn.classList.toggle('on',editor.config.syntaxHighlight);}
else if(cmd==='wrap'){editor.toggleWordWrap();btn.classList.toggle('on',!editor.isWordWrap());}
else if(cmd==='toast'){
var types=['success','error','warning','info'];
-2
View File
@@ -62,8 +62,6 @@ export const DEFAULTS = Object.freeze({
wordWrap: true,
// 最大字符数(v0.1.140=不限)
maxLength: 0,
// 语法高亮编辑区(v0.1.14
syntaxHighlight: true,
// 主题:light / dark / auto / warm / 自定义
theme: 'auto',
// 国际化
+1 -93
View File
@@ -6,7 +6,7 @@
* v0.1.6: 插件依赖拓扑排序、实例 i18n、快捷键/工具栏定制、右键菜单、Toast
* v0.1.7: 行号装订线、自动格式化、括号自动闭合、拖放、大纲面板
* v0.1.12: 工具栏键盘导航、aria-live、Zen模式、滚动同步v2、WordWrap
* v0.1.14: 语法高亮编辑区、图片上传钩子、预览链接拦截、Mermaid、文件存储
* v0.1.14: 预览链接拦截、Mermaid、文件存储
*/
import { generateId, escapeHTML, isBrowser } from './utils.js';
@@ -115,7 +115,6 @@ class MarkdownEditor {
this._zenMode = false; // v0.1.12: Zen 模式
this._wordWrap = true; // v0.1.12: 自动换行
this._syncing = false; // 滚动同步锁
this._hlRaf = null; // v0.1.14: 高亮节流
// 渲染函数:自定义覆盖内置解析器
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
@@ -217,12 +216,6 @@ class MarkdownEditor {
if (!this.config.lineNumbers) gutter.style.display = 'none';
editorInner.appendChild(gutter);
// 语法高亮叠加层(v0.1.14
const highlightLayer = document.createElement('pre');
highlightLayer.className = 'me-highlight-layer';
highlightLayer.setAttribute('aria-hidden', 'true');
editorInner.appendChild(highlightLayer);
const textarea = document.createElement('textarea');
textarea.className = 'me-textarea';
textarea.spellcheck = !!this.config.spellcheck;
@@ -230,12 +223,6 @@ class MarkdownEditor {
textarea.style.tabSize = String(this.config.tabSize || 2);
textarea.placeholder = this.config.placeholder || i18nT('placeholder');
textarea.setAttribute('aria-label', i18nT('edit'));
if (this.config.syntaxHighlight) {
textarea.classList.add('me-textarea-highlight');
}
if (highlightLayer && !this.config.syntaxHighlight) {
highlightLayer.hidden = true;
}
editorInner.appendChild(textarea);
editorPane.appendChild(editorInner);
@@ -276,7 +263,6 @@ class MarkdownEditor {
this.editorPane = editorPane;
this.editorInner = editorInner;
this.gutter = gutter;
this.highlightLayer = highlightLayer;
this.previewPane = previewPane;
this.dividerEl = divider;
this.textarea = textarea;
@@ -343,7 +329,6 @@ class MarkdownEditor {
this._updateWordCount();
this._renderGutter();
this._updateOutline();
this._highlightEditor(); // v0.1.14
this._emit('input', this._value);
this._emit('change', this._value);
if (typeof this.config.onInput === 'function') {
@@ -1319,82 +1304,6 @@ class MarkdownEditor {
requestAnimationFrame(() => { this._syncing = false; });
}
// ============ 语法高亮编辑区(v0.1.14 ============
_highlightEditor() {
if (!this.highlightLayer || !this.config.syntaxHighlight) return;
// rAF 节流,避免高频更新
if (this._hlRaf) return;
this._hlRaf = requestAnimationFrame(() => {
this._hlRaf = null;
this._doHighlight();
});
}
_doHighlight() {
if (!this.highlightLayer || this.highlightLayer.hidden || this._mode === 'preview') return;
const text = this._value || '';
if (!text) { this.highlightLayer.innerHTML = ''; return; }
if (text.length > 50000) { this.highlightLayer.innerHTML = escapeHTML(text) + '\n'; return; }
let html = escapeHTML(text);
this._positionHighlightLayer();
// 正则按优先级:先匹配更具体的模式,避免被泛模式吞掉
// 1. 图片(先于链接)
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>');
// 9. 引用
html = html.replace(/^(&gt;)\s?/gm, '<span class="mh-quote">$&</span>');
// 10. 水平线
html = html.replace(/^([-*_]{3,})\s*$/gm, '<span class="mh-hr">$&</span>');
this.highlightLayer.innerHTML = html + '\n';
// 同步滚动
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.highlightLayer) {
this.highlightLayer.hidden = !this.config.syntaxHighlight;
}
if (this.config.syntaxHighlight) {
this._positionHighlightLayer();
this._doHighlight();
}
return this;
}
_positionHighlightLayer() {
if (!this.highlightLayer) return;
// 动态设置左边距:有行号时对齐行号宽度,无行号时从 0 开始
if (this.gutter && this.config.lineNumbers && this.gutter.style.display !== 'none') {
const w = this.gutter.getBoundingClientRect().width;
this.highlightLayer.style.left = w + 'px';
} else {
this.highlightLayer.style.left = '0';
}
}
_updateWordCount() {
if (!this.config.wordCount || !this.statusEl) return;
const text = this._value || '';
@@ -1781,7 +1690,6 @@ class MarkdownEditor {
if (this._renderRaf) cancelAnimationFrame(this._renderRaf);
if (this._historyTimer) clearTimeout(this._historyTimer);
if (this._outlineTimer) clearTimeout(this._outlineTimer);
if (this._hlRaf) cancelAnimationFrame(this._hlRaf);
this._cleanups.forEach((fn) => { try { fn(); } catch (_) {} });
this._cleanups = [];
-25
View File
@@ -132,31 +132,6 @@ const generateCSS = () => {
/* ============ 编辑区内层(v0.1.7 ============ */
.me-editor-inner { display: flex; height: 100%; overflow: hidden; position: relative; }
/* 语法高亮叠加层(v0.1.14 */
.me-highlight-layer {
position: absolute; top: 0; right: 0; bottom: 0;
padding: 16px 18px; margin: 0; border: 0; outline: 0; overflow: hidden;
font-family: var(--md-mono); font-size: 13.5px; line-height: 1.7;
white-space: pre-wrap; word-wrap: break-word;
pointer-events: none; background: var(--md-textarea-bg);
color: var(--md-text); z-index: 0;
}
/* textarea 在高亮模式下透明背景,露出下方高亮层 */
.me-textarea-highlight {
background: transparent !important;
}
/* 隐藏高亮层(语法高亮关闭时) */
.me-highlight-layer[hidden] { display: none; }
.mh-heading { color: var(--md-accent); font-weight: 700; }
.mh-bold { color: var(--md-text); font-weight: 700; }
.mh-italic { font-style: italic; }
.mh-strike { color: var(--md-muted); text-decoration: line-through; }
.mh-code { background: var(--md-code-bg); border-radius: 3px; padding: 0 2px; }
.mh-link { color: var(--md-accent); text-decoration: underline; }
.mh-image { color: #8b5cf6; }
.mh-list { color: var(--md-accent); font-weight: 600; }
.mh-quote { color: var(--md-muted); }
.mh-hr { color: var(--md-muted); }
.me-gutter {
flex: 0 0 auto;
min-width: 36px;