Quill編輯器插入自定義HTML記錄

轉眼已經2020年,飢渴的人類再也不知足於簡單的文本,因而有了花裏胡哨的攜帶各類樣式的文本,然而有文本還不夠,咱們還須要讓用戶在編輯的時候,可以插入各類自定義消息類型,讓咱們發出去的軟文更加好看,所以有了這篇文章。

前言

因爲Quill編輯器自帶的富文本過濾(大部分主流編輯器都會對富文本進行過濾處理),致使開發者想要配置自定義HTML模板時,遇到了很多麻煩。css

1、Quill渲染邏輯分析

爲了自定義Quill中的HTML塊內容,首先須要瞭解Quill內部的渲染流程,這裏有幾個關鍵的概念須要瞭解:html

一、Delta

Delta是Quill內部定義的一個數據格式,用於表示文檔內容以及文檔修改操做,易讀且格式簡單,經過Delta的形式來維護文檔內容,HTML內容和Delta二者能夠相互轉化。node

舉個例子:
image.png
這樣一段富文本會被表示成如下的格式:git

{  
"ops":[ 
{"insert":"this is a simple text.\\nbut when "}, 
{"attributes":{"bold":true},"insert":"it is "}, 
{"insert":"not bold.\\nlet me try "}, 
{"attributes":{"italic":true},"insert":"italic "}, 
{"insert":"haha\\nwhat about "}, {"attributes": 
{"italic":true,"bold":true},"insert":"both"}, 
{"insert":" ?\\n"} ]  
}"

普通的文本會被定義成一個個的insert動做,每一項表明這一個delta,都是對文本內容的描述。github

相似的,若是修改和刪除也會生成對應的delta,以後會將新生成的change delta,與原有的delta進行合併操做,生成新的delta。(delta中一共包含三種操做:insert、delete、retain)api

保留前10個字符,對後續的20個字符進行加粗操做的delta以下:app

{
  "ops": [
    { "retain":  },
    { "retain": , "attributes": { "bold":  } }
  ]
}

保留前10個字符,對後續的20個字符進行刪除操做以下:xss

{
  "ops": [
    { "retain":  },
    { "delete":  }
  ]
}

二、Parchment

Parchment是抽象的文檔模型,對Blot進行管理。
將Parchment理解成完整的DOM樹結構的話,那麼Blot就是其中一個個單一的節點。而Blot去了Quill中默認的之外,還容許咱們進行自定義,給了更大的擴展空間。編輯器

三、Blot

Blot是Parchment文檔的組成部分,至關於對DOM節點類型的抽象,而一個具體的Blot實例裏仍有其餘的節點信息。ui

全局的根節點Blot是由Quill內部自定義的Scroll類型Blot,管理其下面的全部Blot。

對於Blot的實現定義能夠參照這裏:https://github.com/quilljs/parchment#blots

Quill中默認定義的Blot以下:
image.png
這其中常見的包括TextBlot(行內普通文本)、Inline(行內攜帶樣式的普通文本)、Block(塊級行,通常以段落p爲單位)、Break(換行)、Image(圖片IMG插入)、Bold(加粗文本)。

而一段HTML如何構建出Blot?Quill中會根據節點類型優先排除文本節點,若是是元素節點會根據節點的ClassName進行再次判斷,若是仍然沒法找到匹配的BlotName,則默認匹配如下的映射關係,來找到對應的BlotClass。
image.png

四、Delta的實際意義

既然已經有Blot能夠來表示咱們的內容結構了,爲何還須要Delta?Delta自己只是一分內容數據的維護,也就是說HTML的更新,不管是用戶輸入,仍是API操做,都會同步更新到Delta中,而Delta若是不做爲HTML的數據源的話,那麼維護一份Delta數據的意義又在哪裏?

若是HTML => Delta,而不存在Delta=>HTML,那麼不停地去維護一份delta的意義是什麼?

一、由Delta生成HTML實際上是存在的,只不過應用場景只限於初始化文檔的時候,Quill會對傳入的初始化HTML字符串進行解析處理,生成對應的Delta,其次經過applyDelta的方式,生成DOM節點回顯與頁面中。

二、看到這裏你可能還不滿意,爲啥非要走這一步流程,初始化的時候直接一段字符串document.getElementById('container').innerHTML = val不行嗎,是的,能夠,可是Delta的存在讓用戶的文檔變得粒度更細小,變得易維護,變得可追溯。假如A和B同時編輯着一份文檔,A刪除了第二行的10個字符,不須要將文檔內容全量更新,只須要提交action操做,同步本身的行爲,而B這邊也只須要進行衝突處理後merge便可。雖然Delta的維護讓邏輯變得複雜了很多,但它的存在也讓文檔有了更多擴展的可能。

五、編輯器渲染與更新流程

對於內容的修改一共有如下3種方式:

一、初始化編輯器內容:初始化調用quill.pasteHTML,通過HTML過濾和解析回顯到編輯框中。

二、Input Event:用戶輸入和編輯操做,經過MutationObserver監聽處理,更新delta。

三、API調用:調用內部提供API,經過modify方法,然後調用全局Scroll實例的方法去修改。
image.png

2、插入自定義HTML塊

因爲文章內容愈來愈多樣化,在文章插入地圖、音樂播放器、廣告面板等需求的存在,讓咱們須要對富文本編輯器擴展出更多的功能。可是同時也要作好xss防禦攻擊。

按照第一部分的講述,咱們須要插入一個自定義HTML塊,同時又要Quill可以識別,聰明的你必定想到了,咱們須要自定義一個Blot。經過定義好Blot的方式,讓Quill在初始化的時候可以識別咱們的HTML塊展現,同時也讓咱們在插入HTML塊的時候不會被Quill進行髒HTML過濾。

註冊Blot方法以下:

export default function (Quill) {
  // 引入源碼中的BlockEmbed
  const BlockEmbed = Quill.import('blots/block/embed');
  // 定義新的blot類型
  class AppPanelEmbed extends BlockEmbed {
    static create(value) {
      const node = super.create(value);
      node.setAttribute('contenteditable', 'false');
      node.setAttribute('width', '100%');
      //   設置自定義html
      node.innerHTML = this.transformValue(value)
      return node;
    }

    static transformValue(value) {
      let handleArr = value.split('\n')
      handleArr = handleArr.map(e => e.replace(/^[\s]+/, '')
        .replace(/[\s]+$/, ''))
      return handleArr.join('')
    }

    // 返回節點自身的value值 用於撤銷操做
    static value(node) {
      return node.innerHTML
    }
  }
  // blotName
  AppPanelEmbed.blotName = 'AppPanelEmbed';
  // class名將用於匹配blot名稱
  AppPanelEmbed.className = 'embed-innerApp';
  // 標籤類型自定義
  AppPanelEmbed.tagName = 'div';
  Quill.register(AppPanelEmbed, true);
}

接下來你只須要這樣調用,即可以在編輯器中插入自定義的HTML塊:

quill.insertEmbed(quill.getSelection().index || 0, 'AppPanelEmbed', `
          <div class="app_card_header">     
              自定義面板標題
          </div>
          <div class="app_card_content">     
              自定義面板內容
          </div>
          <div class="app_card_footer">     
              footer
          </div>
      `);

傳參格式要求以下:

insertEmbed(index: Number, type: String, value: any, source: String \= 'api'): Delta

這裏僅僅這是個簡單的示例,若是想豐富自定義Blot的功能,能夠參照:https://github.com/quilljs/parchment#blots

因爲contenteditable屬性放開,爲了防止形成xss攻擊,因此須要咱們對該屬性作特殊的過濾處理,這裏以xss模塊處理爲例:

handleWithXss(content) {
      const options = {
        whiteList: {
         ...
          div: ['class', 'style', 'data-id','contenteditable'],
         ...
        },
        css: {
          whiteList: {
            color: true,
            'background-color': true,
            'max-width': true,
          },
        },
        stripIgnoreTag: true,
        onTagAttr: (tag, name, value, isWhiteAttr) => {
          // 針對div的contenteditable 處理
          if (isWhiteAttr && tag === 'div' && name === 'contenteditable') {
            return 'contenteditable="false"';
          }
        },
      } // 自定義規則
      const myxss = new xss.FilterXSS(options)
      return myxss.process(content)
    }

到這裏,就大功告成啦~

感謝觀看~

相關文章
相關標籤/搜索