为什么选择prosemirror

编辑器一向是前端领域的一个难点,一款成熟的编辑器,需要涉及许多方面的东西。

到底有多少东西...这个可以看看掘金上一位大哥在知乎上的回答

至于为什么要踩这个天坑,是公司想要一个所见即所得的markdown编辑器,不需要markdown源码,要有用markdown语法一样的输入规则,最后还需要输出markdown文档作为存储,在次之上还需要一些制定的需求。这就要求这个选型应该是一个灵活,可配置模块化编辑器框架,而不是一个开箱即可用的一个应用

在选型的时候,之前公司已经有人用prosemirror进行一些特殊编辑器的开发(然而那位同事在我没来之前就走了),同时考虑的还有slate.js,上面那位大哥也有在掘金上发布过一篇文章。那为什么不选择slate.js呢(另外还有个Draft.js没有去了解过)。原因很简单,就是因为我们的技术栈是Vue而不是Reactslate.js依赖于React作为视图层,作为一个Vue应用,还是不想再专门引入一个React来为slate.js服务。

综上的原因,就踩上了这个天坑。虽然我没有用过slate.js,但是根据热度以及在github上的star也好,活跃度也好,我觉得应该不会比slate.js小,但是它能产出的编辑器,不会比slate.js差。

但正因为活跃度等原因,你在谷歌或者百度上搜索,是没有关于prosemirror的任何中文资料的,我一度认为这个框架在国内就没人用,直到有一天在discuss看到了上面说的那位大佬的头像,我才知道原来国内还是有人用的。理所当然的,也不会有对应的中文文档,踩了坑也只能上discuss或者issue搜索提问。但万幸的是,作者非常热心,几乎每一个问题都会回答你,就算是非常入门级的问题,这一点在开发上帮了我很多忙。

以下的内容,几乎是官网的文档,通过自己理解和简化写下来的,有兴趣的可以去官网了解更加详细的内容。

prosemirror简介

如果你觉得prosemirror很陌生,那你也许听过大名鼎鼎的codemirror。对,就是那个在浏览器上的代码编辑器,两个是同个作者,一位非常有实力的德国人Marijn。上面说到的slate也是有些核心的概念例如schema是来自于prosemirror的。

prosemirror不是一个大而全的框架,甚至于你去npm上搜索prosemirror压根没有这个包。

prosemirror由无数个小的模块组成,正如它官网上说的类似于乐高一样堆叠成一个健壮编辑器

The core library is not an easy drop-in component—we are prioritizing modularity and customizeability over simplicity, with the hope that, in the future, people will distribute drop-in editors based on ProseMirror. As such, this is more of a lego set than a matchbox car.

它的核心库有

  • prosemirror-model:定义编辑器的文档模型,用来描述编辑器内容的数据结构

  • prosemirror-state:提供描述编辑器整个状态的数据结构,包括选择,以及从一个状态转移到下一个状态的事务处理系统。

  • prosemirror-view:实现一个用户界面组件,该组件在浏览器中将给定的编辑器状态显示为可编辑元素,并处理用户与该元素的交互。

  • prosemirror-transform:包含以可记录和重放的方式修改文档的功能,这是state模块中事务的基础,并使撤消历史记录和协作编辑成为可能。

看完这些描述是不是感觉很熟悉,一个非常像React的一组核心库。他们构成了整个编辑器的基础。当然,除了核心库,还需要各种各样的库来实现快捷键prosemirror-commands、编辑历史prosemirror-history等等。

实现一个小编辑器

这是一个功能非常有限的,只有一些基本的按键(例如enter换行bacakspace删除)等,然后我们再加上一个ctrl-z撤回和ctrl-y重做。

一开始觉得是个小demo,就用了parcel打包,发现会报错,第一次用parcel,不知道是我问题还是parcel问题。

import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
// schama,校验规则
import {schema} from "prosemirror-schema-basic"
// 历史记录以及撤回重做
import {undo, redo, history} from "prosemirror-history"
// 一个
import {keymap} from "prosemirror-keymap"
import {baseKeymap} from "prosemirror-commands"
//
let content = document.getElementById("content")
// 生成一个state
let state = EditorState.create({doc: DOMParser.fromSchema(schema).parse(content),schema,plugins: [history(),keymap(baseKeymap),keymap({"Mod-z": undo, "Mod-y": redo})]})
// 生成视图
let view = new EditorView(document.getElementById('prosemirror'), {state})
复制代码

这段代码,把content的内容转化为编辑器的初始文本,作为初始的编辑状态。只能够做简单的编辑,例如删除、撤回、换行等。

parser是什么?

我们来看看上面那段代码做了什么事情。首先,预定了一个conetentid的内容,这个在最后展示是不可见的,为的是把已有的html文档先存在dom里。紧接着,通过DOMParse解析顺着schema(下面会说这是什么)这个html文本,获得一个Node类型的对象,这个对象就可以传入doc属性作为一个初始的文本数据渲染成编辑器的可编辑文本。

这里的DOMParse就是一个作为把DOM渲染成Node对象的一个解析器。除了DOMParse,还有一个解析器就是MarkdownParser,专门把markdown文档转化为Node类数据。

那么有解析器,就有对应的序列器,调用EditorState.JSON()可以把当前状态的doc序列化成JSON格式,便于存储。

schema是什么?

schema是一套描述文档和Dom之间的关联的一套转化规则,如何把DOm转化为Node或者说Node转化为Dom,这是个关键,下面是一个基本的标题的schema

// heading的schema
heading: {// 可选的属性attrs: {level: {default: 1}},// 节点内容的类型,是行还是块content: "inline*",// 自身的类型,是行还是块group: "block",// 解析Dom的规则以及属性parseDOM: [{tag: "h1", attrs: {level: 1}},{tag: "h2", attrs: {level: 2}},{tag: "h3", attrs: {level: 3}},{tag: "h4", attrs: {level: 4}},{tag: "h5", attrs: {level: 5}},{tag: "h6", attrs: {level: 6}}],、// 生成Dom的规则toDOM(node) { return ["h" + node.attrs.level, 0] }
},
复制代码

这样就是一个描述一个标题的文本规则,不过没有这个文本规则,解析器或者序列器不知道如何去解析。任何一个在编辑器中出现的Dom以及任何一个需要转化成Dom的节点类型,都需要有一个对应的schema否则无法编译。

schema可以自行创建或者在现有的schema上进行添加。一个健壮的schema对每一个属性的设置都有较高的要求,在这里不举例子了,免得带偏,可以自行上官网学习。

Node是什么?

Node类构成了Prosemirror文档的节点树,它的子节点也是Node类。Node类并不能直接被改变,是一个持久的数据结构,类似于React中的state,需要通过apply一个transaction类才能够改变doc的结构。而Node的结构又非常像Virtual Dom,都具有树型和递归,通过实例解构来描述Dom,而且prosemirror也有自己一套高效的更新算法来转化NodeDom

Node的属性非常多,比如在文档的位置、子节点的数量、节点大小、文本内容等等等等,在许多情况下,这些属性都为实现某些特定的功能提供了非常大的帮助。

Transaction是什么?

transaction是一个描述编辑器状态改变的一个数据类型。在Prosemirror中,调用EditorView.updateState可以更新整个编辑器的状态,就算是敲打一个空格,都必须要通过state进行更新。那么,如果每次都用DOMParse创建新的Node来形成新的state,历史记录等东西必然不会保留,而且在Prosemirror中,到真正调用EditorState.apply的过程中,会经过很多的Plugins(如果有的话)去加工这个transaction,所以一定要经过EditorState.apply去应用一个transaction生成一个新的state,接着调用,才可以真正改变整个编辑器的状态,并保存好整个的状态,在编辑的时候也是如此。我们可以先看看一个例子

let view = new EditorView(document.body, {state,// 这是一个钩子函数,最后应用transaction的函数dispatchTransaction(transaction) {console.log("Document size went from", transaction.before.content.size,"to", transaction.doc.content.size)// 应用transaction,并生成一个新的statelet newState = view.state.apply(transaction)// 更新stateview.updateState(newState)}
})
复制代码

dispatchTransaction实际上是在调用EditorState.apply前的最后一个方法,这里也可以不调用dispatchTransaction,默认进行了更新。在这里的作用是,每次更新(不管是编辑还是插入删除等操作)都会log一段文字,仅此而已。如果不进行apply和update的操作,将会报错。可以通过Editor.tr获取实时的transaction

keymap、历史记录

keymap是键盘输入规则的插件,history是历史记录的插件,这个略过。

核心内容总结

到此为止,核心内容就已经介绍完毕,当然,核心内容只能作为对prosemirror的一个浅显认知,好让我们在后续的编辑器开发的时候,不会不明白它到底是怎么的一个运作原理。

现在缺少的有一些输入规则,有这些输入规则,才能像写markdown一样实现WYSIWYN编辑器,还有顶部的操作栏等等。这些都是编辑器的一部分,不过因为不是核心库,这里就不讲了。官方有一个example-setup一个设置样例,官方同样推荐通过这个样例来改造成符合我们需求的设置

接下来,就让我们偷懒地实现一个markdown的编辑器。例子同样是来自于官网。

实现一个markdown编辑器

很简单,只需要把parser换成defaultMarkdownParserplugins用默认的设置就可以了,然后再用prosemirror-example-setup的默认样式,一个WYSIWYN编辑器就完成了。

class ProseMirrorView {constructor(target, content) {this.view = new EditorView(target, {state: EditorState.create({// 用默认的markdown parser解析markdown文档doc: defaultMarkdownParser.parse(content),// 设置样例plugins: exampleSetup({schema})})})}// 暴露两个常用方法,便于调用focus() { this.view.focus() }destroy() { this.view.destroy() }
}
new ProseMirrorView(document.getElementById('prosemirror'), '# hello')
复制代码

当然这只是一个非常简单的markdown编辑器,官方给出的defaultMarkdownParser只是用的CommonMark标准,很多的常用markdown语法都没有。我们可以从中进行非常多的自定义。

defaultMarkdownParser的markdown解析器是用markdown-it的,原理是解析成token后,通过schema再进行转化。所以如果想要拓展markdown,需要懂得markdown-it或者其他的markdown解析器。

总结

本篇文章简略地介绍了prosemirror的一些思想和核心内容,这只是涉及一些皮毛,并不是完全展现其魅力。在它的论坛上,有许多的开发者贡献了许多令人拍案叫好的插件或者成熟的编辑器,都非常值得去学习借鉴。希望能更加深入理解篇prosemirror

转载于:https://juejin.im/post/5a93f736f265da4e840956d4

WYSISYN编辑器 Prosemirror 入门相关推荐

  1. Unity 编辑器扩展总结 一:编辑器开发入门

    编辑器扩展总结 工欲善其事必先利其器 引言: 在项目开发中,编辑器扩展为开发者提供了开发自定义工具的功能,让开发者更加便利地使用编辑器开发项目.如若博客中存在错误,还请不吝赐教.所有参考的博客或者视频 ...

  2. 星际争霸2-数据编辑器-菜鸟入门

    Tutorial by Siretu 让大多数接触星际2编辑器的人歇菜的原因, 就是这个数据编辑器(Data Editor). 不管你是个新手, 还是使用魔兽3的编辑器做过一些东西, 当你怀着满腔的热 ...

  3. python手机代码编辑器_Python入门系列14 - 代码编辑器PyCharm篇

    Python入门系列14 代码编辑器PyCharm篇 本篇文字为2412字,阅读时间约为7分钟. 1 前言 古人云:工欲善其事必先利其器!写代码也一样,虽然好多人都说,初学者不推荐使用很高大上,智能, ...

  4. 在线HTML编辑器使用入门(Kindeditor)

    官网: http://kindeditor.net/demo.php   解压,开发中只需要导入选中的文件(通常在 webapp 下,建立 editor 文件夹 ) 在使用 kindeditor 页面 ...

  5. Visual Studio Code 编辑器 使用入门

    目录 为什么使用 VS Code  VS Code 安装 launch.json 简介 使用VS Code 开发 Javascript 安装Script插件 调试Script 如何设置成中文 为什么使 ...

  6. VR原理讲解及开发入门

    本文是作者obuil根据多年心得专门为想要入门的VR开发者所写,由52VR网站提供支持. 1. VR沉浸感和交互作用产生的原理: 在之前,我们观看一个虚拟的创造内容是通过平面显示器的,52VR上次发布 ...

  7. 有哪些好的 LaTeX 编辑器?

    知乎用户 ,喜欢排版 收录于 编辑推荐 •577 人赞同 2016-04-21:更新 Atom 编辑器中文自动换行,见 Atom 配置中的备注 2016-03-06:更新 Atom 编辑器的配置用法, ...

  8. Mac上使用LaTeX(小白入门配置Latex)

    Mac上使用LaTeX(完全小白入门Latex) latex本身可以理解为一种 语言,是专门用来进行排版和字体设置的语言. 在Mac OS X上使用latex,需要先配置环境,再使用编译器(如果不用编 ...

  9. 【积水成渊-逐步定制自己的Emacs神器】1:Emacs入门

    前言 本文介绍了Emacs编辑器的入门知识,看完本文读者会知道Emacs的基本用法以及如何通过Emacs来学习Emacs,这会让你觉得整个学习Emacs的过程都是在被"授之以渔". ...

  10. 强大的UI编辑器-FairyGui简单介绍

    FairyGui简单介绍 学习路径 1.下载FairyGUI编辑器. 2.下载对应你的开发平台的FairyGUI SDK. 3.对照SDK里的例子(包括编辑器里的UI设计,和运行时的代码),阅读全部官 ...

最新文章

  1. 知乎高赞怎么自学 python,大概要多久?
  2. C#windows向窗体传递泛型类
  3. zookeeperclient设置监听
  4. 创新工场2018年夏令营DeepCamp第一套解答笔记
  5. linux基础知识和命令试题,Linux基础试题及答案
  6. Qt修炼手册4_信号与槽
  7. linux ub查看ftp安装,Linux Ubuntu 18.04 安装 FTP服务
  8. 浅谈Windows下SVN在Android Studio中的配置、基本使用及解除关联
  9. 虚拟化技术--服务器虚拟化
  10. 如何实现RTMP推送Android Camera2数据
  11. django “如何”系列4:如何编写自定义模板标签和过滤器
  12. 使用jsBridge实现H5与原生App交互
  13. cisp证书考试费用_cisp考试费多少钱
  14. 生产环境RedisCPU飙高怎么办
  15. 135编辑器嵌入html,135编辑器教程|三步教你搞定表格样式
  16. selenium爬取中国经济与社会发展统计数据库
  17. 高斯约当法求逆矩阵的算法实现(C++)
  18. Java多重继承的两种方式
  19. CreateEvent和SetEvent函数
  20. 解决Win10系统msconfig配置错误后无法启动的问题

热门文章

  1. Win32 编程
  2. ApiException
  3. 从公司买火车票到代理模式和适配器模式
  4. Java 中判断char 是否为空格 和空
  5. Android之Adapter用法总结-(转)
  6. HDOJ-1875-畅通工程再续 解题报告
  7. 为什么使用nginx反向代理
  8. IOS中常见UI细节和常识
  9. Naive Bayes text classification
  10. hadoop eclipse插件