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
275 lines
7.1 KiB
JavaScript
275 lines
7.1 KiB
JavaScript
/**
|
||
* MetonaEditor - 轻量级 Markdown Editor 库
|
||
* @module metona-editor
|
||
* @version 0.1.14
|
||
* @author thzxx
|
||
* @description 现代化、零依赖、易扩展、易维护、易使用的 Markdown 编辑器库。单文件,开箱即用。
|
||
* @license MIT
|
||
*
|
||
* 用法:
|
||
* // 浏览器
|
||
* <script src="dist/metona-editor.js"></script>
|
||
* const editor = MeEditor.create('#editor', { mode: 'split' });
|
||
*
|
||
* // ES Module
|
||
* import MeEditor from 'metona-editor';
|
||
* const editor = MeEditor.create(container, options);
|
||
*
|
||
* // 直接使用类
|
||
* import { MarkdownEditor } from 'metona-editor';
|
||
* const editor = new MarkdownEditor(container, options);
|
||
*/
|
||
|
||
import { MarkdownEditor } from './core.js';
|
||
import { parseMarkdown, safeUrl, slugify, clearRenderCache } from './parser.js';
|
||
import { themeUtils, exportCSSVars, getCSSVariable, followExternalTheme, adoptFromParent, createInstanceTheme } from './themes.js';
|
||
import { i18nUtils, createInstanceI18n, loadRemote } from './i18n.js';
|
||
import { pluginUtils, presetPlugins, topologicalSort, validateConfig } from './plugins.js';
|
||
import { animationUtils } from './animations.js';
|
||
import { DEFAULTS, ICONS, THEMES, EDIT_MODES, DEFAULT_TOOLBAR, TOOLBAR_ACTIONS } from './constants.js';
|
||
|
||
// 版本信息
|
||
const VERSION = '0.1.14';
|
||
|
||
/**
|
||
* 全局默认插件队列
|
||
* 通过 beforeCreate 钩子自动应用到所有后续创建的编辑器实例
|
||
*/
|
||
const globalPlugins = [];
|
||
|
||
// 注册全局钩子:每个新实例创建时自动安装全局默认插件
|
||
// 注意:core.js 构造函数中 beforeCreate 在 config.plugins 安装之前触发,
|
||
// 且 this._plugins 已初始化为 [],因此此处调用 editor.use() 安全。
|
||
MarkdownEditor.on('beforeCreate', (editor) => {
|
||
if (!editor || editor._destroyed) return;
|
||
globalPlugins.forEach((p) => {
|
||
try { editor.use(p); } catch (e) { console.error('MeEditor global plugin install error:', e); }
|
||
});
|
||
});
|
||
|
||
/**
|
||
* 创建编辑器实例(工厂函数,推荐用法)
|
||
* @param {string|HTMLElement} container - 容器选择器或 DOM 元素
|
||
* @param {Object} [options] - 配置项(见 DEFAULTS)
|
||
* @returns {MarkdownEditor} 编辑器实例
|
||
*/
|
||
function create(container, options = {}) {
|
||
return new MarkdownEditor(container, options);
|
||
}
|
||
|
||
/**
|
||
* 注册全局默认插件(作用于所有后续创建的编辑器实例)
|
||
* @param {string|Object} plugin - 预设插件名 或 插件对象
|
||
* @param {Object} [options] - 插件配置(覆盖预设默认值)
|
||
* @returns {typeof api} 返回 api 自身以支持链式调用
|
||
*/
|
||
function use(plugin, options = {}) {
|
||
let p = plugin;
|
||
if (typeof plugin === 'string') {
|
||
p = presetPlugins[plugin];
|
||
if (!p) {
|
||
console.warn(`MeEditor: preset plugin "${plugin}" not found`);
|
||
return api;
|
||
}
|
||
}
|
||
if (!p || typeof p !== 'object') {
|
||
console.warn('MeEditor: invalid plugin');
|
||
return api;
|
||
}
|
||
const merged = (options && typeof options === 'object' && Object.keys(options).length > 0)
|
||
? { ...p, ...options }
|
||
: p;
|
||
globalPlugins.push(merged);
|
||
return api;
|
||
}
|
||
|
||
/**
|
||
* 注册全局事件钩子(转发到 MarkdownEditor 静态钩子,所有实例共享)
|
||
* 可用钩子:beforeCreate / afterCreate / beforeRender / afterRender / beforeDestroy / afterDestroy
|
||
* @param {string} name - 钩子名
|
||
* @param {Function} fn - 回调 (editor) => void
|
||
* @returns {Function} 取消注册函数
|
||
*/
|
||
function on(name, fn) {
|
||
return MarkdownEditor.on(name, fn);
|
||
}
|
||
|
||
/**
|
||
* 注销全局事件钩子
|
||
*/
|
||
function off(name, fn) {
|
||
MarkdownEditor.off(name, fn);
|
||
return api;
|
||
}
|
||
|
||
/**
|
||
* 切换全局主题(转发到 themeUtils)
|
||
*/
|
||
function setTheme(theme, options) {
|
||
if (themeUtils && typeof themeUtils.switchTheme === 'function') {
|
||
themeUtils.switchTheme(theme, options);
|
||
}
|
||
return api;
|
||
}
|
||
|
||
/**
|
||
* 切换全局语言(转发到 i18nUtils)
|
||
*/
|
||
function setLocale(locale) {
|
||
if (i18nUtils && typeof i18nUtils.setCurrentLocale === 'function') {
|
||
i18nUtils.setCurrentLocale(locale);
|
||
}
|
||
return api;
|
||
}
|
||
|
||
/**
|
||
* 销毁所有全局资源(主题监听、i18n 监听、动画、全局插件队列)
|
||
* 注意:此方法不销毁已创建的编辑器实例,请单独调用每个实例的 destroy()
|
||
*/
|
||
function destroy() {
|
||
if (themeUtils) {
|
||
if (typeof themeUtils.clearThemeListeners === 'function') {
|
||
try { themeUtils.clearThemeListeners(); } catch (_) {}
|
||
}
|
||
if (typeof themeUtils.unwatchSystemTheme === 'function') {
|
||
try { themeUtils.unwatchSystemTheme(); } catch (_) {}
|
||
}
|
||
}
|
||
if (i18nUtils && typeof i18nUtils.clearLocaleListeners === 'function') {
|
||
try { i18nUtils.clearLocaleListeners(); } catch (_) {}
|
||
}
|
||
if (animationUtils && typeof animationUtils.cancelAll === 'function') {
|
||
try { animationUtils.cancelAll(); } catch (_) {}
|
||
}
|
||
globalPlugins.length = 0;
|
||
}
|
||
|
||
/**
|
||
* 获取库运行状态
|
||
*/
|
||
function getStatus() {
|
||
return {
|
||
version: VERSION,
|
||
theme: (themeUtils && typeof themeUtils.getCurrentTheme === 'function') ? themeUtils.getCurrentTheme() : 'auto',
|
||
locale: (i18nUtils && typeof i18nUtils.getCurrentLocale === 'function') ? i18nUtils.getCurrentLocale() : 'zh-CN',
|
||
globalPlugins: globalPlugins.map((p) => p.name || 'custom'),
|
||
presetPlugins: Object.keys(presetPlugins || {}),
|
||
};
|
||
}
|
||
|
||
// 初始化主题与国际化(浏览器环境)
|
||
if (typeof themeUtils === 'object' && typeof themeUtils.initTheme === 'function') {
|
||
try { themeUtils.initTheme(); } catch (_) {}
|
||
}
|
||
if (typeof i18nUtils === 'object' && typeof i18nUtils.initI18n === 'function') {
|
||
try { i18nUtils.initI18n(); } catch (_) {}
|
||
}
|
||
|
||
/**
|
||
* 顶层 API 对象(默认导出)
|
||
*/
|
||
const api = {
|
||
// 版本
|
||
VERSION,
|
||
version: VERSION,
|
||
|
||
// 核心类
|
||
MarkdownEditor,
|
||
Editor: MarkdownEditor,
|
||
|
||
// 工厂函数(推荐入口)
|
||
create,
|
||
|
||
// 顶层 API
|
||
use,
|
||
on,
|
||
off,
|
||
setTheme,
|
||
setLocale,
|
||
destroy,
|
||
getStatus,
|
||
|
||
// 内置解析器(可单独使用或替换)
|
||
parseMarkdown,
|
||
safeUrl,
|
||
slugify,
|
||
clearRenderCache,
|
||
|
||
// 工具集
|
||
themes: themeUtils,
|
||
i18n: i18nUtils,
|
||
animations: animationUtils,
|
||
plugins: pluginUtils,
|
||
presetPlugins,
|
||
|
||
// 插件工具(v0.1.6)
|
||
topologicalSort,
|
||
validateConfig,
|
||
|
||
// i18n 工具(v0.1.6)
|
||
createInstanceI18n,
|
||
loadRemote,
|
||
|
||
// 主题工具(v0.1.5 新增)
|
||
exportCSSVars,
|
||
getCSSVariable,
|
||
followExternalTheme,
|
||
adoptFromParent,
|
||
createInstanceTheme,
|
||
|
||
// 常量
|
||
DEFAULTS,
|
||
ICONS,
|
||
THEMES,
|
||
EDIT_MODES,
|
||
DEFAULT_TOOLBAR,
|
||
TOOLBAR_ACTIONS,
|
||
};
|
||
|
||
// 浏览器环境全局注册(UMD/CDN 场景:window.MeEditor)
|
||
if (typeof window !== 'undefined') {
|
||
window.MeEditor = api;
|
||
}
|
||
|
||
export default api;
|
||
export {
|
||
api,
|
||
api as meEditor,
|
||
api as MeEditor,
|
||
MarkdownEditor,
|
||
MarkdownEditor as Editor,
|
||
create,
|
||
use,
|
||
on,
|
||
off,
|
||
setTheme,
|
||
setLocale,
|
||
destroy,
|
||
getStatus,
|
||
parseMarkdown,
|
||
safeUrl,
|
||
slugify,
|
||
clearRenderCache,
|
||
themeUtils,
|
||
i18nUtils,
|
||
pluginUtils,
|
||
presetPlugins,
|
||
animationUtils,
|
||
topologicalSort,
|
||
validateConfig,
|
||
createInstanceI18n,
|
||
loadRemote,
|
||
exportCSSVars,
|
||
getCSSVariable,
|
||
followExternalTheme,
|
||
adoptFromParent,
|
||
createInstanceTheme,
|
||
DEFAULTS,
|
||
ICONS,
|
||
THEMES,
|
||
EDIT_MODES,
|
||
DEFAULT_TOOLBAR,
|
||
TOOLBAR_ACTIONS,
|
||
VERSION,
|
||
};
|