CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-quill

A modern, powerful rich text editor built for compatibility and extensibility

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

formatting-system.mddocs/

Formatting System

Comprehensive formatting system supporting inline formats (bold, italic, links), block formats (headers, lists, blockquotes), embed formats (images, videos), and custom attributors for style-based formatting. Quill's formatting system is built on Parchment blots that map directly to DOM elements.

Capabilities

Inline Formats

Text-level formatting that can be applied to character ranges within a line.

// Bold formatting
class Bold extends Inline {
  static blotName: 'bold';
  static tagName: ['STRONG', 'B'];
}

// Italic formatting  
class Italic extends Inline {
  static blotName: 'italic';
  static tagName: ['EM', 'I'];
}

// Underline formatting
class Underline extends Inline {
  static blotName: 'underline';
  static tagName: 'U';
}

// Strikethrough formatting
class Strike extends Inline {
  static blotName: 'strike';
  static tagName: ['S', 'STRIKE'];
}

// Inline code formatting
class Code extends Inline {
  static blotName: 'code';
  static tagName: 'CODE';
}

// Superscript/subscript formatting
class Script extends Inline {
  static blotName: 'script';
  static tagName: ['SUB', 'SUP'];
  static create(value: 'sub' | 'super'): HTMLElement;
  static formats(domNode: HTMLElement): string;
}

Usage Examples:

// Apply inline formatting
quill.formatText(0, 5, 'bold', true);
quill.formatText(6, 4, 'italic', true);
quill.formatText(11, 3, 'underline', true);
quill.formatText(15, 6, 'strike', true);
quill.formatText(22, 4, 'code', true);
quill.formatText(27, 4, 'script', 'super');

// Multiple formats at once
quill.formatText(0, 10, {
  bold: true,
  italic: true,
  color: '#ff0000'
});

// Remove formatting
quill.formatText(0, 10, {
  bold: false,
  italic: null  // null also removes formatting
});

Link Format

Hyperlink formatting with URL validation and sanitization.

class Link extends Inline {
  static blotName: 'link';
  static tagName: 'A';
  static SANITIZED_URL: 'about:blank';
  static PROTOCOL_WHITELIST: ['http', 'https', 'mailto', 'tel', 'sms'];
  
  /**
   * Create link element with href attribute
   * @param value - URL for the link
   * @returns HTMLAnchorElement with href set
   */
  static create(value: string): HTMLAnchorElement;
  
  /**
   * Get href value from link element
   * @param domNode - Link element
   * @returns URL from href attribute
   */
  static formats(domNode: HTMLAnchorElement): string;
  
  /**
   * Sanitize URL to prevent XSS attacks
   * @param url - URL to sanitize
   * @returns Sanitized URL or fallback URL
   */
  static sanitize(url: string): string;
  
  /**
   * Format this link blot
   * @param name - Format name ('link')
   * @param value - URL value or false to remove link
   */
  format(name: string, value: string | boolean): void;
}

/**
 * Standalone URL sanitization function
 * @param url - URL to sanitize
 * @param protocols - Allowed protocols (default: Link.PROTOCOL_WHITELIST)
 * @returns Sanitized URL
 */
function sanitize(url: string, protocols?: string[]): string;

Usage Examples:

// Create links
quill.formatText(0, 5, 'link', 'https://example.com');
quill.formatText(10, 8, 'link', 'mailto:user@example.com');
quill.formatText(20, 12, 'link', 'tel:+1234567890');

// Get current link value
const formats = quill.getFormat(5, 0);
if (formats.link) {
  console.log('Link URL:', formats.link);
}

// Remove link
quill.formatText(0, 5, 'link', false);

// Insert link with text
quill.insertText(0, 'Click here', { link: 'https://example.com' });

Block Formats

Line-level formatting that affects entire lines or paragraphs.

// Header formatting (H1-H6)
class Header extends Block {
  static blotName: 'header';
  static tagName: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
  
  /**
   * Get header level from DOM element
   * @param domNode - Header element
   * @returns Header level (1-6)
   */
  static formats(domNode: HTMLElement): number;
  
  /**
   * Create header element for specified level
   * @param value - Header level (1-6)
   * @returns Header element
   */
  static create(value: number): HTMLElement;
}

// Blockquote formatting
class Blockquote extends Block {
  static blotName: 'blockquote';
  static tagName: 'blockquote';
}

// Code block formatting
class CodeBlock extends Block {
  static blotName: 'code-block';
  static tagName: 'DIV';
  static className: 'ql-code-block';
  static TAB: string;
  
  /**
   * Handle tab key in code blocks
   * @param range - Current selection range
   * @param context - Keyboard context
   */
  static handleTab(range: Range, context: any): boolean;
}

Usage Examples:

// Apply block formatting
quill.formatLine(0, 1, 'header', 1);    // H1
quill.formatLine(20, 1, 'header', 2);   // H2
quill.formatLine(40, 1, 'blockquote', true);
quill.formatLine(60, 1, 'code-block', true);

// Multiple lines
quill.formatLine(0, 50, 'header', 3);   // Format multiple lines as H3

// Remove block formatting
quill.formatLine(0, 1, 'header', false);
quill.formatLine(20, 1, 'blockquote', false);

List Formats

Ordered and unordered list formatting with nested list support.

// List item formatting
class ListItem extends Block {
  static blotName: 'list';
  static tagName: 'LI';
  
  /**
   * Create list item element
   * @param value - List type ('ordered' | 'bullet')
   * @returns List item element
   */
  static create(value: 'ordered' | 'bullet'): HTMLLIElement;
  
  /**
   * Get list type from DOM element
   * @param domNode - List item element
   * @returns List type string
   */
  static formats(domNode: HTMLLIElement): string;
  
  /**
   * Register list container with Parchment
   */
  static register(): void;
  
  /**
   * Format this list item
   * @param name - Format name
   * @param value - Format value
   */
  format(name: string, value: any): void;
}

// List container formatting
class ListContainer extends Container {
  static blotName: 'list-container';
  static tagName: 'OL';
  
  /**
   * Create list container element
   * @param value - List type
   * @returns List container element
   */
  static create(value: string): HTMLOListElement | HTMLUListElement;
}

Usage Examples:

// Create bullet list
quill.formatLine(0, 1, 'list', 'bullet');
quill.formatLine(20, 1, 'list', 'bullet');
quill.formatLine(40, 1, 'list', 'bullet');

// Create ordered list
quill.formatLine(0, 1, 'list', 'ordered');
quill.formatLine(20, 1, 'list', 'ordered');
quill.formatLine(40, 1, 'list', 'ordered');

// Remove list formatting
quill.formatLine(0, 1, 'list', false);

// Insert list items
quill.insertText(0, 'First item\n', { list: 'bullet' });
quill.insertText(11, 'Second item\n', { list: 'bullet' });

Embed Formats

Self-contained elements like images, videos, and formulas.

// Image embed
class Image extends EmbedBlot {
  static blotName: 'image';
  static tagName: 'IMG';
  
  /**
   * Create image element
   * @param value - Image URL or configuration object
   * @returns Image element
   */
  static create(value: string | { src: string; alt?: string; [key: string]: any }): HTMLImageElement;
  
  /**
   * Get image attributes
   * @param domNode - Image element
   * @returns Object with image attributes
   */
  static formats(domNode: HTMLImageElement): Record<string, string>;
  
  /**
   * Check if URL is valid image
   * @param url - URL to check
   * @returns True if URL appears to be an image
   */
  static match(url: string): boolean;
  
  /**
   * Sanitize image URL
   * @param url - URL to sanitize
   * @returns Sanitized URL
   */
  static sanitize(url: string): string;
  
  /**
   * Get image source URL
   * @param domNode - Image element
   * @returns Image source URL
   */
  static value(domNode: HTMLImageElement): string;
  
  /**
   * Format image attributes
   * @param name - Attribute name
   * @param value - Attribute value
   */
  format(name: string, value: any): void;
}

// Video embed
class Video extends BlockEmbed {
  static blotName: 'video';
  static tagName: 'IFRAME';
  static ATTRIBUTES: string[];
  
  /**
   * Create video iframe element
   * @param value - Video URL
   * @returns Iframe element
   */
  static create(value: string): HTMLIFrameElement;
  
  /**
   * Get video URL from iframe
   * @param domNode - Iframe element
   * @returns Video URL
   */
  static formats(domNode: HTMLIFrameElement): string;
  
  /**
   * Sanitize video URL
   * @param url - URL to sanitize
   * @returns Sanitized URL
   */
  static sanitize(url: string): string;
  
  /**
   * Get video URL value
   * @param domNode - Iframe element
   * @returns Video URL
   */
  static value(domNode: HTMLIFrameElement): string;
}

// Formula embed (for mathematical expressions)
class Formula extends EmbedBlot {
  static blotName: 'formula';
  static tagName: 'SPAN';
  static className: 'ql-formula';
  
  /**
   * Create formula element
   * @param value - LaTeX formula string
   * @returns Span element with formula
   */
  static create(value: string): HTMLSpanElement;
  
  /**
   * Get formula value
   * @param domNode - Formula element
   * @returns LaTeX formula string
   */
  static value(domNode: HTMLSpanElement): string;
  
  /**
   * Render formula (requires KaTeX or similar)
   * @param value - LaTeX formula
   * @returns Rendered formula HTML
   */
  html(): string;
}

Usage Examples:

// Insert image
quill.insertEmbed(0, 'image', 'https://example.com/image.jpg');

// Insert image with attributes
quill.insertEmbed(0, 'image', {
  src: 'https://example.com/image.jpg',
  alt: 'Example image',
  width: '300'
});

// Insert video
quill.insertEmbed(10, 'video', 'https://youtube.com/watch?v=abc123');

// Insert formula
quill.insertEmbed(20, 'formula', 'E = mc^2');

// Get embed values
const delta = quill.getContents();
delta.ops.forEach(op => {
  if (op.insert && typeof op.insert === 'object') {
    if (op.insert.image) {
      console.log('Image URL:', op.insert.image);
    } else if (op.insert.video) {
      console.log('Video URL:', op.insert.video);
    } else if (op.insert.formula) {
      console.log('Formula:', op.insert.formula);
    }
  }
});

Attributor-Based Formats

Style-based formatting using CSS classes or inline styles.

// Color attributor (style-based)
class ColorStyle extends StyleAttributor {
  static attrName: 'color';
  static keyName: 'color';
  static styleName: 'color';
}

// Color attributor (class-based)
class ColorClass extends ClassAttributor {
  static attrName: 'color';
  static keyName: 'color';
  static className: 'ql-color';
}

// Font attributor
class FontClass extends ClassAttributor {
  static attrName: 'font';
  static keyName: 'font';
  static className: 'ql-font';
}

class FontStyle extends StyleAttributor {
  static attrName: 'font';
  static keyName: 'font';
  static styleName: 'font-family';
}

// Size attributor
class SizeClass extends ClassAttributor {
  static attrName: 'size';
  static keyName: 'size';
  static className: 'ql-size';
}

class SizeStyle extends StyleAttributor {
  static attrName: 'size';
  static keyName: 'size';
  static styleName: 'font-size';
}

// Background attributor
class BackgroundClass extends ClassAttributor {
  static attrName: 'background';
  static keyName: 'background';
  static className: 'ql-bg';
}

class BackgroundStyle extends StyleAttributor {
  static attrName: 'background';
  static keyName: 'background';
  static styleName: 'background-color';
}

// Alignment attributor
class AlignClass extends ClassAttributor {
  static attrName: 'align';
  static keyName: 'align';
  static className: 'ql-align';
}

class AlignStyle extends StyleAttributor {
  static attrName: 'align';
  static keyName: 'align';
  static styleName: 'text-align';
}

// Direction attributor
class DirectionAttribute extends AttributeAttributor {
  static attrName: 'direction';
  static keyName: 'direction';
  static htmlAttribute: 'dir';
}

class DirectionClass extends ClassAttributor {
  static attrName: 'direction';
  static keyName: 'direction';
  static className: 'ql-direction';
}

class DirectionStyle extends StyleAttributor {
  static attrName: 'direction';
  static keyName: 'direction';
  static styleName: 'direction';
}

// Indent attributor
class Indent extends ClassAttributor {
  static attrName: 'indent';
  static keyName: 'indent';
  static className: 'ql-indent';
  
  /**
   * Add value to current indent level
   * @param node - DOM node
   * @param value - Indent level to add
   */
  add(node: HTMLElement, value: number): void;
  
  /**
   * Compute new indent level
   * @param value - Current indent level
   * @param delta - Change in indent level
   * @returns New indent level
   */
  canAdd(value: number, delta: number): boolean;
  
  /**
   * Remove indent level
   * @param node - DOM node
   */
  remove(node: HTMLElement): void;
  
  /**
   * Get current indent value
   * @param node - DOM node
   * @returns Current indent level
   */
  value(node: HTMLElement): number;
}

Usage Examples:

// Apply color formatting
quill.formatText(0, 10, 'color', '#ff0000');
quill.formatText(15, 5, 'background', '#ffff00');

// Apply font formatting
quill.formatText(0, 10, 'font', 'serif');
quill.formatText(15, 5, 'size', 'large');

// Apply alignment
quill.formatLine(0, 1, 'align', 'center');
quill.formatLine(20, 1, 'align', 'right');

// Apply direction
quill.formatLine(0, 1, 'direction', 'rtl');

// Apply indentation
quill.formatLine(0, 1, 'indent', 1);    // Indent once
quill.formatLine(20, 1, 'indent', 2);   // Indent twice

// Remove attributor formatting
quill.formatText(0, 10, 'color', false);
quill.formatLine(0, 1, 'align', false);

Custom Format Registration

Register custom formats and blots with Quill.

// Register custom formats
Quill.register('formats/highlight', HighlightBlot);
Quill.register('formats/mention', MentionBlot);

// Register multiple formats
Quill.register({
  'formats/highlight': HighlightBlot,
  'formats/mention': MentionBlot,
  'attributors/style/custom': CustomStyleAttributor
});

// Register with overwrite option
Quill.register('formats/bold', CustomBoldBlot, true);

Usage Examples:

// Define custom highlight format
class Highlight extends Inline {
  static blotName = 'highlight';
  static tagName = 'MARK';
  static className = 'ql-highlight';
  
  static create(value) {
    const node = super.create();
    node.setAttribute('data-color', value);
    node.style.backgroundColor = value;
    return node;
  }
  
  static formats(node) {
    return node.getAttribute('data-color');
  }
}

// Register and use
Quill.register('formats/highlight', Highlight);

const quill = new Quill('#editor', {
  formats: ['bold', 'italic', 'highlight']
});

// Use custom format
quill.formatText(0, 10, 'highlight', 'yellow');

Table Formats

Table formatting system for creating and managing tabular data with cells, rows, and full table structures.

Table Structure

Tables in Quill follow a hierarchical structure: TableContainer > TableBody > TableRow > TableCell.

TableCell

Individual table cell format for containing content within table rows.

class TableCell extends Block {
  static blotName: 'table';
  static tagName: 'TD';
  static create(value?: string): HTMLTableCellElement;
  static formats(domNode: HTMLElement): string | undefined;
  
  cellOffset(): number;
  format(name: string, value: string): void;
  row(): TableRow;
  rowOffset(): number;
  table(): TableContainer | null;
}

TableRow

Table row format for containing table cells in a horizontal layout.

class TableRow extends Container {
  static blotName: 'table-row';
  static tagName: 'TR';
  
  checkMerge(): boolean;
  optimize(context: { [key: string]: any }): void;
  rowOffset(): number;
  table(): TableContainer | null;
}

TableBody

Table body format for containing table rows within a table structure.

class TableBody extends Container {
  static blotName: 'table-body';
  static tagName: 'TBODY';
}

TableContainer

Main table container format providing table manipulation methods and structure management.

class TableContainer extends Container {
  static blotName: 'table-container';
  static tagName: 'TABLE';
  
  balanceCells(): void;
  cells(column: number): (TableCell | null)[];
  deleteColumn(index: number): void;
  insertColumn(index: number): void;
  insertRow(index: number): void;
  rows(): TableRow[];
}

Usage Examples:

import { TableCell, TableRow, TableBody, TableContainer } from 'quill';

// Tables are typically created through the module system
// rather than direct format application
const quill = new Quill('#editor', {
  modules: {
    table: true
  }
});

// Use table module methods for table manipulation
const tableModule = quill.getModule('table');
tableModule.insertTable(3, 3); // 3x3 table

Install with Tessl CLI

npx tessl i tessl/npm-quill

docs

delta-operations.md

editor-core.md

formatting-system.md

index.md

module-system.md

registry-system.md

theme-system.md

tile.json