v2.0.0.1
Publisium
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/slugger.php';
-
- final class TableOfContents
- {
- public static function build(string $html): array
- {
- $items = [];
- $usedIds = [];
-
- $processed = preg_replace_callback('#<h([23])>(.*?)</h\1>#s', static function (array $match) use (&$items, &$usedIds): string {
- $level = (int) $match[1];
- $label = trim(html_entity_decode(strip_tags($match[2]), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
-
- if ($label === '') {
- return $match[0];
- }
-
- $base = Slugger::make($label);
- $base = $base !== '' ? $base : 'section';
- $id = 'toc-' . $base;
- $suffix = 2;
- while (isset($usedIds[$id])) {
- $id = 'toc-' . $base . '-' . $suffix;
- $suffix++;
- }
- $usedIds[$id] = true;
-
- $items[] = ['id' => $id, 'label' => $label, 'level' => $level];
-
- return '<h' . $level . ' id="' . $id . '">' . $match[2] . '</h' . $level . '>';
- }, $html);
-
- return [$processed ?? $html, $items];
- }
-
- public static function render(array $items): string
- {
- if (count($items) < 2) {
- return '';
- }
-
- $html = '<details class="article-toc" open><summary>' . htmlspecialchars(Language::get('article_toc_title', 'Table of contents')) . '</summary><ul class="article-toc-list">';
- foreach ($items as $item) {
- $html .= '<li class="article-toc-item article-toc-level-' . (int) $item['level'] . '"><a href="#' . htmlspecialchars($item['id'], ENT_QUOTES) . '">' . htmlspecialchars($item['label']) . '</a></li>';
- }
-
- return $html . '</ul></details>';
- }
- }
-