Passed
Push — master ( 32a4a2...da9bd5 )
by Caen
04:12 queued 14s
created

SemanticDocumentationArticle::make()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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