昨天我們新增了一個元件並初始化 Quill 的核心,今天繼續實現 Medium 編輯器的練習。
實作基礎格式
我們之前提到過,Inline 不貢獻任何格式。這是為 Inline 基礎類別所制定的例外,而不是規則。基本的 block Blot 和區塊級的元素 (Block level element) 的運作方式相同。
要實作粗體和斜體,我們只需要繼承 Inline,設定 blotName 和 tagName,並註冊到 Quill 中即可。有關繼承和靜態方法和變數的內容介紹可以參考 Parchment 的介紹。
import Quill from 'quill';
const Inline = Quill.import('blots/inline');
export class BoldBlot extends Inline {
static blotName = 'myBold';
static tagName = 'strong';
}
export class ItalicBlot extends Inline {
static blotName = 'myItalic';
static tagName = 'em';
}
這裡跟著 Medium 的範例使用 Strong
以及 em
標籤,但我們也可以使用 b
和 i
標籤。Quill 將使用 blot 的名稱當作格式名稱,透過註冊我們的 Blot,我們現在可以在新格式上使用 Quill 的完整 API:
ngAfterViewInit(): void {
this.registerBasicFormatting();
this.quillInstance = new Quill(this.editorContainer.nativeElement);
}
registerBasicFormatting() {
Quill.register(BoldBlot);
Quill.register(ItalicBlot);
}
insertText() {
this.quillInstance.insertText(0, 'Test', { myBold: true });
}
formatText() {
this.quillInstance.formatText(0, 4, 'myItalic', true);
}
接著將按鈕的 click
事件加上,這邊為了示範方便,我們直接寫死一個 true
在程式裡面,這樣就會一直是加上格式的操作。在 App 中,我們可以使用 getFormat()
來尋找指定範圍內的文本格式,來決定是否新增或刪除格式。Toolbar 模組因為 Quill 已經實現了,就不在這重新實作。
兩個按鈕都點擊之後的效果如下:
實作連結 (Link)
與其他格式(如粗體或斜體)不同,Link 需要存入更多資訊,特別是 URL。這主要影響到「Link blot」的兩個方面:建立和格式檢索。
- 建立(Creation): 當建立一個 Link 時,除了表示它是一個 Link 外,還需要加上 URL。這通常會以字串的形式來表示。
- 格式檢索(Format Retrieval): 當需要找出或修改一個已存在的 Link 格式時,除了知道它是一個Link 外,我們還需要取得或修改 Link 的 URL。
雖然 URL 通常以字串的型別存入,但也可以用其他方式來表示,例如以一個包含 URL Key value 的物件。這樣做可以允許我們加入其他的 Key/Value 來定義一個連結,提供更多自定義的選項。
新增一個 Link Blot:
export class LinkBlot extends Inline {
static blotName = 'myLink';
static tagName = 'a';
static create(value: string) {
let node = super.create();
// Sanitize url value if desired
node.setAttribute('href', value);
// Okay to set other non-format related attributes
// These are invisible to Parchment so must be static
node.setAttribute('target', '_blank');
return node;
}
static formats(node: HTMLElement) {
// We will only be called with a node already
// determined to be a Link blot, so we do
// not need to check ourselves
return node.getAttribute('href');
}
}
Component 加入新的註冊和方法:
registerBasicFormatting() {
Quill.register(BoldBlot);
Quill.register(ItalicBlot);
Quill.register(LinkBlot);
}
考慮到安全性,這裡我們可以在 constructor
注入 Angular 提供的 DomSanitizer
“消毒” (sanitize)輸入的 URL 避免 XSS 問題發生:
constructor(private sanitizer: DomSanitizer) {}
addLink() {
const url = prompt('請輸入 URL');
const safeUrl = this.sanitizer.sanitize(SecurityContext.URL, url);
this.quillInstance.format('myLink', safeUrl);
}
接著嘗試選取文本內容,並點擊加入連結的按鈕,輸入網址後可以看到效果:
區塊引用 (Blockquote) 與標題 (Headers)
Blockquotes 繼承自 Block,這是基本的 Block Blot(一種自定義的文本塊)。與 Inline blots 不同的是,Block Blots 不能被嵌套。如果對同一範圍的文字套用多個 Block blots,它們不會互相包裹,而是會相互替換。也就是說,新套用的 Block Blot 會取代原有的 Block Blot。
建立 BlockquoteBlot:
const Block = Quill.import('blots/block');
export class BlockquoteBlot extends Block {
static blotName = 'myBlockquote';
static tagName = 'blockquote';
}
註冊 Blot:
Quill.register(BlockquoteBlot);
Header 的實作方式完全相同,只有一處不同:它可以由多個 DOM 元素表示。預設情況下,格式的值將成為 tagName,而不僅僅是 true。我們可以透過擴充 formats() 來自訂,類似於我們對連結所做的那樣:
export class HeaderBlot extends Block {
static blotName = 'myHeader';
static tagName = ['H1', 'H2'];
static formats(node: HTMLElement) {
return HeaderBlot.tagName.indexOf(node.tagName) + 1;
}
}
為了方便測試,加入 CSS 的部分:
::ng-deep h1, ::ng-deep h2 {
margin-top: 0.5em;
color: purple;
}
::ng-deep blockquote {
border-left: 4px solid #111;
padding-left: 1em;
}
最後在 Component 加入 event function,再和 template 的 click
事件綁定:
addBlockquote() {
this.quillInstance.format('myBlockquote', true);
}
addHeader1() {
this.quillInstance.format('myHeader', 1);
}
addHeader2() {
this.quillInstance.format('myHeader', 2);
}
輸入不同段落的內容後,點擊按鈕試試看套用格式效果,可以看到對應的 HTML 元素也被成功加入了,並且套用了設定好的 CSS Style:
小結
今天嘗試跟著實現自訂的 Inline Blot 和 Block Blot,實際操作過一遍會比較有感覺,官方文件提供的範例是 JavaScript,那我們就直接以 Angular 的專案當作練習,以 Angular 的方式來實現對應的功能。對於自訂的 Blot 內容有進一步的理解,明天再接著練習後面的其他功能。
雜記
轉眼間就來到第 28 天了,時間真的過的很快,也平安度過試用期(?)。但對於很多細節和產業的觀念還是持續學習中,白天工作內容的轟炸與考古,晚上則持續學習及寫文章做紀錄,上週的連假則是邊出去旅遊,回到住宿的地方後,繼續準備文章內容,腦袋裝了滿滿的東西。生活的節奏也比以往要快了許多,從進辦公室開始工作,回過神來就快下班了,除了充實,還是充實 XD…
Reference
文章同步發表於2023 iThome 鐵人賽