WebOrbiton
v1.0.0.6

Publisium

54 lines · 1.7 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/slugger.php';
  6. ​
  7. final class TableOfContents
  8. {
  9. public static function build(string $html): array
  10. {
  11. $items = [];
  12. $usedIds = [];
  13. ​
  14. $processed = preg_replace_callback('#<h([23])>(.*?)</h\1>#s', static function (array $match) use (&$items, &$usedIds): string {
  15. $level = (int) $match[1];
  16. $label = trim(html_entity_decode(strip_tags($match[2]), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
  17. ​
  18. if ($label === '') {
  19. return $match[0];
  20. }
  21. ​
  22. $base = Slugger::make($label);
  23. $base = $base !== '' ? $base : 'section';
  24. $id = 'toc-' . $base;
  25. $suffix = 2;
  26. while (isset($usedIds[$id])) {
  27. $id = 'toc-' . $base . '-' . $suffix;
  28. $suffix++;
  29. }
  30. $usedIds[$id] = true;
  31. ​
  32. $items[] = ['id' => $id, 'label' => $label, 'level' => $level];
  33. ​
  34. return '<h' . $level . ' id="' . $id . '">' . $match[2] . '</h' . $level . '>';
  35. }, $html);
  36. ​
  37. return [$processed ?? $html, $items];
  38. }
  39. ​
  40. public static function render(array $items): string
  41. {
  42. if (count($items) < 2) {
  43. return '';
  44. }
  45. ​
  46. $html = '<details class="article-toc" open><summary>' . htmlspecialchars(Language::get('article_toc_title', 'Table of contents')) . '</summary><ul class="article-toc-list">';
  47. foreach ($items as $item) {
  48. $html .= '<li class="article-toc-item article-toc-level-' . (int) $item['level'] . '"><a href="#' . htmlspecialchars($item['id'], ENT_QUOTES) . '">' . htmlspecialchars($item['label']) . '</a></li>';
  49. }
  50. ​
  51. return $html . '</ul></details>';
  52. }
  53. }
  54. ​