内观一个你可能认为理所当然的技术内部运作
如果你和美国的大多数人一样, 几乎每天都会使用某个文本编辑器. 无论是基本的 Apple Notes, 还是像 Google Docs,Microsoft Word 或 Mediumz 等更高级的东西, 我们的文本编辑器都允许我们记录和呈现我们重要的想法和信息, 使我们能够以最吸引人的方式讲述故事.
但是你可能没有想过这些文本编辑器的后台运作原理. 每次你按下某个键时, 可能会执行数百行的代码来在页面上呈现你想要的字符. 看似很小的操作, 例如拖动选择文本中的几段文字或将文本转换为标题, 这实际上会触发程序系统底层的大量变化.
虽然你可能无需考虑为这些复杂的文本编辑操作提供动力的代码, 但我在纽约时报的团队确不断在思考它. 我们的主要任务是为新闻工作室创建一个高度定制的报道编辑器. 除了输入和呈现内容的基础功能之外, 这个新的报道编辑器需要将 Google Docs 的高级特性与 Medium 的直观设计重点结合起来, 并且添加新闻室工作流程独有的许多功能特性.
多年以来, 纽约时代报新闻编辑室使用了一个传统的自制文本编辑器, 它并没有满足其众多需求. 虽然我们的旧版编辑器非常适合新闻编辑室的生产工作流程, 但它的用户界面还有许多不足: 它严重的分隔了工作流程, 将报道的不同部分 (例如文本, 照片, 社交媒体和文案编辑) 分离成应用程序的完全不同的部分. 因此, 要在这个较老的编辑器中生成一片文章需要浏览一系列冗长的, 非直观的, 并且视觉上没有吸引力的标签.
除了使用户的工作流程碎片化之外, 传统的编辑器在工程方面也造成很大的痛苦. 它依赖于直接操作 DOM 来在编辑器中呈现所有内容, 例如添加各种 html 标记以表示已删除文本, 新文本和注释之间的区别. 这意味着其他团队的工程师必须在文章发布并呈现到网站之前对文章进行大量严格的标记清理, 将会是一个耗时并且容易出错的过程.
随着新闻编辑室的发展, 我们设想了一个新的报道编辑器, 它可以直观的将报道的不同组成部分内联, 这样记者和编辑都可以在发布前准确的看到报道的样子. 另外, 理想情况下, 新的方法在其代码实现中更加直观和灵活, 避免了旧版编辑器的许多问题.
考虑到这两个目标, 我的团队开始开发这个新型文本编辑器, 并将其命名为 Oak. 经过大量研究和数月的原型设计, 我们选择在 ProseMirror http://prosemirror.net/ 的基础上开发它. ProseMirror 是一个用于构建富文本编辑器的强大开源 JavaScript 工具包, 它采用了和我们旧版编辑器完全不同的方法, 使用它自己的非 HTML 树形结构 来表示文档, 该结构由段落, 标题, 列表和连接等来描述文本的构成.
与我们旧版的编辑器所不同的是, 基于 ProseMirror 开发的文本编辑器的输出可以最终可以呈现为 DOM 树, Markdown 文本或任何其他可以表达其编码概念的其他格式, 使它非常通用并且解决许多我们在旧版文本编辑器上遇到的问题.
那么 ProseMirror 究竟是如何工作的呢? 让我们赶快深入它背后的技术.
一切都是节点
ProseMirror 将其主要元素 - 段落, 标题, 列表, 图片等 - 构造为节点. 许多节点都可以具有子节点, 例如 heading_basic 节点可以具有包括 heading1 ,byline,timestamp 和 image 等子节点. 这构成了我上面所提到的属性结构.
这种树状结构有趣的例外在于段落节点编纂文本的方式. 考虑由以下句子组成的段落,"This is strong text with emphasis".
DOM 会将该句子编成树, 如下所示:
- export function nytBodySchemaSpec() {
- const schemaSpec = {
- nodes: {
- doc: new DocSpec({ content: 'block+', marks: '_' }),
- paragraph: new ParagraphSpec({ content: 'inline*', group: 'block', marks: '_' }),
- heading1: new Heading1Spec({ content: 'inline*', group: 'block', marks: 'comment' }),
- blockquote: new BlockquoteSpec({ content: 'inline*', group: 'block', marks: '_' }),
- summary: new SummarySpec({ content: 'inline*', group: 'block', marks: 'comment' }),
- header_timestamp: new HeaderTimestampSpec({ group: 'header-child-block', marks: 'comment' }),
- ...
- },
- marks:
- link: new LinkSpec(),
- em: new EmSpec(),
- strong: new StrongSpec(),
- comment: new CommentMarkSpec(),
- },
- };
- }
来源: https://juejin.im/post/5bc2f8a86fb9a05d151ccba8