阅读 208

ProseMirror学习笔记 1——基本属性

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

前言

最近想学习一下prosemirror。全网搜索了一下,好像没有比较好的prosemirror的学习文档(还好我是一个文档输出小王子hhhh 手动狗头)。 所以把自己的学习经历,学习结果记录下来,给自己还有想学习这个框架的同学一个可以参考的文档。

这一章算是开篇吧,主要就是介绍一下基本属性,然后如何完成一个小demo 。暂且当做是一个helloworld吧。

球球了。 在这里插入图片描述

 

最简单的编辑器

ProseMirror 提供了一整套构建富文本编辑器的工具和概念, 它使用的用户界面受 所见即所得 概念的启发。

Prosemirror 的核心模块并不是开箱即用的, 在开发这个库的时候, 我们坚持它的模块化和自定义程度的优先级高于简洁性

     

四大模块:

  1. prosemirror-model 定义了编辑器的 Document Model, 它用来描述编辑器的内容.
  2. prosemirror-state 提供了一个描述编辑器完整状态的单一数据结构, 包括编辑器的选区操作, 和一个用来处理从当前 state 到下一个 state 的一个叫做 transaction 的系统.
  3. prosemirror-view 用来将给定的 state 展示成相对应的可编辑元素显示在编辑器中, 同时处理用户交互.
  4. prosemirror-transform 包含了一种可以被重做和撤销的修改文档的功能, 它是 prosemirror-state 库的 transaction 功能的基础, 这使得撤销操作历史记录和协同编辑成为可能.
import {schema} from "prosemirror-schema-basic"
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"

let state = EditorState.create({schema})
let view = new EditorView(document.body, {state})
复制代码

Prosemirror 需要你手动指定一个 document 需要遵守的 Schema (来规定哪些元素能包含哪些不能包含以及元素之间的关系), 这里拿了一个现成的包含基本元素的 schema 做实例).

之后, 这个基础 schema 被用来创建一个 state, 该 state 会生成一个遵守 schema 约束的一个空的文档, 以及一个默认的选区在这个文档的开头(这个选区是空的, 因此这里指的是光标). 最终, 这个 state 会生成一个 view 被 append 到 document.body。 上述的 state 的文档最终将被渲染成一个可编辑的 DOM 节点(就是 contenteditable 的节点) 和一个会对用户输入做出反应的 state transaction.

到目前为止这个编辑器还不能用. 例如, 如果你在刚刚的编辑器中按 Enter 键, 则什么也不会发生, 因为上述提到的四个核心模块并不知道输入 Enter 之后应该做什么, 我们将在稍后告诉它如何响应各种输入行为.

state

ProseMirror的state是一个只读的、持久的数据结构,只有使用apply方法从旧的状态值计算新的状态值才会触发更新。

一个状态包含许多内置字段,使用插件可以自定义其他字段。 - state对象如下 在这里插入图片描述

     

Transactions

当用户输入的时候, 或者更广泛的说, 当用户与页面的 view 进行交互的时候, prosemirror 会产生 ‘state transactions’. 每一个变化都会有一个 transaction 被创建, 它描述了 state 被应用的变化, 这些变化可以被用来创建一个新的 state, 然后这个新的 state 被用来更新 view.

默认情况下, 上述的这些变化是框架进行的, 你无需关注. 不过你可以通过写一个 plugin 或者自定义你的 view 的方式, 来往这个变化的过程中挂载一些 hook. 举个例子, 下面的代码增加了一个 dispatch Transaction prop, 它在每一个 transaction 被创建的时候调用:

let state = EditorState.create({schema})
let view = new EditorView(document.body, {
  state,
  dispatchTransaction(transaction) {
    console.log("Document size went from", transaction.before.content.size,
    "to", transaction.doc.content.size)
    let newState = view.state.apply(transaction)
    view.updateState(newState)
  }
})
复制代码

每次的 state 更新最终都需要执行 updateState 方法, 而且每 dispatching 一个 transaction 一般情况下都会触发一个编辑状态的更新.

  • transaction对象如下

在这里插入图片描述

此外,你可以在transaction中存储meteData属性,这些属性可以是 client codeplugin(用来描述transaction所代表的一些额外信息,以便它们可以相应地更新自己的状态。)

prosemirror view 使用一些meteData属性:它将附加一个属性“pointer”,置为true,用于直接由鼠标或触摸输入引起的选择transactions,其“uiEvent”属性可以是“paste”、“cut”或“drop”。

     

Plugins

Plugins 被用来以多种不同的方式扩展编辑行为和编辑状态. 一些插件比较简单, 比如 keymap 插件, 它用来绑定键盘输入的 actions. 还有些插件相对复杂一点, 比如 history 插件, 它通过监视 transactions 和按照相反的顺序存储它们以便用户想要撤销一个 transactions 来实现一个 undo/redo 的功能.

让我们先增加下面两个 plugin 以获得 undo/redo 的功能:

// 忽略重复的导入

import {undo, redo, history} from "prosemirror-history"
import {keymap} from "prosemirror-keymap"

let state = EditorState.create({
  schema,
  plugins: [
    history(),
    keymap({"Mod-z": undo, "Mod-y": redo})
  ]
})
let view = new EditorView(document.body, {state})
复制代码

     

Commands

上面示例中, 被绑定到相关键盘按键的的特殊的函数叫做 commands. 大多数的编辑行为都会被写成 commands 的形式, 因此可以被绑定到特定的键上, 以供编辑菜单调用, 或者暴露给用户来操作.

prosemirror-commands 这个包提供了很多基本的编辑 commands, 包括在编辑器中按照你的期望映射 enter 和 delete 按键的行为.

import {baseKeymap} from "prosemirror-commands"

let state = EditorState.create({
  schema,
  plugins: [
    history(),
    keymap({"Mod-z": undo, "Mod-y": redo}),
    keymap(baseKeymap)
  ]
})
let view = new EditorView(document.body, {state})
复制代码

- baseKeymap对象 在这里插入图片描述

到此为止, 你应该有了一个基本能 work 的编辑器了.

如果还想增加一个菜单方便编辑操作, 或者想增加一些 schema 允许的按键绑定, 诸如此类的东西, 那么你可能想要看下 prosemirror-example-setup 这个包. 这个包提供了实现一个基本编辑器的一系列设置好的插件, 不过就像这个包名所表示的含义那样, 它仅仅是用来示例一些 API 的用法, 而不是一个可以用在生产环境的包. 对于一个真实的开发环境, 你可能想要用自己的代码替换其中的一些内容, 以精确实现你想要的效果.

项目demo

import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {Schema, DOMParser} from "prosemirror-model"
import {schema} from "prosemirror-schema-basic"
import {addListNodes} from "prosemirror-schema-list"
import {exampleSetup} from "prosemirror-example-setup"
import {baseKeymap} from "prosemirror-commands"

// 将 prosemirror-schema-list 和基本 schema 放在一起形成一个支持 list 的 schema
const mySchema = new Schema({
  nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
  marks: schema.spec.marks
})

console.log('baseKeymap',baseKeymap);

window.view = new EditorView(document.querySelector("#editor"), {
  state: EditorState.create({
    doc: DOMParser.fromSchema(mySchema).parse(document.querySelector("#content")),
    plugins: exampleSetup({schema: mySchema})
  }),
  dispatchTransaction(transaction) {
    console.log("Document size went from", transaction.before.content.size,
    "to", transaction.doc.content.size)
    console.log('view.state',view.state);
    
    let newState = view.state.apply(transaction)
    view.updateState(newState)
  }

})
复制代码

运行之后大概是这个样子:

在这里插入图片描述 在这里插入图片描述

文章分类
前端
文章标签