导入自定义模块syntaxerror: invalid syntax_技术分享 | Quill的模块机制

482acd3574d4d9970d9b7c3ffff60398.pngcb9fd3df13c4035a062a6ec3366bb6b8.pngDevUI技术体验部是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部上百个中后台系统,主打产品 DevUI Design 服务于设计师和前端工程师。官方网站:devui.design。Ng组件库:ng-devui。DevUI Design:https://devui.design/homeNg组件库:ng-devui:https://github.com/DevCloudFE/ng-devuifcb297a93c20e6eb41ba8d2a961a2d2a.png

引言

EditorX是DevUI开发的一款好用、易用、功能强大的富文本编辑器,它的底层基于Quill,一款API驱动、支持格式和模块定制的开源Web富文本编辑器,目前在Github的Star数超过25k。如果还没有接触过Quill,建议先去官网了解下Quill的基本概念。本文将结合DevUI的实践,先简单介绍什么是Quill模块,怎么配置Quill模块;然后重点分析Quill模块的执行机制,Quill模块和Quill通信的方式;最后通过实例讲解如何创建自定义的Quill模块,以扩展编辑器的能力。fcb297a93c20e6eb41ba8d2a961a2d2a.png

Quill模块初探

使用Quill开发过富文本应用的人,应该都对Quill的模块有所了解。比如,当我们需要定制自己的工具栏按钮时,会配置toolbar模块:
var quill = new Quill('#editor', {  theme: 'snow',  modules: {    toolbar: [['bold', 'italic'], ['link', 'image']]  }});
其中的modules参数就是用来配置模块的,toolbar参数用来配置工具栏模块,这里传入一个二维数组,表示分组后的工具栏按钮。渲染出来的编辑器里面将包含4个工具栏按钮:78c3c3e2b782f70096f1ac38116e2df9.pngfcb297a93c20e6eb41ba8d2a961a2d2a.png

Quill模块就是一个普通的JavaScript类

那么模块是什么呢?模块其实就是一个普通的JavaScript类,有构造函数,有类成员变量,有类方法,以下是Toolbar模块的大致源码结构:
class Toolbar {  constructor(quill, options) {    super(quill, options);    if (Array.isArray(this.options.container)) {      const container = document.createElement('div');      addControls(container, this.options.container);      quill.container.parentNode.insertBefore(container, quill.container);      this.container = container;    } else {      ...    }    this.container.classList.add('ql-toolbar');    this.controls = [];    this.handlers = {};    Object.keys(this.options.handlers).forEach(format => {      this.addHandler(format, this.options.handlers[format]);    });    Array.from(this.container.querySelectorAll('button, select')).forEach(      input => {       this.attach(input);      },    );    ...  }  addHandler(format, handler) {    this.handlers[format] = handler;  }  ...}
可以看到Toolbar模块就是一个普通类,在constructor构造函数中传入了quill的实例和options配置,Toolbar类拿到quill实例就可以对编辑器进行控制和操作,比如Toolbar模块会根据options配置构造工具栏container,并将按钮/下拉框等元素填充到container中,添加ql-class类,绑定处理事件等。该模块的最终渲染结果就是在编辑器主体上方渲染了一个工具栏,可以通过里面的按钮/下拉框编辑器内的元素设置格式,或者插入新元素。那么Quill模块和Quill交互的方式是怎么样的,Quill实例是如何注入到Quill模块中的呢?这些问题我们后面会继续分析,先来看看Quill内置了一些什么模块吧。fcb297a93c20e6eb41ba8d2a961a2d2a.png

Quill内置模块

Quill一共内置6个模块:
  • Clipboard 粘贴版

  • History 操作历史

  • Keyboard 键盘事件

  • Syntax 语法高亮

  • Toolbar 工具栏

  • Uploader 文件上传

Clipboard、History、Keyboard是Quill必需的内置模块,会自动开启,可以配置但不能取消。其中Clipboard模块用于处理复制/粘贴事件、HTML元素节点的匹配以及HTML到Delta的转换。History模块维护了一个操作的堆栈,记录了每一次的编辑器操作,比如插入/删除内容、格式化内容等,可以方便地实现撤销和重做等功能。Keyboard模块用于配置键盘事件,为实现快捷键提供便利。Syntax模块用于代码语法高亮,它依赖外部库highlight.js,默认关闭,要使用语法高亮功能,必须安装highlight.js,并手动开启该功能。其他模块不多做介绍,想了解可以参考Quill的模块文档:https://quilljs.com/docs/modules。fcb297a93c20e6eb41ba8d2a961a2d2a.png

Quill模块的配置

刚才提到Keyboard键盘事件模块,该模块默认支持很多快捷键,比如加粗的快捷键是Ctrl+B,超链接的快捷键是Ctrl+K,但它不支持删除线的快捷键,如果我们想定制删除线的快捷键,假设是Ctrl+Shift+S,我们可以这样配置:
modules: {  keyboard: {    bindings: {      strike: {        key: 'S',        ctrlKey: true,        shiftKey: true,        handler: function(range, context) {          const format = this.quill.getFormat(range);          this.quill.format('strike', !format.strike);        }      },    }  },  toolbar: [['bold', 'italic'], ['link', 'image']]}
在使用Quill开发富文本编辑器过程中,我们会遇到各种模块,也会创建很多自定义模块,所有模块都是通过modules参数进行配置的。后面会介绍Quill内部如何通过读取该配置进行模块的加载和渲染。fcb297a93c20e6eb41ba8d2a961a2d2a.png

模块加载机制

在研究Quill的模块加载机制之前,有必要对Quill的初始化过程做一个简单的介绍。

Quill类的初始化

当我们执行new Quill()的时候,其实执行的是Quill类的constructor方法,该方法位于Quill源码的core/quill.js文件中。初始化方法的大致源码结构如下(移除模块加载无关的代码):
constructor(container, options = {}) {  this.options = expandConfig(container, options); // 扩展配置数据,包括增加主题类  ...  this.theme = new this.options.theme(this, this.options); // 使用options中的主题类初始化主题实例  // 增加必需的模块  this.keyboard = this.theme.addModule('keyboard');  this.clipboard = this.theme.addModule('clipboard');  this.history = this.theme.addModule('history');  this.theme.init(); // 初始化主题,将主题元素渲染到DOM中  ...}
Quill在初始化时,会使用expandConfig方法对传入的options进行扩展,加入主题类等元素,用于初始化主题。之后调用主题实例的addModule方法获取到内置必需模块,将其挂载到Quill实例中,调用addModule方法还将使主题实例拿到该模块。最后调用主题实例的init方法将主题元素渲染到DOM,如果是snow主题,此时将会看到编辑器上方出现工具栏,如果是bubble主题,那么当选中一段文本时,会出现工具栏浮框。snow主题:3eaed58354b95a1d428fe033073a42ec.pngbubble主题:0aaf9d12d100cfbff5794ed30603d460.png模块加载的秘密就在与theme.init()方法,如刚才看到的,Quill初始化时会通过addModule方法加载3个内置必需模块,其他模块的加载都在init方法里,并且会将二者合在一起。我们以Toolbar模块为例,介绍Quill加载和渲染模块的原理:

工具栏模块的加载

以snow主题为例,当初始化Quill实例时配置以下参数:
{  theme: 'snow',  modules: {    toolbar: [['bold', 'italic', 'strike'], ['link', 'image']]  }}
Quill的constructor方法中获取到的this.theme是SnowTheme类的实例,执行this.theme.init()方法时调用的是其父类Theme的init方法,该方法位于core/theme.js文件,它会遍历options.modules参数中的所有模块,并通过调用addModule方法将这些自定义模块的实例挂载到主题类的modules成员变量中(此时该成员变量已有内置必须模块的实例)。
init() {  // 循环Quill options中的modules参数,将所有用户配置的modules挂载到主题类中  Object.keys(this.options.modules).forEach(name => {    if (this.modules[name] == null) {      this.addModule(name);    }  });}addModule(name) {  const ModuleClass = this.quill.constructor.import(`modules/${name}`); // 导入模块类,创建自定义模块的时候需要通过Quill.register方法将类注册到Quill,才能导入  // 初始化模块类  this.modules[name] = new ModuleClass(    this.quill,    this.options.modules[name] || {},  );  return this.modules[name];
在addModule方法中会初始化Toolbar类,解析modules.toolbar参数,生成工具栏按钮和下拉框,并绑定工具栏事件。
constructor(quill, options) {  super(quill, options);  // 解析modules.toolbar参数,生成工具栏结构  if (Array.isArray(this.options.container)) {    const container = document.createElement('div');    addControls(container, this.options.container);    quill.container.parentNode.insertBefore(container, quill.container);    this.container = container;  } else {    ...  }  this.container.classList.add('ql-toolbar');  this.controls = [];  this.handlers = {};  Object.keys(this.options.handlers).forEach(format => {    this.addHandler(format, this.options.handlers[format]);  });  // 绑定工具栏事件  Array.from(this.container.querySelectorAll('button, select')).forEach(    input => {      this.attach(input);    },  );  ...}
在init方法中执行addModule时,其实先执行的是BaseTheme的addModule方法,拿到Toolbar模块实例之后,会判断当前模块名是否是toolbar,如果是则执行SnowTheme的extendToolbar,这个方法位于themes/snow.js文件,它的作用是渲染工具栏按钮和下拉框的图标,以及绑定超链接事件,源码大致结构如下:
extendToolbar(toolbar) {  toolbar.container.classList.add('ql-snow'); // 增加snow主题的css class  this.buildButtons(toolbar.container.querySelectorAll('button'), icons); // 创建并渲染工具栏按钮图标  this.buildPickers(toolbar.container.querySelectorAll('select'), icons); // 创建并渲染工具栏下拉框图标  this.tooltip = new SnowTooltip(this.quill, this.options.bounds);  // 绑定超链接快捷键  if (toolbar.container.querySelector('.ql-link')) {    this.quill.keyboard.addBinding(      { key: 'k', shortKey: true },      (range, context) => {        toolbar.handlers.link.call(toolbar, !context.format.link);      },    );  }}
该方法先会依次调用buildButtons/buildPickers方法给工具栏按钮和下拉框加上图标元素,然后绑定超链接快捷键。Toolbar模块就这样被加载并渲染到富文本编辑器中,为编辑器操作提供便利。Toolbar模块的加载机制做一个小结:
  • Quill初始化时会通过addModule方法,将内置必需模块加载到主题类;

  • Quill初始化时会执行theme的init方法,并将option.modules参数里配置的所有模块记载到主题类的成员变量modules中,与内置必需模块合并;

  • addModule方法会先通过import方法导入模块类,然后通过new关键字创建模块实例;

  • 创建模块实例时会执行模块的初始化方法,Toolbar模块在初始化方法中根据toolbar配置信息构建了工具栏的结构,并填充按钮/下拉框,绑定它们的事件处理函数;

  • Toolbar模块在调用theme的addModule方法之前会先调用BaseTheme的addModule方法,判断是工具栏模块,则会为其添加图标,此外,超链接的快捷键事件也是在BaseTheme的addModule方法绑定的。

以下是模块与编辑器实例的关系图:7dfa8deaaf5d0e91420061c7c7a167ab.png所有模块都是类似的机制,接下来我们要讲解的内容——创建自定义模块——将会用到上面提到的信息。fcb297a93c20e6eb41ba8d2a961a2d2a.png

创建自定义模块

通过上一节Toolbar模块加载机制的介绍,我们了解到其实工具栏模块就是一个普通的JavaScript类,并没有什么特殊的,在该类的初始化参数中会传入Quill实例和该模块的options配置参数,然后就可以控制并增强编辑器的功能。现在我们尝试自己创建一个Quill模块,比如我们希望统计编辑器当前的字数,这就可以做成一个简单的Quill模块。创建Quill模块的第一步是新建一个TS文件,里面是一个普通的Javascript类:
class Counter {  constructor(quill, options) {    console.log('quill:', quill);    console.log('options:', options);  }}export default Counter
这是一个空类,什么都没有,只是在初始化方法中打印了Quill实例和模块的options配置信息。通过之前对Toolbar模块的分析,我们了解到在Quill的初始化过程中,会通过调用主题模块的init方法完成所有模块的渲染,其中会循环options.modules参数里的配置的所有模块,并将其渲染到编辑器。所以第二步是配置模块参数:
modules: {  toolbar: [    ['bold', 'italic'],    ['link', 'image']  ],  counter: true}
我们先不传配置数据,只是简单地将该模块启用起来,结果发现并没有打印信息。前面我们了解到在addModule的时候需要import该模块类,而要想让Quill import一个模块,需要在Quill初始化之前先调用Quill.register注册该类,并且由于我们需要扩展的是模块(module),所以前缀需要以modules开头:
import Quill from 'quill';import Counter from './counter';Quill.register('modules/counter', Counter);
这时我们能看到信息已经打印出来。这时我们在Counter模块中加点逻辑,用于统计当前编辑器内容的字数:
constructor(quill, options) {  this.container = quill.addContainer('ql-counter');  quill.on(Quill.events.TEXT_CHANGE, () => {    const text = quill.getText(); // 获取编辑器中的纯文本内容    const char = text.replace(/\s/g, ''); // 使用正则表达式将空白字符去掉    this.container.innerHTML = `当前字数:${char.length}`;  });}
在Counter模块的初始化方法中,我们调用Quill提供的addContainer,为编辑器增加一个空的容器,用于存放字数统计模块的位置,然后绑定编辑器的内容变更事件,这样当我们在编辑器中输入内容时,字数能实时统计。在Text Change事件中,我们调用Quill实例的getText方法获取编辑器内放入纯文本内容,然后用正则表达式将其中的空白字符去掉,最后将字数信息插入到字符统计的容器中。展示的大致效果如下:36cf0bb169faddc814af5c8f212e5bb4.png

fcb297a93c20e6eb41ba8d2a961a2d2a.png

总结

本文先通过2个例子简单介绍了Quill模块的配置方法,让读者对Quill的模块有个直观初步的印象。然后通过剖析Quill的初始化过程,逐步切入Quill模块的加载机制,并详细阐述了工具栏模块的加载过程。最后通过字符统计模块的例子介绍如何开发自己的Quill模块,对富文本的功能进行扩展。fcb297a93c20e6eb41ba8d2a961a2d2a.png

加入我们

我们是DevUI技术体验部,欢迎来这里和我们一起打造优雅高效的人机设计/研发体系。招聘邮箱:zenglingka@huawei.com。8d4f65861480bd7120f8692a5632f86a.png↓点击

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/488370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我们对时间的理解错了吗?

来源: 利维坦文/Claudia Hammond译/boomchacha校对/Rachel原文/www.bbc.com/future/article/20191203-what-we-get-wrong-about-time“时间”是常见的名词。我们都熟知时间流逝的感觉:现在变成过去;今天变成昨天。你要是住在温带,…

SVN使用过程中遇到的一些问题

更新svn的客户端TortoiseSVN后 ,之前使用svn管理的文件的关联图标消失了 说明:下面的解决方法及图片来自博客:装了SVN,你的关联图标变了没有? 解决办法:在同步的文件点击右键如下图 ...现则Settings,出现的…

脑神经计算建模揭示前额叶皮层不同类型中间神经元在信息维持中的作用

来源:智能的本质与未来尽管占比相对锥形神经元数量少,但是中间神经元在大脑皮层实现认知功能中的作用却不容小觑。中间神经元的显著特点就是种类丰富,因此对不同类型中间经元在特定认知功能的分工作用的探索是揭示智能机制的关键之一。中国科…

支付宝支付-刷卡支付(条码支付)

此项目已开源欢迎Start、PR、发起Issues一起讨论交流共同进步 https://github.com/Javen205/IJPay http://git.oschina.net/javen205/IJPay 在官方的产品是叫做当面付 1、什么是当面付呢? 简单的讲就是条码支付(刷卡支付)、扫码支付、声波支付。 【官方是这样解释的…

solr做索引时抛出异常_Solr---gt;01

Solr介绍 什么叫做全文检索呢?这要从我们生活中的数据说起。 我们生活中的数据总体分为两种:结构化数据和非结构化数据。 1、结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。 2、非结构化数据&a…

新冠肺炎疫情把科研推上“云端”

来源:新华网美国威斯康星国家灵长类动物研究中心的戴夫奥康纳清晨收到在伦敦的一名合作伙伴发来的论文预印本。这项研究在中国完成,两人通过企业协同云端办公软件Slack讨论了一上午。下午2点,奥康纳打开高清会议系统GoToMeeting,和…

CSDN专訪:大数据时代下的商业存储

原文地址:http://www.csdn.net/article/2014-06-03/2820044-cloud-emc-hadoop摘要:EMC公司作为全球信息存储及管理产品方面的率先公司,不久前。EMC宣布收购DSSD加强和巩固了其在行业内的领导地位,日前我们有幸採訪到EMC中国的张安…

兵棋推演有助于我们了解哪些战争知识?

来源:兵推天下菲利普塞班博士是英国伦敦国王学院战争研究系的战略研究教授,他也是兵棋专家和兵棋设计师。在30多年的教学生涯中,他将兵棋融合到了课堂教学中,向学生展示兵棋推演对军事规划工作的实际作用。在一次访谈中&#xff0…

2020 五大技术趋势:无人驾驶发展、机器视觉崛起、区块链实用化、人类增强技术、超自动化...

来源:机器人创新生态__自动驾驶技术的发展_近年来,自动驾驶技术一直在发展,特斯拉、英特尔等大公司在这一领域取得了长足的进展。虽然我们还没有达到L4级或L5级自动驾驶汽车的水平,但我们已经很接近了。为了解释每个级别的含义&am…

基于java的qq屏幕截图工具的设计与实现论文_众包学习:Web界面众包评估的通用工具包...

论文:Nebeling M , Speicher M , Norrie M C . CrowdStudy: general toolkit for crowdsourced evaluation of web interfaces[C]// Acm Sigchi Symposium on Engineering Interactive Computing Systems. ACM, 2013.摘要:传统的可用性测试方法既费时又昂…

福布斯2020年AI领域10大预测:人工智能越来越“边缘化”!

来源:人工智能和大数据毫无疑问,人工智能(AI)一直是2010年代的技术主题,随着新的十年的来临,这一趋势似乎不会消失。在过去的十年中,人们会回想起真正可以被视为“智能”机器的时代,…

spss数据_职场白骨精进阶秘籍——SPSS数据分析基础

点击上方“蓝字”关注我们吧!想做数据分析,不会编程怎么办?如何让自己的数据分析更加专业?职场打拼,如何快速提升自己的竞争力?著名的未来学家托夫勒在其所著的《第三次浪潮》中将“大数据”称颂为“第三次…

人工智能和自主系统在美军联合职能中的应用

来源:知远战略与防务研究所【知远导读】随着人工智能/自主系统技术的快速发展及其在军事领域的持续应用,智能化、无人化日渐成为未来战争的发展方向。美国作为世界军事发展潮流的引领者,正在积极探索人工智能/自主系统与联合部队作战职能的融…

oracle怎么把整形,【用bbed工具对Oracle进行微整形】

CUUG ORACLE大师网络免费课程——将个人姓名、电话发送到SIGNUPCUUG.COM即可报名CUUG新增“即时同步互动远程授课”,足不出户学ORACLE!详情见:HTTP://ORACLE.CUUG.COM/DBA1.HTML2013韩国小姐选美比赛佳丽样貌神似,难以分辨,无论是…

这篇长达165页的论文,用一个里程碑式的证明同时解决了量子物理学和理论数学的难题...

来源:机器之心计算机科学、数学、物理学,这三个学科各自的一些重大难题在近日发布的一篇标题简洁的论文《MIP*RE》中同时得到了解答。在该论文中,五位计算机科学家为可通过计算方式验证的知识确立了一个新的边界。基于此,他们又为…

mysql timestamp 不走索引_面试 - 要不简单聊一下你对MySQL索引的理解?

MySQL索引?这玩意儿还能简单聊?明显是在挖坑,幸好老夫早有准备,切听我一一道来。 一、索引是什么?索引是帮助MySQL高效获取数据的数据结构。二、索引能干什么?索引非常关键,尤其是当表中的数据量越来越大时&#xff…

亚马逊首家“无人超市”系统存在bug?!开业当天,记者中途换装成功骗过摄像头...

来源:大数据文摘2018年,亚马逊推出了无人便利店Amazon Go,本着“无需排队、拿完就走”的理念在当时掀起了一番热潮。两年后,亚马逊“无人购物”升级,又在西雅图开设了“Plus版无人超市”Amazon Go Grocery(…

创新是低情商的人做的

评语:这时一个有趣的思考,不算是正式的研究,但可以算是有价值的科学火花,创新本身或许就是得罪人的事情,或者要推翻别人的观点,引起他人不高兴,或者他人有不同意见,要争论和辩解&…

【12.23】转行小白历险记-算法02

不会算法的小白不是好小白,可恶还有什么可以难倒我这个美女的,不做花瓶第二天! 一、螺旋矩阵 59. 螺旋矩阵 II - 力扣(LeetCode) 1.核心思路:确定循环的路线,左闭右开循环,思路简…

MySQL 5.7.18 解压版安装

原文链接:https://my.oschina.net/u/3474266/blog/895696 我在安装免安装版的5.7.18的时候出现了问题,正好找到这个,十分感激 今天下载安装了MySQL Community Edition 5.7.18压缩版,过程中遇到了一些坑,特地写个博客记…