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

registry-system.mddocs/

Registry System

Dynamic registration system for formats, modules, themes, and blots enabling extensibility and customization. The registry system allows developers to register custom components, override existing ones, and import registered components at runtime.

Capabilities

Static Registration Methods

Methods on the Quill class for registering and importing components.

class Quill {
  /** Registry of imported components */
  static imports: Record<string, unknown>;
  
  /**
   * Register component with path and target
   * @param path - Registration path (e.g., 'formats/bold')
   * @param target - Component to register
   * @param overwrite - Whether to overwrite existing registration
   */
  static register(path: string, target: any, overwrite?: boolean): void;
  
  /**
   * Register multiple components from object
   * @param targets - Object with path->component mappings
   * @param overwrite - Whether to overwrite existing registrations
   */
  static register(targets: Record<string, any>, overwrite?: boolean): void;
  
  /**
   * Register single component (auto-detect path from blotName/attrName)
   * @param target - Component with blotName or attrName property
   * @param overwrite - Whether to overwrite existing registration
   */
  static register(target: RegistryDefinition, overwrite?: boolean): void;
  
  /**
   * Import registered component
   * @param name - Component path or name
   * @returns Imported component or undefined
   */
  static import(name: string): unknown;
  
  /**
   * Import core module
   * @param name - 'core/module'
   * @returns Module base class
   */
  static import(name: 'core/module'): typeof Module;
  
  /**
   * Import theme
   * @param name - Theme path (e.g., 'themes/snow')
   * @returns Theme class
   */
  static import(name: `themes/${string}`): typeof Theme;
  
  /**
   * Import Parchment
   * @param name - 'parchment'
   * @returns Parchment namespace
   */
  static import(name: 'parchment'): typeof Parchment;
  
  /**
   * Import Delta
   * @param name - 'delta'
   * @returns Delta class
   */
  static import(name: 'delta'): typeof Delta;
  
  /**
   * Find blot instance for DOM node
   * @param node - DOM node to find blot for
   * @param bubble - Whether to search up the DOM tree
   * @returns Blot instance or null
   */
  static find(node: Node, bubble?: boolean): Blot | null;
  
  /**
   * Set debug level for logging
   * @param level - Debug level or boolean
   */
  static debug(level: DebugLevel | boolean): void;
}

interface RegistryDefinition {
  blotName?: string;
  attrName?: string;
  [key: string]: any;
}

type DebugLevel = 'error' | 'warn' | 'log' | 'info';

Usage Examples:

import Quill from 'quill';

// Register single component with path
Quill.register('formats/highlight', HighlightBlot);

// Register multiple components
Quill.register({
  'formats/mention': MentionBlot,
  'modules/autoformat': AutoFormatModule,
  'themes/minimal': MinimalTheme
});

// Register with auto-detection (uses blotName)
class CustomBlot extends Inline {
  static blotName = 'custom';
}
Quill.register(CustomBlot); // Registers as 'formats/custom'

// Import registered components
const BoldFormat = Quill.import('formats/bold');
const ToolbarModule = Quill.import('modules/toolbar');
const SnowTheme = Quill.import('themes/snow');

// Import core components
const Module = Quill.import('core/module');
const Parchment = Quill.import('parchment');
const Delta = Quill.import('delta');

Registration Paths

Standard paths for registering different types of components.

// Format registration paths
'formats/bold'         // Bold inline format
'formats/italic'       // Italic inline format
'formats/header'       // Header block format
'formats/list'         // List block format
'formats/image'        // Image embed format
'formats/link'         // Link inline format

// Module registration paths
'modules/toolbar'      // Toolbar module
'modules/keyboard'     // Keyboard module
'modules/history'      // History module
'modules/clipboard'    // Clipboard module
'modules/uploader'     // Uploader module

// Theme registration paths
'themes/snow'          // Snow theme
'themes/bubble'        // Bubble theme

// Blot registration paths
'blots/inline'         // Inline blot
'blots/block'          // Block blot
'blots/embed'          // Embed blot
'blots/scroll'         // Scroll blot
'blots/container'      // Container blot

// Attributor registration paths
'attributors/attribute/direction'  // Attribute-based direction
'attributors/class/color'          // Class-based color
'attributors/style/font'           // Style-based font

// Core component paths
'core/module'          // Module base class
'core/theme'           // Theme base class
'parchment'           // Parchment system
'delta'               // Delta class

// UI component paths
'ui/picker'           // Picker component
'ui/tooltip'          // Tooltip component
'ui/icons'            // Icon definitions

Usage Examples:

// Register formats
Quill.register('formats/highlight', HighlightFormat);
Quill.register('formats/spoiler', SpoilerFormat);

// Register modules
Quill.register('modules/counter', WordCountModule);
Quill.register('modules/autosave', AutoSaveModule);

// Register themes
Quill.register('themes/dark', DarkTheme);
Quill.register('themes/minimal', MinimalTheme);

// Register attributors
Quill.register('attributors/style/line-height', LineHeightAttributor);

// Use registered components
const quill = new Quill('#editor', {
  theme: 'dark',
  formats: ['bold', 'italic', 'highlight', 'spoiler'],
  modules: {
    counter: { container: '#word-count' },
    autosave: { interval: 5000 }
  }
});

Custom Format Registration

Register custom text formats and blots.

// Custom inline format
class Highlight extends Inline {
  static blotName = 'highlight';
  static tagName = 'MARK';
  static className = 'ql-highlight';
  
  static create(value) {
    const node = super.create();
    if (value) {
      node.setAttribute('data-color', value);
      node.style.backgroundColor = value;
    }
    return node;
  }
  
  static formats(domNode) {
    return domNode.getAttribute('data-color') || true;
  }
  
  format(name, value) {
    if (name !== this.statics.blotName || !value) {
      super.format(name, value);
    } else {
      this.domNode.setAttribute('data-color', value);
      this.domNode.style.backgroundColor = value;
    }
  }
}

// Custom block format
class Alert extends Block {
  static blotName = 'alert';
  static tagName = 'DIV';
  static className = 'alert';
  
  static create(value) {
    const node = super.create();
    node.classList.add(`alert-${value || 'info'}`);
    return node;
  }
  
  static formats(domNode) {
    const classList = domNode.classList;
    for (const className of classList) {
      if (className.startsWith('alert-')) {
        return className.replace('alert-', '');
      }
    }
    return undefined;
  }
}

// Custom embed format
class Tweet extends BlockEmbed {
  static blotName = 'tweet';
  static tagName = 'DIV';
  static className = 'tweet-embed';
  
  static create(value) {
    const node = super.create();
    node.setAttribute('data-tweet-id', value);
    node.innerHTML = `<p>Loading tweet ${value}...</p>`;
    // Load tweet content asynchronously
    this.loadTweet(node, value);
    return node;
  }
  
  static value(domNode) {
    return domNode.getAttribute('data-tweet-id');
  }
  
  static loadTweet(node, tweetId) {
    // Implementation for loading tweet content
  }
}

Usage Examples:

// Register custom formats
Quill.register('formats/highlight', Highlight);
Quill.register('formats/alert', Alert);
Quill.register('formats/tweet', Tweet);

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

// Apply custom formatting
quill.formatText(0, 10, 'highlight', 'yellow');
quill.formatLine(20, 1, 'alert', 'warning');
quill.insertEmbed(30, 'tweet', '1234567890');

Custom Module Registration

Register custom modules to extend editor functionality.

// Custom module
class AutoSave extends Module {
  static DEFAULTS = {
    interval: 10000, // 10 seconds
    url: '/autosave',
    method: 'POST'
  };
  
  constructor(quill, options) {
    super(quill, options);
    this.timer = null;
    this.lastSaved = '';
    this.setupAutoSave();
  }
  
  setupAutoSave() {
    this.quill.on('text-change', () => {
      this.scheduleAutoSave();
    });
  }
  
  scheduleAutoSave() {
    if (this.timer) {
      clearTimeout(this.timer);
    }
    
    this.timer = setTimeout(() => {
      this.save();
    }, this.options.interval);
  }
  
  save() {
    const content = JSON.stringify(this.quill.getContents());
    if (content !== this.lastSaved) {
      fetch(this.options.url, {
        method: this.options.method,
        headers: { 'Content-Type': 'application/json' },
        body: content
      }).then(() => {
        this.lastSaved = content;
        console.log('Auto-saved');
      });
    }
  }
}

// Mention module
class Mentions extends Module {
  static DEFAULTS = {
    source: null,
    mentionDenotationChars: ['@'],
    showDenotationChar: true,
    allowedChars: /^[a-zA-Z0-9_]*$/,
    minChars: 0,
    maxChars: 31,
    offsetTop: 2,
    offsetLeft: 0
  };
  
  constructor(quill, options) {
    super(quill, options);
    this.setupMentions();
  }
  
  setupMentions() {
    this.quill.keyboard.addBinding({
      key: '@'
    }, this.handleMentionChar.bind(this));
  }
  
  handleMentionChar(range, context) {
    // Implementation for mention handling
    this.showMentionList(range);
  }
  
  showMentionList(range) {
    // Show mention dropdown
  }
}

Usage Examples:

// Register custom modules
Quill.register('modules/autosave', AutoSave);
Quill.register('modules/mentions', Mentions);

// Use custom modules
const quill = new Quill('#editor', {
  modules: {
    autosave: {
      interval: 5000,
      url: '/api/documents/123/autosave'
    },
    mentions: {
      source: (searchTerm, renderList) => {
        // Fetch mention suggestions
        fetch(`/api/users/search?q=${searchTerm}`)
          .then(response => response.json())
          .then(users => {
            renderList(users.map(user => ({
              id: user.id,
              value: user.username,
              label: user.displayName
            })));
          });
      }
    }
  }
});

Custom Theme Registration

Register custom themes with unique UI and behavior.

// Custom theme
class DarkTheme extends SnowTheme {
  static DEFAULTS = {
    ...SnowTheme.DEFAULTS,
    modules: {
      ...SnowTheme.DEFAULTS.modules,
      toolbar: [
        [{ 'header': [1, 2, 3, false] }],
        ['bold', 'italic', 'underline'],
        [{ 'color': [] }, { 'background': [] }],
        ['link', 'image'],
        ['clean']
      ]
    }
  };
  
  constructor(quill, options) {
    super(quill, options);
    this.applyDarkTheme();
  }
  
  applyDarkTheme() {
    this.quill.container.classList.add('ql-dark');
    this.addDarkStyles();
  }
  
  addDarkStyles() {
    // Add dark theme CSS
    const style = document.createElement('style');
    style.textContent = `
      .ql-dark .ql-editor {
        background: #2d2d2d;
        color: #ffffff;
      }
      .ql-dark .ql-toolbar {
        background: #1e1e1e;
        border-color: #444;
      }
    `;
    document.head.appendChild(style);
  }
}

// Minimal theme
class MinimalTheme extends Theme {
  static DEFAULTS = {
    modules: {}
  };
  
  constructor(quill, options) {
    super(quill, options);
    this.quill.container.classList.add('ql-minimal');
  }
  
  init() {
    // Minimal initialization
    this.quill.root.classList.add('ql-minimal-editor');
  }
}

Usage Examples:

// Register custom themes
Quill.register('themes/dark', DarkTheme);
Quill.register('themes/minimal', MinimalTheme);

// Use custom themes
const darkQuill = new Quill('#dark-editor', {
  theme: 'dark'
});

const minimalQuill = new Quill('#minimal-editor', {
  theme: 'minimal'
});

Registry Querying and Inspection

Methods for inspecting and querying the registry.

// Check if component is registered
const isRegistered = Quill.import('formats/custom') !== undefined;

// Get all registered formats
const formats = Object.keys(Quill.imports)
  .filter(key => key.startsWith('formats/'))
  .map(key => key.replace('formats/', ''));

// Get all registered modules
const modules = Object.keys(Quill.imports)
  .filter(key => key.startsWith('modules/'))
  .map(key => key.replace('modules/', ''));

// Get all registered themes
const themes = Object.keys(Quill.imports)
  .filter(key => key.startsWith('themes/'))
  .map(key => key.replace('themes/', ''));

Usage Examples:

// List available components
console.log('Available formats:', 
  Object.keys(Quill.imports)
    .filter(key => key.startsWith('formats/'))
    .map(key => key.replace('formats/', ''))
);

console.log('Available modules:', 
  Object.keys(Quill.imports)
    .filter(key => key.startsWith('modules/'))
    .map(key => key.replace('modules/', ''))
);

// Check if specific component exists
function hasFormat(formatName) {
  return Quill.import(`formats/${formatName}`) !== undefined;
}

function hasModule(moduleName) {
  return Quill.import(`modules/${moduleName}`) !== undefined;
}

// Dynamic component loading
function loadComponent(type, name) {
  const path = `${type}/${name}`;
  const component = Quill.import(path);
  
  if (!component) {
    throw new Error(`Component ${path} not found`);
  }
  
  return component;
}

// Usage
const BoldFormat = loadComponent('formats', 'bold');
const ToolbarModule = loadComponent('modules', 'toolbar');

Overriding Existing Components

Replace built-in components with custom implementations.

// Override built-in bold format
class CustomBold extends Inline {
  static blotName = 'bold';
  static tagName = 'STRONG';
  
  static create() {
    const node = super.create();
    node.setAttribute('data-custom', 'true');
    return node;
  }
}

// Override with overwrite flag
Quill.register('formats/bold', CustomBold, true);

// Override toolbar module
class CustomToolbar extends Toolbar {
  constructor(quill, options) {
    super(quill, options);
    this.addCustomFeatures();
  }
  
  addCustomFeatures() {
    // Add custom toolbar features
  }
}

Quill.register('modules/toolbar', CustomToolbar, true);

Usage Examples:

// Custom implementation of existing components
class EnhancedHistory extends History {
  constructor(quill, options) {
    super(quill, options);
    this.addHistoryPersistence();
  }
  
  addHistoryPersistence() {
    // Save history to localStorage
    this.quill.on('text-change', () => {
      localStorage.setItem('quill-history', JSON.stringify(this.stack));
    });
    
    // Restore history on load
    const saved = localStorage.getItem('quill-history');
    if (saved) {
      this.stack = JSON.parse(saved);
    }
  }
}

// Override existing history module
Quill.register('modules/history', EnhancedHistory, true);

// Now all editors use enhanced history
const quill = new Quill('#editor', {
  modules: {
    history: true // Uses EnhancedHistory
  }
});

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