release: v0.1.14 — syntax highlight, Mermaid, fileSystem, preview links
feat(core): syntax highlight editor overlay - Transparent highlight-layer behind textarea with regex-based token coloring - Supports heading, bold, italic, strikethrough, code, link, image, list, quote, hr - Scroll-synced with textarea, config.syntaxHighlight toggle feat(core): preview link click interception - Clicks on preview links intercepted, emitted as 'linkClick' event - Configurable via onLinkClick(uri, text, editor) callback - Default: opens in new tab, anchor links pass through feat(parser): Mermaid diagram rendering - ```mermaid code blocks render as <div class="me-mermaid"><pre class="mermaid"> feat(plugins): fileSystem plugin (IndexedDB persistence) - saveToFile(id) / loadFromFile(id) / deleteFile(id) / listFiles() - Uses IndexedDB 'metona-editor-fs' database feat(config): maxLength (char limit), syntaxHighlight toggle style: syntax highlight theme colors, Mermaid container chore: bump version 0.1.13 → 0.1.14 across all files
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用,中文优先。
|
> 轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用,中文优先。
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/@metona-team/metona-editor)
|
[](https://www.npmjs.com/package/@metona-team/metona-editor)
|
||||||
[](./LICENSE)
|
[](./LICENSE)
|
||||||
[](./tests)
|
[](./tests)
|
||||||
[](./tests)
|
[](./tests)
|
||||||
@@ -19,12 +19,12 @@
|
|||||||
- **安全** — 内置 XSS 防护(`javascript:`/`vbscript:`/`file:` 协议过滤),HTML 转义,`safeUrl` 净化
|
- **安全** — 内置 XSS 防护(`javascript:`/`vbscript:`/`file:` 协议过滤),HTML 转义,`safeUrl` 净化
|
||||||
- **内置解析器** — 自研 CommonMark 子集 + GFM 扩展(表格含列对齐、任务列表、上下标、数学公式、脚注、嵌套列表、emoji 短码等),可整体替换
|
- **内置解析器** — 自研 CommonMark 子集 + GFM 扩展(表格含列对齐、任务列表、上下标、数学公式、脚注、嵌套列表、emoji 短码等),可整体替换
|
||||||
- **三模式视图** — edit / split / preview 自由切换,分屏拖拽调整比例,滚动同步,模式切换保持滚动位置
|
- **三模式视图** — edit / split / preview 自由切换,分屏拖拽调整比例,滚动同步,模式切换保持滚动位置
|
||||||
- **编辑体验** — 行号装订线 + 当前行高亮、智能 Enter(列表/引用自动延续)、括号/引号自动闭合、拖放文件/图片/文本、大纲/目录面板
|
- **编辑体验** — 语法高亮编辑区、行号装订线 + 当前行高亮、智能 Enter、括号闭合、拖放文件、大纲面板、Zen 专注模式、预览链接拦截
|
||||||
- **主题系统** — light / dark / auto / warm 四套预设,CSS 变量定制,实例级主题隔离,外部主题跟随(syncWithElement / adoptFromParent),主题继承(extends),跟随系统主题,@media print 打印样式
|
- **主题系统** — light / dark / auto / warm 四套预设,CSS 变量定制,实例级主题隔离,外部主题跟随(syncWithElement / adoptFromParent),主题继承(extends),跟随系统主题,@media print 打印样式
|
||||||
- **国际化** — zh-CN / en-US 完整翻译,实例级语言隔离,复数规则,命名空间,动态远程加载翻译包,RTL 支持
|
- **国际化** — zh-CN / en-US 完整翻译,实例级语言隔离,复数规则,命名空间,动态远程加载翻译包,RTL 支持
|
||||||
- **历史栈** — 撤销/重做,防抖合并(可配置延迟),可配置上限
|
- **历史栈** — 撤销/重做,防抖合并(可配置延迟),可配置上限
|
||||||
- **只读模式** — `readOnly` 配置 / `setReadOnly()` API,适用于预览/展示场景
|
- **只读模式** — `readOnly` 配置 / `setReadOnly()` API,适用于预览/展示场景
|
||||||
- **预设插件** — autoSave(草稿)、exportTool(导出 .md/.html)、searchReplace(Ctrl+F 查找替换)、imagePaste(粘贴图片转 base64)
|
- **预设插件** — autoSave、exportTool、searchReplace、imagePaste、shortcutHelp、fileSystem(IndexedDB 存储)
|
||||||
- **性能优化** — 渲染缓存跳过重复解析,`refresh()` 强制刷新,模式切换无冗余渲染
|
- **性能优化** — 渲染缓存跳过重复解析,`refresh()` 强制刷新,模式切换无冗余渲染
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@metona-team/metona-editor",
|
"name": "@metona-team/metona-editor",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"description": "轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用。",
|
"description": "轻量、零依赖、精致美观的 Markdown Editor 库。单文件,开箱即用。",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/metona-editor.js",
|
"main": "dist/metona-editor.js",
|
||||||
|
|||||||
+4
-4
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📝</text></svg>"/>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📝</text></svg>"/>
|
||||||
<title>MetonaEditor v0.1.13 — 在线演示</title>
|
<title>MetonaEditor v0.1.14 — 在线演示</title>
|
||||||
<style>
|
<style>
|
||||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||||
:root {
|
:root {
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
<header class="header">
|
<header class="header">
|
||||||
<h1><span class="grad">Metona</span>Editor</h1>
|
<h1><span class="grad">Metona</span>Editor</h1>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
<span class="badge">v0.1.13</span>
|
<span class="badge">v0.1.14</span>
|
||||||
<span class="badge">零依赖</span>
|
<span class="badge">零依赖</span>
|
||||||
<span class="badge">532 tests</span>
|
<span class="badge">532 tests</span>
|
||||||
<span class="badge">MIT</span>
|
<span class="badge">MIT</span>
|
||||||
@@ -291,7 +291,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
MetonaEditor v0.1.13 ·
|
MetonaEditor v0.1.14 ·
|
||||||
<a href="index.html">首页</a> ·
|
<a href="index.html">首页</a> ·
|
||||||
<a href="docs.html">API 文档</a> ·
|
<a href="docs.html">API 文档</a> ·
|
||||||
<a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> ·
|
<a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> ·
|
||||||
@@ -329,7 +329,7 @@
|
|||||||
'',
|
'',
|
||||||
'| 版本 | 日期 | 测试数 |',
|
'| 版本 | 日期 | 测试数 |',
|
||||||
'| :--- | :---: | ---: |',
|
'| :--- | :---: | ---: |',
|
||||||
'| v0.1.13 | 2026-07 | 532+ |',
|
'| v0.1.14 | 2026-07 | 532+ |',
|
||||||
'| v0.1.6 | 2026-07 | 521 |',
|
'| v0.1.6 | 2026-07 | 521 |',
|
||||||
'| v0.1.5 | 2026-07 | 499 |',
|
'| v0.1.5 | 2026-07 | 499 |',
|
||||||
'',
|
'',
|
||||||
|
|||||||
+2
-2
@@ -176,7 +176,7 @@
|
|||||||
|
|
||||||
<header class="docs-header">
|
<header class="docs-header">
|
||||||
<h1><span class="grad">文档</span></h1>
|
<h1><span class="grad">文档</span></h1>
|
||||||
<p class="sub">MetonaEditor v0.1.13 完整 API、配置与使用指南</p>
|
<p class="sub">MetonaEditor v0.1.14 完整 API、配置与使用指南</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -619,7 +619,7 @@ i18nUtils<span class="c-punc">.</span><span class="c-fn">formatDate</span><span
|
|||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
MetonaEditor v0.1.13 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
|
MetonaEditor v0.1.14 · <a href="https://git.metona.cn/MetonaTeam/MetonaEditor" target="_blank" rel="noopener">源码仓库</a> · MIT License
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Animations - 动画管理
|
* MetonaEditor Animations - 动画管理
|
||||||
* @module animations
|
* @module animations
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 动画注册与管理(当前为 CSS 动画元数据管理,供未来扩展如 Toast/通知组件的进出场动画使用;
|
* @description 动画注册与管理(当前为 CSS 动画元数据管理,供未来扩展如 Toast/通知组件的进出场动画使用;
|
||||||
* 内置 7 种预设动画曲线配置;编辑器核心当前未直接调用此模块)
|
* 内置 7 种预设动画曲线配置;编辑器核心当前未直接调用此模块)
|
||||||
*/
|
*/
|
||||||
|
|||||||
+5
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Constants - 常量定义
|
* MetonaEditor Constants - 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 默认配置、主题、动画、工具栏等常量
|
* @description 默认配置、主题、动画、工具栏等常量
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -60,6 +60,10 @@ export const DEFAULTS = Object.freeze({
|
|||||||
zenMode: false,
|
zenMode: false,
|
||||||
// 自动换行(v0.1.12)
|
// 自动换行(v0.1.12)
|
||||||
wordWrap: true,
|
wordWrap: true,
|
||||||
|
// 最大字符数(v0.1.14,0=不限)
|
||||||
|
maxLength: 0,
|
||||||
|
// 语法高亮编辑区(v0.1.14)
|
||||||
|
syntaxHighlight: true,
|
||||||
// 主题:light / dark / auto / warm / 自定义
|
// 主题:light / dark / auto / warm / 自定义
|
||||||
theme: 'auto',
|
theme: 'auto',
|
||||||
// 国际化
|
// 国际化
|
||||||
|
|||||||
+65
-1
@@ -1,11 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Core - 编辑器核心
|
* MetonaEditor Core - 编辑器核心
|
||||||
* @module core
|
* @module core
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description MarkdownEditor 类实现:DOM 构建、事件、渲染、历史栈、选区操作、模式切换
|
* @description MarkdownEditor 类实现:DOM 构建、事件、渲染、历史栈、选区操作、模式切换
|
||||||
* v0.1.6: 插件依赖拓扑排序、实例 i18n、快捷键/工具栏定制、右键菜单、Toast
|
* v0.1.6: 插件依赖拓扑排序、实例 i18n、快捷键/工具栏定制、右键菜单、Toast
|
||||||
* v0.1.7: 行号装订线、自动格式化、括号自动闭合、拖放、大纲面板
|
* v0.1.7: 行号装订线、自动格式化、括号自动闭合、拖放、大纲面板
|
||||||
* v0.1.12: 工具栏键盘导航、aria-live、Zen模式、滚动同步v2、WordWrap
|
* v0.1.12: 工具栏键盘导航、aria-live、Zen模式、滚动同步v2、WordWrap
|
||||||
|
* v0.1.14: 语法高亮编辑区、图片上传钩子、预览链接拦截、Mermaid、文件存储
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateId, escapeHTML, isBrowser } from './utils.js';
|
import { generateId, escapeHTML, isBrowser } from './utils.js';
|
||||||
@@ -215,6 +216,12 @@ class MarkdownEditor {
|
|||||||
if (!this.config.lineNumbers) gutter.style.display = 'none';
|
if (!this.config.lineNumbers) gutter.style.display = 'none';
|
||||||
editorInner.appendChild(gutter);
|
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');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.className = 'me-textarea';
|
textarea.className = 'me-textarea';
|
||||||
textarea.spellcheck = !!this.config.spellcheck;
|
textarea.spellcheck = !!this.config.spellcheck;
|
||||||
@@ -262,6 +269,7 @@ class MarkdownEditor {
|
|||||||
this.editorPane = editorPane;
|
this.editorPane = editorPane;
|
||||||
this.editorInner = editorInner;
|
this.editorInner = editorInner;
|
||||||
this.gutter = gutter;
|
this.gutter = gutter;
|
||||||
|
this.highlightLayer = highlightLayer;
|
||||||
this.previewPane = previewPane;
|
this.previewPane = previewPane;
|
||||||
this.dividerEl = divider;
|
this.dividerEl = divider;
|
||||||
this.textarea = textarea;
|
this.textarea = textarea;
|
||||||
@@ -328,6 +336,7 @@ class MarkdownEditor {
|
|||||||
this._updateWordCount();
|
this._updateWordCount();
|
||||||
this._renderGutter();
|
this._renderGutter();
|
||||||
this._updateOutline();
|
this._updateOutline();
|
||||||
|
this._highlightEditor(); // v0.1.14
|
||||||
this._emit('input', this._value);
|
this._emit('input', this._value);
|
||||||
this._emit('change', this._value);
|
this._emit('change', this._value);
|
||||||
if (typeof this.config.onInput === 'function') {
|
if (typeof this.config.onInput === 'function') {
|
||||||
@@ -395,6 +404,25 @@ class MarkdownEditor {
|
|||||||
ta.addEventListener('focus', onFocus);
|
ta.addEventListener('focus', onFocus);
|
||||||
ta.addEventListener('blur', onBlur);
|
ta.addEventListener('blur', onBlur);
|
||||||
|
|
||||||
|
// v0.1.14: 预览区链接点击拦截
|
||||||
|
const onPreviewClick = (e) => {
|
||||||
|
const a = e.target.closest('a');
|
||||||
|
if (!a) return;
|
||||||
|
const href = a.getAttribute('href');
|
||||||
|
if (!href || href.startsWith('#')) return; // 锚点链接不拦截
|
||||||
|
e.preventDefault();
|
||||||
|
// 触发自定义事件,用户可监听处理
|
||||||
|
this._emit('linkClick', { href, text: a.textContent });
|
||||||
|
// 如果用户提供了 onLinkClick 回调
|
||||||
|
if (typeof this.config.onLinkClick === 'function') {
|
||||||
|
try { this.config.onLinkClick(href, a.textContent, this); } catch (_) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 默认:新窗口打开
|
||||||
|
window.open(href, '_blank', 'noopener');
|
||||||
|
};
|
||||||
|
this.previewEl.addEventListener('click', onPreviewClick);
|
||||||
|
|
||||||
// 工具栏点击
|
// 工具栏点击
|
||||||
const onToolbarClick = (e) => {
|
const onToolbarClick = (e) => {
|
||||||
const btn = e.target.closest('.me-btn');
|
const btn = e.target.closest('.me-btn');
|
||||||
@@ -1284,6 +1312,42 @@ class MarkdownEditor {
|
|||||||
requestAnimationFrame(() => { this._syncing = false; });
|
requestAnimationFrame(() => { this._syncing = false; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ 语法高亮编辑区(v0.1.14) ============
|
||||||
|
|
||||||
|
_highlightEditor() {
|
||||||
|
if (!this.highlightLayer) return;
|
||||||
|
const text = this._value || '';
|
||||||
|
if (!text) { this.highlightLayer.innerHTML = ''; return; }
|
||||||
|
|
||||||
|
// 轻量级 tokenizer:正则匹配所有语法模式,包裹为 span
|
||||||
|
let html = escapeHTML(text);
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
html = html.replace(/^(#{1,6})\s+(.+)$/gm, '<span class="mh-heading">$&</span>');
|
||||||
|
// 粗体
|
||||||
|
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(/^(\s*[-*+]|\d+\.)\s/gm, '<span class="mh-list">$&</span>');
|
||||||
|
// 引用
|
||||||
|
html = html.replace(/^(>)\s?/gm, '<span class="mh-quote">$&</span>');
|
||||||
|
// 水平线
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
_updateWordCount() {
|
_updateWordCount() {
|
||||||
if (!this.config.wordCount || !this.statusEl) return;
|
if (!this.config.wordCount || !this.statusEl) return;
|
||||||
const text = this._value || '';
|
const text = this._value || '';
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor i18n — 国际化管理(增强版)
|
* MetonaEditor i18n — 国际化管理(增强版)
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 多语言支持、实例级语言隔离、复数规则、动态加载、命名空间
|
* @description 多语言支持、实例级语言隔离、复数规则、动态加载、命名空间
|
||||||
*
|
*
|
||||||
* v0.1.6 增强:
|
* v0.1.6 增强:
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Icons — 工具栏图标SVG定义
|
* MetonaEditor Icons — 工具栏图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 工具栏格式化按钮 SVG 图标
|
* @description 工具栏格式化按钮 SVG 图标
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor - 轻量级 Markdown Editor 库
|
* MetonaEditor - 轻量级 Markdown Editor 库
|
||||||
* @module metona-editor
|
* @module metona-editor
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @description 现代化、零依赖、易扩展、易维护、易使用的 Markdown 编辑器库。单文件,开箱即用。
|
* @description 现代化、零依赖、易扩展、易维护、易使用的 Markdown 编辑器库。单文件,开箱即用。
|
||||||
* @license MIT
|
* @license MIT
|
||||||
@@ -29,7 +29,7 @@ import { animationUtils } from './animations.js';
|
|||||||
import { DEFAULTS, ICONS, THEMES, EDIT_MODES, DEFAULT_TOOLBAR, TOOLBAR_ACTIONS } from './constants.js';
|
import { DEFAULTS, ICONS, THEMES, EDIT_MODES, DEFAULT_TOOLBAR, TOOLBAR_ACTIONS } from './constants.js';
|
||||||
|
|
||||||
// 版本信息
|
// 版本信息
|
||||||
const VERSION = '0.1.13';
|
const VERSION = '0.1.14';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局默认插件队列
|
* 全局默认插件队列
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Locales — 国际化翻译数据
|
* MetonaEditor Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Parser - 轻量 Markdown 解析器(增强版)
|
* MetonaEditor Parser - 轻量 Markdown 解析器(增强版)
|
||||||
* @module parser
|
* @module parser
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 自研零依赖 Markdown 解析器,覆盖 CommonMark 子集 + GFM 扩展
|
* @description 自研零依赖 Markdown 解析器,覆盖 CommonMark 子集 + GFM 扩展
|
||||||
*
|
*
|
||||||
* v0.1.4 增强:
|
* v0.1.4 增强:
|
||||||
@@ -638,6 +638,10 @@ const renderListItem = (it, env, idx) => {
|
|||||||
// ============ 代码块渲染 ============
|
// ============ 代码块渲染 ============
|
||||||
|
|
||||||
const renderCode = (code, lang, env) => {
|
const renderCode = (code, lang, env) => {
|
||||||
|
// Mermaid 图表(v0.1.14)
|
||||||
|
if (lang === 'mermaid') {
|
||||||
|
return `<div class="me-mermaid"><pre class="mermaid">${escapeHTML(code)}</pre></div>`;
|
||||||
|
}
|
||||||
const langClass = lang ? ` class="language-${escapeHTML(lang)}"` : '';
|
const langClass = lang ? ` class="language-${escapeHTML(lang)}"` : '';
|
||||||
if (env.highlight && typeof env.highlight === 'function' && lang) {
|
if (env.highlight && typeof env.highlight === 'function' && lang) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+103
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Plugins — 插件系统 v2
|
* MetonaEditor Plugins — 插件系统 v2
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 插件管理器 + 预设插件 + 依赖/生命周期/异步/卸载
|
* @description 插件管理器 + 预设插件 + 依赖/生命周期/异步/卸载
|
||||||
*
|
*
|
||||||
* v0.1.6 增强:
|
* v0.1.6 增强:
|
||||||
@@ -599,6 +599,107 @@ const shortcutHelpPlugin = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件系统存储插件(v0.1.14)
|
||||||
|
* 使用 IndexedDB 持久化编辑器内容
|
||||||
|
*/
|
||||||
|
const fileSystemPlugin = {
|
||||||
|
name: 'fileSystem',
|
||||||
|
version: '0.1.0',
|
||||||
|
description: 'IndexedDB 文件系统存储',
|
||||||
|
priority: 90,
|
||||||
|
dbName: 'metona-editor-fs',
|
||||||
|
storeName: 'documents',
|
||||||
|
|
||||||
|
_db: null,
|
||||||
|
|
||||||
|
_openDB() {
|
||||||
|
if (this._db) return Promise.resolve(this._db);
|
||||||
|
if (typeof indexedDB === 'undefined') return Promise.reject(new Error('IndexedDB not supported'));
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = indexedDB.open(this.dbName, 1);
|
||||||
|
req.onupgradeneeded = (e) => {
|
||||||
|
const db = e.target.result;
|
||||||
|
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||||
|
db.createObjectStore(this.storeName, { keyPath: 'id' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
req.onsuccess = (e) => { this._db = e.target.result; resolve(this._db); };
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
install(editor) {
|
||||||
|
if (!editor || typeof indexedDB === 'undefined') return;
|
||||||
|
if (typeof editor.getValue !== 'function') return;
|
||||||
|
|
||||||
|
editor.saveToFile = async (docId) => {
|
||||||
|
const id = docId || (editor.id || 'default');
|
||||||
|
const content = editor.getValue();
|
||||||
|
try {
|
||||||
|
const db = await this._openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(this.storeName, 'readwrite');
|
||||||
|
tx.objectStore(this.storeName).put({ id, content, updatedAt: Date.now() });
|
||||||
|
tx.oncomplete = () => resolve(true);
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
} catch (e) { console.error('FileSystem save error:', e); return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.loadFromFile = async (docId) => {
|
||||||
|
const id = docId || (editor.id || 'default');
|
||||||
|
try {
|
||||||
|
const db = await this._openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(this.storeName, 'readonly');
|
||||||
|
const req = tx.objectStore(this.storeName).get(id);
|
||||||
|
req.onsuccess = () => {
|
||||||
|
if (req.result) { editor.setValue(req.result.content, { silent: true }); }
|
||||||
|
resolve(req.result ? req.result.content : null);
|
||||||
|
};
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
} catch (e) { console.error('FileSystem load error:', e); return null; }
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.deleteFile = async (docId) => {
|
||||||
|
const id = docId || (editor.id || 'default');
|
||||||
|
try {
|
||||||
|
const db = await this._openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(this.storeName, 'readwrite');
|
||||||
|
tx.objectStore(this.storeName).delete(id);
|
||||||
|
tx.oncomplete = () => resolve(true);
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
} catch (e) { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.listFiles = async () => {
|
||||||
|
try {
|
||||||
|
const db = await this._openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(this.storeName, 'readonly');
|
||||||
|
const req = tx.objectStore(this.storeName).getAll();
|
||||||
|
req.onsuccess = () => resolve(req.result || []);
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
} catch (e) { return []; }
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy(editor) {
|
||||||
|
if (this._db) { this._db.close(); this._db = null; }
|
||||||
|
if (editor) {
|
||||||
|
delete editor.saveToFile;
|
||||||
|
delete editor.loadFromFile;
|
||||||
|
delete editor.deleteFile;
|
||||||
|
delete editor.listFiles;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ============ 预设插件表 ============
|
// ============ 预设插件表 ============
|
||||||
|
|
||||||
const presetPlugins = {
|
const presetPlugins = {
|
||||||
@@ -607,6 +708,7 @@ const presetPlugins = {
|
|||||||
searchReplace: searchReplacePlugin,
|
searchReplace: searchReplacePlugin,
|
||||||
imagePaste: imagePastePlugin,
|
imagePaste: imagePastePlugin,
|
||||||
shortcutHelp: shortcutHelpPlugin,
|
shortcutHelp: shortcutHelpPlugin,
|
||||||
|
fileSystem: fileSystemPlugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============ 实用工具 ============
|
// ============ 实用工具 ============
|
||||||
|
|||||||
+19
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Styles - 编辑器样式
|
* MetonaEditor Styles - 编辑器样式
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 编辑器 UI 样式注入与管理
|
* @description 编辑器 UI 样式注入与管理
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -132,6 +132,24 @@ const generateCSS = () => {
|
|||||||
|
|
||||||
/* ============ 编辑区内层(v0.1.7) ============ */
|
/* ============ 编辑区内层(v0.1.7) ============ */
|
||||||
.me-editor-inner { display: flex; height: 100%; overflow: hidden; position: relative; }
|
.me-editor-inner { display: flex; height: 100%; overflow: hidden; position: relative; }
|
||||||
|
/* 语法高亮叠加层(v0.1.14) */
|
||||||
|
.me-highlight-layer {
|
||||||
|
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
padding: 16px 18px; margin: 0; border: 0; outline: 0; overflow: auto;
|
||||||
|
font-family: var(--md-mono); font-size: 13.5px; line-height: 1.7;
|
||||||
|
white-space: pre-wrap; word-wrap: break-word;
|
||||||
|
color: transparent; pointer-events: none; background: transparent;
|
||||||
|
}
|
||||||
|
.mh-heading { color: var(--md-accent); font-weight: 700; }
|
||||||
|
.mh-bold { 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 {
|
.me-gutter {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
min-width: 36px;
|
min-width: 36px;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Themes — 主题系统(增强版)
|
* MetonaEditor Themes — 主题系统(增强版)
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 全局+实例级主题管理、CSS 变量隔离、外部主题跟随、主题继承
|
* @description 全局+实例级主题管理、CSS 变量隔离、外部主题跟随、主题继承
|
||||||
*
|
*
|
||||||
* v0.1.5 增强:
|
* v0.1.5 增强:
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaEditor Utils - 工具函数
|
* MetonaEditor Utils - 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.1.13
|
* @version 0.1.14
|
||||||
* @description 通用工具函数集合
|
* @description 通用工具函数集合
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Type definitions for MetonaEditor v0.1.13
|
// Type definitions for MetonaEditor v0.1.14
|
||||||
// Project: https://git.metona.cn/MetonaTeam/MetonaEditor
|
// Project: https://git.metona.cn/MetonaTeam/MetonaEditor
|
||||||
// Author: thzxx
|
// Author: thzxx
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user