Skip to content

Commit ec0dad5

Browse files
committed
[FEATURE] Add option to create an automatic menu
For now all Document entries are added to the root document entry. In a follow up I plan to create a nested menu as well References #1108
1 parent 9499293 commit ec0dad5

File tree

41 files changed

+1190
-7
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1190
-7
lines changed

packages/guides-cli/resources/schema/guides.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<xsd:attribute name="theme" type="xsd:string"/>
3232
<xsd:attribute name="default-code-language" type="xsd:string"/>
3333
<xsd:attribute name="links-are-relative" type="xsd:string"/>
34+
<xsd:attribute name="automatic-menu" type="xsd:string"/>
3435
<xsd:attribute name="max-menu-depth" type="xsd:int"/>
3536

3637
</xsd:complexType>

packages/guides-markdown/src/Markdown/MarkupLanguageParser.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use phpDocumentor\Guides\Nodes\DocumentNode;
2525
use phpDocumentor\Guides\Nodes\Node;
2626
use phpDocumentor\Guides\ParserContext;
27+
use phpDocumentor\Guides\Settings\ProjectSettings;
28+
use phpDocumentor\Guides\Settings\SettingsManager;
2729
use Psr\Log\LoggerInterface;
2830
use RuntimeException;
2931

@@ -40,16 +42,21 @@ final class MarkupLanguageParser implements MarkupLanguageParserInterface
4042

4143
private DocumentNode|null $document = null;
4244

45+
private SettingsManager $settingsManager;
46+
4347
/** @param iterable<ParserInterface<Node>> $parsers */
4448
public function __construct(
4549
private readonly LoggerInterface $logger,
4650
private readonly iterable $parsers,
51+
SettingsManager|null $settingsManager,
4752
) {
4853
$cmEnvironment = new CommonMarkEnvironment(['html_input' => 'strip']);
4954
$cmEnvironment->addExtension(new CommonMarkCoreExtension());
5055
$cmEnvironment->addExtension(new TableExtension());
5156
$cmEnvironment->addExtension(new AutolinkExtension());
5257
$this->markdownParser = new MarkdownParser($cmEnvironment);
58+
// if for backward compatibility reasons no settings manager was passed, use the defaults
59+
$this->settingsManager = $settingsManager ?? new SettingsManager(new ProjectSettings());
5360
}
5461

5562
public function supports(string $inputFormat): bool
@@ -69,7 +76,7 @@ public function parse(ParserContext $parserContext, string $contents): DocumentN
6976
private function parseDocument(NodeWalker $walker, string $hash): DocumentNode
7077
{
7178
$document = new DocumentNode($hash, ltrim($this->getParserContext()->getCurrentAbsolutePath(), '/'));
72-
$document->setOrphan(true);
79+
$document->setOrphan(!$this->settingsManager->getProjectSettings()->isAutomaticMenu());
7380
$this->document = $document;
7481

7582
while ($event = $walker->next()) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Compiler\Passes;
15+
16+
use phpDocumentor\Guides\Compiler\CompilerContextInterface;
17+
use phpDocumentor\Guides\Compiler\CompilerPass;
18+
use phpDocumentor\Guides\Nodes\DocumentNode;
19+
use phpDocumentor\Guides\Settings\SettingsManager;
20+
21+
final class AutomaticMenuPass implements CompilerPass
22+
{
23+
public function __construct(
24+
private readonly SettingsManager $settingsManager,
25+
) {
26+
}
27+
28+
public function getPriority(): int
29+
{
30+
return 20; // must be run very late
31+
}
32+
33+
/**
34+
* @param DocumentNode[] $documents
35+
*
36+
* @return DocumentNode[]
37+
*/
38+
public function run(array $documents, CompilerContextInterface $compilerContext): array
39+
{
40+
if (!$this->settingsManager->getProjectSettings()->isAutomaticMenu()) {
41+
return $documents;
42+
}
43+
44+
$projectNode = $compilerContext->getProjectNode();
45+
$rootDocumentEntry = $projectNode->getRootDocumentEntry();
46+
foreach ($documents as $documentNode) {
47+
if ($documentNode->isOrphan()) {
48+
// Do not add orphans to the automatic menu
49+
continue;
50+
}
51+
52+
if ($documentNode->isRoot()) {
53+
continue;
54+
}
55+
56+
$documentEntry = $projectNode->getDocumentEntry($documentNode->getFilePath());
57+
$documentEntry->setParent($rootDocumentEntry);
58+
$rootDocumentEntry->addChild($documentEntry);
59+
}
60+
61+
return $documents;
62+
}
63+
}

packages/guides/src/Compiler/Passes/GlobalMenuPass.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
use function array_map;
3232
use function assert;
33+
use function count;
3334

3435
use const PHP_INT_MAX;
3536

@@ -78,11 +79,27 @@ public function run(array $documents, CompilerContextInterface $compilerContext)
7879
$menuNodes[] = $menuNode->withCaption($tocNode->getCaption());
7980
}
8081

82+
if ($this->settingsManager->getProjectSettings()->isAutomaticMenu() && count($menuNodes) === 0) {
83+
$menuNodes[] = $this->getNavMenuNodeFromDocumentEntries($compilerContext);
84+
}
85+
8186
$projectNode->setGlobalMenues($menuNodes);
8287

8388
return $documents;
8489
}
8590

91+
private function getNavMenuNodeFromDocumentEntries(CompilerContextInterface $compilerContext): NavMenuNode
92+
{
93+
$menuEntries = [];
94+
$rootDocumentEntry = $compilerContext->getProjectNode()->getRootDocumentEntry();
95+
foreach ($rootDocumentEntry->getChildren() as $documentEntryNode) {
96+
$newMenuEntry = new InternalMenuEntryNode($documentEntryNode->getFile(), $documentEntryNode->getTitle(), [], false, 1);
97+
$menuEntries[] = $newMenuEntry;
98+
}
99+
100+
return new NavMenuNode($menuEntries);
101+
}
102+
86103
private function getNavMenuNodefromTocNode(CompilerContextInterface $compilerContext, TocNode $tocNode, string|null $menuType = null): NavMenuNode
87104
{
88105
$self = $this;

packages/guides/src/Compiler/Passes/ToctreeValidationPass.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@
1818
use phpDocumentor\Guides\Nodes\DocumentNode;
1919
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
2020
use phpDocumentor\Guides\Nodes\ProjectNode;
21+
use phpDocumentor\Guides\Settings\ProjectSettings;
22+
use phpDocumentor\Guides\Settings\SettingsManager;
2123
use Psr\Log\LoggerInterface;
2224

2325
final class ToctreeValidationPass implements CompilerPass
2426
{
27+
private SettingsManager $settingsManager;
28+
2529
public function __construct(
2630
private readonly LoggerInterface $logger,
31+
SettingsManager|null $settingsManager = null,
2732
) {
33+
// if for backward compatibility reasons no settings manager was passed, use the defaults
34+
$this->settingsManager = $settingsManager ?? new SettingsManager(new ProjectSettings());
2835
}
2936

3037
public function getPriority(): int
@@ -39,6 +46,10 @@ public function getPriority(): int
3946
*/
4047
public function run(array $documents, CompilerContextInterface $compilerContext): array
4148
{
49+
if ($this->settingsManager->getProjectSettings()->isAutomaticMenu()) {
50+
return $documents;
51+
}
52+
4253
$projectNode = $compilerContext->getProjectNode();
4354

4455
foreach ($documents as $document) {

packages/guides/src/DependencyInjection/GuidesExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ static function ($value) {
143143
->scalarNode('show_progress')->end()
144144
->scalarNode('links_are_relative')->end()
145145
->scalarNode('max_menu_depth')->end()
146+
->scalarNode('automatic_menu')->end()
146147
->arrayNode('base_template_paths')
147148
->defaultValue([])
148149
->scalarPrototype()->end()
@@ -278,6 +279,10 @@ public function load(array $configs, ContainerBuilder $container): void
278279
$projectSettings->setMaxMenuDepth((int) $config['max_menu_depth']);
279280
}
280281

282+
if (isset($config['automatic_menu'])) {
283+
$projectSettings->setAutomaticMenu((bool) $config['automatic_menu']);
284+
}
285+
281286
if (isset($config['default_code_language'])) {
282287
$projectSettings->setDefaultCodeLanguage((string) $config['default_code_language']);
283288
}

packages/guides/src/Settings/ProjectSettings.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ final class ProjectSettings
3535
private bool $linksRelative = false;
3636
private string $defaultCodeLanguage = '';
3737
private int $maxMenuDepth = 0;
38+
private bool $automaticMenu = false;
3839

3940
/** @var string[] */
4041
private array $ignoredDomains = [];
@@ -243,4 +244,16 @@ public function setIndexName(string $indexName): ProjectSettings
243244

244245
return $this;
245246
}
247+
248+
public function isAutomaticMenu(): bool
249+
{
250+
return $this->automaticMenu;
251+
}
252+
253+
public function setAutomaticMenu(bool $automaticMenu): ProjectSettings
254+
{
255+
$this->automaticMenu = $automaticMenu;
256+
257+
return $this;
258+
}
246259
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<!DOCTYPE html>
2+
<html class="no-js" lang="en">
3+
<head>
4+
<title>Another Page - Bootstrap Theme</title>
5+
<!-- Required meta tags -->
6+
<meta charset="utf-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
9+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
10+
</head>
11+
<body>
12+
<header class="">
13+
14+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
15+
<div class="container">
16+
17+
<a class="navbar-brand" href="#">Navbar</a>
18+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
19+
<span class="navbar-toggler-icon"></span>
20+
</button>
21+
<div class="collapse navbar-collapse" id="navbarSupportedContent">
22+
23+
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
24+
<li class="nav-item">
25+
<a href="/anotherPage.html" class="nav-link current active" aria-current="page">
26+
Another Page
27+
</a>
28+
</li><li class="nav-item">
29+
<a href="/somePage.html" class="nav-link">
30+
Some Page
31+
</a>
32+
</li><li class="nav-item">
33+
<a href="/yetAnotherPage.html" class="nav-link">
34+
Yet Another Page
35+
</a>
36+
</li></ul>
37+
38+
</div>
39+
</div>
40+
</nav>
41+
</header>
42+
<main id="main-content">
43+
<div class="container">
44+
<div class="container">
45+
<div class="row">
46+
<div class="col-lg-3">
47+
<nav class="nav flex-column">
48+
<ul class="menu-level-main">
49+
<li>
50+
<a href="/anotherPage.html"
51+
class="nav-link current active" aria-current="page">Another Page</a>
52+
</li>
53+
<li>
54+
<a href="/somePage.html"
55+
class="nav-link">Some Page</a>
56+
</li>
57+
<li>
58+
<a href="/yetAnotherPage.html"
59+
class="nav-link">Yet Another Page</a>
60+
</li>
61+
</ul>
62+
</nav>
63+
64+
</div>
65+
<div class="col-lg-9">
66+
67+
<nav aria-label="breadcrumb">
68+
<ol class="breadcrumb">
69+
<li class="breadcrumb-item"><a href="/index.html">Document Title</a></li>
70+
<li class="breadcrumb-item"><a href="/anotherPage.html">Another Page</a></li>
71+
</ol>
72+
</nav>
73+
<!-- content start -->
74+
75+
<div class="section" id="another-page">
76+
<h1>Another Page</h1>
77+
78+
<p>Lorem Ipsum Dolor.</p>
79+
80+
</div>
81+
<!-- content end -->
82+
</div>
83+
</div>
84+
</div>
85+
</div>
86+
</main>
87+
<footer class="bg-primary text-light">
88+
<div class="container">
89+
90+
<p>Generated by <a href="https://www.phpdoc.org/">phpDocumentor</a>.</p>
91+
92+
</div>
93+
</footer>
94+
95+
<!-- Optional JavaScript; choose one of the two! -->
96+
97+
<!-- Option 1: Bootstrap Bundle with Popper -->
98+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
99+
100+
<!-- Option 2: Separate Popper and Bootstrap JS -->
101+
<!--
102+
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
103+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
104+
-->
105+
</body>
106+
</html>

0 commit comments

Comments
 (0)