Day 5. Compare × G1

https://ithelp.ithome.com.tw/upload/images/20210920/20139359bBS9xjqBDA.png

The World of G1


G1 的编辑器在网路还没进入到前端 framework 御三家(Angular、React、Vue)之前就已经存在了

在 WEB 界 WYSIWYG 圈的一团迷雾之中首先抢得风采的是 TinyMCE 与 CKEditor 这两包 Library,一直到现在我们依然能时不时看见他们的身影,许多知名企业也都是使用他们作为自家网页 APP 的 Rich text editor 的核心,可想而知他们在社群发展上经营出的规模之大。

https://ithelp.ithome.com.tw/upload/images/20210920/20139359NOWeSI1pwt.png

CKEditor 背书团队

https://ithelp.ithome.com.tw/upload/images/20210920/201393597xwBOpmW6K.png

TinyMCE 背书团队

在各个前端 frameworks 陆续登场以後,它们也接着推出了各式各样的扩充套件让使用者能跟这些新兴的网页前端技术自由结合,避免自己淹没於这来势汹汹的网页改革浪潮。

即便如此,身为第一世代编辑器,使用 contenteditable 属性做为编辑器主要的资料来源依附对象仍让它们难以在当前愈趋复杂的 Modern web develop 中存活。

上一篇 我们有提到第二世代的编辑器透过自订义编辑器依赖的 Data model 以及专注於如何将资料转换为 DOM structure render 於 Web 上。

这边为了让读者更有画面,我先稍微破梗一下让大家看看 Slate 喂给 Document model 的资料格式:

https://ithelp.ithome.com.tw/upload/images/20210920/20139359SEWODUxwrr.png

https://ithelp.ithome.com.tw/upload/images/20210920/20139359DzlNrDQN0Q.png

上方截图取自 Slate 官网提供的 richtext example,稍作比对後我们可以发现 user 主要是透过一包 JSON object 在与 Slate 进行沟通的。喂给 Slate 一包自定义的、符合格式规范的 JSON object 後,会经由一层 Render engine 去决定出渲染在画面上的最终结果,後续 user 在画面上与编辑器进行互动也都是间接地操作这包 object ,并再次经由 Render engine 去进行渲染结果的运算。

与上一篇对第二世代编辑器所描述的相同, Slate 依赖於浏览器的只有取得当前使用者与页面互动的即时资讯,如:光标、反白,等,其余的一切都是由 JS 完成,使用者需要依循着套件的规范与使用逻辑去建立自己的编辑器,这也让开发的自由度很高。

『对於 Slate 而言, HTML 的 contenteditable 属性单纯就只是 input ,拿来决定 user 能不能打字而已。』

反观第一世代的 TinyMCE 与 CKEditor 则是完全依赖於 contenteditable 属性实作的,他们视 HTML document 上的 DOM 资料为『唯一真值表示法( single source of truth )』,并没有额外拆出一层资料与编辑器进行互动,也因此存取的资料正是网页上直接能看到的,完整的 HTML tags 。所有样式与功能也都是 library 事先提供好的。

Library 提供给开发者操作编辑器的 api 也是为了取代 execCommand 所制造的,解决跨浏览器相容性问题的 DOM api 操作语法糖,有点像是帮你多包装一层优化处理 DOM 的转译器的感觉?

让我们再拿另一个第一世代编辑器:百度的 UEditor 。为例,它也是依附於 contenteditable 却放弃大部分 execCommand 的原生功能,自己实现一套贴近於原生功能语法糖的 apis 提供给开发者使用。

这边我们以 UEditor 的 bold 操作为例:

同时附上 资料来源

UEditor 自己定义了一个 bold 操作的命令逻辑,可以看到操作的尾端做的事是新建一个 <strong> tag 并插入进 DOM 中。

https://ithelp.ithome.com.tw/upload/images/20210920/20139359GGJpmsJHFZ.jpg

https://ithelp.ithome.com.tw/upload/images/20210920/20139359uC4WoFxpLg.jpg

它同时也实践了自己的一套 undo 、 redo 功能,透过 array 来记录 redo 、 undo content:

https://ithelp.ithome.com.tw/upload/images/20210920/201393595sv92n6Q5P.jpg

当触发 undo 时会直接重新设置 innerHTML 来恢复内容

https://ithelp.ithome.com.tw/upload/images/20210920/20139359BWjXHXrcQw.jpg

但我们可以看到这些功能并没有被系统性地管理,而且因为是一直 access nature DOM 所以不难想像使用它的可维护性与效能并不会多高。


The Difficulties in G1


第一世代的编辑器几乎都属於『储存的资料为整包结构订死的 HTML Tags 』的类型,而这种做法延伸出了以下几点问题,这也是为何它们难以在 Modern Web 之下存活的原因:

  • 违背 Data / View 应该拆开的资料结构原则
  • 因为排版的灵活度大幅降低,因而提高了实现 RWD 的难度
  • 要针对内容做相关性分析时也要先清除这些无意义的杂讯( HTML tags )
  • 要传递的数据实在太容易变得肥大了!还记得 JSON 是如何战胜 XML 的吗?

再来就是我们上一篇也有提到过的:虽然操作上很容易,客制化的程度却非常有限,几乎只能活在他们提供的 plugin 下。难以满足於当前快速发展、常常蹦出各种新需求的网页社群。

拿 TinyMCE 官网提供的 Full-Featured init 起手式当作范例,可以从满满的 config code 看出它的使用性质就是围绕在他提供给你的各种 config options 上。

var useDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;

tinymce.init({
  selector: 'textarea#full-featured',
  plugins: 'print preview powerpaste casechange importcss tinydrive searchreplace autolink autosave save directionality advcode visualblocks visualchars fullscreen image link media mediaembed template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists checklist wordcount tinymcespellchecker a11ychecker imagetools textpattern noneditable help formatpainter permanentpen pageembed charmap tinycomments mentions quickbars linkchecker emoticons advtable export',
  tinydrive_token_provider: 'URL_TO_YOUR_TOKEN_PROVIDER',
  tinydrive_dropbox_app_key: 'YOUR_DROPBOX_APP_KEY',
  tinydrive_google_drive_key: 'YOUR_GOOGLE_DRIVE_KEY',
  tinydrive_google_drive_client_id: 'YOUR_GOOGLE_DRIVE_CLIENT_ID',
  mobile: {
    plugins: 'print preview powerpaste casechange importcss tinydrive searchreplace autolink autosave save directionality advcode visualblocks visualchars fullscreen image link media mediaembed template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists checklist wordcount tinymcespellchecker a11ychecker textpattern noneditable help formatpainter pageembed charmap mentions quickbars linkchecker emoticons advtable'
  },
  menu: {
    tc: {
      title: 'Comments',
      items: 'addcomment showcomments deleteallconversations'
    }
  },
  menubar: 'file edit view insert format tools table tc help',
  toolbar: 'undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent |  numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | fullscreen  preview save print | insertfile image media pageembed template link anchor codesample | a11ycheck ltr rtl | showcomments addcomment',
  autosave_ask_before_unload: true,
  autosave_interval: '30s',
  autosave_prefix: '{path}{query}-{id}-',
  autosave_restore_when_empty: false,
  autosave_retention: '2m',
  image_advtab: true,
  link_list: [
    { title: 'My page 1', value: 'https://www.tiny.cloud' },
    { title: 'My page 2', value: 'http://www.moxiecode.com' }
  ],
  image_list: [
    { title: 'My page 1', value: 'https://www.tiny.cloud' },
    { title: 'My page 2', value: 'http://www.moxiecode.com' }
  ],
  image_class_list: [
    { title: 'None', value: '' },
    { title: 'Some class', value: 'class-name' }
  ],
  importcss_append: true,
  templates: [
        { title: 'New Table', description: 'creates a new table', content: '<div class="mceTmpl"><table width="98%%"  border="0" cellspacing="0" cellpadding="0"><tr><th scope="col"> </th><th scope="col"> </th></tr><tr><td> </td><td> </td></tr></table></div>' },
    { title: 'Starting my story', description: 'A cure for writers block', content: 'Once upon a time...' },
    { title: 'New list with dates', description: 'New List with dates', content: '<div class="mceTmpl"><span class="cdate">cdate</span><br /><span class="mdate">mdate</span><h2>My List</h2><ul><li></li><li></li></ul></div>' }
  ],
  template_cdate_format: '[Date Created (CDATE): %m/%d/%Y : %H:%M:%S]',
  template_mdate_format: '[Date Modified (MDATE): %m/%d/%Y : %H:%M:%S]',
  height: 600,
  image_caption: true,
  quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage quicktable',
  noneditable_noneditable_class: 'mceNonEditable',
  toolbar_mode: 'sliding',
  spellchecker_ignore_list: ['Ephox', 'Moxiecode'],
  tinycomments_mode: 'embedded',
  content_style: '.mymention{ color: gray; }',
  contextmenu: 'link image imagetools table configurepermanentpen',
  a11y_advanced_options: true,
  skin: useDarkMode ? 'oxide-dark' : 'oxide',
  content_css: useDarkMode ? 'dark' : 'default',
  /*
  The following settings require more configuration than shown here.
  For information on configuring the mentions plugin, see:
  https://www.tiny.cloud/docs/plugins/premium/mentions/.
  */
  mentions_selector: '.mymention',
  mentions_fetch: mentions_fetch,
  mentions_menu_hover: mentions_menu_hover,
  mentions_menu_complete: mentions_menu_complete,
  mentions_select: mentions_select,
  mentions_item_type: 'profile'
});

看得出来这两种类型的library在使用上的差异真的很大呢!但我认为是各有利弊拉,我想针对旧网页的支援程度,以及在操作上的容易度肯定是後者更胜一筹的


你分析得没错, Tiny 与 CK 对旧网页的支援程度真的是其他 libraries 所望尘莫及的(毕竟他真的很老嘛XD),操作也相对单纯,几乎就是 plugins 的装载而已。

而提到操作上的难易程度,我们又可以再把所有 WYSIWYG 相关的 Libraries 分成两大类:

  • Editor
  • Editor Framework。

前者包含我们这篇文章提到的第一世代编辑器,以及下一篇文章的主角 Quill 都属於这类型的 Library ,它们主要的卖点在於『直接提供你一个现成的编辑器让你运用在你的 Web App 上』。

开发者在这类 Library 做开发时起步会较容易,你不一定要写一堆客制化的 code 因为他们已经提供给你一堆可重用的预设模组了。虽然因为程序码设计的不同而在客制化程度上有所差异,但相对於後者他们是较难在样式与功能上做客制化,或是要实现客制化这件事时会经过更多复杂的步骤。

後者就包含整个系列的主角: Slate ,以及 Draft ,他们的理念更贴近於『提供开发者去建立自己的编辑器的框架』。

这类的 Library 基本上就是设计来让开发者去建立客制化的编辑器的,他们通常会定义好自己的建立规则,并在这些规则下提供各种 api 让开发者做操作,基本上只要开发者遵循这些规则,想开什麽脑洞功能都没问题,当然相对的学习曲线就会较高,毕竟要先花时间去学习对方的逻辑嘛~

那我们接下来就要进到 Modern web 的范畴了!会挑出几个现代网页社群下较为知名的几个 Libraries 做比较,但这又是另一项大主题了,我们就留到下一篇吧~


<<:  D-10.Rails N+1 queries and kill N+1

>>:  [Python 爬虫这样学,一定是大拇指拉!] DAY05 - URL / URN / URI (1)

Day 22 中场休息,来做点酷东西(终於要完成了)

终於要把它做完了!!! 今天做了两件事 新增 ProjectItem class,让每一次渲染时都能...

[day20]谈购物流程设计

本来想除了管理功能外全部都在Line介面里面解决,但做了一阵子觉得越想越不对劲,重新考量了一下思路,...

[Day 21] 妈! Keras 和 TensorFlow 在乱存模型啦! ( TFLite 轻量模型)

前言 受惠於深度学习框架的多元性,开发者可以选自己喜欢的框架, 像是: Theano、Caffe、O...

Day27:终於要进去新手村了-HTML DOM 基本观念

今天要先说到关於阶层的部分,因为明後天的文章会是关於HTML与JS简单的一些互动,一样是由彭彭影片内...

【31】30天在Colab尝试的30个影像分类训练实验 - 完赛心得

比赛动机 这是我第三次参加铁人赛,每次参赛都刚好隔一年,後来我发现这样的间隔其实很刚好,因为在中间的...