Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
GeneratesTableOfContents
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
3 / 3
4
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
 extractTableOfContents
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Framework\Actions;
6
7use Hyde\Facades\Config;
8use Hyde\Markdown\Models\Markdown;
9use League\CommonMark\Environment\Environment;
10use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
11use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
12use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
13use League\CommonMark\MarkdownConverter;
14
15use function strpos;
16use function substr;
17
18/**
19 * Generates a table of contents for the Markdown document, most commonly used for the sidebar.
20 */
21class GeneratesTableOfContents
22{
23    protected string $markdown;
24
25    public function __construct(Markdown|string $markdown)
26    {
27        $this->markdown = (string) $markdown;
28    }
29
30    public function execute(): string
31    {
32        $config = [
33            'table_of_contents' => [
34                'html_class' => 'table-of-contents',
35                'position' => 'placeholder',
36                'placeholder' => '[[START_TOC]]',
37                'style' => 'bullet',
38                'min_heading_level' => Config::getInt('docs.table_of_contents.min_heading_level', 2),
39                'max_heading_level' => Config::getInt('docs.table_of_contents.max_heading_level', 4),
40                'normalize' => 'relative',
41            ],
42            'heading_permalink' => [
43                'fragment_prefix' => '',
44            ],
45        ];
46
47        $environment = new Environment($config);
48        $environment->addExtension(new CommonMarkCoreExtension());
49        $environment->addExtension(new HeadingPermalinkExtension());
50        $environment->addExtension(new TableOfContentsExtension());
51
52        $converter = new MarkdownConverter($environment);
53        $html = $converter->convert($this->markdown."\n[[START_TOC]]")->getContent();
54
55        return $this->extractTableOfContents($html);
56    }
57
58    protected function extractTableOfContents(string $html): string
59    {
60        // The table of contents is always at the end of the document, so we can just strip everything before it.
61        $position = strpos($html, '<ul class="table-of-contents">');
62        if ($position === false) {
63            // The document has no headings, so we'll just return an empty string.
64            return '';
65        }
66
67        return substr($html, $position);
68    }
69}