昨天體驗了基本的行內格式 Blot 以及區塊格式 Blot,今天繼續實現類似 Medium 編輯器的最後四個部分,分別為分隔線、圖片、影片、以及推文的自訂功能實現。
分隔線 (Dividers)
接下來的步驟中,我們將實作第一個所謂的「葉子 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
Reference
文章同步發表於2023 iThome 鐵人賽