Позиционирование ToC в документе

Основные концепции ToC

В экосистеме Remark и Rehype ToC (Table of Contents) представляет собой структурированный список заголовков документа, который позволяет пользователю ориентироваться в содержимом. Для генерации и позиционирования ToC используется комбинация парсинга Markdown через Remark и последующей трансформации в HTML через Rehype.

Важной особенностью является то, что ToC формируется на основе AST (Abstract Syntax Tree) документа. AST предоставляет возможность детально анализировать заголовки (heading) и их уровни (depth), что критически важно для правильной вложенности и структуры ToC.

Получение AST с помощью Remark

Remark позволяет получить AST документа с помощью функции remark().parse(). Структура AST содержит ключевые свойства:

  • type — тип узла (например, root, heading, paragraph).
  • depth — уровень заголовка (1–6).
  • children — дочерние узлы, содержащие текстовые элементы (text) и другие вложенные структуры.

Пример обхода AST для извлечения заголовков:

import { remark } from 'remark';

const markdown = `
# Заголовок 1
## Заголовок 1.1
### Заголовок 1.1.1
`;

const tree = remark().parse(markdown);

function extractHeadings(node, headings = []) {
  if (node.type === 'heading') {
    const text = node.children
      .filter(child => child.type === 'text')
      .map(child => child.value)
      .join('');
    headings.push({ depth: node.depth, text });
  }
  if (node.children) {
    node.children.forEach(child => extractHeadings(child, headings));
  }
  return headings;
}

const toc = extractHeadings(tree);
console.log(toc);

Результат:

[
  { "depth": 1, "text": "Заголовок 1" },
  { "depth": 2, "text": "Заголовок 1.1" },
  { "depth": 3, "text": "Заголовок 1.1.1" }
]

Встраивание ToC в документ

После генерации структуры ToC необходимо вставить его в конкретное место документа. Это достигается через Remark-плагины, которые модифицируют AST перед передачей Rehype. Типовой подход:

  1. Найти узел, где должен появиться ToC (tocPlaceholder).
  2. Сформировать узлы списка (list) с элементами (listItem) для каждого заголовка.
  3. Вставить узел списка на место плейсхолдера.

Пример создания узлов списка:

import { u } from 'unist-builder';

function createTocNode(headings) {
  return u('list', { ordered: false }, 
    headings.map(heading => 
      u('listItem', {}, [
        u('paragraph', {}, [
          u('text', `${'  '.repeat(heading.depth - 1)}${heading.text}`)
        ])
      ])
    )
  );
}

В этом примере отступ создается на основе уровня заголовка, что позволяет визуально отражать иерархию документа.

Управление позиционированием с Rehype

После вставки ToC в AST Markdown его необходимо преобразовать в HTML. Rehype предоставляет гибкость для управления позиционированием через свойства HTML и CSS-классы:

  • Можно использовать id для привязки якорей к заголовкам.
  • CSS-классы позволяют задать фиксированное расположение, плавающий блок или sidebar.
  • Позиционирование ToC можно изменять динамически с помощью JavaScript, если необходимо фиксировать блок при прокрутке документа.

Пример привязки якорей к заголовкам:

import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';

const processedHtml = await remark()
  .use(rehypeSlug)
  .use(rehypeAutolinkHeadings, { beh * avior: 'wrap' })
  .process(markdown);

Здесь каждый заголовок получает уникальный id, что позволяет ToC ссылаться на соответствующие секции документа.

Продвинутые стратегии позиционирования

  1. Фиксированный sidebar ToC может быть закреплен слева или справа от основного контента с помощью CSS (position: sticky), что позволяет пользователю всегда видеть структуру документа при прокрутке.

  2. Встроенный ToC в начале документа Вставка ToC как первого элемента AST делает его частью контента, что удобно для PDF-генерации или статичных HTML-страниц.

  3. Динамическая генерация при рендеринге Если Markdown часто меняется, можно генерировать ToC на лету в браузере, обходя уже скомпилированный HTML через Rehype. Это позволяет минимизировать время сборки и поддерживать актуальность ссылок.

  4. Поддержка глубоких уровней вложенности Для больших документов важно корректно визуализировать уровни h4–h6. AST-подход Remark позволяет создавать вложенные списки (list внутри listItem) для точного отражения структуры.

Оптимизация производительности

  • Избегать многократного обхода AST, если можно извлечь все заголовки за один проход.
  • Для больших документов лучше генерировать ToC до передачи AST в Rehype, чтобы минимизировать количество модификаций узлов.
  • Использовать кэширование структуры заголовков при динамическом рендеринге страниц с одинаковым шаблоном ToC.

Итоговая архитектура

Архитектурно процесс выглядит так:

  1. Markdown → Remark → AST
  2. AST → извлечение заголовков → структура ToC
  3. AST → вставка ToC на нужное место
  4. AST → Rehype → HTML
  5. HTML → стили и поведение через CSS/JS

Такой подход обеспечивает точное позиционирование ToC, правильную вложенность, удобство навигации и возможность гибко управлять отображением на любых типах платформ и устройств.