1. 安装依赖
npm install prosemirror-state prosemirror-view prosemirror-model prosemirror-keymap prosemirror-commands prosemirror-schema-list prosemirror-history prosemirror-transform
2. 创建脑图大纲的 Schema
import { Schema } from 'prosemirror-model';
import { orderedList, bulletList, listItem } from 'prosemirror-schema-list';
// 创建脑图专用的 schema
const mindmapSchema = new Schema({
nodes: {
doc: {
content: 'mindmap'
},
mindmap: {
content: 'mindmap_node+',
toDOM: () => ['div', { class: 'mindmap-container' }, 0]
},
mindmap_node: {
content: 'paragraph (mindmap_node*)?',
defining: true,
toDOM: () => ['div', { class: 'mindmap-node' }, 0],
parseDOM: [{ tag: 'div.mindmap-node' }]
},
paragraph: {
content: 'text*',
toDOM: () => ['p', 0],
parseDOM: [{ tag: 'p' }]
},
text: {
group: 'inline'
}
}
});
3. 创建插件系统
import { Plugin } from 'prosemirror-state';
import { keymap } from 'prosemirror-keymap';
import { baseKeymap } from 'prosemirror-commands';
import { history, redo, undo } from 'prosemirror-history';
// 脑图专用按键绑定
const mindmapKeymap = keymap({
'Tab': indentNode,
'Shift-Tab': dedentNode,
'Enter': splitNode,
'Backspace': mergeWithPrevious,
'Mod-z': undo,
'Mod-y': redo,
'Mod-Shift-z': redo
});
// 缩进节点(创建子节点)
function indentNode(state, dispatch) {
const { $from, $to } = state.selection;
if ($from.sameParent($to) && $from.depth > 1) {
const tr = state.tr;
const start = $from.before($from.depth);
const end = $from.after($from.depth);
// 将当前节点转换为前一个节点的子节点
tr.wrap(tr.doc.resolve(start).blockRange(tr.doc.resolve(end)), {
type: state.schema.nodes.mindmap_node
});
if (dispatch) dispatch(tr);
return true;
}
return false;
}
// 取消缩进(提升节点层级)
function dedentNode(state, dispatch) {
const { $from } = state.selection;
if ($from.depth < 3) return false;
const tr = state.tr;
const start = $from.before($from.depth - 1);
const end = $from.after($from.depth - 1);
tr.lift(tr.doc.resolve(start).blockRange(tr.doc.resolve(end)), $from.depth - 2);
if (dispatch) dispatch(tr);
return true;
}
// 分割节点
function splitNode(state, dispatch) {
const { $from, $to } = state.selection;
if ($from.sameParent($to)) {
const tr = state.tr.split($from.pos);
if (dispatch) dispatch(tr);
return true;
}
return false;
}
// 合并节点
function mergeWithPrevious(state, dispatch) {
const { $from } = state.selection;
if ($from.parentOffset === 0 && $from.depth > 2) {
const before = $from.before($from.depth);
if (before > 1) {
const tr = state.tr.delete(before - 1, before + 1);
if (dispatch) dispatch(tr);
return true;
}
}
return false;
}
// 节点选择插件
const nodeSelectionPlugin = new Plugin({
props: {
handleClick(view, pos) {
const $pos = view.state.doc.resolve(pos);
if ($pos.parent.type.name === 'mindmap_node') {
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, $pos.before())));
return true;
}
return false;
}
}
});
4. 创建编辑器状态和视图
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { NodeSelection } from 'prosemirror-state';
function createMindmapState(schema) {
return EditorState.create({
doc: schema.node('doc', null, [
schema.node('mindmap', null, [
createMindmapNode(schema, '中心主题'),
createMindmapNode(schema, '主要分支 1', [
createMindmapNode(schema, '子主题 1.1'),
createMindmapNode(schema, '子主题 1.2')
]),
createMindmapNode(schema, '主要分支 2', [
createMindmapNode(schema, '子主题 2.1'),
createMindmapNode(schema, '子主题 2.2')
])
])
]),
plugins: [
history(),
mindmapKeymap,
nodeSelectionPlugin,
keymap(baseKeymap)
]
});
}
function createMindmapNode(schema, text, children = []) {
const paragraph = schema.node('paragraph', null, [
schema.text(text)
]);
return schema.node('mindmap_node', null, [
paragraph,
...children
]);
}
5. 样式和主题
// 创建带有样式的编辑器视图
function createMindmapEditor(placeholder) {
const schema = mindmapSchema;
const state = createMindmapState(schema);
return new EditorView(placeholder, {
state,
dispatchTransaction(transaction) {
const newState = this.state.apply(transaction);
this.updateState(newState);
},
nodeViews: {
mindmap_node(node, view, getPos) {
return new MindmapNodeView(node, view, getPos);
}
}
});
}
// 自定义节点视图
class MindmapNodeView {
constructor(node, view, getPos) {
this.node = node;
this.view = view;
this.getPos = getPos;
this.dom = document.createElement('div');
this.dom.className = 'mindmap-node';
this.contentDOM = document.createElement('div');
this.contentDOM.className = 'node-content';
this.dom.appendChild(this.contentDOM);
this.update(node);
}
update(node) {
if (node.type !== this.node.type) return false;
this.node = node;
return true;
}
selectNode() {
this.dom.classList.add('ProseMirror-selectednode');
}
deselectNode() {
this.dom.classList.remove('ProseMirror-selectednode');
}
}
6. 完整的初始化代码
import { Schema } from 'prosemirror-model';
import { EditorState, NodeSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { keymap } from 'prosemirror-keymap';
import { baseKeymap } from 'prosemirror-commands';
import { history, redo, undo } from 'prosemirror-history';
import { Plugin } from 'prosemirror-state';
// 定义脑图 schema
const mindmapSchema = new Schema({
nodes: {
doc: { content: 'mindmap' },
mindmap: {
content: 'mindmap_node+',
toDOM: () => ['div', { class: 'mindmap-container' }, 0]
},
mindmap_node: {
content: 'paragraph (mindmap_node*)?',
defining: true,
toDOM: () => ['div', { class: 'mindmap-node' }, 0],
parseDOM: [{ tag: 'div.mindmap-node' }]
},
paragraph: {
content: 'text*',
toDOM: () => ['p', 0],
parseDOM: [{ tag: 'p' }]
},
text: { group: 'inline' }
}
});
// 创建脑图编辑器
export function createMindmapEditor(element) {
// 创建初始文档
const initialDoc = mindmapSchema.node('doc', null, [
mindmapSchema.node('mindmap', null, [
createNode('中心主题'),
createNode('主要分支 1', [
createNode('子主题 1.1'),
createNode('子主题 1.2')
]),
createNode('主要分支 2', [
createNode('子主题 2.1'),
createNode('子主题 2.2', [
createNode('子子主题 2.2.1')
])
])
])
]);
function createNode(text, children = []) {
const paragraph = mindmapSchema.node('paragraph', null, [
mindmapSchema.text(text)
]);
return mindmapSchema.node('mindmap_node', null, [
paragraph,
...children
]);
}
// 创建编辑器状态
const state = EditorState.create({
doc: initialDoc,
plugins: [
history(),
keymap(mindmapKeymap),
keymap(baseKeymap),
nodeSelectionPlugin
]
});
// 创建编辑器视图
return new EditorView(element, {
state,
nodeViews: {
mindmap_node: (node, view, getPos) => new MindmapNodeView(node, view, getPos)
}
});
}
// 使用示例
const editorElement = document.getElementById('mindmap-editor');
const editor = createMindmapEditor(editorElement);
7. 扩展功能
添加序列化方法
// 导出脑图数据
export function exportMindmapData(editor) {
const doc = editor.state.doc;
const mindmap = doc.firstChild;
const nodes = [];
function traverseNode(node, depth = 0) {
if (node.type.name === 'mindmap_node') {
const text = node.firstChild.textContent;
const children = [];
// 遍历子节点
for (let i = 1; i < node.childCount; i++) {
const child = node.child(i);
if (child.type.name === 'mindmap_node') {
children.push(traverseNode(child, depth + 1));
}
}
return {
text,
depth,
children: children.length > 0 ? children : undefined
};
}
return null;
}
mindmap.forEach(node => {
const nodeData = traverseNode(node);
if (nodeData) nodes.push(nodeData);
});
return nodes;
}
// 导入脑图数据
export function importMindmapData(editor, data) {
const { schema } = editor.state;
const tr = editor.state.tr;
function createNodeFromData(nodeData) {
const children = [schema.node('paragraph', null, [
schema.text(nodeData.text)
])];
if (nodeData.children) {
nodeData.children.forEach(childData => {
children.push(createNodeFromData(childData));
});
}
return schema.node('mindmap_node', null, children);
}
const mindmapNodes = data.map(createNodeFromData);
const mindmap = schema.node('mindmap', null, mindmapNodes);
const doc = schema.node('doc', null, [mindmap]);
tr.replaceWith(0, editor.state.doc.content.size, doc.content);
editor.dispatch(tr);
}
添加工具栏
// 创建工具栏
export function createMindmapToolbar(editor, container) {
const toolbar = document.createElement('div');
toolbar.className = 'mindmap-toolbar';
const buttons = [
{ text: '缩进', action: () => indentNode(editor.state, editor.dispatch) },
{ text: '取消缩进', action: () => dedentNode(editor.state, editor.dispatch) },
{ text: '添加子节点', action: addChildNode },
{ text: '添加兄弟节点', action: addSiblingNode },
{ text: '删除节点', action: deleteNode }
];
buttons.forEach(btn => {
const button = document.createElement('button');
button.textContent = btn.text;
button.addEventListener('click', btn.action);
toolbar.appendChild(button);
});
container.appendChild(toolbar);
}
function addChildNode(state, dispatch) {
const { $from } = state.selection;
const node = $from.node($from.depth);
if (node.type.name === 'mindmap_node') {
const tr = state.tr;
const newNode = createNode('新子节点');
const insertPos = $from.after($from.depth);
tr.insert(insertPos, newNode);
if (dispatch) dispatch(tr);
return true;
}
return false;
}
function addSiblingNode(state, dispatch) {
const { $from } = state.selection;
if ($from.depth < 2) return false;
const tr = state.tr;
const newNode = createNode('新兄弟节点');
const insertPos = $from.after($from.depth);
tr.insert(insertPos, newNode);
if (dispatch) dispatch(tr);
return true;
}
function deleteNode(state, dispatch) {
const { $from } = state.selection;
if ($from.depth < 2) return false;
const tr = state.tr;
const start = $from.before($from.depth);
const end = $from.after($from.depth);
tr.delete(start, end);
if (dispatch) dispatch(tr);
return true;
}
这个实现提供了完整的脑图大纲功能,包括:
- 层级结构显示
- 缩进/取消缩进
- 节点选择
- 添加/删除节点
- 历史记录
- 数据导入导出
根据需要进一步扩展功能,如添加拖拽排序、折叠展开、样式定制等。
ProseMirror实现脑图大纲
1482

被折叠的 条评论
为什么被折叠?



