欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

富文本編輯器quill.js開(kāi)發(fā)之自定義格式擴(kuò)展

 更新時(shí)間:2023年08月14日 09:37:11   作者:Grewer  
這篇文章主要為大家介紹了富文本編輯器quill.js開(kāi)發(fā)之自定義格式擴(kuò)展,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

鑒于各種繁雜的需求,quill.js 編輯器也面臨著各種挑戰(zhàn),例如我們需要添加“table”布局樣式以適應(yīng)郵件發(fā)送格式,手動(dòng)擴(kuò)展表情符號(hào)功能等等。本文將對(duì)這些可定制化功能進(jìn)行講解和實(shí)現(xiàn)。

區(qū)分 format 和 module

首先需要明確的是,我們應(yīng)該清楚自己所需的擴(kuò)展具體是什么?

比如想要新增一個(gè)自定義 emoji, 那么想象一下步驟:

  • 點(diǎn)擊工具欄
  • 彈出彈窗或者對(duì)應(yīng)的 popover
  • 在 2 中選中 emoji

這些步驟是一種常見(jiàn)的添加流程。

我們需要明確的是,添加自定義表情符號(hào)必然需要一個(gè)相應(yīng)的格式。

本文將以 format 為例,對(duì)此進(jìn)行詳細(xì)講解。

quill 的格式類(lèi)型

說(shuō)起 quill 的格式類(lèi)型, 他的常用格式可以分成 3 類(lèi):

  • Inline

常見(jiàn)的有 BoldColorFont 等等, 不占據(jù)一行的標(biāo)簽, 類(lèi)似于 html 里 span 的特性, 是一個(gè)行內(nèi)樣式, Inline格式之間可以相互影響

  • Block

添加 Block 樣式, 必然會(huì)占據(jù)一整行, 并且 Block 樣式之間不能兼容(共存), 常見(jiàn)的有 ListHeaderCode Block 等等

  • Embeds

媒體文件, 常見(jiàn)的有 ImageVideoFormula, 這類(lèi)格式擴(kuò)展的比較少, 但是本次要加的 emoji 但是這種格式

自定義樣式

新增 emoji.ts 文件來(lái)存儲(chǔ)格式, 關(guān)于他的類(lèi)型, 我們選擇 Embeds 格式, 使用這種格式有以下原因:

  • 他是一種獨(dú)特的類(lèi)型, 不能和顏色, 字體大小等等用在一起
  • 需要和字體并列, 所以也不能是 Block 類(lèi)型
import Quill from 'quill';
const Embed = Quill.import('blots/embed');
class EmojiBlot extends Embed {
  static blotName: string;
  static tagName: string;
  static create(value: HTMLImageElement) {
    const node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.src);
    node.setAttribute('width', value.width);
    node.setAttribute('height', value.height);
    return node;
  }
  static formats(node: HTMLImageElement) {
    return {
      alt: node.getAttribute('alt'),
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }
  static value(node: HTMLImageElement) {
    // 主要在有初始值時(shí)起作用
    return {
      alt: node.getAttribute('alt'),
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }
}
EmojiBlot.blotName = 'emoji';
EmojiBlot.tagName = 'img';
EmojiBlot.className = 'emoji_icon'
export default EmojiBlot;

因?yàn)檫€有正常的圖片類(lèi)型會(huì)使用 img, 這里就需要加上 className, 來(lái)消除歧義
一般來(lái)說(shuō), 新開(kāi)發(fā)的擴(kuò)展性類(lèi)型, 盡量都加上 className

這樣一個(gè) emoji 類(lèi)型就創(chuàng)建完成了!

最后我們注冊(cè)到 Quill 上即可:

import EmojiBlot from "./formats/emoji";
Quill.register(EmojiBlot);

這里我們?cè)诩由献远x的 popover, 用來(lái)點(diǎn)擊獲取 emoji:

<Popover content={<div className={'emoji-popover'} onClick={proxyEmojiClick}>
  <img alt={'圖片說(shuō)明'} width={32} height={32} src="https://grewer.github.io/dataSave/emoji/img.png"/>
  <img alt={'圖片說(shuō)明'} width={32} height={32} src="https://grewer.github.io/dataSave/emoji/img_1.png"/>
</div>}>
  <button className="ql-emoji">emoji</button>
</Popover>

通過(guò)代理的方式, 來(lái)獲取 dom 上的具體屬性:

const proxyEmojiClick = ev => {
  const img = ev.target
  if (img?.nodeName === 'IMG') {
    const quill = getEditor();
    const range = quill.getSelection();
    // 這里可以用 img 的屬性, 也可以通過(guò) data-* 來(lái)傳遞一些數(shù)據(jù)
    quill.insertEmbed(range.index, 'emoji', {
      alt: img.alt,
      src: img.src,
      width: img.width,
      height: img.height,
    });
    quill.setSelection(range.index + 1);
  }
}

展示下新增 emoji 的效果:

基礎(chǔ)格式說(shuō)明

我們的自定義格式都是基于 quill 的基礎(chǔ)庫(kù): parchment

這里我們就介紹下他的幾個(gè)重要 API:

class Blot {
  // 在手動(dòng)創(chuàng)建/初始值時(shí), 都會(huì)觸發(fā) create 函數(shù)
  static create(value?: any): Node;
  // 從 domNode 上獲取想要的數(shù)據(jù)
  static formats(domNode: Node);
  // static formats 返回的數(shù)據(jù)會(huì)被傳遞給 format
  // 此函數(shù)的作用是將數(shù)據(jù)設(shè)置到 domNode
  // 如果 name 是 quill 里的格式走默認(rèn)邏輯是會(huì)被正確使用的
  // 如果是特殊的name, 不處理就不會(huì)起效
  format(format: name, value: any);
  // 返回一個(gè)值, 通常在初始化的時(shí)候傳給 static create
  // 通常實(shí)現(xiàn)一個(gè)自定義格式, value 和 format 使用一個(gè)即可達(dá)到目標(biāo)
  value(): any;
}

上述幾個(gè) API 便是創(chuàng)建自定義格式時(shí)常用到的

詳情可參考: https://www.npmjs.com/package/parchment#blots

在上文講到了 format 和 value 的作用, 我們也可以對(duì)于 EmojiBlot 做出一些改造:

class EmojiBlot extends Embed {
  static blotName: string;
  static tagName: string;
  static create(value: HTMLImageElement) {
    const node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.src);
    node.setAttribute('width', value.width);
    node.setAttribute('height', value.height);
    return node;
  }
  static formats(node: HTMLImageElement) {
    return {
      alt: node.getAttribute('alt'),
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }
  format(name, value) {
    if (['alt', 'src', 'width', 'height'].includes(name)) {
      this.domNode.setAttribute(name, value);
    } else {
      super.format(name, value);
    }
  }
}

目前來(lái)說(shuō), 這兩種方案都能實(shí)現(xiàn)我們的 EmojiBlot

當(dāng)然 format 的作用, 并不僅僅在于 新增屬性到 dom 上, 也可以針對(duì)某些屬性, 修改、刪除 dom 上的信息

其他格式

上面我們講述了三個(gè)常見(jiàn)的格式: Inline 、Embeds 、Block, 其實(shí)在 quill 還有一些特殊的 blot:
如: TextBlot 、 ContainerBlot 、 ScrollBlot

其中 ScrollBlot 屬于是所有 blot 的根節(jié)點(diǎn):

class Scroll extends ScrollBlot {
  // ...
}

Scroll.blotName = 'scroll';
Scroll.className = 'ql-editor';
Scroll.tagName = 'DIV';
Scroll.defaultChild = Block;
Scroll.allowedChildren = [Block, BlockEmbed, Container];

至于 TextBlot, 是在定義一些屬性時(shí)常用到的值:

例如源碼中 CodeBlock 的部分:

CodeBlock.allowedChildren = [TextBlot, Break, Cursor];

意味著 CodeBlock 的格式下, 他的子節(jié)點(diǎn), 只能是文本, 換行, 光標(biāo)
(換行符和光標(biāo)都屬于 EmbedBlot)

這樣就控制住了子節(jié)點(diǎn)的類(lèi)型, 避免結(jié)構(gòu)錯(cuò)亂

ContainerBlot

最后要說(shuō)一下 ContainerBlot, 這是一個(gè)在自定義節(jié)點(diǎn)時(shí), 創(chuàng)建 Block 類(lèi)型時(shí)經(jīng)常會(huì)用到的值:

在源碼中, 并沒(méi)有默認(rèn)的子節(jié)點(diǎn)配置, 所以導(dǎo)致看上去就像這樣, 但其實(shí) container 的自由度是非常強(qiáng)的

這里就給出一個(gè)我之前創(chuàng)建的信件格式例子:

在富文本中擴(kuò)展格式生成能兼容大部分信件的外層格式, 格式要求:

格式占據(jù)一定寬度, 如 500px, 需要讓這部分居中, 格式內(nèi)可以輸入其他的樣式

大家可能覺(jué)得簡(jiǎn)單, 只需要 div 套上, 再加上一個(gè)樣式 width 和 text-align 即可

但是這種方案不太適合郵件的場(chǎng)景, 在桌面和移動(dòng)端渲染電子郵件大約有上百萬(wàn)種不同的組合方式。

所以最穩(wěn)定的布局方案只有 table 布局

所以我們開(kāi)始創(chuàng)建一個(gè) table 布局的外殼:

class WidthFormatTable extends Container {
  static create() {
    const node = super.create();
    node.setAttribute('cellspacing', 0);
    node.setAttribute('align', 'center');
    return node;
  }
}
WidthFormatTable.blotName = 'width-format-table';
WidthFormatTable.className = 'width-format-table';
WidthFormatTable.tagName = 'table';

有了 table 標(biāo)簽, 那么同樣也會(huì)需要 tr 和 rd:

也是類(lèi)似的創(chuàng)建方法:

class WidthFormatTR extends Container {
}
class WidthFormatTD extends Container {
}

最后通過(guò) API 將其關(guān)聯(lián)起來(lái):

WidthFormatTable.allowedChildren = [WidthFormatTR];
WidthFormatTR.allowedChildren = [WidthFormatTD];
WidthFormatTR.requiredContainer = WidthFormatTable;
WidthFormatTD.requiredContainer = WidthFormatTR;
WidthFormatTD.allowedChildren = [WidthFormat];
WidthFormat.requiredContainer = WidthFormatTD;

這一段的含義就是, 保證各個(gè)格式的父元素與子元素分別是什么, 不會(huì)出現(xiàn)亂套的情況

格式中最后的主體:

class WidthFormat extends Block {
  static register() {
    Quill.register(WidthFormatTable);
    Quill.register(WidthFormatTR);
    Quill.register(WidthFormatTD);
  }
}
WidthFormat.blotName = 'width-format';
WidthFormat.className = 'width-format';
WidthFormat.tagName = 'div';

register 函數(shù)的作用就是在注冊(cè)當(dāng)前的 WidthFormat 格式時(shí), 自動(dòng)注冊(cè)其他的依賴(lài)格式; 避免人多注冊(cè)多次

最后我們新增一個(gè)按鈕, 來(lái)格式化編輯器內(nèi)容:

const widthFormatHandle = () => {
  const editor = getEditor();
  editor.format('width-format', {})
}

展示下效果:

比較遺憾的是, 同樣作為 Block 格式, 這兩類(lèi)是不能兼容的, 也就是說(shuō)在 width-format 格式中, 不能使用 List , Header , Code 這幾項(xiàng)屬性
個(gè)人吐槽幾句, 之前嘗試兼容過(guò), 但是在 HTML 和 delta 相互轉(zhuǎn)換時(shí)被卡主了, 感覺(jué)轉(zhuǎn)換的方式?jīng)]做好

總結(jié)

demo鏈接: 點(diǎn)此查看

本文介紹了 quill.js 在面臨多種需求挑戰(zhàn)時(shí)需要添加可定制化功能。quill.js 的常用格式包括 Inline、Block 和 Embeds 三類(lèi),而ContainerBlot 則是創(chuàng)建 Block 類(lèi)型時(shí)常用的值,具有極高的自由度。希望本文能夠幫助讀者更好地了解和思考富文本編輯的相關(guān)問(wèn)題。

以上就是富文本編輯器quill.js開(kāi)發(fā)之自定義格式擴(kuò)展的詳細(xì)內(nèi)容,更多關(guān)于富文本編輯器quill.js格式擴(kuò)展的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論