昨天探討了 Delta,初步有了一些概念,今天就來嘗試練習看看,為了方便查看,先新增一個 quill-editor.service
,並將 Delta 相關的練習內容都放到這裡面。
在上一篇文章中,官方不建議手動建立 Delta 物件,應該要透過可連結 Deltas 物件的方法像是:insert()
、delete()
,和 keep()
等方法來建立新的 Delta。
因此使用的操作方式會是每次都直接 new 一個 Delta
,並把要進行的文本操作透過鏈式呼叫(Method Chaining) 的方式處理,最後再呼叫 quill.updateContents
方法並帶入新增的 helloWorldDelta
:
const helloWorldDelta = new Delta().insert('Hello World!');
quill.updateContents(helloWorldDelta as any); // types 問題,暫時 as any
從上面看到我在 helloWorldDelta
後面加了 as any,如果沒加的話會導致型別的錯誤,因為 @types/quill
並不是官方的類型定義庫,因此在後來的版本有修改了 Op
這個 interface,導致在編譯的時候會發生型別錯誤,目前暫解就是 as any
,我也提個一個 PR,主要是變更 quill-delta
的版本號,還不確定能不能過 XD 先等看看 reviewer 有沒有什麼回應了。
如果直接執行上方的範例,應該會看到編輯器出現了 Hello World!,但當你先輸入一些內容之後再執行這個方法,就會看到 Hello World! 並不是從游標後面接著進去的,原因是 delta 加入內容的操作都是從頭開始塞進去的,所以這裡我們需要使用鏈式呼叫的方式,在插入新內容之前計算一下目前的游標位置,並在這個位置後面加上內容:
// 使用 `getSelection()` 取得選取狀態
const currentIndex = quill.getSelection()?.index;
if (typeof currentIndex === 'number') {
// 將內容插入
const insertContent = 'Hello World!';
const helloWorldDelta = new Delta()
.retain(currentIndex)
.insert(insertContent);
quill.updateContents(helloWorldDelta);
}
這時我們就能跟著游標位置插入內容,但又注意到另一個問題,插入內容之後游標卻還是在原地,印象中好的操作體驗應該是插入內容後,游標也應該跟著移動到新增的內容後面才對。這時我們還需要呼叫一個方法來更新游標的位置。
使用 setSelection
更新編輯器游標位置,新的 index
可以用 currentIndex
+ insertContent.length
來獲得:
// 使用 `getSelection()` 取得選取狀態
const currentIndex = quill.getSelection()?.index;
if (typeof currentIndex === 'number') {
// 將內容插入
const insertContent = 'Hello World!';
const helloWorldDelta = new Delta()
.retain(currentIndex) // 保留到游標前的內容
.insert(insertContent); // 插入內容
quill.updateContents(helloWorldDelta); // 帶入 Delta 更新內容
quill.setSelection(currentIndex + insertContent.length, 0); // 更新游標位置
}
除了內容的輸入,有時候需要加入整行的內容並加上文字格式,例如我們可以插入一個 H1 級別的 Header 內容,為了能夠套用到整行,必須再加上一個換行的 Delta
並加上 Header 的 attribute:
const currentLength = quill.getLength();
const currentIndex = quill.getSelection()?.index;
if (typeof currentIndex === 'number') {
const headerContent = 'This is Header';
const headerDelta = new Delta()
.retain(currentLength)
.insert(headerContent)
.insert('\n', { header: 1 });
quill.updateContents(headerDelta);
quill.setSelection(currentIndex + headerContent.length + 1, 0);
}
上面這個範例可以看到除了 insert(headerContent)
將 header 的內容加上之外,後面還 insert('\n', { header:1 })
表示 header 的樣式是加在換行符號上的。即使最後一行沒有套用格式,所有 Quill 文件都必須以換行符號結尾,這樣我們就始終能有一個字元位置來套用行格式。
我們可以註冊 Quill 的 text-change
事件,這個事件會提供 Delta 作為文本內容發生變化時的描述。先在 quill-editor.service.ts
新增一個 updateQuillChanges
方法來處理 Quill 事件註冊:
@Injectable({
providedIn: 'root',
})
export class QuillEditorService {
quillUpdateSubject$ = new Subject<Delta>();
// ...
updateQuillChanges(quill: Quill) {
quill.on('text-change', (delta) => this.quillUpdateSubject$.next(delta));
}
}
在 updateQuillChanges
底下註冊 text-change
事件,並把獲得帶有內容變更的 delta 使用 subejct
方式打出去。這個方法可以在 Quill 初始化後,接著加入事件監聽並訂閱。
Delta 的操作其實還滿單純的,我們可以透過監聽事件觀察 Delta 如何描述內容變更,同時我們也可以使用 quill.getContent()
取得完整描述文本的 Delta 狀態,透過這樣的觀察可以回推當有特定需求的時候,我們可以如何實現較正確的方式來變更編輯器的內容。搭配 Angular 實現 Quill 相關的機制在專案管理及維護上都能有所幫助。
今天室友發了一個很酷的 30 天鐵人賽,是關於桃園青埔的建案描述,雖然跟 IT 沒有太大的關聯,但也是滿有趣的挑...戰? XD 這讓我想到有一個說法是要培養一個習慣需要花 30 天來練習。希望寫文章的習慣可以在這次挑戰之後,對於寫文章就比較不會太卡,往往都是面對一頁空白的時候不知道從何下筆,但都用電腦設備打文章了,先寫一點東西就對了 XD