Пользовательская функция форматированного текста Quill

JavaScript

предисловие

Расширенный текстовый контент предназначен для увеличения ввода стиля, Quill предоставляет способ определить API для изменения DOM, в отличие от многих других редакторов, которые делают это путем обхода дерева DOM. Поддержка Quill и возможности настройки, следующие за следующим, чтобы знатьКак использовать Quill и связанный с ним исходный код.

quickstart

Подобно Vue и React, Quill также требует наличия существующего элемента dom в качестве цели для монтирования экземпляра Quill.

Базовая конфигурация

Параметр 1: может быть dom, может быть селектором (автоматически преобразуется в соответствующий dom). Параметр 2: объект конфигурации, некоторые параметры конфигурации для форматированного текста.

var editor = new Quill('#editor', options);

объект конфигурации опций

var options = {
  modules: {
    toolbar: '#toolbar'
  },
  placeholder: 'Compose an epic...',
  theme: 'snow'
};

modules.toolbar

Используется для настройки панели инструментов
Способ 1: css-теги

var quill = new Quill('#editor', {
  modules: {
    // Equivalent to { toolbar: { container: '#toolbar' }}
    toolbar: '#toolbar'
  }
});

Способ 2: Объект

var quill = new Quill('#editor', {
  modules: {
    toolbar: {
        container:'#toolbar'
    }
  }
});

Способ 3: Массив

var quill = new Quill('#editor', {
  modules: {
    toolbar: ['bold', 'italic', 'underline', 'strike']
  }
});

Область редактирования и область панели инструментов

Как видно из этих конфигураций, весь форматированный текст разделен на: область редактирования и область панели инструментов.#editorЦель смонтированного элемента будет заменена частью поля ввода, и#toolbarЦель смонтированного элемента будет заменена на: Область панели инструментов.

var editor = new Quill('#editor', {
    modules: {
    toolbar: {
        container:'#toolbar'
    }
  }
});

Настройка

Благодаря приведенной выше простой конфигурации вы можете использовать некоторые основные функции редактора форматированного текста для настройки функции Quill.

Как реализованы собственные функции Quill

Функция шрифта реализуется в два этапа:

  1. Создайте экземпляр класса Parchment.Attributor.Class. В предыдущем введении мы знали, что существует базовый класс под атрибутом Parchment.Attributor.Attributorи три функциональных классаclass,style,store
  2. Мы создаем узел blots через класс, затем нам нужны зарегистрированные узлы.
// 步骤1
import Parchment from 'parchment';

let config = {
  scope: Parchment.Scope.INLINE,
  whitelist: ['serif', 'monospace']
};

let FontClass = new Parchment.Attributor.Class('font', 'ql-font', config);

export { FontClass };
// 步骤2 
import { FontClass } from './formats/font';
Quill.register({
'formats/font': FontClass,
},true)

Пользовательская функция высоты строки

Сам Quill не поддерживает функцию высоты строки, нам нужно настроить эту функцию, реализация функции имитации локального шрифта Quill выглядит следующим образом:демонстрация пользовательской высоты строки codepen

const config = {
        scope: Parchment.Scope.INLINE,
        whitelist: this.lineHeightList
}
const Parchment = Quill.import("parchment");
    // 步骤一实例化 lineHeight 类(自定义的)
    class lineHeightAttributor extends Parchment.Attributor.Class {}
    const lineHeightStyle = new lineHeightAttributor(
      "lineHeight",
      "ql-lineHeight",
       config
    );
    // 注册实例
    Quill.register({ "formats/lineHeight": lineHeightStyle }, true);

После регистрации экземпляра, как использовать функцию высоты строки?

  1. Поскольку мы наследуемParchment.Attributor.ClassОн будет управлять стилем узла, манипулируя именем класса узла, поэтому его можно настроить с помощьюql-lineHeightкласс для управления选中文本的行高
<div id="app">
  <div id="toolbar">
    <select class="ql-lineHeight">
        // 通过 selected 来设置默认选中的行高样式
        <option v-for="(lineHeight,index) in lineHeightList" :key="lineHeight" :value="lineHeight" :selected="index === 3">{{ lineHeight }}</option>
      </select>
  </div>
  <div id="container"></div>
</div>
  1. Поскольку структура DOM определила определение будущего, нам нужно настроить эти стили высокой соответствующей строки,

Глядя на консольные элементы, вы можете видеть, что Quill создаст раскрывающийся список с пользовательским стилем в соответствии с установленным нами выбором, например, стиль высоты строки 2.0, мы можем установить следующий стиль css

// 该类名对应的行高
.ql-lineHeight-2 {
  line-height: 2;
}
// 设置 option 下拉框中选项的样式
.ql-picker.ql-lineHeight .ql-picker-label[data-value="2"]::before,
// 设置 选中 option 后显示的样式
.ql-picker.ql-lineHeight .ql-picker-item[data-value="2"]::before {
  content: "2";
}

пользовательский размер шрифта

  • Тип подэлемента белого списка должен быть типа String, иначе соответствующее имя класса не будет применено после выбора подэлемента.
data(){
    return {
        // whitelist 子项类型必须为 String 类型,否则会导致选中子项后没有应用上对应都类名
        sizeList:Array.from(Array(58),(item,index)=>String(index+12)),
    }
}
const Parchment = Quill.import("parchment")
      class Font extends Parchment.Attributor.Class{}
      const FontStyle = new Font('size','ql-size',{
        scope:Parchment.Scope.INLINE,
        whitelist:this.sizeList
      })
      Quill.register({
        'formats/size':FontStyle
      },true)
// 使用 less 中 range 和 each 方法来减少重复 css 代码
@list:range(11,70,1);
each(@list,{
  .ql-size-@{value}{
    font-size:@value*1px;
  }
})

Более подробный код в демо можноНажмите, чтобы просмотреть

мероприятие

событие изменения текста

this.quill.on('text-change',function(delta,oldDelta,source){
    console.log('delata',delta,oldDelta,source);
})

  • Вы можете видеть, что объект дельта-данных содержит:

    1. сохранить: сколько цифр данных сохранить перед
    2. вставка: вставленные данные
  • источник указывает источник триггера события, если он инициирован пользователем, этоuserЕсли операция APIapi

другие события

тип события: можно пройтиon(name: String, handler: Function): Quillquill.on() для регистрации событий, таких как:text-change,editor-change
Добавить пользовательские события:

// 获取到 toolbar 操作对象
let toolbar = quill.getModule('toolbar');
toolbar.addHandler('image', ()=>{
      // 添加点击图片触发的逻辑
    });

Посмотреть больше событий

Исходный код Quill

Узнайте, как использовать Quill из фонда и как настроить некоторые функции, а затем просмотреть структуру исходного кода Quill.

Конструктор

путь/core/quill.js, Вы можете видеть, что многие статические методы и методы-прототипы монтируются в конструкторе, чтобы понять реализацию некоторых часто используемых методов.

class Quill {
    static debug(limit) {
    if (limit === true) {
      limit = 'log';
    }
    logger.level(limit);
  }

  static find(node) {
    return node.__quill || Parchment.find(node);
  }

  static import(name) {
    if (this.imports[name] == null) {
      debug.error(`Cannot import ${name}. Are you sure it was registered?`);
    }
    return this.imports[name];
  }

  static register(path, target, overwrite = false) {
  }
  // ...
}

Quill.import

Например, мы можем пройтиQuill.importдля вызова статического методаimport, выполнитьthis.imports[name]Следующие четыре модуля по умолчанию монтируются в объекте Quill.imports.

Quill.imports = {
  'delta'       : Delta,
  'parchment'   : Parchment,
  'core/module' : Module,
  'core/theme'  : Theme
};

Quill.register

Если вы хотите добавить дополнительные модули, вы можете пройтиregisterспособ регистрации новых путей и соответствующих модулей, Когда мы используем код, обычно выглядит следующим образом

class LinkBlot extends Inline {}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';
Quill.register(LinkBlot);

Давайте посмотрим на выполнение в исходном коде Quill, порядок выполнения в следующих методах

  1. this.register('formats/' + 'link', LinkBlot, undefined);
  2. this.imports['formats/link'] = LinkBlot
  3. Наконец-то зарегистрировал модуль через Parchment.register(LinkBlot)
class Quill{
    static register(path, target, overwrite = false) {
        if (typeof path !== 'string') {
          let name = path.attrName || path.blotName;
          if (typeof name === 'string') {
            // register(Blot | Attributor, overwrite)
            this.register('formats/' + name, path, target);
          } else {
            Object.keys(path).forEach((key) => {
              this.register(key, path[key], target);
            });
          }
        }else {
          if (this.imports[path] != null && !overwrite) {
            debug.warn(`Overwriting ${path} with`, target);
          }
          this.imports[path] = target;
          if ((path.startsWith('blots/') || path.startsWith('formats/')) &&
              target.blotName !== 'abstract') {
            Parchment.register(target);
          } else if (path.startsWith('modules') && typeof target.register === 'function') {
            target.register();
          }
        }
    }
 }

Внутренняя реализация функции шрифта Quill

Делится на два этапа:

  1. Создайте экземпляр класса Parchment.Attributor.Class. В предыдущем введении мы знали, что существует базовый класс под атрибутом Parchment.Attributor.Attributorи три функциональных классаclass,style,store
  2. Зарегистрируйте экземпляр с помощью Quill.register()
import Parchment from 'parchment';

let config = {
  scope: Parchment.Scope.INLINE,
  whitelist: ['serif', 'monospace']
};

let FontClass = new Parchment.Attributor.Class('font', 'ql-font', config);

export { FontClass };
  1. Сначала создайте экземпляр конструктора Parchment.Attributor.Class, показанного ранее.src/attributor/class.tsЧтобы создать экземпляр и изменить класс, нужно выполнить конструктор конструктора.
class ClassAttributor extends Attributor {}
  1. Поскольку класс класса наследуется от Attributor, см.src/attributor/attributor.ts
export default class Attributor {
  constructor(attrName: string, keyName: string, options: AttributorOptions = {}) {}
 }

Итак, мы выполняемlet FontClass = new Parchment.Attributor.Class('font', 'ql-font', config);Когда вы хотите передать эти три параметра в конструктор.
3. Что делает эта строка кода

  constructor(attrName: string, keyName: string, options: AttributorOptions = {}) {
    // 记录属性名和类名
    this.attrName = attrName;
    this.keyName = keyName;
    let attributeBit = Registry.Scope.TYPE & Registry.Scope.ATTRIBUTE;
    // 判断是否定义 scope 属性
    if (options.scope != null) {
      // 由于它是 scope 默认是通过二进制来表示的,所以,这里采用位运算来判断
      this.scope = (options.scope & Registry.Scope.LEVEL) | attributeBit;
    } else {
      // 如果没有设置scope,则默认使用 ATTRIBUTE 二进制
      this.scope = Registry.Scope.ATTRIBUTE;
    }
    if (options.whitelist != null) this.whitelist = options.whitelist;
  }

Вы можете просмотреть путь к исходному коду реестра.tssrc/registry.tsНеобязательные значения, определяющие область действия, следующие: область действия определяет, является ли тип блота встроенным или блочным элементом.О том, какие операции являются встроенными, блочными, встраивания могут щелкнуть здесь, чтобы просмотреть

export enum Scope {
 TYPE = (1 << 2) - 1, // 0011 Lower two bits
 LEVEL = ((1 << 2) - 1) << 2, // 1100 Higher two bits

 ATTRIBUTE = (1 << 0) | LEVEL, // 1101
 BLOT = (1 << 1) | LEVEL, // 1110
 INLINE = (1 << 2) | TYPE, // 0111
 BLOCK = (1 << 3) | TYPE, // 1011

 BLOCK_BLOT = BLOCK & BLOT, // 1010
 INLINE_BLOT = INLINE & BLOT, // 0110
 BLOCK_ATTRIBUTE = BLOCK & ATTRIBUTE, // 1001
 INLINE_ATTRIBUTE = INLINE & ATTRIBUTE, // 0101

 ANY = TYPE | LEVEL,
}

Quill другие методы API

Нажмите, чтобы просмотреть официальный документЭти методы могут бытьcore/quill.jsсм. в документации, чтобы увидеть, как это реализовано

Пергаменты и дельты

Управление моделями документов и описание форматированного текстового содержимого в Quill основано на Parchment и Delta, соответственно, на обоих,Quill才能够通过 API 来操作富文本样式,定制化和扩展富文本功能。Эти две функции следующие:

  • Parchment использует Blots вместо dom для описания документов. Основная функция Parchment — управлять моделью документа. Мы можем инициализировать DOM через предоставляемый им интерфейс, возвращать указанный формат или указывать метку и область действия и так далее.
  • Создавая экземпляр delta, мы перейдем в参数配置项挂载到 ops, и многие доступные методы смонтированы на этом прототипе экземпляра для работы с документом.

исходный код пергамента

основной файл

дорожка:src/Parchment.ts ,

let Parchment = {
  Scope: Registry.Scope,
  create: Registry.create,
  register: Registry.register,

  Container: ContainerBlot,
  Format: FormatBlot,
  Embed: EmbedBlot,

  Scroll: ScrollBlot,
  Block: BlockBlot,
  Inline: InlineBlot,
  Text: TextBlot,
  Attributor: {
    Attribute: Attributor,
    Class: ClassAttributor,
    Style: StyleAttributor,
    Store: AttributorStore,
  },
}

Просмотр дополнительных свойств параметра

модуль управления стилем

Папка атрибутов содержит некоторые методы для установки атрибутов узла. Путь:src/Parchment.tsОбъект, предоставляемый файлом, содержит все методы, предоставляемые пергаментом.

import Attributor from './attributor/attributor';
import ClassAttributor from './attributor/class';
import StyleAttributor from './attributor/style';
import AttributorStore from './attributor/store';
let Parchment = {
    Attributor: {
        Attribute: Attributor,
        Class: ClassAttributor,
        Style: StyleAttributor,
        Store: AttributorStore,
    },
}

В качестве примера стиля вы можете увидеть, как Attributor реализует управление стилями узлов.

class StyleAttributor extends Attributor {
  add(node: HTMLElement, value: string): boolean {
    if (!this.canAdd(node, value)) return false;
    // @ts-ignore
    node.style[camelize(this.keyName)] = value;
    return true;
  }
  remove(node: HTMLElement): void {
    // @ts-ignore
    node.style[camelize(this.keyName)] = '';
    if (!node.getAttribute('style')) {
      node.removeAttribute('style');
    }
  }
  value(node: HTMLElement): string {
    // @ts-ignore
    let value = node.style[camelize(this.keyName)];
    return this.canAdd(node, value) ? value : '';
  }
}

Способ реализации на самом деле очень прост, т. е. черезelement.style.color = '#f00'Такие форматы стилизуют элемент.

  • class.tsиспользуется для установки класса
  • store.tsФормат, используемый для установки стиля записи атрибута, следующий: атрибуты записывают стиль. в следующей структуреattributesИспользуется для управления изменениями свойств
{
  ops: [
    { insert: 'Gandalf', attributes: { bold: true } },
    { insert: 'Grey', attributes: { color: '#cccccc' } }
  ]
}
  • attributor.tsЭквивалентный базовому классу трех других классов, он определяет некоторые общедоступные свойства и методы.
export default class Attributor {
  attrName: string;
  keyName: string;
  scope: Registry.Scope;
  whitelist: string[] | undefined;
 }

Модуль управления элементами

Путь:src/blot/abstractСтруктура папки следующая

В этих файлах определены многие основные общие методы, такие какformat.tsНекоторые методы форматирования определены в файле следующим образом

class FormatBlot extends ContainerBlot implements Formattable {
    format(){}
    formats(){}
    replaceWith(){}
    update(){}
    wrap(){}
}

Затем в пути какsrc/blot/blot.tsВведено использование файла format.ts, а повторное использование логики в FormatBlot реализовано через наследование.

import FormatBlot from './abstract/format';
class BlockBlot extends FormatBlot {
  static blotName = 'block';
  static scope = Registry.Scope.BLOCK_BLOT;
  static tagName = 'P';
  format(name: string, value: any) {
    if (Registry.query(name, Registry.Scope.BLOCK) == null) {
      return;
    } else if (name === this.statics.blotName && !value) {
      this.replaceWith(BlockBlot.blotName);
    } else {
      super.format(name, value);
    }
  }
}

Blots

Пергамент заменяет dom для описания документов. Кляксы эквивалентны элементам. Ниже приводится определение клякс.

// 拥有以下字段和静态方法,以及这些属性的类型
class Blot {
  static blotName: string;
  static className: string;
  static tagName: string;
  //  inline or block
  static scope: Scope;
  domNode: Node;
  prev: Blot;
  next: Blot;
  parent: Blot;
  // Creates corresponding DOM node
  static create(value?: any): Node;
  // Apply format to blot. Should not pass onto child or other blot.
  format(format: name, value: any);
  insertAt(index: number, text: string);
  // ... 
}

Deltas

var delta = new Delta([
  { insert: 'Gandalf', attributes: { bold: true } }
]);

При создании Delta, элементы конфигурации параметров, которые мы передаем, будут установлены под OPS, и многие доступные методы установлены на этом прототипе этого экземпляра.

генерировать текст

var delta = new Delta([
  { insert: 'Gandalf', attributes: { bold: true } },
  { insert: ' the ' },
  { insert: 'Grey', attributes: { color: '#ccc' } }
]);

Дельта — это простой формат JSON, используемый для описания расширенного текстового содержимого. В приведенном выше примере показано, чтоGandalf the Greyстрока иGandalf 字样为 bold , Grey 的字体颜色为 #ccc

API изменить текст

Выше мы модифицируем документ через API, предоставленный Deltas:

var death = new Delta().retain(12).delete(4).insert('White', { color: '#fff' });

JSON, описывающее вышеуказанное поведение, заключается в следующем, Delta сохраняет первые 12 битов исходной строки, удаляет последние четыре бита на вершине этого и вставляетWhiteСтройный стиль шрифта белый

{
  ops: [
    { retain: 12 },
    { delete: 4 },
    { insert: 'White', attributes: { color: '#fff' } }
  ]
}
Категории