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

相关推荐
程序员小寒1 小时前
JavaScript设计模式(八):命令模式实现与应用
前端·javascript·设计模式·ecmascript·命令模式
wgod1 小时前
new AbortController()
前端
UXbot2 小时前
UXbot 是什么?一句指令生成完整应用的 AI 工具
前端·ai·交互·个人开发·ai编程·原型模式·ux
棒棒的唐2 小时前
WSL2用npm安装的openclaw,无法正常使用openclaw gateway start启动服务的问题
前端·npm·gateway
哔哩哔哩技术2 小时前
使用Compose Navigation3进行屏幕适配
前端
咬人喵喵3 小时前
E2.COOL 平台深度解析:从特效分类到实战操作指南
前端·编辑器·svg
RisunJan4 小时前
Linux命令-named-checkzone
linux·前端
小陈工4 小时前
Python Web开发入门(十):数据库迁移与版本管理——让数据库变更可控可回滚
前端·数据库·人工智能·python·sql·云原生·架构
吹晚风吧4 小时前
解决vite打包,base配置前缀,nginx的dist包找不到资源
服务器·前端·nginx
weixin199701080164 小时前
《施耐德商品详情页前端性能优化实战》
前端·性能优化