ProseMirror初探

引言

ProseMirror是代码编辑器CodeMirror(codemirror.net/) "https://codemirror.net/)")的作者Marijn Haverbeke开发的一款开源的富文本编辑器。 提供了一组工具和设计概念用来构建富文本编辑器,像乐高积木一样,需要开发者自己组装,并不是开箱即用。

ProseMirror基础知识

ProseMirror提供了四个核心模块,编辑器任何操作都需要他们。

四个核心模块如下:

  • prosemirror-model定义编辑器文档模型,用来描述编辑器内容

  • prosemirror-state描述整个编辑器状态的数据结构

  • prosemirror-view将编辑器状态state 展示成对应视图

  • prosemirror-transform提供了可以撤销和恢复的修改文档的能力

第一个简单的编辑器

1. 创建一个react项目

js 复制代码
// 使用react脚手架创建一个react项目
create-react-app + 项目名称

2. 安装prosemirror相关依赖包

js 复制代码
npm i prosemirror-view prosemirror-model prosemirror-state

3. 创建Editor编辑器对象

js 复制代码
// Editor.js
import React from 'react';
import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { Schema } from 'prosemirror-model';
import './Editor.css';

class Editor extends React.PureComponent {
    constructor() {
        super();
        this.editorRef = React.createRef(null);
    }

    componentDidMount() {
        this.createEditor();
    }

    createSchema() {
        // 创建schema
        this.schema = new Schema({
            nodes: {
                doc: {
                content: 'block'
            },
            paragraph: {
                content: 'inline*',
                group: 'block',
                toDOM: () => ['p', 0]
            },
            text: {
                group: 'inline'
            }
           },
        })
    }

    createPlugins() {
        // 编辑器插件,暂时不使用任何插件
        return [];
    }

    createView() {
        const { current: place } = this.editorRef;
        // 创建编辑器视图对象
        this.view = new EditorView(place, {
            state: EditorState.create({
                schema: this.schema
            })

        });

        // 创建编辑器数据对象
        const newState = this.view.state.reconfigure({
            plugins: this.createPlugins()
        });
        // 更新编辑器数据到视图
        this.view.updateState(newState);

    }

    createEditor() {
        this.createSchema();
        this.createView();
    }

    render() {
        return (
            <div className='editor-container' ref={this.editorRef}></div>
        )
    }

}

export default Editor;

// Editor.css
.ProseMirror {
    outline-width: 0px;
    margin: 100px 60px;
}

4. 入口文件中引入编辑器

js 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Editor from './Editor';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Editor />);

schema

描述编辑器中nodes类型和nodes之间嵌套关系,文档节点与dom节点之间的转化关系。例如定义一个最简单的文档schema结构,由文本节点构建的文档。

js 复制代码
new Schema({
    nodes: {
        // 文档顶层节点,必须有
        doc: {
            // 内容由0个或者多个text节点组成
            content: 'text*'
        },
        // text 节点
        text: {}
    }
})

上面schema结构应用到编辑器后对应dom结构如下:

给上面shema增加一个段落节点,即文档doc顶层节点由多个段落节点paragraph组成,段落节点由多个text节点组成。

css 复制代码
new Schema({
   nodes: {
    // 文档顶层节点,必须有
    doc: {
    // 内容由0个或者多个paragraph节点组成
       content: 'paragraph*'
    },
    paragraph: {
       // 内容由0个或者多个text节点组成    
        content: 'text*',
        // 定义当前节点的默认序列化成 DOM/HTML 的方式, 子节点内容在p标签中。下面详细解释下toDOM应该返回的结构
        toDOM: () => ['p', 0]
    },
    // text 节点
    text: {}
   }
})

schema中增加paragraph后,对应dom节点增加了一层p标签。

上面schema结构中有使用到toDOM,这里详细说明下。toDOM是一个函数,定义了当前节点渲染到dom中的序列化方式,需要返回DOMOutputSpec结构。

toDOM: (node) -> DOMOutputSpec

DOMOutputSpec 是DOM结构的描述方式,具体有三种形式:

  1. 字符串,表示文本节点
  2. dom节点,表示自身
  3. 一个数组,数组中第一个值表示DOM元素对应的标题名,第二个值是一个对象表示当前节点的attributes属性。数组第二个值之后的任何值(包括第二个值,如果它不是一个普通属性对象的话) 都被认为是该 DOM 元素的子元素,后面的值必须是有一个有效的 DOMOutputSpec 值,或者是数字 0。例如:
less 复制代码
['div', {style:'background:green'}, 0], 表示<div style="color:red">子元素<div>
['div', {style:'color:red'}, ['p', 0]],表示 <div style="color:red"><p>子元素</p><div>

schema还有很多属性,在后面的文档中一点点介绍具体使用方法。

plugin

ProseMirror中提供了插件系统,用来扩展编辑行为和编辑状态。ProseMirror官方提供了很多插件,例如快捷键 keymap 插件,undo/redo功能的 history 插件。下面我们实现一个统计编辑次数的插件,感受下插件是什么。

javascript 复制代码
const EditCountPlugin = new Plugin({
    state: {
        init() {
           reutrn 0;
        },
        apply(tr, value) {
            console.log('editCount:', vlaue + 1);
            return value + 1;
        }
    }
})

插件中可以定义自己的数据state,插件初始化时会调用init方式,文档数据发生变化时会执行插件中apply方案。类似react中组件渲染的生命周期,后面写一篇详细的文章介绍ProseMirror中插件系统的设计原理。

相关推荐
Pedantic1 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘1 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆2 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师3 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆3 小时前
VSCode自动格式化三要素
前端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen4 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518136 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode6 小时前
Redis 在生产项目的使用
前端·后端