Passed
Push — master ( ad6b2a...b41ca0 )
by Caen
07:45 queued 14s
created

HeadingRenderer::canAddPermalink()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 5
nc 5
nop 2
dl 0
loc 7
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Markdown\Processing;
6
7
use Hyde\Pages\DocumentationPage;
8
use Illuminate\Support\Str;
9
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
10
use League\CommonMark\Node\Node;
11
use League\CommonMark\Renderer\ChildNodeRendererInterface;
12
use League\CommonMark\Renderer\NodeRendererInterface;
13
14
/**
15
 * Renders a heading node, and supports built-in permalink generation.
16
 *
17
 * @internal This class is an internal implementation detail of our Markdown processing and is not indented for use outside of the framework.
18
 *
19
 * @see \League\CommonMark\Extension\CommonMark\Renderer\Block\HeadingRenderer
20
 */
21
class HeadingRenderer implements NodeRendererInterface
22
{
23
    /** @var ?class-string<\Hyde\Pages\Concerns\HydePage> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?class-string<\Hyde\Pages\Concerns\HydePage> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in ?class-string<\Hyde\Pages\Concerns\HydePage>.
Loading history...
24
    protected ?string $pageClass = null;
25
26
    /** @var array<string> */
27
    protected array $headingRegistry = [];
28
29
    /** @param ?class-string<\Hyde\Pages\Concerns\HydePage> $pageClass */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?class-string<\Hyde\Pages\Concerns\HydePage> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in ?class-string<\Hyde\Pages\Concerns\HydePage>.
Loading history...
30
    public function __construct(?string $pageClass = null, array &$headingRegistry = [])
31
    {
32
        $this->pageClass = $pageClass;
33
        $this->headingRegistry = &$headingRegistry;
34
    }
35
36
    public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
37
    {
38
        if (! ($node instanceof Heading)) {
39
            throw new \InvalidArgumentException('Incompatible node type: '.get_class($node));
40
        }
41
42
        $content = $childRenderer->renderNodes($node->children());
43
44
        $rendered = view('hyde::components.markdown-heading', [
45
            'level' => $node->getLevel(),
46
            'slot' => $content,
47
            'id' => $this->makeHeadingId($content),
48
            'addPermalink' => $this->canAddPermalink($content, $node->getLevel()),
49
            'extraAttributes' => $node->data->get('attributes'),
50
        ])->render();
51
52
        return $this->postProcess($rendered);
53
    }
54
55
    /** @internal */
56
    public function canAddPermalink(string $content, int $level): bool
57
    {
58
        return config('markdown.permalinks.enabled', true)
59
            && $level >= config('markdown.permalinks.min_level', 2)
60
            && $level <= config('markdown.permalinks.max_level', 6)
61
            && ! str_contains($content, 'class="heading-permalink"')
62
            && in_array($this->pageClass, config('markdown.permalinks.pages', [DocumentationPage::class]));
63
    }
64
65
    /** @internal */
66
    public function postProcess(string $html): string
67
    {
68
        $html = str_replace('class=""', '', $html);
69
        $html = preg_replace('/<h([1-6]) >/', '<h$1>', $html);
70
71
        return implode('', array_map('trim', explode("\n", $html)));
72
    }
73
74
    protected function makeHeadingId(string $contents): string
75
    {
76
        $identifier = $this->ensureIdentifierIsUnique(static::makeIdentifier($contents));
77
78
        $this->headingRegistry[] = $identifier;
79
80
        return $identifier;
81
    }
82
83
    protected function ensureIdentifierIsUnique(string $slug): string
84
    {
85
        $identifier = $slug;
86
        $suffix = 2;
87
88
        while (in_array($identifier, $this->headingRegistry)) {
89
            $identifier = $slug.'-'.$suffix++;
90
        }
91
92
        return $identifier;
93
    }
94
95
    /** @internal */
96
    public static function makeIdentifier(string $title): string
97
    {
98
        return e(Str::slug(Str::transliterate(html_entity_decode($title)), dictionary: ['@' => 'at', '&' => 'and', '<' => '', '>' => '']));
99
    }
100
}
101