1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Libraries\Wiki;
7
8use App\Libraries\Markdown\OsuMarkdown;
9use League\CommonMark\Environment\Environment;
10use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
11use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
12use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource;
13use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
14use League\CommonMark\Node\Block\Document;
15use League\CommonMark\Node\Block\Paragraph;
16use League\CommonMark\Node\Node;
17use League\CommonMark\Node\NodeWalkerEvent;
18use League\CommonMark\Parser\MarkdownParser;
19use League\CommonMark\Renderer\HtmlRenderer;
20
21class MainPageRenderer extends Renderer
22{
23 private MarkdownParser $parser;
24
25 private HtmlRenderer $renderer;
26
27 public function __construct($page, $body)
28 {
29 parent::__construct($page, $body);
30
31 $config = array_merge(
32 OsuMarkdown::DEFAULT_COMMONMARK_CONFIG,
33 ['html_input' => 'allow'],
34 );
35
36 $env = new Environment($config);
37 $env->addExtension(new CommonMarkCoreExtension());
38
39 $this->parser = new MarkdownParser($env);
40 $this->renderer = new HtmlRenderer($env);
41 }
42
43 /**
44 * {@inheritdoc}
45 */
46 public function render()
47 {
48 $body = OsuMarkdown::parseYamlHeader($this->body);
49 $document = $this->parser->parse($body['document']);
50
51 $this->addClasses($document);
52
53 $page = [
54 'header' => $body['header'],
55 'output' => $this->renderer->renderDocument($document)->getContent(),
56 ];
57
58 return $page;
59 }
60
61 /**
62 * {@inheritdoc}
63 */
64 public function renderIndexable()
65 {
66 // returning nothing since the main page isn't searchable anyway
67 return '';
68 }
69
70 /**
71 * @param \League\CommonMark\Block\Element\Document $document
72 * @return void
73 */
74 private function addClasses(Document $document)
75 {
76 $walker = $document->walker();
77
78 while ($event = $walker->next()) {
79 $node = $event->getNode();
80
81 $this->fixLinks($event, $node);
82
83 if ($event->isEntering() || isset($node->data['attributes']['class'])) {
84 continue;
85 }
86
87 $blockClass = 'wiki-main-page';
88 $class = '';
89
90 switch (get_class($node)) {
91 case Heading::class:
92 $class = "{$blockClass}__heading";
93 break;
94 case Paragraph::class:
95 $class = "{$blockClass}__paragraph";
96 break;
97 case Link::class:
98 $class = "{$blockClass}__link";
99 break;
100 }
101
102 if (present($class)) {
103 $node->data->set('attributes/class', $class);
104 }
105 }
106 }
107
108 private function fixLinks(NodeWalkerEvent $event, Node $node)
109 {
110 if (!$event->isEntering() || !($node instanceof AbstractWebResource)) {
111 return;
112 }
113
114 // this assumes links are in form /wiki/Path/To/Page
115 $relativeUrl = preg_replace('#^/wiki/#', './', $node->getUrl());
116
117 $node->setUrl($relativeUrl);
118 }
119}