Passed
Push — master ( 7dcd3c...6c7933 )
by Caen
03:11 queued 12s
created

getTokenizedDataArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 3
c 4
b 0
f 0
dl 0
loc 8
rs 10
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Framework\Features\Documentation;
6
7
use Hyde\Facades\Features;
8
use Hyde\Pages\DocumentationPage;
9
use Illuminate\Support\HtmlString;
10
use Illuminate\Support\Str;
11
use function str_contains;
12
13
/**
14
 * Class to make Hyde documentation pages smarter,
15
 * by dynamically enriching them with semantic HTML.
16
 *
17
 * @see \Hyde\Framework\Testing\Feature\Services\HydeSmartDocsTest
18
 */
19
class SemanticDocumentationArticle
20
{
21
    protected DocumentationPage $page;
22
    protected string $html;
23
24
    protected string $header = '';
25
    protected string $body;
26
    protected string $footer = '';
27
28
    /**
29
     * Create a new SemanticDocumentationArticle instance, process, and return it.
30
     *
31
     * @param  \Hyde\Pages\DocumentationPage  $page  The source page object to process.
32
     * @return static new processed instance
33
     */
34
    public static function create(DocumentationPage $page): static
35
    {
36
        return new self($page);
37
    }
38
39
    public function __construct(DocumentationPage $page)
40
    {
41
        $this->page = $page;
42
        $this->html = $page->markdown->compile($page::class);
43
44
        $this->process();
45
    }
46
47
    public function renderHeader(): HtmlString
48
    {
49
        return new HtmlString($this->header);
50
    }
51
52
    public function renderBody(): HtmlString
53
    {
54
        return new HtmlString($this->body);
55
    }
56
57
    public function renderFooter(): HtmlString
58
    {
59
        return new HtmlString($this->footer);
60
    }
61
62
    /** @internal */
63
    protected function process(): self
64
    {
65
        $this->tokenize();
66
67
        $this->addDynamicHeaderContent();
68
        $this->addDynamicFooterContent();
69
70
        return $this;
71
    }
72
73
    protected function tokenize(): static
74
    {
75
        // The HTML content is expected to be two parts. To create semantic HTML,
76
        // we need to split the content into header and body. We do this by
77
        // extracting the first <h1> tag and everything before it.
78
79
        [$this->header, $this->body] = $this->getTokenizedDataArray();
80
81
        $this->normalizeBody();
82
83
        return $this;
84
    }
85
86
    protected function getTokenizedDataArray(): array
87
    {
88
        // Split the HTML content by the first newline, which is always after the <h1> tag
89
        if (str_contains($this->html, '<h1>')) {
90
            return explode("\n", $this->html, 2);
91
        }
92
93
        return ['', $this->html];
94
    }
95
96
    protected function normalizeBody(): void
97
    {
98
        // Remove possible trailing newlines added by the Markdown compiler to normalize the body.
99
        $this->body = trim($this->body, "\n");
100
    }
101
102
    protected function addDynamicHeaderContent(): static
103
    {
104
        // Hook to add dynamic content to the header.
105
        // This is where we can add TOC, breadcrumbs, etc.
106
107
        if ($this->canRenderSourceLink('header')) {
108
            $this->header .= $this->renderSourceLink();
109
        }
110
111
        return $this;
112
    }
113
114
    protected function addDynamicFooterContent(): static
115
    {
116
        // Hook to add dynamic content to the footer.
117
        // This is where we can add copyright, attributions, info, etc.
118
119
        if (config('torchlight.attribution.enabled', true) && $this->hasTorchlight()) {
120
            $this->footer .= Str::markdown(config(
121
                'torchlight.attribution.markdown',
122
                'Syntax highlighted by torchlight.dev'
123
            ));
124
        }
125
126
        if ($this->canRenderSourceLink('footer')) {
127
            $this->footer .= $this->renderSourceLink();
128
        }
129
130
        return $this;
131
    }
132
133
    protected function renderSourceLink(): string
134
    {
135
        return view('hyde::components.docs.edit-source-button', [
136
            'href' => $this->page->getOnlineSourcePath(),
137
        ])->render();
138
    }
139
140
    /**
141
     * Do we satisfy the requirements to render an edit source button in the supplied position?
142
     */
143
    protected function canRenderSourceLink(string $inPosition): bool
144
    {
145
        $config = config('docs.edit_source_link_position', 'both');
146
        $positions = $config === 'both' ? ['header', 'footer'] : [$config];
147
148
        return ($this->page->getOnlineSourcePath() !== false) && in_array($inPosition, $positions);
149
    }
150
151
    /**
152
     * Does the current document use Torchlight?
153
     */
154
    public function hasTorchlight(): bool
155
    {
156
        return Features::hasTorchlight() && str_contains($this->html, 'Syntax highlighted by torchlight.dev');
157
    }
158
}
159