作者:闲鱼技能——光酒

闲鱼作为一个二手闲置交易平台,卖家发布产品产出优质的供给尤为重要;产品发布器希望拥有富文本修改才能,让用户简单便捷的方法产出更加优质的内容;Flutter本身没有富文本修改器的才能的,只有最基础的文本修改器TextField;关于更加杂乱的场景,比方支撑自界说表情、主题、有序阶段等才能,现在flutter组件是无法满意咱们的事务诉求,别的在交互体会上与Native仍然存在必定的距离;为了解决事务中面临的以上问题,咱们决议规划并完成一个Flutter场景下高功能、可扩展的富文本修改器。

富文本修改器全体架构规划

首先咱们来看一看全体的架构规区分层:

打造Flutter高性能富文本编辑器——协议篇

自下而上首要分四层:

  1. 1. 协议层:首要担任Model的界说、Selection描绘、Commond事情逻辑处理,以及协议Normalizing校验;
  2. 2. 才能扩展层:才能扩展层提供丰厚的plugin才能,既有内置的plugin,如:纯文本转化,undo/redo等才能,一起也十分便利的支撑事务层自界说的扩展,例如支撑站外H5页面展示的model to HTML的序列化plugin;
  3. 3. 烘托层:烘托层首要完成将富文本Model转化成Flutter Widget烘托,以及光标、选区、ToolBar等计算和烘托,以及用户手势交互事情等;
  4. 4. 事务扩展层:在Mural的规划之初,可扩展便是规划进程中十分重要的一部分,咱们为事务方提供了十分灵活、功能强大的扩展才能,经过自界说Node、Plugin、Normalizing,完成如自界说表情、主题、阶段、语法高亮等才能;

协议层规划

富文本修改器对咱们来说并不生疏,开展至今,已经涌现出十分多有优异的开源富文本修改器;当咱们想要做Flutter富文本协议的时分,第一个主意便是先了解优异的开源富文本修改器方案,防止闭门造车;

现在比较优异的开源富文本修改器,如CKEditor、Quill、Prosemirror、Draft、Slate等等;在了解和比照过后,咱们决议使用Slate作为咱们的富文本修改器的协议;

why Slatejs

咱们为什么挑选Slate?

打造Flutter高性能富文本编辑器——协议篇

插件是一等公民,能够很好的满意咱们关于扩展性的要求; Slatjs在规划上支撑嵌套结构,能够满意杂乱的事务场景;

与Dom相同的Data model,关于后面flutter烘托层的完成,也变得更加便利;

直观的指令规划,能够十分好的支撑plugin的自界说扩展;

Slate在规划上,协议层与烘托层是有清晰的中心区分,这让咱们能够复用Slate协议层的规划,烘托层交给flutter来处理;

除了上面的原因,咱们挑选Slate别的一个很重要的原因,便是它的单元测试覆盖率和完好度,让咱们对它的稳定性更有决心;

Slate协议层规划

协议层的全体架构规划如下图:

打造Flutter高性能富文本编辑器——协议篇

下面咱们就以Slate为例,来看一看富文本修改器的协议层规划,需求界说的中心概念和模块:

  1. 1. 嵌套Model界说;
  2. 2. 原子才能Operation规划;
  3. 3. 次序维护者Normalizing的规划;

协议层规划——嵌套Model规划

Slate界说了三种类型的Node节点:

  • • Editor:包含整个文档内容的根节点;
  • Element:在自界说域中拥有语义的容器节点;
  • • Text:包含文档文本的叶子节点;

打造Flutter高性能富文本编辑器——协议篇

Editor

Editor抽象接口界说如下:

abstract class BaseEditor {
  BaseEditor();
  Selection selection;
  List<Operation> operations;
  Map<dynamic, dynamic> marks;
  bool isInline(Element element);
  bool isVoid(Element element);
  void normalizeNode(Tuple2<Node, Path> entry);
  void onChange();
  void addMark(String key, dynamic any);
  void apply(Operation operation);
  void deleteBackward(String unit);
  void deleteForward(String unit);
  void deleteFragment(String direction);
  List<Descendant> getFragment();
  void insertBreak();
  void insertFragment(List<Node> fragment);
  void insertNode(Node node, {Location at});
  void insertText(String text);
  void removeMark(String text);
}

Element

Element节点比较特殊,既是Ancentor节点,作为容器节点包含子节点;一起又是Descendant节点,能够作为其他容器节点的子节点存在。

  • • 块(Blocks):Element默以为Block类型的节点,也便是独立的一个阶段;在Slate协议规划中,一个阶段是不允许存在换行符的,当输入换行符的时分,就会生成一个新的Block类型的Element;
  • • 行内(Inlines):一起Element也能够是Inline类型的节点,作为别的一个Element的嵌套子节点存在,作为行内元素烘托在一行;
  • • 空元素(Void):Element也能够是Void类型,这儿Void与HTML中Void的是同一个概念:如果某个Node为Void,则表明这个Node节点是不行修改状况,光标无法定位到节点内部,会被全体输入和删去;比方:@某个人、主题、富文本中的图片或许视频等等;

Text

Text节点是树中的最低级叶子节点,描绘了文本内容以及其他自界说的烘托元素;一切的自界说特点都包含在properties特点中:

class Text implements Descendant {
  String text;
  Map<String, dynamic> properties;
  Text({@required String text, Map<String, dynamic> properties})
      : this.text = text ?? '',
        this.properties = properties ?? <String, dynamic>{};
  @override
  String string() {
    return text;
  }
  @override
  Text clone() {
    return Text(text: text, properties: Map.from(properties));
  }
  @override
  String nativeString() {
    return text;
  }
}

咱们以下面这这段富文本为例:

打造Flutter高性能富文本编辑器——协议篇

终究这样一段富文本对应的Mode界说如下:

打造Flutter高性能富文本编辑器——协议篇

能够看到,Model的树形结构还是比较简单的,一切的特点都存放在properties字段中,这也十分便利完成自界说扩展;Flutter烘托层根据Node节点的Type以及properties特点,将富文本内容烘托到屏幕上;

协议层规划——原子才能Operation

接下来需求富文本Commond协议的规划,用户的每一次的文字输入、删去、文字加粗、换行等操作都是一次Command指令;Slate抽象界说了九个最根本的Operations,协议层一切的Commond指令,终究在协议层,都会转化成一个或许多个operation操作:

  • • insert_node:刺进Node节点;
  • • insert_text:刺进文本;
  • • merge_node:兼并相同特点的Node节点;
  • • move_node:移动Node;
  • • remove_node:删去Node;
  • • remove_text:删去文本;
  • • set_node:设置Node特点;
  • • set_selection:设置Selection;
  • • split_node:拆分Node;

下面咱们经过对选中文本加粗操作为例,来了解Slate协议层Commond的处理进程:

打造Flutter高性能富文本编辑器——协议篇

对选中文本加粗这样一个Commond,协议层会将这个Commond拆解成三个Opeartion:

  • split_node:将一个Text Node拆分红三个Text Node;
  • set_selection:更新光标挑选区域Selection;
  • set_node:设置需求加粗Text Node节点properties的加粗特点;

当一个Commond被协议层拆分红一个或许多个Opeartion履行之后,会履行一个十分重要的操作——Normalizing;

次序维护者——Normalizing

每一次Command操作,绝大部分情况会对Model进行相应修改;咱们需求一个次序维护者——Normalizing,时间保证对协议Model修改过之后,保持数据结构的正确性;

Slate界说了几个根本的内置Normalizing规矩:

打造Flutter高性能富文本编辑器——协议篇

每一次Commond之后,Editor都会调用normalizeNode方法,在Normalizing的进程中,发现存在协议结构错误,需求进行错误修复;

Normalizing的另一个强大之处在于,咱们能够经过自界说Normalizing,添加自界说的校验规矩,完成自界说的需求;在后面的事务扩展章节会,咱们会具体讲解怎么经过自界说Normalizing快速完成一个自界说主题的才能;

总结

现在Mural已经在闲鱼产品发布、产品概况、音讯等场景落地,支撑了自界说表情、主题等事务才能,用户体会方面也有了十分大的提升。

本次首要介绍了富文本修改器Mural全体的架构规划以及协议层的规划;后续咱们会系列文章的方法介绍Mural在烘托层的规划、自界说扩展规划,以及交互体会、功能方面的优化实践,敬请期待!

参阅链接: [1] Slate:github.com/ianstormtay…