Task lists: чекбоксы в списках

Task lists — это особый вид списков в Markdown, позволяющий создавать интерактивные чекбоксы. Они часто используются для списков задач, TODO-листов или контрольных списков. В синтаксисе Markdown каждая задача оформляется как пункт списка с квадратными скобками, где [ ] обозначает пустой чекбокс, а [x] — выполненный. Пример базового синтаксиса:

- [ ] Написать документацию
- [x] Настроить сборку проекта
- [ ] Проверить тесты

В этом примере первый и третий пункты остаются невыполненными, второй пункт отмечен как завершённый.


Поддержка в Remark

Remark предоставляет мощные инструменты для работы с Markdown через AST (Abstract Syntax Tree). Для распознавания task lists в Remark используется плагин remark-gfm, который включает поддержку расширений GitHub Flavored Markdown, включая:

  • Таблицы
  • Зачёркнутый текст
  • Авто-ссылки
  • Task lists

Пример конфигурации Remark с remark-gfm:

import { remark } from 'remark';
import remarkGfm from 'remark-gfm';

const markdown = `
- [ ] Купить продукты
- [x] Оплатить счета
`;

remark()
  .use(remarkGfm)
  .process(markdown)
  .then(file => {
    console.log(String(file));
  });

В AST, формируемом Remark, пункты списка с чекбоксами имеют дополнительное свойство checked, которое принимает true или false в зависимости от состояния чекбокса. Структура элемента выглядит следующим образом:

{
  "type": "listItem",
  "checked": false,
  "children": [
    {
      "type": "paragraph",
      "children": [
        {
          "type": "text",
          "value": "Купить продукты"
        }
      ]
    }
  ]
}

Это свойство позволяет легко различать выполненные и невыполненные задачи на уровне обработки AST.


Рендеринг через Rehype

После разбора Markdown с помощью Remark можно конвертировать его в HTML через Rehype. Для сохранения чекбоксов необходимо корректно обрабатывать поле checked и создавать соответствующие <input type="checkbox">.

Пример обработки с remark-rehype:

import { remark } from 'remark';
import remarkGfm from 'remark-gfm';
import rehypeStringify from 'rehype-stringify';
import remarkRehype from 'remark-rehype';

const markdown = `
- [ ] Выучить JavaScript
- [x] Настроить окружение
`;

remark()
  .use(remarkGfm)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdown)
  .then(file => {
    console.log(String(file));
  });

В результирующем HTML задача с чекбоксом преобразуется в тег <li> с <input> внутри:

<ul>
  <li><input type="checkbox" disabled> Выучить JavaScript</li>
  <li><input type="checkbox" checked disabled> Настроить окружение</li>
</ul>

Атрибут disabled используется по умолчанию, так как Markdown — это статический формат. Для интерактивных списков можно динамически управлять состоянием чекбоксов через JavaScript.


Модификация AST для кастомного рендеринга

С помощью Remark можно программно модифицировать AST перед конвертацией в HTML. Например, добавление CSS-классов для разных состояний чекбоксов:

import { visit } from 'unist-util-visit';

remark()
  .use(remarkGfm)
  .use(() => tree => {
    visit(tree, 'listItem', node => {
      if (node.checked === true) {
        node.data = { hProperties: { class: 'task-completed' } };
      } else if (node.checked === false) {
        node.data = { hProperties: { class: 'task-pending' } };
      }
    });
  })
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdown)
  .then(file => {
    console.log(String(file));
  });

В HTML можно получить:

<ul>
  <li class="task-pending"><input type="checkbox" disabled> Выучить JavaScript</li>
  <li class="task-completed"><input type="checkbox" checked disabled> Настроить окружение</li>
</ul>

Такой подход позволяет применять стили CSS для визуального различия между выполненными и невыполненными задачами.


Взаимодействие с пользовательским интерфейсом

Хотя Markdown по умолчанию статичен, после генерации HTML чекбоксы можно сделать интерактивными с помощью Jav * aScript:

document.querySelectorAll('li input[type="checkbox"]').forEach(checkbox => {
  checkbox.disabled = false;
  checkbox.addEventListener('change', e => {
    const li = e.target.closest('li');
    li.classList.toggle('task-completed', e.target.checked);
    li.classList.toggle('task-pending', !e.target.checked);
  });
});

Таким образом, можно синхронизировать состояние чекбоксов с визуальными стилями, реализуя полноценные TODO-листы на сайте.


Особенности и ограничения

  • Совместимость с Markdown: Task lists поддерживаются только при включении remark-gfm.
  • Статический рендеринг: В исходном Markdown нет интерактивности; любые действия с чекбоксами требуют JavaScript.
  • Атрибут checked: Определяет состояние задачи; отсутствует — считается невыполненной.
  • Вложенные списки: Task lists поддерживаются на любом уровне вложенности, при этом checked применяется только к соответствующему listItem.

Эти механизмы позволяют строить сложные интерактивные интерфейсы на основе Markdown, сохраняя контроль над структурой и стилем через AST и Rehype.