我們的模型映射到DOM樹(shù)后,看起來(lái)像下面這樣:
1 2 3 4 5 6 | <div> <!-- root -->
<section> <!-- section -->
<!-- section-inner -->
<div class="section-inner layout-column">
<p> <!-- paragraph -->
<strong><em>Baggins</em></strong> <!-- text -->
|
這個(gè)區(qū)域(section)節(jié)點(diǎn)是從區(qū)域模型中產(chǎn)生的,并且會(huì)將背景圖片和顏色應(yīng)用到一連串的段落中。
這個(gè)區(qū)域內(nèi)(section-inner)節(jié)點(diǎn)是根據(jù)段落排版屬性而產(chǎn)生的,并且決定了主列的寬度。對(duì)于大部分段落來(lái)說(shuō),它是狹窄并且居中的。對(duì)于全寬的圖片段落來(lái)說(shuō),它是100%寬的。對(duì)于上面的網(wǎng)格來(lái)說(shuō),它是原始的一般。
下一個(gè)節(jié)點(diǎn)是段落的語(yǔ)義類(lèi)別: P, H2, H3, PRE, FIGURE, BLOCKQUOTE, OL-LI (有序列表項(xiàng)), 和 UL-LI (無(wú)序列表項(xiàng)).
當(dāng)我們把標(biāo)簽類(lèi)別轉(zhuǎn)換到DOM節(jié)點(diǎn)的時(shí)候,我們會(huì)按照類(lèi)別排序它們:A,然后 STRONG,再然后 EM。我們永遠(yuǎn)不會(huì)打印一個(gè)包含錨定的STRONG標(biāo)簽。我們會(huì)拆散它而讓錨定包含STRONG標(biāo)簽。
編輯操作
編輯器主要包括6個(gè)編輯操作:插入段落,移除段落,更新段落,插入?yún)^(qū)域,移除區(qū)域,以及更新區(qū)域。
這些操作表現(xiàn)的會(huì)和描述的那樣。段落操作會(huì)接受一個(gè)段落模型和一個(gè)索引。區(qū)域操作會(huì)接受一個(gè)區(qū)域模型和一個(gè)索引。
所有可能的編輯器內(nèi)容都能夠被一系列的這些操作所表述,并且構(gòu)造這樣一個(gè)序列通常是比較簡(jiǎn)單的。
顯而易見(jiàn),內(nèi)容在這些編輯操作下能夠表現(xiàn)的很好。這些操作是直接應(yīng)用于我們的模型上面的,而不是DOM上面,并且這個(gè)模型能夠更容易地區(qū)分兩件東西在視覺(jué)上是否是相等的。
捕獲編輯操作
當(dāng)你和編輯器交互的時(shí)候,我們必須將你的按鍵操作和鼠標(biāo)點(diǎn)擊操作轉(zhuǎn)換成那6個(gè)操作的一個(gè)序列。
這是最復(fù)雜的部分。我們不會(huì)對(duì)那么多的每種可能按鍵的序列履行職責(zé)。這對(duì)于一個(gè)以英語(yǔ)為語(yǔ)言的用戶(hù)來(lái)說(shuō)是一個(gè)非常巨大的列表,永遠(yuǎn)不考慮非拉丁字符和鍵盤(pán)。
通過(guò)觀(guān)察,我們能夠用常規(guī)的ContentEditable鍵盤(pán)操作枚舉所有對(duì)段落插入和移除的操作方式。他們是:回車(chē)(enter,ctrl-m,等。),刪除(delete,backspace,等。),懸浮輸入(type-over)(在一段選擇的文本上輸入),以及拷貝。所以我們能夠捕獲,取消,以及手動(dòng)將這些鍵盤(pán)事件轉(zhuǎn)換到我們的編輯器內(nèi)部操作上來(lái)。
對(duì)于所有其他鍵盤(pán)事件,我們讓原有的ContentEditable 行為生效。在鍵盤(pán)事件結(jié)束之后,我們把段落的DOM映射回到段落的模型中來(lái),并且和我們之前的模型進(jìn)行比較。如果DOM改變了,我們會(huì)創(chuàng)建一個(gè)新的更新段落操作并且通過(guò)編輯器管線(xiàn)應(yīng)用它,保持DOM和模型同步。
快速捕獲編輯操作
如果我們有無(wú)限計(jì)算的能力,那么直接應(yīng)用這些編輯操作就可以了。我們應(yīng)用這些操作到模型上,重新渲染的整個(gè)頁(yè)面,最后結(jié)束操作。
但是在現(xiàn)實(shí)生活中,對(duì)每個(gè)按鍵操作都重新渲染整個(gè)頁(yè)面是非常慢的。并且你會(huì)看到許多丑陋的閃爍現(xiàn)象,因?yàn)閮?nèi)嵌框架(iframes)和圖片會(huì)一直處于加載中。相反,我們會(huì)對(duì)模型的改變事件進(jìn)行監(jiān)聽(tīng),并且盡最大可能減少對(duì)DOM的改變。
當(dāng)我在寫(xiě)篇文章時(shí),我可以看到Chrome拼寫(xiě)檢查程序在“keypress”單詞下面所加的紅色下劃線(xiàn)在閃爍。這是因?yàn)榫庉嬈髡谕瑫r(shí)對(duì)整個(gè)段落進(jìn)行改變,而不是僅僅改變這個(gè)段落的一小塊。如果我們僅僅對(duì)DOM進(jìn)行相對(duì)很小的改變,那么閃爍就會(huì)消失,但是這樣的代碼會(huì)相對(duì)的更復(fù)雜。
期待將來(lái)有一個(gè)更智能的文本編輯操作
最近有一些來(lái)自Chromium貢獻(xiàn)者 (Levi Weintraub, Julie Parent, and Jelte Liebrand) 的流言說(shuō)這些貢獻(xiàn)者想去基于聚合元素(Polymer Elements)和Shadow DOM特性重做ContentEditable。這個(gè)方案也會(huì)像編輯器一樣去嘗試解決許多同樣高級(jí)架構(gòu)方面的問(wèn)題。
-
創(chuàng)建一個(gè)由自定義 聚合元素(Polymer elements)構(gòu)成的編輯器模型
-
定義編輯器模型與真實(shí)的具有 Shadow DOM特性的DOM之間的映射
-
所有在ContentEditable中的按鍵操作和鼠標(biāo)點(diǎn)擊操作都會(huì)被轉(zhuǎn)換成一種抽象的編輯含義,被表示成像{editIntent: ‘delete’}這樣的JSON對(duì)象。
-
聚合元素(Polymer Elements)對(duì)這樣的編輯含義操作定義相應(yīng)的處理方法
如果編輯器能夠獲取某種編輯含義的API,那么我們就能夠拋棄許多轉(zhuǎn)換按鍵操作到抽象編輯操作的自定義代碼了。將我們的段落模型當(dāng)作聚合(Polymer)/ ShadowDOM元素是一件很有趣的嘗試。
ContentEditable是什么
不管我什么時(shí)候向那些從事于文本編輯器工作的人解釋的時(shí)候,他們都認(rèn)為我在玩花招。
“當(dāng)然編輯器要比ContentEditable更棒一些。你錯(cuò)了。ContentEditable努力的去成為一個(gè)通用的所見(jiàn)即所得HTML編輯器。而一般的編輯器放棄了'通用目的'的需求,所以你能夠挑選你想去處理的任何HTML結(jié)構(gòu)?!?
這是事實(shí)。但是確是誤導(dǎo)的。
一個(gè)好的所見(jiàn)即所得編輯器和一個(gè)好的具有通用目的HTML編輯器在理論上是不一致的。不可能把ContentEditable 構(gòu)建成那樣的,因?yàn)樗鼈冊(cè)谛枨笊鲜菦_突的。