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

todolist.mddocs/

TodoList

Interactive to-do list functionality with checkbox interactions, state management, and visual feedback for task completion tracking.

Capabilities

TodoList Plugin

Main plugin that provides complete to-do list functionality.

/**
 * The to-do list feature.
 * This is a "glue" plugin that loads the to-do list editing feature and to-do list UI feature.
 */
class TodoList extends Plugin {
  /** @inheritDoc */
  static get requires(): readonly [typeof TodoListEditing, typeof TodoListUI];
  
  /** @inheritDoc */
  static get pluginName(): "TodoList";
  
  /** @inheritDoc */
  static override get isOfficialPlugin(): true;
}

TodoListEditing Plugin

Core editing functionality for to-do lists including checkbox state management and interactions.

/**
 * The editing part of the to-do list feature. It handles creating, editing and removing to-do lists and to-do list items.
 */
class TodoListEditing extends Plugin {
  /** @inheritDoc */
  static get pluginName(): "TodoListEditing";
  
  /** @inheritDoc */
  static get requires(): readonly [typeof ListEditing, typeof TodoCheckboxChangeObserver];
}

TodoListUI Plugin

User interface for to-do lists including toolbar buttons and visual elements.

/**
 * The to-do list UI feature. It introduces the `'todoList'` button that allows to convert paragraphs
 * to and from to-do list items and to toggle the checked state of the to-do list items.
 */
class TodoListUI extends Plugin {
  /** @inheritDoc */
  static get pluginName(): "TodoListUI";
  
  /** @inheritDoc */
  static get requires(): readonly [typeof TodoListEditing];
}

Commands

CheckTodoListCommand

Command for toggling the checked state of to-do list items.

/**
 * The check to-do list command.
 * This command is used by the to-do list feature to check and uncheck to-do list items.
 */
class CheckTodoListCommand extends Command {
  /**
   * @inheritDoc
   */
  constructor(editor: Editor);
  
  /**
   * Executes the check to-do list command.
   * @param options Command execution options
   * @param options.forceValue Force the checked state to a specific value. If not provided, toggles the current state
   */
  execute(options?: { forceValue?: boolean }): void;
  
  /**
   * The current value of the command (whether the current to-do item is checked).
   */
  value: boolean | null;
  
  /**
   * Checks whether the command can be enabled in the current context.
   */
  isEnabled: boolean;
}

Events and Observers

ViewDocumentTodoCheckboxChangeEvent

Event fired when a to-do checkbox is clicked in the editing view.

import { type ViewDocumentDomEventData } from "ckeditor5/src/engine.js";

/**
 * Event fired when a to-do checkbox is clicked in the editing view.
 */
export type ViewDocumentTodoCheckboxChangeEvent = {
  name: 'todoCheckboxChange';
  args: [ data: ViewDocumentDomEventData<Event> ];
};

TodoCheckboxChangeObserver

Observer that monitors checkbox interactions in the editing view.

/**
 * Observer for the to-do checkbox change event.
 * It observes changes to the to-do list checkboxes in the editing view and fires the `todoCheckboxChange` event.
 */
class TodoCheckboxChangeObserver extends Observer {
  /**
   * @inheritDoc
   */
  constructor(view: View);
  
  /**
   * @inheritDoc
   */
  observe(domElement: Element): void;
  
  /**
   * @inheritDoc
   */
  stopObserving(domElement: Element): void;
}

Usage Examples

Basic TodoList Setup

import { ClassicEditor } from "@ckeditor/ckeditor5-editor-classic";
import { List, TodoList } from "@ckeditor/ckeditor5-list";

ClassicEditor
  .create(document.querySelector('#editor'), {
    plugins: [List, TodoList],
    toolbar: ['numberedList', 'bulletedList', 'todoList']
  })
  .then(editor => {
    console.log('Editor with TodoList initialized');
  });

Programmatic TodoList Control

// Get the to-do list command
const todoListCommand = editor.commands.get('todoList');
const checkTodoCommand = editor.commands.get('checkTodoList');

// Create a to-do list
if (todoListCommand.isEnabled) {
  todoListCommand.execute();
}

// Toggle checkbox state
if (checkTodoCommand.isEnabled) {
  checkTodoCommand.execute(); // Toggle current state
  
  // Or force a specific state
  checkTodoCommand.execute({ forceValue: true }); // Check
  checkTodoCommand.execute({ forceValue: false }); // Uncheck
}

// Check current state
console.log('Current to-do item checked:', checkTodoCommand.value);
console.log('In to-do list:', todoListCommand.value);

Handling TodoList Events

// Listen to checkbox change events
editor.editing.view.document.on('todoCheckboxChange', (evt, data) => {
  console.log('Checkbox clicked:', data.target);
  console.log('New checked state:', data.checked);
  
  // Perform custom actions based on checkbox changes
  if (data.checked) {
    console.log('Task completed!');
    // Could trigger animations, sounds, analytics, etc.
  } else {
    console.log('Task unchecked');
  }
});

// Listen to command state changes
const checkCommand = editor.commands.get('checkTodoList');
checkCommand.on('change:value', () => {
  console.log('Todo item checked state changed:', checkCommand.value);
});

checkCommand.on('change:isEnabled', () => {
  console.log('Check command enabled:', checkCommand.isEnabled);
});

Custom TodoList Integration

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

class TodoListExtensionPlugin extends Plugin {
  static get requires() {
    return [TodoList];
  }
  
  init() {
    const editor = this.editor;
    
    // Add custom behavior when tasks are completed
    editor.editing.view.document.on('todoCheckboxChange', (evt, data) => {
      if (data.checked) {
        this.onTaskCompleted(data.target);
      } else {
        this.onTaskUnchecked(data.target);
      }
    });
    
    // Add custom command for bulk operations
    editor.commands.add('checkAllTodos', new CheckAllTodosCommand(editor));
    editor.commands.add('uncheckAllTodos', new UncheckAllTodosCommand(editor));
  }
  
  onTaskCompleted(element) {
    // Add completion timestamp or visual effects
    console.log('Task completed at:', new Date().toISOString());
    
    // Could add strikethrough style or fade effect
    element.style.opacity = '0.6';
  }
  
  onTaskUnchecked(element) {
    // Remove completion effects
    element.style.opacity = '';
  }
}

class CheckAllTodosCommand extends Command {
  execute() {
    const model = this.editor.model;
    const todoItems = this.getAllTodoItems();
    
    model.change(writer => {
      todoItems.forEach(item => {
        writer.setAttribute('todoListChecked', true, item);
      });
    });
  }
  
  getAllTodoItems() {
    const model = this.editor.model;
    const root = model.document.getRoot();
    const todoItems = [];
    
    model.createRangeIn(root).getWalker().forEach(({ item }) => {
      if (item.is('element') && item.hasAttribute('listType') && 
          item.getAttribute('listType') === 'todo') {
        todoItems.push(item);
      }
    });
    
    return todoItems;
  }
}

TodoList with Progress Tracking

class TodoProgressPlugin extends Plugin {
  static get requires() {
    return [TodoList];
  }
  
  init() {
    const editor = this.editor;
    
    // Track progress changes
    editor.model.document.on('change:data', () => {
      this.updateProgress();
    });
    
    // Initial progress calculation
    editor.on('ready', () => {
      this.updateProgress();
    });
  }
  
  updateProgress() {
    const progress = this.calculateProgress();
    this.displayProgress(progress);
  }
  
  calculateProgress() {
    const model = this.editor.model;
    const root = model.document.getRoot();
    
    let totalTodos = 0;
    let checkedTodos = 0;
    
    model.createRangeIn(root).getWalker().forEach(({ item }) => {
      if (item.is('element') && item.hasAttribute('listType') && 
          item.getAttribute('listType') === 'todo') {
        totalTodos++;
        if (item.hasAttribute('todoListChecked') && 
            item.getAttribute('todoListChecked')) {
          checkedTodos++;
        }
      }
    });
    
    return {
      total: totalTodos,
      completed: checkedTodos,
      percentage: totalTodos > 0 ? Math.round((checkedTodos / totalTodos) * 100) : 0
    };
  }
  
  displayProgress(progress) {
    const progressElement = document.querySelector('#todo-progress');
    if (progressElement) {
      progressElement.innerHTML = `
        <div class="progress-bar">
          <div class="progress-fill" style="width: ${progress.percentage}%"></div>
        </div>
        <div class="progress-text">
          ${progress.completed}/${progress.total} tasks completed (${progress.percentage}%)
        </div>
      `;
    }
  }
}

Advanced TodoList Configuration

// Custom configuration with keyboard shortcuts and styling
ClassicEditor
  .create(document.querySelector('#editor'), {
    plugins: [List, TodoList],
    toolbar: ['numberedList', 'bulletedList', 'todoList'],
    
    // Custom keystrokes for todo operations
    keystrokes: [
      // Ctrl+Shift+T to toggle todo list
      ['Ctrl+Shift+T', 'todoList'],
      // Ctrl+Shift+C to toggle checkbox
      ['Ctrl+Shift+C', 'checkTodoList']
    ]
  })
  .then(editor => {
    // Add custom styling for completed todos
    const editingView = editor.editing.view;
    
    editingView.change(writer => {
      writer.setStyle('color', '#666', 
        editingView.document.getRoot().getChild(0));
    });
    
    console.log('Advanced TodoList setup complete');
  });

TodoList State Persistence

class TodoPersistencePlugin extends Plugin {
  static get requires() {
    return [TodoList];
  }
  
  init() {
    const editor = this.editor;
    
    // Save state on changes
    editor.model.document.on('change:data', () => {
      this.saveState();
    });
    
    // Restore state on load
    editor.on('ready', () => {
      this.restoreState();
    });
  }
  
  saveState() {
    const todoStates = this.extractTodoStates();
    localStorage.setItem('todoStates', JSON.stringify(todoStates));
  }
  
  restoreState() {
    const savedStates = localStorage.getItem('todoStates');
    if (savedStates) {
      const todoStates = JSON.parse(savedStates);
      this.applyTodoStates(todoStates);
    }
  }
  
  extractTodoStates() {
    const model = this.editor.model;
    const root = model.document.getRoot();
    const states = [];
    
    model.createRangeIn(root).getWalker().forEach(({ item, type }) => {
      if (type === 'elementStart' && 
          item.hasAttribute('listType') && 
          item.getAttribute('listType') === 'todo') {
        states.push({
          id: item.getAttribute('listItemId'),
          checked: item.hasAttribute('todoListChecked') && 
                  item.getAttribute('todoListChecked')
        });
      }
    });
    
    return states;
  }
  
  applyTodoStates(states) {
    const model = this.editor.model;
    
    model.change(writer => {
      states.forEach(state => {
        const item = this.findTodoItemById(state.id);
        if (item) {
          writer.setAttribute('todoListChecked', state.checked, item);
        }
      });
    });
  }
  
  findTodoItemById(id) {
    const model = this.editor.model;
    const root = model.document.getRoot();
    
    for (const { item } of model.createRangeIn(root).getWalker()) {
      if (item.is('element') && 
          item.getAttribute('listItemId') === id) {
        return item;
      }
    }
    
    return null;
  }
}