Backward compatibility implementations for legacy list functionality, enabling smooth migration from older CKEditor 5 list implementations.
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;
}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];
}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 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;
}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 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;
}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];
}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 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;
}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];
}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;
}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');
});// 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');
});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);
}
}// 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));
}
}// 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);