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中插件系统的设计原理。

相关推荐
好_快10 分钟前
Lodash源码阅读-head
前端·javascript·源码阅读
好_快11 分钟前
Lodash源码阅读-last
前端·javascript·源码阅读
阿鲁冶夫12 分钟前
20250311
前端
WHOAMI_老猫1 小时前
XSS-LABS靶场通关讲解
前端·xss
要加油哦~1 小时前
前端 | 向后端传数据,判断问题所在的调试过程
前端·javascript·vue.js
中工钱袋3 小时前
Vue 中地址栏参数与 HTTP 请求参数的同步问题
前端·vue.js·http
zzlyx993 小时前
设备管理系统功能与.NET+VUE(IVIEW)技术实现
前端·vue.js·view design
秋月华星5 小时前
【flutter】TextField输入框工具栏文本为英文解决(不用安装插件版本
前端·javascript·flutter
千里码aicood6 小时前
[含文档+PPT+源码等]精品基于Python实现的校园小助手小程序的设计与实现
开发语言·前端·python
青红光硫化黑6 小时前
React基础之React.memo
前端·javascript·react.js