fix: 复查修复 5 处问题 — URL 双重转义/zen 泄漏/高亮环境不一致等

- parser: href/src/alt 改最小属性转义(escapeAttr 会把已实体化的 &
  再转义成 &,导致链接 URL 双重转义错误);新增 URL/alt 防回归测试
- core: _pushHistory 增加 maxLength 兜底截断(exec/Tab/Smart Enter/括号
  自动闭合等程序化路径此前绕过 maxlength 限制)
- core: destroy 时清理 Zen 模式的 document mousemove 监听(此前泄漏)
- highlight: 自写环境无关转义(仅 & < >),修复 Node 环境下字符串高亮
  失效、浏览器与 SSR 输出不一致的问题
- floating-toolbar/context-menu: toggleFloatingToolbar/registerContextMenu
  恢复链式 return this(拆分时丢失)
- plugins: 补齐 3 个硬编码文案的 i18n(regex/shortcuts/imageTooLarge),
  6 种语言同步
- 测试 768 → 780
This commit is contained in:
2026-08-09 09:26:20 +08:00
parent 5919dd9f66
commit e5f99aee83
12 changed files with 169 additions and 18 deletions
+8
View File
@@ -512,6 +512,13 @@ export class MarkdownEditor {
_scheduleHistory(): void { if (this._historyTimer) clearTimeout(this._historyTimer); this._historyTimer = setTimeout(() => this._pushHistory(), this.config.historyDebounce || 400); }
_pushHistory(): void {
// Enforce maxLength as a last-resort guard for programmatic edit paths
// (tab/indent, smart-enter, bracket auto-close, exec commands) that write
// to the textarea directly and bypass the native maxlength attribute.
if (this.config.maxLength && this.config.maxLength > 0 && this._value.length > this.config.maxLength) {
this._value = this._value.slice(0, this.config.maxLength);
if (this.textarea) this.textarea.value = this._value;
}
const cur = this._history[this._historyIndex]; if (cur === this._value) return;
this._history = this._history.slice(0, this._historyIndex + 1); this._history.push(this._value);
const limit = this.config.historyLimit! > 0 ? this.config.historyLimit! : 100;
@@ -846,6 +853,7 @@ export class MarkdownEditor {
if (this._historyTimer) clearTimeout(this._historyTimer);
if (this._outlineTimer) clearTimeout(this._outlineTimer);
this._cleanups.forEach((fn) => { try { fn(); } catch (_) {} }); this._cleanups = [];
if (this._zenMouseHandler) { document.removeEventListener('mousemove', this._zenMouseHandler); this._zenMouseHandler = null; }
this._shortcuts = []; this._contextMenuItems = []; this._customActions = {};
if (this._floatingToolbar && this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
this._floatingToolbar = null;