Skip to content

Commit 5e8aa57

Browse files
authored
[FEATURE] Render main menu JSON (#757)
1 parent ef87954 commit 5e8aa57

File tree

13 files changed

+626
-1
lines changed

13 files changed

+626
-1
lines changed

packages/typo3-docs-theme/resources/config/typo3-docs-theme.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use phpDocumentor\Guides\Event\PostRenderProcess;
99
use phpDocumentor\Guides\Event\PreParseProcess;
1010
use phpDocumentor\Guides\Graphs\Renderer\PlantumlServerRenderer;
11+
use phpDocumentor\Guides\ReferenceResolvers\DelegatingReferenceResolver;
1112
use phpDocumentor\Guides\ReferenceResolvers\Interlink\InventoryRepository;
1213
use phpDocumentor\Guides\RestructuredText\Directives\BaseDirective;
1314
use phpDocumentor\Guides\RestructuredText\Directives\SubDirective;
@@ -22,6 +23,7 @@
2223
use T3Docs\Typo3DocsTheme\Directives\GroupTabDirective;
2324
use T3Docs\Typo3DocsTheme\Directives\IncludeDirective;
2425
use T3Docs\Typo3DocsTheme\Directives\LiteralincludeDirective;
26+
use T3Docs\Typo3DocsTheme\Directives\MainMenuJsonDirective;
2527
use T3Docs\Typo3DocsTheme\Directives\RawDirective;
2628
use T3Docs\Typo3DocsTheme\Directives\SiteSetSettingsDirective;
2729
use T3Docs\Typo3DocsTheme\Directives\T3FieldListTableDirective;
@@ -38,6 +40,8 @@
3840
use T3Docs\Typo3DocsTheme\Parser\Productions\FieldList\EditOnGitHubFieldListItemRule;
3941
use T3Docs\Typo3DocsTheme\Parser\Productions\FieldList\TemplateFieldListItemRule;
4042
use T3Docs\Typo3DocsTheme\Renderer\DecoratingPlantumlRenderer;
43+
use T3Docs\Typo3DocsTheme\Renderer\MainMenuJsonRenderer;
44+
use T3Docs\Typo3DocsTheme\Renderer\NodeRenderer\MainMenuJsonDocumentRenderer;
4145
use T3Docs\Typo3DocsTheme\TextRoles\ApiClassTextRole;
4246
use T3Docs\Typo3DocsTheme\TextRoles\ComposerTextRole;
4347
use T3Docs\Typo3DocsTheme\TextRoles\FluidTextTextRole;
@@ -63,6 +67,7 @@
6367

6468
use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
6569
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
70+
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;
6671

6772
return static function (ContainerConfigurator $container): void {
6873
$container->services()
@@ -79,6 +84,17 @@
7984
->set(TwigExtension::class)
8085
->tag('twig.extension')
8186
->autowire()
87+
88+
->set(MainMenuJsonRenderer::class)
89+
->tag(
90+
'phpdoc.renderer.typerenderer',
91+
[
92+
'noderender_tag' => 'phpdoc.guides.noderenderer.mainmenujson',
93+
'format' => 'mainmenujson',
94+
],
95+
)
96+
->set(MainMenuJsonDocumentRenderer::class)
97+
->tag('phpdoc.guides.noderenderer.mainmenu')
8298
->set(IssueReferenceTextRole::class)
8399
->tag('phpdoc.guides.parser.rst.text_role')
84100
->set(phpDocumentor\Guides\ReferenceResolvers\Interlink\DefaultInventoryLoader::class)
@@ -131,6 +147,8 @@
131147
->set(EditOnGitHubFieldListItemRule::class)
132148
->tag('phpdoc.guides.parser.rst.fieldlist')
133149

150+
->set(DelegatingReferenceResolver::class)
151+
->arg('$resolvers', tagged_iterator('phpdoc.guides.reference_resolver', defaultPriorityMethod: 'getPriority'))
134152

135153
->set(DecoratingPlantumlRenderer::class)
136154
->decorate(PlantumlServerRenderer::class)
@@ -141,6 +159,7 @@
141159
->set(GroupTabDirective::class)
142160
->set(IncludeDirective::class)
143161
->set(LiteralincludeDirective::class)
162+
->set(MainMenuJsonDirective::class)
144163
->set(RawDirective::class)
145164
->set(SiteSetSettingsDirective::class)
146165
->set(T3FieldListTableDirective::class)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{#-
2+
This directive is only supported in JSON output
3+
-#}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace T3Docs\Typo3DocsTheme\Directives;
6+
7+
use phpDocumentor\Guides\Nodes\CollectionNode;
8+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
9+
use phpDocumentor\Guides\Nodes\Node;
10+
use phpDocumentor\Guides\RestructuredText\Directives\SubDirective;
11+
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
12+
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
13+
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
14+
use T3Docs\Typo3DocsTheme\Nodes\MainMenuJsonNode;
15+
16+
/**
17+
* Used in the main documentation page docs.typo3.org to configure
18+
* the main menu docs.typo3.org/mainMenu.json.
19+
*
20+
* To be used together with the MainMenuJsonRenderer and the template
21+
* :template: mainMenu.json
22+
*/
23+
class MainMenuJsonDirective extends SubDirective
24+
{
25+
public function __construct(
26+
Rule $startingRule,
27+
) {
28+
parent::__construct($startingRule);
29+
}
30+
31+
public function getName(): string
32+
{
33+
return 'main-menu-json';
34+
}
35+
36+
protected function processSub(
37+
BlockContext $blockContext,
38+
CollectionNode $collectionNode,
39+
Directive $directive,
40+
): Node|null {
41+
return new MainMenuJsonNode(
42+
$directive->getData(),
43+
$directive->getDataNode() ?? new InlineCompoundNode(),
44+
$collectionNode->getChildren(),
45+
);
46+
}
47+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace T3Docs\Typo3DocsTheme\Nodes;
4+
5+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
6+
use phpDocumentor\Guides\Nodes\Node;
7+
use phpDocumentor\Guides\RestructuredText\Nodes\GeneralDirectiveNode;
8+
9+
final class MainMenuJsonNode extends GeneralDirectiveNode
10+
{
11+
/**
12+
* @param Node[] $value
13+
*/
14+
public function __construct(
15+
protected readonly string $plainContent,
16+
protected readonly InlineCompoundNode $content,
17+
array $value = [],
18+
) {
19+
parent::__construct('main-menu-json', $plainContent, $content, $value);
20+
}
21+
22+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace T3Docs\Typo3DocsTheme\Renderer;
4+
5+
use phpDocumentor\Guides\Handlers\RenderCommand;
6+
use phpDocumentor\Guides\RenderContext;
7+
use phpDocumentor\Guides\Renderer\TypeRenderer;
8+
use T3Docs\Typo3DocsTheme\Nodes\Metadata\TemplateNode;
9+
use T3Docs\Typo3DocsTheme\Renderer\NodeRenderer\MainMenuJsonDocumentRenderer;
10+
11+
final class MainMenuJsonRenderer implements TypeRenderer
12+
{
13+
public function __construct(
14+
private readonly MainMenuJsonDocumentRenderer $renderer
15+
) {}
16+
17+
public function render(RenderCommand $renderCommand): void
18+
{
19+
$projectNode = $renderCommand->getProjectNode();
20+
21+
$context = RenderContext::forProject(
22+
$projectNode,
23+
$renderCommand->getDocumentArray(),
24+
$renderCommand->getOrigin(),
25+
$renderCommand->getDestination(),
26+
$renderCommand->getDestinationPath(),
27+
'mainmenujson',
28+
)->withIterator($renderCommand->getDocumentIterator())
29+
->withOutputFilePath('mainmenu.json');
30+
31+
foreach ($renderCommand->getDocumentArray() as $key => $document) {
32+
$headerNodes = $document->getHeaderNodes();
33+
foreach ($headerNodes as $headerNode) {
34+
if ($headerNode instanceof TemplateNode && $headerNode->getValue() === 'mainmenu.json') {
35+
$context = $context->withDocument($document);
36+
$renderCommand->getDestination()->put(
37+
'mainmenu.json',
38+
$this->renderer->render(
39+
$document,
40+
$context,
41+
),
42+
);
43+
}
44+
}
45+
}
46+
}
47+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
namespace T3Docs\Typo3DocsTheme\Renderer\NodeRenderer;
4+
5+
use phpDocumentor\Guides\NodeRenderers\NodeRenderer;
6+
use phpDocumentor\Guides\Nodes\CollectionNode;
7+
use phpDocumentor\Guides\Nodes\CompoundNode;
8+
use phpDocumentor\Guides\Nodes\DocumentNode;
9+
use phpDocumentor\Guides\Nodes\Inline\LinkInlineNode;
10+
use phpDocumentor\Guides\Nodes\ListItemNode;
11+
use phpDocumentor\Guides\Nodes\ListNode;
12+
use phpDocumentor\Guides\Nodes\Node;
13+
use phpDocumentor\Guides\ReferenceResolvers\DelegatingReferenceResolver;
14+
use phpDocumentor\Guides\ReferenceResolvers\Messages;
15+
use phpDocumentor\Guides\RenderContext;
16+
use T3Docs\Typo3DocsTheme\Nodes\MainMenuJsonNode;
17+
18+
/** @implements NodeRenderer<Node> */
19+
class MainMenuJsonDocumentRenderer implements NodeRenderer
20+
{
21+
public function __construct(
22+
private readonly DelegatingReferenceResolver $delegatingReferenceResolver,
23+
) {}
24+
25+
public function supports(string $nodeFqcn): bool
26+
{
27+
return DocumentNode::class === $nodeFqcn;
28+
}
29+
30+
public function render(Node $node, RenderContext $renderContext): string
31+
{
32+
$result = '';
33+
if ($node instanceof DocumentNode) {
34+
foreach ($node->getChildren() as $childNode) {
35+
$result .= $this->render($childNode, $renderContext);
36+
}
37+
}
38+
if ($node instanceof CollectionNode) {
39+
foreach ($node->getChildren() as $childNode) {
40+
$result .= $this->render($childNode, $renderContext);
41+
}
42+
}
43+
if (!$node instanceof MainMenuJsonNode) {
44+
return $result;
45+
}
46+
return $result . $this->renderMainMenu($node, $renderContext);
47+
}
48+
49+
private function renderMainMenu(MainMenuJsonNode $node, RenderContext $renderContext): string
50+
{
51+
$stringResult = '';
52+
$result = [];
53+
foreach ($node->getChildren() as $listNode) {
54+
$this->renderSubEntry($listNode, $renderContext, $result);
55+
}
56+
return $stringResult . json_encode($result, JSON_PRETTY_PRINT);
57+
}
58+
59+
/**
60+
* @param array<mixed> $result
61+
*/
62+
public function renderSubEntry(Node $node, RenderContext $renderContext, array &$result): void
63+
{
64+
if ($node instanceof ListNode) {
65+
foreach ($node->getChildren() as $listItemNode) {
66+
if (!$listItemNode instanceof ListItemNode) {
67+
continue;
68+
}
69+
$menuEntry = [];
70+
foreach ($listItemNode->getChildren() as $childNode) {
71+
if ($childNode instanceof ListNode) {
72+
$this->renderSubEntryList($menuEntry, $childNode, $renderContext);
73+
} elseif ($childNode instanceof CompoundNode) {
74+
$this->renderMenuEntry($menuEntry, $childNode, $renderContext);
75+
}
76+
}
77+
$result[] = $menuEntry;
78+
}
79+
} elseif ($node instanceof CompoundNode) {
80+
foreach ($node->getChildren() as $childNode) {
81+
$this->renderSubEntry($childNode, $renderContext, $result);
82+
}
83+
}
84+
}
85+
86+
/**
87+
* @param array<mixed> $menuEntry
88+
*/
89+
private function renderMenuEntry(array &$menuEntry, Node $node, RenderContext $renderContext): void
90+
{
91+
if ($node instanceof CompoundNode) {
92+
foreach ($node->getChildren() as $childNode) {
93+
$this->renderMenuEntry($menuEntry, $childNode, $renderContext);
94+
}
95+
return;
96+
}
97+
if ($node instanceof LinkInlineNode) {
98+
$this->delegatingReferenceResolver->resolve($node, $renderContext, new Messages());
99+
$url = $node->getUrl();
100+
$parsedUrl = parse_url($url);
101+
if (!isset($parsedUrl['scheme'])) {
102+
$url = 'https://docs.typo3.org/' . $url;
103+
}
104+
$menuEntry['name'] = $node->getValue();
105+
$menuEntry['href'] = $url;
106+
}
107+
}
108+
/**
109+
* @param array<mixed> $menuEntry
110+
*/
111+
private function renderSubEntryList(array &$menuEntry, ListNode $listNode, RenderContext $renderContext): void
112+
{
113+
$subListItems = [];
114+
$this->renderSubEntry($listNode, $renderContext, $subListItems);
115+
$menuEntry['children'] = $subListItems;
116+
}
117+
118+
}

packages/typo3-guides-extension/resources/config/typo3-guides.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
use phpDocumentor\Guides\Renderer\UrlGenerator\UrlGeneratorInterface;
88
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
99
use T3Docs\GuidesExtension\Command\RunDecorator;
10-
use T3Docs\Typo3DocsTheme\Inventory\Typo3InventoryRepository;
1110
use T3Docs\GuidesExtension\Renderer\UrlGenerator\RenderOutputUrlGenerator;
1211
use T3Docs\GuidesExtension\Renderer\UrlGenerator\SingleHtmlUrlGenerator;
12+
use T3Docs\Typo3DocsTheme\Inventory\Typo3InventoryRepository;
1313

1414
use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
1515
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;

0 commit comments

Comments
 (0)