@nestos/mdxeditor
v0.5.2
Published
A powerful, plugin-driven MDX editor component based on CodeMirror 6, designed for extensibility and rich content creation.
Maintainers
Readme
@nestos/mdxeditor
一个功能强大、由插件驱动、基于 CodeMirror 6 的 MDX 编辑器组件,专为可扩展性和富内容创作而设计。
@nestos/mdxeditor 提供了一个完整的解决方案,集成了强大的 CodeMirror 6 编辑核心和一个基于 marked 的可扩展渲染器。它通过灵活的插件系统,实现了编辑和预览两种模式,并内置了大量开箱即用的功能。
✨ 核心特性
- 🚀 双模式操作: 在原生 Markdown/MDX 编辑器 (CodeMirror 6) 和精美的渲染视图之间无缝切换。
- 🧩 可扩展的插件系统: 包含丰富的内置插件,并提供简单的 API 来创建您自己的插件,轻松定制编辑器功能。
- 📚 丰富的语法支持:
- 通过 MathJax 支持 LaTeX 数学公式 (
...和...)。 - 使用 Mermaid 绘制图表和流程图。
- 支持 Anki 风格的挖空填词 (Cloze) (
--挖空内容--),并支持音频提示。 - 创建可折叠内容块 (
::> 标题) 以组织长文。 - 自定义媒体嵌入 (
!video[...],!file[...])。
- 通过 MathJax 支持 LaTeX 数学公式 (
- 🎨 交互式 UI 组件:
- 可配置的工具栏和标题栏,用于执行常用操作。
- 交互式任务列表 (
- [x] 任务),点击即可更改状态。 - 高级的代码块控件,提供一键复制、下载和折叠功能。
- HTML/CSS/JS 在线预览:用
html live运行独立示例,或用playground=名称组合多个代码块,点击后在隔离的 iframe 中运行。语法与配置。
- ✍️ 高级编辑体验:
- 强大的自动完成框架,支持标签 (
#)、提及 (@) 等。 - 源码同步: 在渲染视图上按住
Ctrl/Cmd并双击,可立即跳转到其源码位置。 - 为挖空填词集成了间隔重复系统 (SRS)。
- 强大的自动完成框架,支持标签 (
- 💾 灵活集成: 通过公开接口接入宿主的保存、附件与持久化服务;不依赖其他
@nestos/*包。
嵌入宿主界面
编辑器保留挂载元素已有的 class。独立文档默认由编辑器滚动,原始 HTML 继续作为 HTML 渲染;宿主可以显式选择嵌入配置:
const editor = await createMDxEditor(mount, {
initialMode: 'render',
scrollMode: 'host',
htmlPolicy: 'literal',
});scrollMode: 'host' 使预览自然撑高,由宿主负责滚动;编辑模式保留编辑器滚动。以下公共 CSS 变量可设置在挂载元素或祖先上,明暗主题均生效:
.embedded-content {
--mdx-content-background: transparent;
--mdx-content-color: currentColor;
--mdx-content-padding: 0;
--mdx-content-end-margin: 0;
}末尾留白变量用于宿主滚动模式。htmlPolicy: 'literal' 对正文、标题和折叠内容的原始 HTML 统一按字面显示,语法扩展生成的 HTML 继续渲染;直接创建 MDxRenderer 时也支持此选项。插件解析内部 Markdown 时使用 context.renderMarkdown(text, options),继承外层语法扩展与 HTML 策略,不重复执行生命周期钩子;afterRender 钩子支持异步转换。
插件生成的 <details data-mdx-disclosure-key="host:stable-key"> 会在完整渲染、流式更新和流式结束时保留展开状态。key 在同一渲染器内须唯一,由宿主提供普通字符串,核心不解释其业务含义。FoldablePlugin 和代码块控件自动生成 key;相同内容通过出现次序区分,内容改变时需要显式 key 才能维持同一身份。状态只存在于实例内存,销毁时清理,不自动写入宿主存储。
宿主搜索定位可调用 revealRenderedContent(target, root):仅展开 root 内目标的祖先 details 和代码块,代码块状态继续由控件维护;函数不滚动页面,宿主应在调用后测量目标并滚动自己的视口。
📦 安装
# 使用 pnpm
pnpm add @nestos/mdxeditor
# 使用 npm
npm install @nestos/mdxeditor
# 使用 yarn
yarn add @nestos/mdxeditor🚀 快速上手
准备 HTML 容器
在您的 HTML 文件中,创建一个用于挂载编辑器的容器。
<div id="editor-container" style="height: 600px; border: 1px solid #ccc;"></div>初始化编辑器
在您的 JavaScript 或 TypeScript 文件中,导入并使用
createMDxEditor工厂函数。import { createMDxEditor } from '@nestos/mdxeditor'; // 引入基础样式,您可以根据需要覆盖它 import '@nestos/mdxeditor/style.css'; async function initializeEditor() { const container = document.getElementById('editor-container'); if (container) { const initialContent = `# Hello, MDxEditor!
This is a demo. Try some syntax:
- [x] Interactive task list
- [ ] Another task
$$E=mc^2$$
```mermaid graph TD; A[Start]-->B{Is it?}; B-->|Yes|C[OK]; B-->|No|D[Find out]; ````;
const editor = await createMDxEditor(container, {
initialContent: initialContent,
});
console.log('Editor is ready!', editor);
// 您现在可以与 editor 实例交互
// editor.setText('New content!');
}
}
initializeEditor();
```⚙️ 配置
createMDxEditor 函数接受一个配置对象,允许您深度自定义编辑器的行为和功能。
插件配置
您可以通过 plugins 数组和 defaultPluginOptions 对象来管理插件。
import { createMDxEditor } from '@nestos/mdxeditor';
const editor = await createMDxEditor(container, {
initialContent: '...',
// 自定义插件列表
plugins: [
'-mermaid', // 禁用默认的 Mermaid 插件
'core:titlebar',
'autocomplete:tag',
'cloze:cloze', // Add cloze syntax
'cloze:memory', // Add spaced repetition
],
// 为特定插件提供配置
defaultPluginOptions: {
// 配置任务列表插件
'task-list': {
autoUpdateMarkdown: false, // 检查任务时不要自动更新 Markdown 源码
},
// 配置标签自动完成插件(需要手动启用 'autocomplete:tag')
'autocomplete:tag': {
getTags: async () => ['bug', 'feature', 'docs', 'refactor']
},
// 配置标题栏
'core:titlebar': {
enableToggleEditMode: true, // 启用编辑/阅读模式切换按钮
saveCallback: (editor) => {
console.log('Content saved:', editor.getText());
}
}
}
});- 启用插件: 在
plugins数组中添加插件名称 (e.g.,'cloze:cloze')。 - 禁用默认插件: 在插件名称前添加
-(e.g.,'-mermaid')。 - 禁用所有默认插件: 将
'-all'作为plugins数组的第一个元素。
🔌 核心插件
模块功能说明表
以下是根据代码 (factory.ts 和各插件文件) 整理的模块功能、默认状态及常用配置选项。
| 模块/插件名称 | 注册名称 (Name) | 功能说明 | 默认状态 (Default) | 常用选项 (Common Options) |
| :--- | :--- | :--- | :--- | :--- |
| Core Editor | editor:core | 提供基础编辑能力 (CodeMirror 6),包括行号、软换行、折叠、撤销重做等。 | 已启用行号: 关闭自动换行: 开启折叠: 开启 | enableLineNumbers: boolean (行号)enableLineWrapping: boolean (长行自动换行)enableHistory: boolean (历史记录)enableAutocompletion: boolean (自动补全) |
| Title Bar | core:titlebar | 顶部标题栏,包含标题、模式切换、保存、打印及 AI 按钮。 | defaultEditorFactory 默认启用;createMDxEditor 需在 plugins 中添加 | enableToggleEditMode: booleantitle: stringonSidebarToggle: function |
| Toolbar | ui:toolbar | 编辑模式的格式化工具栏;render 模式隐藏整行,不占用正文空间。 | 已启用 | className: string (自定义样式类) |
| Formatting | ui:formatting | 提供加粗、斜体、列表、链接等基础 Markdown 格式化按钮和命令。 | 已启用功能: All | enabledFormats: string[] (如 ['bold', 'italic'])customIcons: object (自定义图标) |
| Clipboard | interaction:clipboard | 智能粘贴:富文本转 Markdown;Cmd/Ctrl+Shift+V 原始粘贴。 | 已启用模式: smart | pasteMode: 'smart'\|'plain'\|'rich'enablePlainPasteShortcut: boolean |
| Source Sync | interaction:source-sync | 双击渲染内容可跳转至编辑模式对应的源码位置。 | 已启用 | 无配置项 (依赖 DOM 结构查找) |
| Table | interaction:table | 增强表格功能,支持点击表头排序和表头下方输入框筛选。 | 已启用排序: 开启筛选: 关闭 | enableSorting: booleanenableFiltering: boolean (开启筛选行) |
| Foldable | folder | 支持折叠块语法 ::> 标题,可包含复选框。 | 已启用默认: 展开 | defaultOpen: booleanenableTaskCheckbox: boolean (标题是否支持任务框) |
| MathJax | mathjax | 渲染 LaTeX 数学公式 ($$...$$, $..$)。 | 已启用自动加载 CDN | cdnUrl: string (自定义 CDN)config: object (MathJax 配置) |
| Media | media | 渲染视频、音频、嵌入内容 (YouTube/Bilibili/Office/PDF)。 | 已启用视频控制条: 开启 | videoAutoplay: booleanvideoControls: boolean |
| Callout | callout | 支持 GitHub/Obsidian 风格的提示块 (> [!NOTE])。 | 已启用 | defaultFolded: boolean (暂未实现) |
| Mermaid | mermaid | 渲染 Mermaid 流程图、时序图等。 | 已启用按需加载本地 mermaid 依赖 | theme: 'default'|'dark'等 |
| SVG | svg | 将 ```svg 代码块直接渲染为内联 SVG 图片 (带安全过滤)。 | 已启用Sanitize: 开启 | sanitize: boolean (防XSS)containerClass: string |
| Code Controls | codeblock-controls | 代码块增强:复制、下载、折叠过长代码。 | 已启用折叠阈值: 250px | enableCopy: booleanenableCollapse: booleancollapseThreshold: number (高度阈值) |
| Playground | playground | HTML/CSS/JS 单块或同名分组,在代码块原位置切换代码/预览;单行工具栏提供刷新和代码视图的原有图标操作。 | 已启用仅显式标记的代码块 | height: number (预览高度,默认 360px) |
| Task List | task-list | 交互式任务列表 (- [ ]),支持点击勾选并回写 Markdown。 | 已启用自动回写: 开启 | autoUpdateMarkdown: boolean (点击更新源码)checkboxSelector: string |
| Cloze (Core) | cloze:cloze | 挖空插件核心 (--text--),支持点击显示/隐藏,支持 TTS 发音。 | 按需加载(需在 plugins 列表) | className: stringaudioIconClass: string |
| Cloze UI | cloze:cloze-controls | 挖空控制面板(全显/全隐/导航)。 | 按需加载 | className: string |
| Memory | cloze:memory | 记忆卡片/SRS (间隔重复) 功能,为挖空添加“忘记/简单”评分面板。 | 按需加载 | gradingTimeout: numbercoolingPeriod: number (冷却时间)hideBeforeDueHours: number |
| PlantUML | plantuml | 将 PlantUML 代码块转换为图片 (依赖外部 Server)。 | 默认未启用(不在 DEFAULT_PLUGINS) | serverUrl: string (默认 plantuml.com)format: 'svg'|'png' |
| Vega | vega | 渲染 Vega/Vega-Lite 数据可视化图表。 | 默认未启用(不在 DEFAULT_PLUGINS) | theme: 'quartz'等actions: boolean (显示导出菜单) |
| Autocomplete| autocomplete:tag/mention | 自动补全 (#tag, @mention)。 | 默认未启用 | getTags: function (标签源)providers: array (提及源配置) |
3. 如何配置默认选项
在调用 createMDxEditor 或 defaultEditorFactory 时,可以通过 defaultPluginOptions 修改上述默认状态。
示例:开启行号并禁用表格排序
createMDxEditor(container, {
plugins: [/* ... */], // 如果不传则使用 DEFAULT_PLUGINS
defaultPluginOptions: {
'editor:core': {
enableLineNumbers: true, // 开启行号
enableLineWrapping: true, // 长行仅在视图中自动换行
enableFolding: true
},
'interaction:table': {
enableSorting: false, // 禁用表格排序
enableFiltering: true // 开启表格筛选
}
}
});API
createMDxEditor 返回实现 IEditor 公共接口的 MDxEditor 实例,您可以使用它来与编辑器进行交互。
editor.getText(): string: 获取当前编辑器的 Markdown 全文。editor.setText(markdown: string): void: 设置编辑器的内容。editor.switchToMode(mode: 'edit' | 'render'): Promise<void>: 切换编辑或渲染模式。editor.getHeadings(): Promise<Heading[]>: 获取文档中的标题列表(用于大纲)。editor.setReadOnly(isReadOnly: boolean): void: 设置编辑器为只读模式。editor.destroy(): Promise<void>: 销毁编辑器实例并释放资源。editor.on(event, callback): 监听编辑器事件,如change或ready。
📜 许可证
本项目基于 MIT 许可证。
独立运行与宿主适配
编辑器不依赖其他 @nestos/* 包。创建实例只需 DOM 容器与内容;附件、保存和持久化可按需注入公共接口,接口由 TypeScript 结构化匹配,无需继承特定实现。
import { createMDxEditor, type AssetProvider } from '@nestos/mdxeditor';
import '@nestos/mdxeditor/style.css';
const assets: AssetProvider = {
async read(name) {
const response = await fetch(`/attachments/${encodeURIComponent(name)}`);
return response.ok ? response.arrayBuffer() : null;
},
};
async function saveContent(content: string): Promise<void> {
localStorage.setItem('my-document', content);
}
const editor = await createMDxEditor(document.getElementById('editor-container')!, {
initialContent: '# Hello',
contentFormat: 'markdown',
assets,
onSave: async content => { await saveContent(content); },
});saveContent 是宿主自己的保存函数。EditorHost 提供可选的打开文档、重命名和通知;StoreFactory 提供插件存储,默认使用内存。核心默认按 Markdown 处理,纯文本需显式传入 contentFormat: 'text'。
NestOS 使用 @nestos/mdx-adapter 的 createMDxEditor / defaultEditorFactory,接收 ui-common 的 EditorOptions,校验 namespace/Session 上下文并适配 VFS、附件、插件元数据及文件格式。AssetManagerUI 和 LLMPrintService 也由适配包导出。外部移动文件后调用适配实例的 updateNodeId(newPath),更新后续保存、附件与元数据的文档路径。
适配包目前在 monorepo 内使用;独立用户可直接实现核心公共接口。适配包样式:import '@nestos/mdx-adapter/style.css'。
标签、提及与自动补全也支持用户提供数据:TagPluginOptions.getTags、MentionProvider、AutocompleteProvider 均由核心公开。用户可查询自己的 REST 服务、本地数据或其他存储,再通过 plugins 安装插件,无需引入 itookit 的业务服务。translate 可接入宿主 i18n;复杂交互可实现 MDxPlugin,通过插件上下文使用公开能力。
保存与生命周期
onSave 接收 Markdown 内容,保存失败时应抛出异常,使编辑器保留 dirty 状态。程序调用 setText() 后若需要保存,请显式标记修改:
editor.setText('# Updated');
editor.setDirty(true);
await editor.save();
await editor.destroy();用户编辑支持自动保存;flushPendingSave() 可在导航或引用内容前等待保存。标题栏的保存按钮通过 core:titlebar.saveCallback 配置,例如 async editor => { await editor.save(); }。
公共接口
| 能力 | 配置 | 使用者实现 |
|---|---|---|
| 附件 | assets: AssetProvider | 必需 read(name);可选 upload、prune、mimeType |
| 插件存储 | storeFactory: StoreFactory | 为插件返回含 get、set、remove 的 ScopedPersistenceStore;默认内存存储 |
| 文档交互 | host: EditorHost | 可选 openDocument、renameDocument、notify |
| 保存 | onSave | 异步保存函数 |
| 文档标识 | documentPath | 移动文件后调用 updateDocumentPath(newPath);可通过 onDocumentPathChange 接收更新 |
| 本地化 | locale / translate | 内置 zh-CN、en,或提供自己的翻译函数 |
以上类型均可从 @nestos/mdxeditor 根入口导入。接口采用结构化匹配,用户无需依赖 itookit 的 VFS、LLM 或上下文模块。未配置能力时,相关功能按需省略;核心默认把内容当作 Markdown,纯文本可显式设置 contentFormat: 'text'。
从旧宿主接口迁移
0.5.0 使用 documentPath、assets、storeFactory、host 和 onSave 接收能力,原 target/files/hostContext 应由宿主转成上述配置。核心的 updateNodeId 改为 updateDocumentPath;VFS 附件管理 UI 与会话专用打印迁入本仓库的 mdx-adapter。
开发与发布
corepack enable
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
pnpm build
pnpm pack
npm publish --access public包根提供 ESM、CommonJS 和 TypeScript 声明;样式从 @nestos/mdxeditor/style.css 导入。源码与测试独立维护于本仓库,NestOS 通过 npm 的固定版本接入。
打印文件名
文件打印默认显示去掉最后一个扩展名的文件名,例如 简历.mind 显示为 简历。打印选项中的“显示文件名”复选框默认勾选,取消后隐藏文件名标题;显式传入的作者、日期和版本元数据仍可显示。
PrintOptions.fileName 指定文件名(优先于 title,打印时自动去掉路径和扩展名);showFileName: false 隐藏标题。显式 title 可用于自定义文档标题,其内容不进行后缀处理。设置 fontSize 会跳过选项对话框,可同时通过 showFileName 指定显示策略。
通用单项与多内容阅读视图
MDxEditor 继续提供完整编辑/预览和插件系统;MDxRenderer 继续提供独立与流式渲染。新增的 ContentView 只负责通用标题、摘要、可点击时间详情、折叠、复制和更多菜单,不解释角色、邮箱或 Session。
import {ContentView, ContentReader, ContentListView} from '@nestos/mdxeditor';
const item = new ContentView({title: 'Title', preview: 'Collapsed preview'});
const reader = new ContentReader(item.body, {format: 'markdown', plugins: []});
await reader.setText('**Markdown content**');
item.setCopySource(() => reader.getText(), 'Copy');
const list = new ContentListView(container, {selection: {
run: async (selection, signal) => host.startConversation(selection, signal),
}});
list.sync([{id: 'stable-id', view: item}]);
// Update the same reader to retain its DOM and disclosure state.
await reader.setText('**Streaming content**', true);
list.setExpanded(false);
// The host owns reader/frame lifetimes; the list owns its selection listeners.
list.destroy(); await reader.destroy(); item.destroy();ContentReader 的 text 模式按原文阅读,不创建 CodeMirror,不执行 HTML、远程资源或 Markdown 插件;markdown 模式复用 MDxRenderer,支持宿主提供的插件、资源与流式更新。正文槽也可挂载完整 MDxEditor 或其他宿主内容,列表不会替换正文或改变编辑器语义。
ContentListView.sync 按稳定 ID 排列已创建的视图,保留其 DOM 与折叠状态;layout: 'host' 将分组布局留给宿主。重复 ID 拒绝且不改变现有列表。configureSelection 可替换或移除选区动作,setActive(false) 暂停入口并终止在途观察。默认双语控件文案可由 locale 或 labels 覆盖。
选区回调接收不可变的 text 与有序 items: [{id, text}];排除标题、菜单、隐藏内容和折叠正文。仅在点击 AI 按钮时调用回调,失败后的同一选区复用对象;宿主持有创建与提交身份。正文不被复制进诊断,取消信号只属于观察生命周期,不表示取消远端执行。
标题栏默认提供复制、目录和更多;宿主已有的向上导航继续位于目录之后、更多之前;保存、打印等次要操作保留在更多菜单。文内目录通过 ui-common 的 OutlineView/mountOutlinePopup 展示 H1–H3,编辑与阅读模式共用 Markdown 标题身份;重复标题、Setext 标题与代码块之后的源码定位保持一致。三级标题默认收起,桌面鼠标悬停二级展开,触屏使用独立展开按钮,键盘支持方向键、Home/End 与 Escape。
ContentListItem.outlineName 是可选的窗口目录名称,与稳定的 id 分离;ContentListView.getOutline() 和 reveal(id) 复用公共 WindowOutline,定位时展开目标内容框。无名称的窗口不进入目录;更新列表和销毁会释放旧引用。业务名称由调用方配置,核心不解释用户、回复或邮件。
