preface
Rich text styles the input, and Quill provides an API for judging how to modify the DOM, unlike many other editors that traverse the DOM tree. And Quill supports customization, so let’s learn how to use Quill and its source code.
quickstart
Just like Vue and React, Quill requires an existing DOM element as the target to mount the Quill instance
Basic configuration
Parameter 1: can be DOM, can selectors (automatically to convert to the appropriate DOM). Parameter 2: Configuration object, some configuration options for rich text.
var editor = new Quill('#editor', options);
Copy the code
Options Configuration object
var options = {
modules: {
toolbar: '#toolbar'
},
placeholder: 'Compose an epic... ',
theme: 'snow'
};
Copy the code
modules.toolbar
Toolbar configuration Method 1: CSS labels
var quill = new Quill('#editor', {
modules: {
// Equivalent to { toolbar: { container: '#toolbar' }}
toolbar: '#toolbar'}});Copy the code
Method 2: Object
var quill = new Quill('#editor', {
modules: {
toolbar: {
container:'#toolbar'}}});Copy the code
Method 3: array
var quill = new Quill('#editor', {
modules: {
toolbar: ['bold'.'italic'.'underline'.'strike']}});Copy the code
Edit area and toolbar area
As you can see from these configurations, rich text is divided into an edit area and a toolbar area. The #editor mounted element target will be replaced with the part of the input box, and the #toolbar mounted element target will be replaced with the toolbar area.
var editor = new Quill('#editor', {
modules: {
toolbar: {
container:'#toolbar'}}});Copy the code
Customization function
With the simple configuration above, you can use some of the basic rich text editor features to customize the Quill functionality
How to implement the built-in features of Quill
Font function realization, divided into two steps:
- Instantiate the Parchment. Attributor. Class Class, mentioned earlier that Parchment. Attributor properties have a base Class
Attributor
And three function classesclass
.style
.store
- We instantiate a blots node through the class, and then we need registerd
// Step 1 Import Parchment from Parchment'parchment';
let config = {
scope: Parchment.Scope.INLINE,
whitelist: ['serif'.'monospace']};let FontClass = new Parchment.Attributor.Class('font'.'ql-font', config);
export{ FontClass }; // Step 2 import {FontClass} from'./formats/font';
Quill.register({
'formats/font': FontClass,
},true)
Copy the code
Custom line height function
Quill itself does not support the line height function, we need to customize this function, imitate quill native font function implementation as follows: codepen custom line height demo
const config = {
scope: Parchment.Scope.INLINE,
whitelist: this.lineHeightList
}
const Parchment = Quill.import("parchment"); / / step one instantiation lineHeight class (custom) class lineHeightAttributor extends Parchment. Attributor. Class {} const lineHeightStyle = new lineHeightAttributor("lineHeight"."ql-lineHeight", config ); // register instance quill.register ({"formats/lineHeight": lineHeightStyle }, true);
Copy the code
After registering an instance, how do I use the line height feature?
- Because we inherited
Parchment.Attributor.Class
It controls the style of the node by manipulating the node class name, so it can be customizedql-lineHeight
The class to controlSelect the line height of the text
<div id="app">
<div id="toolbar">
<select class="ql-lineHeight"> // Set the default line height with selected <option v-for="(lineHeight,index) in lineHeightList" :key="lineHeight" :value="lineHeight" :selected="index === 3">{{ lineHeight }}</option>
</select>
</div>
<div id="container"></div>
</div>
Copy the code
- Once we’ve defined our custom DOM structure, we need to customize the row heights for those styles,
Ql-lineheight-2 {line-height: 2; } // Set the style of the options in the option dropdown. Ql-picker. ql-lineheight. ql-picker-label[data-value="2"]::before, // Sets the style displayed after option is selected. Ql-picker.ql-lineheight. ql-picker-item[data-value="2"]::before {
content: "2";
}
Copy the code
Customize font size
- The type of the whitelist subitem must be String. Otherwise, the whitelist subitem will not have the corresponding class name applied
data() {returnSizeList: 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@list:range(11,70,1); @list:range(11,70,1); each(@list,{ .ql-size-@{value}{ font-size:@value*1px; }})Copy the code
For more details on the demo code, click here
The event
The text – change event
this.quill.on('text-change'.function(delta,oldDelta,source){
console.log('delata',delta,oldDelta,source);
})
Copy the code
-
You can see that the Delta data object contains:
- Retain: Retain the previous number of bits of data
- Insert: indicates the data to be inserted
-
Source indicates the event trigger source. If the event is triggered by a user, it is user. If the event is triggered by an API, it is API
Other events
Event types: You can register events with on(name: String, handler: Function): Quill quill.on(), for example, text-change,editor-change to add custom events:
// Get the Toolbar action objectlet toolbar = quill.getModule('toolbar');
toolbar.addHandler('image', ()=>{// add click on the picture trigger logic});Copy the code
More events view
Quill source
Now that you know the basics of how to use Quill and how to customize some of its features, take a look at the source structure of Quill
The constructor
The path is /core/quill.js. You can see that there are many static methods and prototype methods mounted on the constructor.
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{} //... }Copy the code
Quill.import
Imports [name] quill. imports, for example, can be used to call the static import method and perform the following four modules mounted by default on the this.imports[name] Quill
Quill.imports = {
'delta' : Delta,
'parchment' : Parchment,
'core/module' : Module,
'core/theme' : Theme
};
Copy the code
Quill.register
If you want to add additional modules, you can use the register method to register the new path and the corresponding module. The code we use is usually as follows
class LinkBlot extends Inline {}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';
Quill.register(LinkBlot);
Copy the code
Take a look at the Quill source code for the execution sequence in the following methods
this.register('formats/' + 'link', LinkBlot, undefined);
- this.imports[‘formats/link’] = LinkBlot
- Finally, the module was registered through 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(); }}}}Copy the code
Quill font function internal implementation
There are two steps:
- Instantiate the Parchment. Attributor. Class Class, mentioned earlier that Parchment. Attributor properties have a base Class
Attributor
And three function classesclass
.style
.store
- Use quill.register () to register the instance
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 };
Copy the code
- First instantiation Parchment. Attributor. Class constructor, view before
src/attributor/class.ts
Instantiating a class executes the constructor constructor
class ClassAttributor extends Attributor {}
Copy the code
- View it because the class class inherits from Attributor
src/attributor/attributor.ts
export default class Attributor {
constructor(attrName: string, keyName: string, options: AttributorOptions = {}) {}
}
Copy the code
So we perform the let FontClass = new Parchment. Attributor. Class (‘ font ‘, ‘ql – the font, the config); You want to pass these three arguments to the constructor. What does this line of code do
Constructor (attrName: string, keyName: string, options: AttributorOptions = {}) {// Record the name of the attribute and class. this.keyName = keyName;letattributeBit = Registry.Scope.TYPE & Registry.Scope.ATTRIBUTE; // Determine whether the scope attribute is definedif(options.scope ! = null) {/ / because it is the default scope is represented by binary, so the bit arithmetic is used to judge this. Here the scope = (options. The scope & Registry. Scope. LEVEL) | attributeBit; }else{// If scope is not set, the ATTRIBUTE binary this.scope = register.scope.attribute is used by default; }if(options.whitelist ! = null) this.whitelist = options.whitelist; }Copy the code
Ts defines the Scope as follows. Scope determines whether Blot is an inline or block-level element. For details on which operations are inline, block-level, Embeds, click here
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,
}
Copy the code
Quill other API methods
All of these methods are available in the core/quill.js file. How is it implemented
Parchment and Deltas
Manipulating document models and describing rich text content in Quill were Parchment and Delta, respectively, which enabled Quill to manipulate rich text styles, customize and extend rich text functionality through the API. Both functions are as follows:
- Parchment uses Blots instead of DOM to describe documents. Parchment is mainly used to manipulate document models and can be initialized to DOM via the interface provided by Parchment to return to specified formats, labels and scopes, etc.
- Delta will pass us in by instantiating
Parameter Configuration Item Mount to OPS
And the instance prototype has mounted a number of available methods to manipulate documents
Parchment source
Master file
Path: 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,
},
}
Copy the code
More parameter properties to view
Style management module
The Attributor folder lays down some node attributes. The SRC /Parchment. Ts file exposed objects that contained all the methods Parchment provided.
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,
},
}
Copy the code
For style, see how Attributor implements style management for nodes.
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 : ' '; }}Copy the code
The implementation is simple: style the element with element.style.color = ‘#f00’.
class.ts
Used to set classstore.ts
The format used to set the attribute record style is as follows, and attributes records the style. In the following structureattributes
Used to manipulate changes to properties
{
ops: [
{ insert: 'Gandalf', attributes: { bold: true } },
{ insert: 'Grey', attributes: { color: '#cccccc'}}}]Copy the code
attributor.ts
Equivalent to the base class of the other three classes, it defines some common properties and methods.
export default class Attributor {
attrName: string;
keyName: string;
scope: Registry.Scope;
whitelist: string[] | undefined;
}
Copy the code
Element Management module
The path is as follows: the structure of the SRC /blot/abstract folder is as follows
These files define many basic common methods respectively. For example, the format.ts file defines some formatting methods as follows
class FormatBlot extends ContainerBlot implements Formattable {
format() {}formats() {}replaceWith() {}update() {}wrap(){}
}
Copy the code
src/blot/blot.ts
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); }}}Copy the code
Blots
Parchment is used instead of DOM to describe documents and Blots are like elements. Here is the definition of Blots
Class Blot {static blotName: string; // Have the following fields and static methods, and the types of these properties. 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); / /... }Copy the code
Deltas
var delta = new Delta([
{ insert: 'Gandalf', attributes: { bold: true}}]);Copy the code
The generation of textual
var delta = new Delta([
{ insert: 'Gandalf', attributes: { bold: true } },
{ insert: ' the ' },
{ insert: 'Grey', attributes: { color: '#ccc'}}]);Copy the code
Delta is a simple JSON format used to describe rich text content, as shown in the example above: a string of Gandalf the Grey is generated with Gandalf bold and Grey’s font color # CCC
API modify text
Above we use the API provided by Deltas to modify the document:
var death = new Delta().retain(12).delete(4).insert('White', { color: '#fff' });
Copy the code
The JSON that describes the above behavior is as follows, with delta preserving the first 12 bits of the original string, deleting the last four bits above it, and then inserting the White string font style as White
{
ops: [
{ retain: 12 },
{ delete: 4 },
{ insert: 'White', attributes: { color: '#fff'}}}]Copy the code