iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Modern Web

深入 slate.js x 一起打造專屬的富文字編輯器吧!系列 第 20

Day 20. slate × Operation × Interface

https://ithelp.ithome.com.tw/upload/images/20211005/20139359LM3jXh5EDt.png

我們接下來就要進入到 slate 世界裡的另一大領域: Operations 。

這一整段章節其實也是筆者準備整個系列文章中數一數二期待的章節。因為在面對 WYSIWYG 編輯器時,與 CRUD 相關的內容都一定會被放大檢視。

想想我們目前看過的各種富文本編輯器五花八門的互動功能,隨著時代演進各種行銷手法的腦洞大開,各種花式 CRUD 的需求依序出現。開發者除了要正確實現功能邏輯外,效能方面的問題當然也不能馬虎(拉個反白都會 lag 到不行的話誰還想用 XD)。

也因此每個 WYSIWYG 在開發過程中,底層實現 CRUD 相關功能的概念與邏輯必定會被慎重檢視,尤其是像 slate 這種,將目標放在提供一個製作 WYSIWYG 編輯器的 framework library 更是如此。

為了提供開發者一個『擴展性強的底層核心』, slate 除了提供一系列平行於 DOM 的概念讓開發者使用降低學習成本之外,它也在 CRUD 上額外下了許多功夫去拆分出不同的邏輯層以及處理掉常見的效能瓶頸,而這些正是我們在接下來的 Operation 與 Transforms 的章節所要探討的重點!(講一講自己都有點興奮了起來 ?

那麼整個篇章的小前言就到此為止,我們一樣循序漸進,先回到 Interface 的章節被我們暫時略過的 operation.ts 這個 file 開始介紹起吧。


Concepts of Operation


https://ithelp.ithome.com.tw/upload/images/20211005/20139359SjUfFgFOKH.png

這個 file 與我們前面介紹 Interfaces 的章節裡提到的 files 的 code 架構上大同小異,負責定義相對應概念的 types 以及提供它的 method apis ,它們之間的差別就在於: operation.ts 這個 file 裡定義的 types 相對複雜許多。

... // Other types declaration

/**
 * `Operation` objects define the low-level instructions that Slate editors use
 * to apply changes to their internal state. Representing all changes as
 * operations is what allows Slate editors to easily implement history,
 * collaboration, and other features.
 */

export type Operation = NodeOperation | SelectionOperation | TextOperation

export interface OperationInterface {
	... // interface declaration
}

export const Operation: OperationInterface = {
	... // method apis implementation
}

從官方的註釋( /*...*/ )裡面我們可以先初步的了解到 Operation 負責掌控 slate editor 最基本最底層的狀態改變,編輯器的一切修改最終都一定是透過一個或數個 operations 來達成,這讓 slate 更好管理所有狀態修改的紀錄。

slate 將一切的狀態操作都照著: Node 、 Selection 、 Text 這三大項來做分類。我們從 Operation 的 type 定義裡就能看得出來。

export type Operation = NodeOperation | SelectionOperation | TextOperation

從 transforms 目錄底下的分類也能看出這件事:

https://ithelp.ithome.com.tw/upload/images/20211005/20139359VNDPOXuSFA.png

從文字的描述上應該不難理解三者個別負責的部分:

  • Node 負責一切與節點( node )相關的操作
  • Selection 負責一切與『編輯器內的文字反白』相關的操作
  • Text 負責一切與純文字相關的操作

那在我們開始 dig into 這三大項的 Operators 之前,我們要先滲透 low-level action 這件事所代表的含義。Operator 代表最基本的核心操作,就像 slate 不會去猜測開發者的使用情境,只提供最核心的功能一樣,在裡面定義的也都是最基本的 action 。

以我們第一個要介紹的 TextOperation 為例,這裡頭定義了一切 Text 所需的基本操作,但它卻只由 extendable 的 InsertTextOperationRemoveTextOperation 所組成

export type InsertTextOperation = ExtendedType<
  'InsertTextOperation',
  BaseInsertTextOperation
>

export type RemoveTextOperation = ExtendedType<
  'RemoveTextOperation',
  BaseRemoveTextOperation
>

export type TextOperation = InsertTextOperation | RemoveTextOperation

以白話文來說明的話就是:一切與『文字』相關的操作都只需要由『插入』與『移除』文字這兩種『事件』來完成。

再來是這三大類底下所有的 Operations 都是由一組 Base type 延伸並被定義為 extendable 的,開發者能依照自己的需求去擴展,所有的 Base type 裡都同時存在一個 type property 負責描述 operation 的類別,剩下的就是各自所需的 properties 。

我們一樣以TextOperation 底下的 Base type 為例:

export type BaseInsertTextOperation = {
  type: 'insert_text'
  path: Path
  offset: number
  text: string
}

export type BaseRemoveTextOperation = {
  type: 'remove_text'
  path: Path
  offset: number
  text: string
}

Properties inside :

  • type : operation type name
  • path : 欲修改的 node path
  • offset : text node start offset
  • text : 文字 value

接著到 SelectionOperation ,它只由一組 SetSelectionOperation 所組成

export type SetSelectionOperation = ExtendedType<
  'SetSelectionOperation',
  BaseSetSelectionOperation
>

export type SelectionOperation = SetSelectionOperation

以白話文來說明的話就是:一切與『反白』相關的操作都是由『設定反白』這個事件來完成,在 Base type 底下我們能看到他的參數組成單純是由『舊資料』與『新資料』所組成:

export type BaseSetSelectionOperation =
  | {
      type: 'set_selection'
      properties: null
      newProperties: Range
    }
  | {
      type: 'set_selection'
      properties: Partial<Range>
      newProperties: Partial<Range>
    }
  | {
      type: 'set_selection'
      properties: Range
      newProperties: null
    }

Properties inside :

  • type : operation type name
  • properties : 舊的 selection properties ( Range type )
  • newProperties : 新的 selection properties ( Range type )

最後的 NodeOperation 由『插入』、『合併』、『移動』、『刪除』、『設定』、『拆分』這幾種事件所組成

export type InsertNodeOperation = ExtendedType<
  'InsertNodeOperation',
  BaseInsertNodeOperation
>

export type MergeNodeOperation = ExtendedType<
  'MergeNodeOperation',
  BaseMergeNodeOperation
>

export type MoveNodeOperation = ExtendedType<
  'MoveNodeOperation',
  BaseMoveNodeOperation
>

export type RemoveNodeOperation = ExtendedType<
  'RemoveNodeOperation',
  BaseRemoveNodeOperation
>

export type SetNodeOperation = ExtendedType<
  'SetNodeOperation',
  BaseSetNodeOperation
>

export type SplitNodeOperation = ExtendedType<
  'SplitNodeOperation',
  BaseSplitNodeOperation
>

export type NodeOperation =
  | InsertNodeOperation
  | MergeNodeOperation
  | MoveNodeOperation
  | RemoveNodeOperation
  | SetNodeOperation
  | SplitNodeOperation

基本的『插入』、『移動』、『刪除』、『設定』這幾種事件要傳入的參數很直觀,包含要做操作的路徑( Path )、節點的資料內容、要移動到的新路徑等等,就不繁瑣地一一做說明了。

這邊特別提一下『合併( MergeNode )』以及『拆分( SplitNode )』這兩個事件:

export type BaseMergeNodeOperation = {
  type: 'merge_node'
  path: Path
  position: number
  properties: Partial<Node>
}

export type BaseSplitNodeOperation = {
  type: 'split_node'
  path: Path
  position: number
  properties: Partial<Node>
}

MergeNode 的做法是將提供的 path 指向的節點與它同層的前一個 sibling 做合併,而 position 代表著它前一個 sibling 的 childrentext character 的 index ,稍微偷看一下 transforms/node.ts 裡的 mergeNodes method :

mergeNodes(
    // ...args
) {
	// ...

	let position

	// ...
    if (Text.isText(node) && Text.isText(prevNode)) {
		// ...
        position = prevNode.text.length
		// ...
    } else if (Element.isElement(node) && Element.isElement(prevNode)) {
		// ...
        position = prevNode.children.length
		// ...
    }
	// ...
}

它在這裏的用途主要是提供編輯器的 selection 隨著節點合併以後的更新依據。

SplitNode 則是將提供的 path 指向的節點子層,依照 position 之前與之後的 childrentext character ,拆分開來。

這邊主要跟讀者介紹這些 properties 的用途以及大概的運作模式,詳細的實作我們留到之後介紹 Operation 的 transform method 時再介紹。


介紹完 Operations 所有的 Type 以後,下一篇我們要介紹一個完整的 Operation 所會經過的流程,也會大致介紹這些流程大致上負責的工作。

一樣明天見囉~


上一篇
Day 19. slate × Operation × WeakMap
下一篇
Day 21. slate × Operation × Entrance
系列文
深入 slate.js x 一起打造專屬的富文字編輯器吧!30

尚未有邦友留言

立即登入留言