背景
我们学习了schema的nodes和marks, 知道大概nodes和marks的作用。
node节点里面有个group的属性,定义了该节点为block还是inline。
在渲染层面,其实是定义了该节点是块节点还是inline节点
当然还有个重要的content属性,定义了其children的构成
最基本的nodes数据结构需要包含doc / text / 一个block的节点
‘块’ 是什么
nodes: {
doc: {
content: "block+"
} as NodeSpec,
paragraph: {
content: "inline*",
group: "block",
parseDOM: [{tag: "p"}],
toDOM() { return pDOM }
} as NodeSpec,
text: {
group: "inline"
} as NodeSpec,
}
通过这个结构,可以看到
- doc为最顶的块级元素,包含了多个block(paragraph节点), 这里面和doc的content: 'block+' 表现一致
- 段落内包含了一个text节点,当然也可以包含多个inline的节点
这样的块级结构,相信大家是很容易理解的,因为和html的结构理解是一样的。
- doc是块
- paragraph是块
- text也是块
一砖一瓦都是块,拼凑出一个html页面
平常我们是直接去写html, prosemirror上则需要通过nodes去描述这个结构,再通过复制黏贴或者代码操作等的方式去得到dom
‘块’ 的自定义
如果我们想实现类似如下的html结构,需要怎样去定义呢?
- 一个div包裹p
- 一个div包裹h1
<div>
<p>this is p</p>
</div>
<div>
<h1>this is h1</h1>
</div>
从prosemirror的角度来看,上面为2个块。我们先定义出div的这个节点
block_tile: {
content: 'block+',
group: 'block',
inline: false,
toDOM: () => {
return ['div', { class: 'block_tile' }, 0]
},
parseDOM: [{ tag: 'div.block_tile' }]
}
可以打印看下这个nodeType的属性
再和node block_tile的属性配置关联发现,其实是根据schema node 的配置生成的产物: nodeType
通过prosemirror构造dom
const { state, dispatch } = editorView;
const { schema, tr, selection } = state;
const textNode = schema.text('this is block_tile p');
const pNode = schema.nodes.paragraph.create({}, textNode);
const bNode = schema.nodes.block_tile.create({}, pNode);
const pos = selection.from;
tr.insert(pos, bNode);
const textNode1 = schema.text('this is block_tile h1');
const h1Node = schema.nodes.heading.create({ level: 1 }, textNode1);
const bNode1 = schema.nodes.block_tile.create({}, h1Node);
tr.insert(pos + bNode.nodeSize, bNode1);
dispatch(tr);
代码
import { EditorView } from "prosemirror-view";
import { EditorState } from "prosemirror-state";
import { nodes, marks } from "prosemirror-schema-basic";
import { DOMParser, Schema } from "prosemirror-model";
import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'
import { history, undo, redo } from 'prosemirror-history';
import './style.css';
import './prosemirror.css';
const id = 'prosemirror-editor';
// 初始化一个p段落
const content = new window.DOMParser().parseFromString(
``,
"text/html"
).body;
const customNodes = {
block_tile: {
content: 'block+',
group: 'block',
inline: false,
defining: true,
toDOM: () => {
return ['div', { class: 'block_tile' }, 0]
},
parseDOM: [{ tag: 'div.block_tile' }]
}
};
const schema = new Schema({
nodes: Object.assign({}, nodes, customNodes),
marks
});
export const mount = () => {
const el = document.querySelector(`#${id}`);
// 1. 提供schema (文档结构, 这里暂时用现成的)
// 2. 创建一个editor state数据实例
const editorState = EditorState.create({
// schema
doc: DOMParser.fromSchema(schema).parse(content),
plugins: [
keymap(baseKeymap),
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
]
});
// 3. 创建editor view编辑器视图实例
const editorView = new EditorView(el, {
state: editorState
});
window.editorView = editorView;
const blockTileButton = document.querySelector('#blockTileButton');
blockTileButton?.addEventListener('click', () => {
const { state, dispatch } = editorView;
const { schema, tr, selection } = state;
const textNode = schema.text('this is block_tile p');
const pNode = schema.nodes.paragraph.create({}, textNode);
const bNode = schema.nodes.block_tile.create({}, pNode);
const pos = selection.from;
tr.insert(pos, bNode);
const textNode1 = schema.text('this is block_tile h1');
const h1Node = schema.nodes.heading.create({ level: 1 }, textNode1);
const bNode1 = schema.nodes.block_tile.create({}, h1Node);
tr.insert(pos + bNode.nodeSize + 1, bNode1);
dispatch(tr);
});
const headingButton = document.querySelector('#headingButton');
const strongButton = document.querySelector('#strongButton');
headingButton?.addEventListener('click', () => {
const { state, dispatch } = editorView;
const { schema, tr, selection } = state;
const textNode = schema.text('this is h3');
const node = schema.nodes.heading.create({ level: 3 }, textNode);
const pos = selection.from;
tr.insert(pos, node);
dispatch(tr);
});
strongButton?.addEventListener('click', () => {
const { state, dispatch } = editorView;
const { schema, tr, selection } = state;
const { from, to } = selection;
tr.addMark(from, to, schema.marks.strong.create());
dispatch(tr);
});
}
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<div>
<button id="headingButton">插入h1</button>
<button id="strongButton">加粗</button>
<button id="blockTileButton">blockTile</button>
</div>
<div id="${id}"></div>
</div>
`;
mount();
总结
前面三章,其实都是围绕着schema node来给大家讲述schema数据结构的作用,以及呈现到界面上的块级表现。
大家都动手去操作下,围绕着复制黏贴/schema.nodes.xx.create代码方式去体验下,相信大家很容易能理解