or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-list.mdindex.mdlegacy-support.mdlist-commands.mdlist-formatting.mdlist-properties.mdtodolist.md
tile.json

legacy-support.mddocs/

Legacy Support

Backward compatibility implementations for legacy list functionality, enabling smooth migration from older CKEditor 5 list implementations.

Capabilities

Legacy List

Backward compatibility for the old list implementation.

/**
 * The legacy list feature.
 * This is a "glue" plugin that loads the legacy list editing feature and legacy list utils.
 */
class LegacyList extends Plugin {
  /** @inheritDoc */
  static get requires(): readonly [typeof LegacyListEditing, typeof LegacyListUtils];
  
  /** @inheritDoc */
  static get pluginName(): "LegacyList";
  
  /** @inheritDoc */
  static override get isOfficialPlugin(): true;
}

LegacyListEditing Plugin

Legacy editing functionality for lists.

/**
 * The editing part of the legacy list feature. It handles creating, editing and removing legacy lists and list items.
 */
class LegacyListEditing extends Plugin {
  /** @inheritDoc */
  static get pluginName(): "LegacyListEditing";
  
  /** @inheritDoc */
  static get requires(): readonly [typeof Enter, typeof Delete, typeof LegacyListUtils];
}

LegacyListUtils Plugin

Legacy utility functions for list operations.

/**
 * A set of helpers related to legacy document lists.
 */
class LegacyListUtils extends Plugin {
  /** @inheritDoc */
  static get pluginName(): "LegacyListUtils";
  
  /**
   * Checks whether the given list-style-type is supported by numbered or bulleted list.
   */
  getListTypeFromListStyleType(listStyleType: string): 'bulleted' | 'numbered' | null;
  
  /**
   * Creates a list item element.
   */
  createListElement(writer: Writer, indent: number, type: string, id?: string): Element;
  
  /**
   * Returns the model element which is the parent of the given list item.
   */
  getListItemParent(listItem: Element): Element | null;
}

Legacy Commands

LegacyIndentCommand

Legacy implementation of list indentation.

/**
 * The legacy list indent command. It is used by the legacy list indent feature.
 */
class LegacyIndentCommand extends Command {
  /**
   * @inheritDoc
   */
  constructor(editor: Editor, indentDirection: 'forward' | 'backward');
  
  /**
   * Executes the legacy list indent command.
   */
  execute(): void;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

LegacyListCommand

Legacy base command for list operations.

/**
 * The legacy list command. It is used by the legacy numbered list and bulleted list features.
 */
class LegacyListCommand extends Command {
  /**
   * The type of the list created by this command.
   */
  readonly type: 'numbered' | 'bulleted';
  
  /**
   * @inheritDoc
   */
  constructor(editor: Editor, type: 'numbered' | 'bulleted');
  
  /**
   * Executes the legacy list command.
   */
  execute(): void;
  
  /**
   * Checks the command's value.
   */
  value: boolean;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

Legacy List Properties

LegacyListProperties Plugin

Legacy implementation of list properties.

/**
 * The legacy list properties feature.
 */
class LegacyListProperties extends Plugin {
  /** @inheritDoc */
  static get requires(): readonly [typeof LegacyListPropertiesEditing];
  
  /** @inheritDoc */
  static get pluginName(): "LegacyListProperties";
  
  /** @inheritDoc */
  static override get isOfficialPlugin(): true;
}

LegacyListPropertiesEditing Plugin

Legacy editing functionality for list properties.

/**
 * The editing part of the legacy list properties feature.
 */
class LegacyListPropertiesEditing extends Plugin {
  /** @inheritDoc */
  static get pluginName(): "LegacyListPropertiesEditing";
  
  /** @inheritDoc */
  static get requires(): readonly [typeof LegacyListEditing];
}

Legacy List Properties Commands

Commands for legacy list property operations.

/**
 * The legacy list style command.
 */
class LegacyListStyleCommand extends Command {
  /**
   * @inheritDoc
   */
  constructor(editor: Editor);
  
  /**
   * Executes the legacy list style command.
   * @param options Command execution options
   * @param options.type The list style type to apply
   */
  execute(options?: { type?: string }): void;
  
  /**
   * The current value of the command.
   */
  value: string | false;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

/**
 * The legacy list start command.
 */
class LegacyListStartCommand extends Command {
  /**
   * @inheritDoc
   */
  constructor(editor: Editor);
  
  /**
   * Executes the legacy list start command.
   * @param options Command execution options
   * @param options.startIndex The starting index value
   */
  execute(options?: { startIndex?: number }): void;
  
  /**
   * The current value of the command.
   */
  value: number | false;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

/**
 * The legacy list reversed command.
 */
class LegacyListReversedCommand extends Command {
  /**
   * @inheritDoc
   */
  constructor(editor: Editor);
  
  /**
   * Executes the legacy list reversed command.
   * @param options Command execution options
   * @param options.reversed Whether the list should be reversed
   */
  execute(options?: { reversed?: boolean }): void;
  
  /**
   * The current value of the command.
   */
  value: boolean | false;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

Legacy TodoList

LegacyTodoList Plugin

Legacy implementation of to-do lists.

/**
 * The legacy to-do list feature.
 */
class LegacyTodoList extends Plugin {
  /** @inheritDoc */
  static get requires(): readonly [typeof LegacyTodoListEditing];
  
  /** @inheritDoc */
  static get pluginName(): "LegacyTodoList";
  
  /** @inheritDoc */
  static override get isOfficialPlugin(): true;
}

LegacyTodoListEditing Plugin

Legacy editing functionality for to-do lists.

/**
 * The editing part of the legacy to-do list feature.
 */
class LegacyTodoListEditing extends Plugin {
  /** @inheritDoc */
  static get pluginName(): "LegacyTodoListEditing";
  
  /** @inheritDoc */
  static get requires(): readonly [typeof LegacyListEditing];
}

LegacyCheckTodoListCommand

Legacy command for to-do list checkbox operations.

/**
 * The legacy check to-do list command.
 */
class LegacyCheckTodoListCommand extends Command {
  /**
   * @inheritDoc
   */
  constructor(editor: Editor);
  
  /**
   * Executes the legacy check to-do list command.
   * @param options Command execution options
   * @param options.forceValue Force the checked state to a specific value
   */
  execute(options?: { forceValue?: boolean }): void;
  
  /**
   * The current value of the command.
   */
  value: boolean | false;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

Usage Examples

Basic Legacy List Setup

import { ClassicEditor } from "@ckeditor/ckeditor5-editor-classic";
import { 
  LegacyList, 
  LegacyListProperties, 
  LegacyTodoList 
} from "@ckeditor/ckeditor5-list";

ClassicEditor
  .create(document.querySelector('#editor'), {
    plugins: [LegacyList, LegacyListProperties, LegacyTodoList],
    toolbar: ['numberedList', 'bulletedList', 'todoList']
  })
  .then(editor => {
    console.log('Editor with legacy list support initialized');
  });

Migration from Legacy to Modern

// Helper function to migrate from legacy to modern lists
async function migrateLegacyToModern(editorElement) {
  // First, initialize with legacy support to read existing content
  const legacyEditor = await ClassicEditor.create(editorElement, {
    plugins: [LegacyList, LegacyListProperties, LegacyTodoList]
  });
  
  // Extract the content
  const content = legacyEditor.getData();
  
  // Destroy legacy editor
  await legacyEditor.destroy();
  
  // Initialize with modern list implementation
  const modernEditor = await ClassicEditor.create(editorElement, {
    plugins: [List, ListProperties, TodoList],
    initialData: content
  });
  
  console.log('Successfully migrated from legacy to modern lists');
  return modernEditor;
}

// Usage
migrateLegacyToModern(document.querySelector('#editor'))
  .then(editor => {
    console.log('Migration complete');
  });

Gradual Migration Strategy

import { Plugin } from 'ckeditor5/src/core';

class LegacyMigrationPlugin extends Plugin {
  static get requires() {
    return [LegacyList, List]; // Support both implementations
  }
  
  init() {
    const editor = this.editor;
    
    // Add command to migrate specific lists
    editor.commands.add('migrateLegacyList', new MigrateLegacyListCommand(editor));
    
    // Add UI button for migration
    editor.ui.componentFactory.add('migrateLegacy', locale => {
      const button = new ButtonView(locale);
      
      button.set({
        label: 'Migrate Legacy Lists',
        icon: migrationIcon,
        tooltip: true
      });
      
      button.on('execute', () => {
        editor.execute('migrateLegacyList');
      });
      
      return button;
    });
  }
}

class MigrateLegacyListCommand extends Command {
  execute() {
    const model = this.editor.model;
    const legacyLists = this.findLegacyLists();
    
    model.change(writer => {
      legacyLists.forEach(legacyList => {
        this.convertLegacyListToModern(writer, legacyList);
      });
    });
    
    console.log(`Migrated ${legacyLists.length} legacy lists`);
  }
  
  findLegacyLists() {
    // Implementation to find legacy list structures
    const model = this.editor.model;
    const root = model.document.getRoot();
    const legacyLists = [];
    
    // Scan for legacy list patterns
    model.createRangeIn(root).getWalker().forEach(({ item }) => {
      if (this.isLegacyList(item)) {
        legacyLists.push(item);
      }
    });
    
    return legacyLists;
  }
  
  isLegacyList(element) {
    // Check for legacy list attributes/structure
    return element.is('element') && 
           element.hasAttribute('listType') && 
           element.hasAttribute('listIndent') && 
           !element.hasAttribute('listItemId'); // Modern lists have listItemId
  }
  
  convertLegacyListToModern(writer, legacyList) {
    // Convert legacy list structure to modern format
    const modernAttributes = this.translateLegacyAttributes(legacyList);
    
    // Remove legacy attributes
    const legacyAttrs = ['listType', 'listIndent', 'listStyle'];
    legacyAttrs.forEach(attr => {
      if (legacyList.hasAttribute(attr)) {
        writer.removeAttribute(attr, legacyList);
      }
    });
    
    // Add modern attributes
    Object.entries(modernAttributes).forEach(([key, value]) => {
      writer.setAttribute(key, value, legacyList);
    });
  }
  
  translateLegacyAttributes(legacyList) {
    const modern = {};
    
    // Translate list type
    if (legacyList.hasAttribute('listType')) {
      modern.listType = legacyList.getAttribute('listType');
    }
    
    // Generate unique ID for modern lists
    modern.listItemId = this.generateListItemId();
    
    // Translate indent level
    if (legacyList.hasAttribute('listIndent')) {
      modern.listIndent = legacyList.getAttribute('listIndent');
    }
    
    // Translate other legacy attributes as needed
    if (legacyList.hasAttribute('listStyle')) {
      modern.listStyle = legacyList.getAttribute('listStyle');
    }
    
    return modern;
  }
  
  generateListItemId() {
    return 'list-item-' + Math.random().toString(36).substr(2, 9);
  }
}

Compatibility Layer

// Plugin that provides compatibility between legacy and modern lists
class ListCompatibilityPlugin extends Plugin {
  static get requires() {
    return [LegacyList, List];
  }
  
  init() {
    const editor = this.editor;
    
    // Auto-detect and handle mixed content
    editor.model.document.on('change:data', () => {
      this.handleMixedListContent();
    });
    
    // Add compatibility commands
    this.addCompatibilityCommands();
  }
  
  handleMixedListContent() {
    const model = this.editor.model;
    const root = model.document.getRoot();
    
    let hasLegacy = false;
    let hasModern = false;
    
    // Detect mixed content
    model.createRangeIn(root).getWalker().forEach(({ item }) => {
      if (this.isLegacyList(item)) {
        hasLegacy = true;
      } else if (this.isModernList(item)) {
        hasModern = true;
      }
    });
    
    if (hasLegacy && hasModern) {
      console.warn('Mixed legacy and modern list content detected');
      this.showMigrationSuggestion();
    }
  }
  
  isLegacyList(element) {
    return element.is('element') && 
           element.hasAttribute('listType') && 
           !element.hasAttribute('listItemId');
  }
  
  isModernList(element) {
    return element.is('element') && 
           element.hasAttribute('listType') && 
           element.hasAttribute('listItemId');
  }
  
  showMigrationSuggestion() {
    // Show notification or UI prompt for migration
    const notification = this.editor.plugins.get('Notification');
    
    notification.showInfo(
      'Legacy list content detected. Consider migrating to modern lists for better performance.',
      {
        title: 'List Migration Available',
        namespace: 'listMigration'
      }
    );
  }
  
  addCompatibilityCommands() {
    const editor = this.editor;
    
    // Command to check compatibility
    editor.commands.add('checkListCompatibility', new CheckListCompatibilityCommand(editor));
    
    // Command to force migration
    editor.commands.add('forceListMigration', new ForceListMigrationCommand(editor));
  }
}

Testing Legacy Integration

// Test helper for legacy list functionality
class LegacyListTestHelper {
  constructor(editor) {
    this.editor = editor;
  }
  
  // Create legacy list structure for testing
  createLegacyList(items, type = 'bulleted') {
    const model = this.editor.model;
    
    model.change(writer => {
      const position = model.document.selection.getFirstPosition();
      
      items.forEach((itemText, index) => {
        const listItem = writer.createElement('paragraph', {
          listType: type,
          listIndent: 0
        });
        
        writer.insertText(itemText, writer.createPositionAt(listItem, 0));
        writer.insert(listItem, position.getShiftedBy(index));
      });
    });
  }
  
  // Verify legacy list structure
  verifyLegacyListStructure(expectedItems) {
    const model = this.editor.model;
    const root = model.document.getRoot();
    const actualItems = [];
    
    model.createRangeIn(root).getWalker().forEach(({ item, type }) => {
      if (type === 'elementStart' && this.isLegacyList(item)) {
        actualItems.push({
          text: this.getElementText(item),
          type: item.getAttribute('listType'),
          indent: item.getAttribute('listIndent')
        });
      }
    });
    
    return JSON.stringify(actualItems) === JSON.stringify(expectedItems);
  }
  
  isLegacyList(element) {
    return element.is('element') && 
           element.hasAttribute('listType') && 
           !element.hasAttribute('listItemId');
  }
  
  getElementText(element) {
    let text = '';
    for (const child of element.getChildren()) {
      if (child.is('$text')) {
        text += child.data;
      }
    }
    return text;
  }
}

// Usage in tests
const testHelper = new LegacyListTestHelper(editor);

// Create legacy list for testing
testHelper.createLegacyList(['Item 1', 'Item 2', 'Item 3'], 'numbered');

// Verify structure
const isValid = testHelper.verifyLegacyListStructure([
  { text: 'Item 1', type: 'numbered', indent: 0 },
  { text: 'Item 2', type: 'numbered', indent: 0 },
  { text: 'Item 3', type: 'numbered', indent: 0 }
]);

console.log('Legacy list structure valid:', isValid);