昨天體驗了基本的行內格式 Blot 以及區塊格式 Blot,今天繼續實現類似 Medium 編輯器的最後四個部分,分別為分隔線、圖片、影片、以及推文的自訂功能實現。
接下來的步驟中,我們將實作第一個所謂的「葉子 Blot (Leaf Blot) 」。不同於先前我們練習過的 Blot,這些主要是負責文本格式化—例如定義文字的外觀或調整排列,並實作format()方法。Leaf Blot 的主要職責則是提供特定的內容,並透過實作 value() 方法來達成。
Leaf Blot 可以是文本 (Text) 型態或嵌入 (Embed) 型態的 Blot。在本例中,我們會實作一個屬於嵌入型態的 Blot,即分隔線 (Divider)。值得注意的是,一旦 Embed Blot 建立,其內含的值將會是不可變的 (Immutable) 。因此,如果你需要變更這個 Blot 的內容,則必須先將其從文本中刪除,再重新插入新的內容。
首先我們新增一個 TS 檔當作 Leaf Blot 的練習,並加入 Divider 的 Blot:
import Quill from 'quill';
const BlockEmbed = Quill.import('blots/block/embed');
export class DividerBlot extends BlockEmbed {
static blotName = 'myDivider';
static tagName = 'hr';
}
我們的 click handler 呼叫了 insertEmbed() 方法,這個方法不像 format() 那麼方便可以確定、保存和恢復使用者的選擇區域。因此我們需要自行做一些額外的工作來維護這個選擇區域。此外,當我們嘗試在一個 Block 的中間插入一個 Block Embed 時,Quill 會自動為我們將該 Block 分割開來。為了讓這個行為更為明確,我們會在插入分隔線之前明確地插入一個換行符,以自行分割該 Block。
建立 DividerBlot 之後,回到 Component 註冊 DividerBlot 並新增 addDivider 方法:
registerBasicFormatting() {
// ...
// Leaf blot
Quill.register(DividerBlot);
}
addDivider() {
const range = this.quillInstance.getSelection(true);
this.quillInstance.insertText(range.index, '\n', Quill.sources.USER)
this.quillInstance.insertEmbed(
range.index + 1,
'myDivider',
true,
Quill.sources.USER
);
this.quillInstance.setSelection(
{ index: range.index + 2, length: 0 },
Quill.sources.SILENT
);
}
接著將對應的 button 加上事件綁定:
<button
type="button"
title="divider"
id="divider-button"
(click)="addDivider()"
>
<i class="fa fa-minus"></i>
</button>
輸入兩行 Hello World 之後,游標停留在第一行的 Hello 後面,並點擊加入分隔線,可以看到 HTML 被強制換行後加入分隔線:

圖片的處理可以使用我們在建立 Link 和 Divider blots 時所學到的概念來新增。我們會使用一個物件作為圖片的值來展示如何被支援的。我們用於插入圖像的 click handler 直接帶入 hardcode 的內容來專注在插入圖片 Blot 的實現。
建立 ImageBlot,分別有 create 以及 value 兩個靜態方法:
export class ImageBlot extends BlockEmbed {
static blotName = 'myImage';
static tagName = 'img';
static create(value: { alt: string; url: string }) {
const node = super.create();
node.setAttribute('alt', value.alt);
node.setAttribute('src', value.url);
return node;
}
static value(node: HTMLImageElement) {
return {
alt: node.getAttribute('alt'),
url: node.getAttribute('src'),
};
}
}
接著在 Component 加上插入圖片的 handler,並綁定到對應的 button:
<button type="button" title="image" id="image-button" (click)="addImage()">
<i class="fa fa-camera"></i>
</button>
addImage() {
const range = this.quillInstance.getSelection(true);
this.quillInstance.insertText(range.index, '\n', Quill.sources.USER);
this.quillInstance.insertEmbed(
range.index + 1,
'image',
{
alt: 'Quill Cloud',
url: 'https://quilljs.com/0.20/assets/images/cloud.png',
},
Quill.sources.USER
);
this.quillInstance.setSelection(
{ index: range.index + 2, length: range.length },
Quill.sources.SILENT
);
}
看一下加入圖片後的效果:

今天主要就兩個 Embed Blot 的實現,我們透過繼承 Quill 底下的 Parchment Embed Blot 來建立自定義的 Blot,對於 Quill 的方法及應用有比較深入的理解。 整體的實現上都是與 DOM 去做對應在編輯器中加入內容,因此都會經過 Create() 方法來新增 DOM,如果是簡單的 HTML,沒有太多的加工處理,則直接帶上 blotNmae 和 tagName 即可,按照官網文件的說明,Quill 的確也讓編輯器的內容與結構盡可能的單純易懂。
這個週末是魔物獵人 Now 的社群日,有期間限定的櫻火龍,貌似對拿弓箭的玩家來說是不錯的裝備材料收集,準備好今天的文章之後,等等就要出去晃晃,希望不會太快就把藥水喝完 XD