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

相关推荐
oooo_z6 分钟前
美股历史K线数据API接口选型指南:iTick覆盖1分钟到月线全周期
服务器·前端·javascript
今日无bug20 分钟前
从 0 到 1 搭建端侧 AI 项目:DeepSeek-R1 + WebGPU + React + TS + Tailwind 全栈笔记
前端·react.js·typescript
mmsx22 分钟前
MapLibre 实战 08|GPS 点漂了几百米才被发现:GCJ-02 纠偏原理与"转两次"陷阱
android·前端
李高钢25 分钟前
Python Flask 框架入门:从零搭建你的第一个 Web 应用
前端·python·flask
特立独行的猫A26 分钟前
用仓颉语言写 Coding Agent:cjh 是怎么实现的
前端
特立独行的猫A28 分钟前
cjh:基于华为仓颉语言的原生 Coding Agent Harness 实践与设计思考
前端
晚安日记wanna35 分钟前
大文件分片上传的五层追问从 Blob 切片到秒传与弱网容错
前端·面试
Htr_40 分钟前
Anysite.io 使用指南:把整个 Web 变成 AI 智能体的数据库
前端·数据库·人工智能
知兀42 分钟前
【前端】受控和非受控组件
前端·javascript·react
玉宇夕落44 分钟前
别再把 SSE 当普通流式了!从 EventSource 到 LangChain,一次讲透实时推送与流式输出
前端