有道写作浏览器扩展作为一款为网页增加英文语法批改的辅助工具,允许用户在任意网页上绝大部分的富文本编辑器、多行文本输入框中编辑英文文本,可实时得到批改结果反馈,并自行接受建议自动修改,实现完美写作

来源/ 有道技术团队公众号

作者/ 李靖雯

编辑/ 刘振宇

一、背景介绍

有道写作服务是有道出品的写作智能批改产品,为用户提供优质的作文拼写、语法、样式方面的批改服务。有道写作不仅仅支持浏览器扩展形式,还支持在其他平台使用:例如有道词典 APP-作文批改、Web 在线端、Word 插件、PC 词典内。欢迎各位体验。

http://write.youdao.com/

浏览器插件在浏览器里面的称呼是 Browser Extension,也就是浏览器扩展,是一个扩展网页浏览器功能的插件。它主要基于 HTML、JavaScript、CSS 开发,同时由于是扩展特性,可以利用浏览器本身提供的底层 API 接口进行开发,可以给所用页面或者特定页面添加一些特殊功能而不影响原本页面逻辑。

每个支持扩展的浏览器有自己下载扩展的应用商店,可以直接在应用商店下载。有些产品自己提供浏览器扩展的 .crx 文件让用户下载并安装。

二、适配浏览器

有道写作在 Windows/Mac 系统都可安装,适配 Chrome、360安全浏览器、360极速浏览器、Edge 新版浏览器等,在以上浏览器商店中搜索有道写作,点击安装按钮即可。

三、功能介绍&效果展示

在介绍开发思路与实践之前,我们先来直观地看一下有道写作浏览器扩展的实际效果,并对其功能进行简单的介绍。

3.1 表现方式

视觉效果就是,给错误的文本字符下面画一条横线,在 hover 的时候,可以给文本增加一个高亮的效果。在选接受建议的时候,可以替换成我们想要的文本数据。

3.2 适用场景

>>> 在线邮件编辑:

163邮箱

Outlook 邮箱

Gmail

>>> 社交动态、评论:

Facebook

微博动态

评论

>>> 工具、笔记类:

有道翻译

Google 翻译

石墨文档

3.3 功能介绍

>>> 实时批改:

支持一边修改一边实时提供批改反馈,展示批改错误数量。

>>> 语法检测:

>>> 增强编辑框:

可以查看每一个错误反馈详细内容,并可分错误类型过滤查看结果。

>>> 接受建议:

点击接受建议时候替换正确文本。

四、开发思路

需求:扩展需要针对页面上的可输入文本的编辑框赋予批改的功能

4.1 适配编辑器

那么,网页中可输入文本的编辑框都有哪些呢?

通常我们常见可输入编辑框有:

  • 基于 Web 的表单可以输入文本控件:input、textarea
<input value="123"/>
<textarea>123456</textarea>
  • 可编辑属性的元素:contenteditable
<blockquote contenteditable="true"><p>Edit this content to add your own quote</p>
</blockquote> 

Input 元素通常是一行且输入范围较短的内容,考虑到批改交互的功能,我们的扩展针对以下可输入较多文本的编辑器进行兼容:

  • contenteditable 富文本编辑器
  • textarea
  • 其他文档编辑器

4.2 富文本编辑器

我们常见基于 contenteditable 实现的富文本编辑器有百度编辑器、draft.js、 有道云笔记(旧版)等等。

相比 textarea,富文本编辑器可以包含很多不同标签,可以以用来渲染成不同字体颜色的文本、图片、附件、视频、音频等等元素。

实现基于浏览器的富文本编辑器的四要素

四代编辑器的技术选型

  • 第一代编辑器主要是通过有限的 execCommand 指令对 html 文档进行操作。
  • 第二代则是在 execCommand 基础上,添加更多自定义指令甚至自己实现指令方式修改 html 文档。
  • 第三代是引入数据模型(json/xml),绑定自定义实现指令从而渲染html文档。
  • 第四代主要是直接抛弃整个 contenteditbale,单独制定选区和监听输入事件。

更多关于编辑器的介绍,可参考有道技术团队之前发布的文章:

  • 有道云笔记新版编辑器架构设计(上)
  • 有道云笔记新版编辑器架构设计(下)

为什么要介绍富文本编辑器内容呢,因为了解多这些编辑器实现方式和保存机制可以帮助后面实现并优化扩展的功能。

4.3 初想

一开始的想法是,将原始编辑器的纯文本内容提取并发送到服务端,然后根据服务端返回的数据进行重新的拼接,在错误节点位置使用特殊标记标签进行标注。

以有道写作 Web 端为例:

使用这种方法实现批改效果的还有 163 邮箱英文智能检查、Gmail 自带写信语法检测功能等。这种方法适合我们**自定义的编辑器,**可以自己控制文本的渲染和指令。

但由于浏览器扩展是基于别人写的编辑器上进行的辅助工具,不能随意修改其文本格式和样式。比如复制带有划线的文本进行粘贴,会出现冗余的划线(除非原本的编辑器有做粘贴文本的标签过滤),但是不能寄希望于别人写的编辑器都有这个功能。

4.4 实现

需要分别从两个部分进行考虑:

  1. 如何定位画线
  2. 如何接受建议替换正确文本

如何定位画线,并且可以给予其高亮的效果呢?

需要解决的问题是:需要在不影响原编辑器的格式以及功能前提下,将结果划线部分加入到界面上。

>>> contenteditabe:

  • 第一步:虚拟辅助器边框

虚拟一个元素(大小相同,位置相对)在原始编辑器之上,将结果划线标注在这个元素之上,我称之为辅助器。

因为是覆盖在原始编辑器上,需要禁止辅助器的鼠标响应行为,在 hover 的时候需要将鼠标位置同步到辅助器之上。

辅助器需要和原编辑器相同,才能定位准确,需要获得原编辑器以下属性。

  • 第二步:找准定位

**问题:**如果单纯提取元素的 innerHTML/InnerText/textContent 作为批改请求的参数,无法实现准确定位。

**原因:**服务端返回结果是根据纯文本定位,网页上的编辑器格式是HTML文档格式,包含不同字体不同格式的标签。

举个例子:html 中有两个块级元素,分别展示两句话,差异只在于两句话 font-size 不一样。

通过 element.textContent 提取出来的内容都是相同的,校验返回的错误标志结果也相同,如下:

因为无法从纯文本的角度得到两种情况差异,难点就在于:需要解析 html 格式,将服务端数据转换到实际格式位置中。

要知道,这相当于要在一个空白的白板元素里添加一个多个绝对定位的高亮元素。需要知道每个错误相对原编辑框的相对位移,和自身宽高。

下面是一个反向推敲的过程:

  1. 我需要得到的是 hightlightElement : { top, letf, width, height };
  2. 通过 range.getClientRects() 可以获得我们想要的数据。
  3. 于是需要知道如何获取一个错误节点对应的 range。

  1. 需要找到对应的开头节点和它的相对位移、以及结束节点和它的相对位移。range: { startNode, stratOffest, endNode, endOffest}。方法就是通过错误节点在纯文本的开始(fixedposStart)跟结束位置(fixedposEnd)通过遍历全文每一个文本节点(textNodes)的数据长度(textNodes.nodeValue)进行计算得出。

  2. fixedposStart、fixedposEnd根据服务端返回数据经过稍微计算可得出。全文每一个文本节点(textNodes)需要通过过滤某些标签得到。

  3. 所以需要先思考如何处理 html 中各种标签问题。

**所以划线的原理是:**提取其纯文本的 textnode 节点,根据结果 position 匹配开始的节点和位移、结束节点和位移,获取其文本片段 range 对应编辑器的 x,y,height,width,画出高亮区域。

具体步骤如下:

a. 根据原文所有 html 标签加工过滤,提取纯文本和加工后的文本节点集合:

html 内有各种标签节点,需要根据这些标签不同意义,对标签内的文本进行加工。比如针对 p 标签,通常是表示段落,需要将其包裹的内容后面添加一个换行符。

p 标签处理例子: 问题: 这个换行符是一起发给服务端的,服务端返回来的数据定位也算上了这个换行符。 解决方案: 过滤标签的同时记录文本处理过的位置,在后面的计算反向处理。同时还需要注意字符的转义问题,尤其注意零宽字符的处理。

b. 提取纯文本节点:

(上图文本内容根据标签内容分成5个纯文本节点)

c. 结合服务端数据计算每个错误全文定位:

比如 has 错误对应的错误节点信息。

d. 根据定位获取每个错误节点文本片段:

e. 通过文本片段获取相对视口的位置:

划线步骤图

  • 第三步:在assist范围内画出线和高亮

contenteditable 集合辅助器工作的流程图

>>> textarea:

textarea 本身是无法获取其 textnode 的,它相当于只有一个节点。考虑将其转换成文本节点:

  • 创建一个隐形 mirror,这个 mirror 具备与原始编辑器相同边框大小、可编辑区域。

  • textarea 任何文本变动同步到 mirror
this.textarea.addEventListener('input',this.mirror.update);
  • 再为这个 mirror 创建一个 assist,同理上面处理 contenteditbale 的流程相一致。

>>> 关于突变:

编辑器其实就是一个普通的元素,以下编辑器的交互会引起我们页面内文本节点的变化:

  • 文本内容变化
  • 尺寸变化(窗口变大变小)
  • 位置变化
  • 字体大小变化(加粗,居中)
  • 滚动

这些变化也就影响我们定位的变化,称之为突变。需要处理每一个突变引起的重新定位问题(重点难点)。

同时,需要监听原始编辑器的输入、字体变化、编辑器尺寸变化等等触发 assist 的重新定位方法。

// 通过ResizeObserver监听编辑器尺寸变化 objResizeObserver = new ResizeObserver((entries) => {var entry = entries[0];this.elementResizeHandler(entry.target)
}); 

ResizeObserver 兼容性问题需要通过 polyfill 库文件解决。

重新定位方法(mutation):

  • 通过新旧 textnode array 比对,正向遍历节点集合和反向遍历节点集合,得到被修改的 textnode 是哪一个段文本节点 textnode 集合。
  • 只需要处理被影响的 textnode 所对应的错误节点集合根据移动的 offest 计算后面影响的节点位移。

  • 其他错误相对自己 textnode 的位移是不会改变的。

如何接受一个建议,替换文本:

替换文本意味要修改原编辑器的数据甚至格式,就会造成刚才说的对部分编辑器会引起格式错乱和保存失败的情况。

**难点:**不影响原始数据存储格式,不影响原始编辑器撤回操作,同时还能触发原编辑器保存机制。

**解决方法:**不直接用脚本修改 dom 节点,模拟用户修改数据的方式:选中文字,替换内容。

以新版有道云笔记为例子:

  1. 通过之前复杂计算得到结果片段,根据结果片段计算出对于可视窗口的位置,得到 {top, left, height, width}。
  2. 模拟鼠标从左向右滑动的操作事件加在内容区域。

  1. 找到自定义的自绘区域。

  2. 一个错误结果中可能涉及不同的样式,我们仅获取当前节点第一个片段的字体样式,模拟一个粘贴事件。

  1. 在自绘区域触发自定义粘贴事件。

4.5 增强编辑框

入口在点击右下角按钮或者 hover 出现结果卡片时候,点击详细建议进入

>>> 增强编辑框的作用:

  • 提供更大的编辑空间
  • 查看详细的批改结果

增强编辑框是一个特殊的 contenteditable 编辑器。

>>> 初始化、关闭赋值:

在初始化增强编辑器的时候,直接获取原编辑器数据,这里忽略了原编辑器的一些样式、图片,只使用 html 数据部分。

在增强编辑器中编辑后返回原编辑器时候,需要将新数据返回赋值。

>>> 通信:

增强编辑框是嵌入页面的 iframe,只在顶层页面出现。与原来页面的通信是通过postMessage 方式。

(注意:postMessage 不能传递 html 元素和过于复杂的 json object)

如果是原本编辑器是 iframe,需要找到最上层 window.top,利用 window.top 和增强编辑框进行通信。

五、整体流程

上图为有道写作浏览器扩展从注入到浏览器页面,以及运行的大致流程。

为了在不影响用户操作前提下,扩展脚本只会在当前页面空闲时候加载,并且批改功能只在部分被用户点击 focus 的编辑器中激活。

以上是开发有道写作浏览器扩展过程中的开发思路和部分技术实现细节,借此机会分享给大家,欢迎与有道技术团队一起探讨更多关于前端、浏览器扩展的知识问题。

有道写作浏览器扩展实践相关推荐

  1. 为什么浏览器扩展会如此危险?

    浏览器,大家每天都在用,用它上网查资料.办公.看视频等.可以说,浏览器是在线内容消费的中心平台,如在办公中,都是通关浏览器去访问组织数据和应用程序,用来通信.数据共享.操作等. 好用的浏览器扩展 日常 ...

  2. css 好困难字体样式_帮助阅读困难者的字体和浏览器扩展

    css 好困难字体样式 G-Stock Studio/Shutterstock G-Stock Studio / Shutterstock Dyslexia is a learning conditi ...

  3. arXiv论文如何一键链接解读视频,这个浏览器扩展帮你实现

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨杜伟.陈萍 编辑丨机器之心 有了这个浏览器扩展,读者就可以在 arXiv 论文页面直接链接到解读 ...

  4. LWN: 浏览器扩展开源项目中的恶意攻击!

    关注了就能看到更多这么棒的文章哦- Malware in open-source web extensions February 16, 2021 This article was contribut ...

  5. 独立开发变现周刊(第69期):语音转录浏览器扩展插件,一年获取20倍增长

    分享独立开发.产品变现相关内容,每周五发布. 目录 1.JenniAI: 用最先进的自动完成功能来增强你的写作能力. 2.wx_lover:微信公众号情侣消息推送 3.Xnapper: MacOS应用 ...

  6. 浏览器扩展:比你想象得更危险

    我们每个人都可能或多或少地安装过各种浏览器扩展程序:广告拦截器.在线翻译器.拼写检查器.反指纹追踪程序或其他东西.然而,很少有人停下来思考:它安全吗?不幸的是,这些看似无害的迷你应用程序可能比你想象得 ...

  7. Chrome浏览器扩展开发系列之五:Page Action类型的Chrome浏览器扩展

    Page Action类型的Google Chrome浏览器扩展程序,通常也会有一个图标,但这个图标位于Chrome浏览器的地址栏内右端.而且这个图标并非始终出现,而是当某指定的页面打开时才会出现.也 ...

  8. 5分钟学会开发浏览器扩展

    写在前面 做web开发的同学,经常会用到各种chrome浏览器插件,那么我们寄几怎么开发一个插件呢(其实是浏览器扩展)?其实很简单,你意想不到的简单.只要有web开发基础,会写基本的html,css和 ...

  9. Chrome 浏览器扩展 - Dark Web - Dark Theme for Chrome

    Jerry 之前的文章 Chrome 浏览器扩展 - Night Eye ,介绍的 Night Eye 这款 Chrome 浏览器扩展,只有三个月的试用期.因此我又在网上找到了另一款完全免费的 Chr ...

最新文章

  1. Kubernetes数据持久化方案
  2. UI-- Empty Application 新建空工程
  3. app store 关键词
  4. 增量式pid调节方式有何优点_PID控制算法
  5. ORACLE使用中的常见 实用的问题
  6. UIScollView Touch事件
  7. django migrate 抛出异常:ValueError: Found wrong number (0) of constraints for …
  8. 素数的线性筛法java,埃氏筛 线性筛(欧拉筛) 算法解析
  9. 安装MYSql Windows7下MySQL5.5.20免安装版的配置
  10. Android Camera setRecordingHint函数 在部分手机上的问题。
  11. 解决在servlet中输出html的中文输出为问号的问题
  12. 尚硅谷Java8 新特性学习笔记
  13. Linux基本操作之vi编辑器
  14. windows电脑使用iTunes导入视频/音乐(本人使用,仅供参考)
  15. java jtextarea 事件_JTextArea事件处理
  16. 软件测试和硬件测试的区别及概念
  17. 关于密室逃脱的最终解决方案。
  18. 研究生英语复习(一)
  19. 2016年终总结,碌碌无为的一年
  20. 致年轻时如此拼搏的你我

热门文章

  1. R语言下载包出问题了怎么办
  2. mac(4) : 删除系统自带输入法
  3. Postman-newman基础用法
  4. The 2019 ACM-ICPC China Shannxi Provincial Programming Contest J.And And And(启发式合并)
  5. 网站快照被劫持怎么解决?
  6. 写了个 Ping 模块 很实用!
  7. openWRT的SDK编译环境的安装和设置
  8. DUSKTREE SYSTEM
  9. Exchange反压保护机制导致内部邮件传送延迟
  10. 《静儿的服务治理私房菜》网络模型的分类和职业规划思考