划词标注编辑器开发之旅

技术方案

类似于如何把大象塞进冰箱,句式操作页面总共分为三个大块:

  1. 初始化数据;(打开冰箱门)
  2. 编辑操作;(把大象塞进去)
  3. 存储数据;(关上冰箱门)

一、初始化步骤

在一个页面中的句式或许有非常多条(例如任务对话 中的意图触发器),也可能只有一条(任务对话中的填槽句式页面),但是对于句式编辑器来说,一个编辑器对应一条句式。

那么编辑器肯定不会“无中生有”,它所要展示的内容均来自于接口传递的句式数据。这份数据结构中最重要的几个key分别是”content”、”notations”。”content”代表的是句式中存储的文本信息,而”notations”存储的是句式中的词槽实体信息——包括位置、长度、和对应id。

因此,在初始化的时候就进行第一次判断——“notations”字段是否为空,如果为空,说明当前句式中并没有词槽实体信息,就是一个纯文本,可以根据”content”中的内容直接展示;如果不为空,则说明有词槽实体,需要对应的替换后再展示给用户。

替换的方式简单说明一下:”notations”字段接收到的数据是一个数组,里面每一项对应的是一组「词槽slot_id-实体entity_id」,除此之外还有两个字段”start_index”、”length”,”start_index”表示这一组「词槽-实体」对应在存储的句式文本”content”中的下标位置,”length”自然就是代表他们所占的长度,通过这3个数据就能把句式文本中的占位符”[ATID:xx]”替换成为用户喜闻乐见的”@城市:北京”。

「atInfoStore」,顾名思义,他是一个存储“@”信息的变量。这个是纯前端应用的工具字段,在初始化的过程中也初始化了「atInfoStore」,每当解析完一组完整的「词槽-实体、实体」,就会对应地往「atInfoStore」中推一组数据,key为atid,value为词槽和实体信息,并且有一个原则贯穿其中——只增不减,具体下文中详述。

那么高亮是怎么做到的?其实文本不是简单的设置<textarea />\<input />中的value,句式编辑器是一个<div contenteditable="true">...</div>的开启了编辑功能的div。句式的文本内容”content”其实是作为一个文本节点插入了进去,在上一步初始化词槽实体的过程中将对应的数据使用<span id="atid">标签包裹了起来,并且在css中设置<span>标签的不可选中已经背景色的变换就实现了高亮。

二、修改句式

修改也分成两种修改:1. 普通的文本输入;2. 增删改「词槽-实体」信息

第一种没有什么好说的,直接修改的是”content”中的内容;

第二种展开叙述一下:

1. 增加

句式编辑器监听了输入,每当用户输入”@“符号的时候就会自动记录当前光标的在文本中的位置并且弹出词槽实体选择框,当选择「词槽-实体、实体」完毕之后,句式编辑器先往「atinfoStore」里面推了一条新的数据,key: atid负向增长,value: 还是老样子词槽和实体的信息,同时生成了一个dom节点——<span id="newatid">@词槽:实体、实体</span>,这个dom节点随即插入到了之前记录的位置上,这样一次简单的增加”@”信息的操作就完成了。

2. 改

用户鼠标点击对应的高亮区域,句式编辑器从高亮区域的dom节点<span id=”xxatid“>...</span>中取出「xxatid」,然后从atInfoStore中读取到对应的「词槽-实体」信息填入到跳出的编辑词槽信息的弹窗内;等到用户修改完毕之后,点击保存会重新走一遍「1. 增加」的流程。需要注意的是,为了避免复杂的页面节点的替换操作,我做的是删除掉原有节点,重新生成新节点插入到原有的位置上,因此,在修改某一处”@”信息时,atInfoStore中的数据会增加一条新的atId对应的词槽实体数据,而不是修改原有「xxatid」对应的信息。

3. 删

这也没说的,删了就没了。

三、保存句式

为了简化保存时的计算,我特意在编辑器里面做了判断,所有的修改动作都会被判定为原有的句式发生了变化,哪怕是加了一个字又删除掉。如果句式没有发生改变,直接跳过调用接口;如果发生了改变,那么就发生了以下的操作;
我之前说了,在页面上展示的并不是简单的文本,而是文本节点和span包裹的节点,这些信息是无法直接读取且保存到数据库中的,所以第一步就是把HTML元素转换成存储的数据。由于在编辑过程中,所有需要转换的部分都被很好的包裹在了带有id的span标签中,在转换HTML元素的时候我只需要把「span」标签以及标签中的「id」提取出来,id可以对应地从「atInfoStore」中找到词槽-实体信息,而span的存在可以很快的进行占位符「[ATID:xx]」的替换。

被占位符替换完的数据就可以直接存储到”content”字段当中,而对应地从「atInfoStore」中读取的词槽-实体信息,就被一条一条塞入到”notations”字段当中;
最后把它们拼成接口所需要的数据结构,调用graphQL接口提交保存即可;

背景技术

句式编辑器使用的是HTML自己本身的contenteditable能力;弹窗则是采用了ant-design的popover;接口是使用了GraphQL的技术;

效果以及优点

句式编辑器为定制化需求开发,市面上没有找到现成的可以直接使用的富文本编辑器,这是属于来也科技的编辑器。在时间的证明下,它很好地为任务对话提供了触发和收集词槽的功能,用户不需要使用繁琐的表格进行配置,只需要轻松地输入”@“符号即可快速呼出弹窗选择词槽和实体,极快速地配置以及简单明了的操作界面,在很大程度上降低了用户学习和使用词槽-实体的成本。

作者: 张熠
文章链接: https://crazyoctopusdan.github.io/2020/04/05/%E5%88%92%E8%AF%8D%E6%A0%87%E6%B3%A8%E7%BC%96%E8%BE%91%E5%99%A8%E5%BC%80%E5%8F%91%E4%B9%8B%E6%97%85/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.