Skip to content

Commit ac4184a

Browse files
committed
Change TOC generation to make it more flexible
1 parent 4080f30 commit ac4184a

File tree

5 files changed

+167
-24
lines changed

5 files changed

+167
-24
lines changed

src/DocsKernel.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
use Doctrine\RST\Configuration;
1515
use Doctrine\RST\ErrorManager;
1616
use Doctrine\RST\Event\PostBuildRenderEvent;
17+
use Doctrine\RST\Event\PostNodeCreateEvent;
1718
use Doctrine\RST\Event\PreNodeRenderEvent;
1819
use Doctrine\RST\Kernel;
1920
use SymfonyDocsBuilder\Listener\AssetsCopyListener;
2021
use SymfonyDocsBuilder\Listener\CopyImagesListener;
22+
use SymfonyDocsBuilder\Listener\TocCustomizerListener;
2123

2224
class DocsKernel extends Kernel
2325
{
@@ -47,6 +49,11 @@ private function initializeListeners(EventManager $eventManager, ErrorManager $e
4749
new CopyImagesListener($this->buildConfig, $errorManager)
4850
);
4951

52+
$eventManager->addEventListener(
53+
PreNodeRenderEvent::PRE_NODE_RENDER,
54+
new TocCustomizerListener($this->buildConfig, $errorManager)
55+
);
56+
5057
if (!$this->buildConfig->getSubdirectoryToBuild()) {
5158
$eventManager->addEventListener(
5259
[PostBuildRenderEvent::POST_BUILD_RENDER],

src/Generator/JsonGenerator.php

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace SymfonyDocsBuilder\Generator;
1313

14-
use Doctrine\RST\Environment;
1514
use Doctrine\RST\Meta\MetaEntry;
1615
use Doctrine\RST\Meta\Metas;
1716
use Symfony\Component\Console\Helper\ProgressBar;
@@ -20,7 +19,6 @@
2019
use Symfony\Component\DomCrawler\Crawler;
2120
use Symfony\Component\Filesystem\Filesystem;
2221
use SymfonyDocsBuilder\BuildConfig;
23-
use function Symfony\Component\String\u;
2422

2523
class JsonGenerator
2624
{
@@ -67,13 +65,15 @@ public function generateJson(string $masterDocument = 'index'): array
6765

6866
$crawler = new Crawler(file_get_contents($this->buildConfig->getOutputDir().'/'.$filename.'.html'));
6967

68+
$tocGenerator = new TocGenerator($metaEntry);
7069
$next = $this->determineNext($parserFilename, $flattenedTocTree, $masterDocument);
7170
$prev = $this->determinePrev($parserFilename, $flattenedTocTree);
7271
$data = [
7372
'title' => $metaEntry->getTitle(),
7473
'parents' => $this->determineParents($parserFilename, $tocTreeHierarchy) ?: [],
7574
'current_page_name' => $parserFilename,
76-
'toc' => $this->generateToc($metaEntry, current($metaEntry->getTitles())[1]),
75+
'toc' => $tocGenerator->getToc(),
76+
'toc_num_items' => $tocGenerator->getNumItemsPerLevel(),
7777
'next' => $next,
7878
'prev' => $prev,
7979
'body' => $crawler->filter('body')->html(),
@@ -98,27 +98,6 @@ public function setOutput(SymfonyStyle $output)
9898
$this->output = $output;
9999
}
100100

101-
private function generateToc(MetaEntry $metaEntry, ?array $titles): array
102-
{
103-
if (null === $titles) {
104-
return [];
105-
}
106-
107-
$tocTree = [];
108-
109-
foreach ($titles as $title) {
110-
$tocTree[] = [
111-
'url' => sprintf('%s#%s', $metaEntry->getUrl(), Environment::slugify($title[0])),
112-
'page' => u($metaEntry->getUrl())->beforeLast('.html'),
113-
'fragment' => Environment::slugify($title[0]),
114-
'title' => $title[0],
115-
'children' => $this->generateToc($metaEntry, $title[1]),
116-
];
117-
}
118-
119-
return $tocTree;
120-
}
121-
122101
private function determineNext(string $parserFilename, array $flattenedTocTree): ?array
123102
{
124103
$foundCurrentFile = false;

src/Generator/TocGenerator.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Docs Builder package.
7+
* (c) Ryan Weaver <ryan@symfonycasts.com>
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace SymfonyDocsBuilder\Generator;
13+
14+
use Doctrine\RST\Environment;
15+
use Doctrine\RST\Meta\MetaEntry;
16+
use function Symfony\Component\String\u;
17+
18+
/**
19+
* It encapsulates all the logic needed to generate the full TOC items
20+
* from the given doc meta entries and provides some TOC utilities.
21+
*/
22+
class TocGenerator
23+
{
24+
private $metaEntry;
25+
private $cachedToc;
26+
27+
public function __construct(MetaEntry $metaEntry)
28+
{
29+
$this->metaEntry = $metaEntry;
30+
}
31+
32+
public function getToc(): array
33+
{
34+
if (null !== $this->cachedToc) {
35+
return $this->cachedToc;
36+
}
37+
38+
return $this->cachedToc = $this->doGenerateToc($this->metaEntry, current($this->metaEntry->getTitles())[1]);
39+
}
40+
41+
public function getFlatenedToc(): array
42+
{
43+
return $this->doFlattenToc($this->getToc());
44+
}
45+
46+
/**
47+
* Returns the number of TOC items per indentation level
48+
* (which correspond to <h1>, <h2>, <h3>, etc. elements)
49+
*/
50+
public function getNumItemsPerLevel(): array
51+
{
52+
$numItems = [1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
53+
foreach ($this->getFlatenedToc() as $tocItem) {
54+
++$numItems[$tocItem['level']];
55+
}
56+
57+
$numItems['total'] = array_sum($numItems);
58+
59+
return $numItems;
60+
}
61+
62+
private function doGenerateToc(MetaEntry $metaEntry, ?array $titles, $level = 1): array
63+
{
64+
if (null === $titles) {
65+
return [];
66+
}
67+
68+
$toc = [];
69+
foreach ($titles as $title) {
70+
$toc[] = [
71+
'url' => sprintf('%s#%s', $metaEntry->getUrl(), Environment::slugify($title[0])),
72+
'page' => u($metaEntry->getUrl())->beforeLast('.html')->toString(),
73+
'fragment' => Environment::slugify($title[0]),
74+
'title' => $title[0],
75+
'level' => $level,
76+
'children' => $this->doGenerateToc($metaEntry, $title[1], $level + 1),
77+
];
78+
}
79+
80+
return $toc;
81+
}
82+
83+
private function doFlattenToc(array $toc, array &$flattenedToc = []): array
84+
{
85+
foreach ($toc as $item) {
86+
$flattenedToc[] = $item;
87+
88+
if ([] !== $item['children']) {
89+
$this->doFlattenToc($item['children'], $flattenedToc);
90+
}
91+
}
92+
93+
return $flattenedToc;
94+
}
95+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Docs Builder package.
7+
* (c) Ryan Weaver <ryan@symfonycasts.com>
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace SymfonyDocsBuilder\Listener;
13+
14+
use Doctrine\RST\ErrorManager;
15+
use Doctrine\RST\Event\PreNodeRenderEvent;
16+
use Doctrine\RST\Nodes\TocNode;
17+
use SymfonyDocsBuilder\BuildConfig;
18+
use SymfonyDocsBuilder\Generator\TocGenerator;
19+
use SymfonyDocsBuilder\Toc\TocHandler;
20+
21+
class TocCustomizerListener
22+
{
23+
private $buildConfig;
24+
private $errorManager;
25+
26+
public function __construct(BuildConfig $buildConfig, ErrorManager $errorManager)
27+
{
28+
$this->buildConfig = $buildConfig;
29+
$this->errorManager = $errorManager;
30+
}
31+
32+
public function preNodeRender(PreNodeRenderEvent $event)
33+
{
34+
$node = $event->getNode();
35+
if (!$node instanceof TocNode) {
36+
return;
37+
}
38+
39+
$tocMaxDepth = $node->getDepth();
40+
$metaEntry = '???'; // <--- How can I get this?
41+
$tocNumItems = (new TocGenerator($metaEntry))->getNumItemsPerLevel();
42+
43+
$numVisibleTocItems = 0;
44+
foreach ($tocNumItems as $level => $numItemsInThisLevel) {
45+
if ($level > $tocMaxDepth) {
46+
break;
47+
}
48+
49+
$numVisibleTocItems += $numItemsInThisLevel;
50+
}
51+
52+
$tocSize = $numVisibleTocItems < 10 ? 'md' : ($numVisibleTocItems < 20 ? 'lg' : 'xl');
53+
$node->setClasses(array_merge($node->getClasses(), ['toc-size-'.$tocSize]));
54+
}
55+
}

src/Templates/default/html/toc.html.twig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{% apply spaceless %}
2+
{# this defines the size of the TOC (which depends on how many visible items that TOC has)
3+
which is used to display TOCs differently (e.g. show large TOCs in 2 columns, etc.) #}
4+
{% set is_first_deep_level = 1 == (toc_deep_level ?? 1) %}
5+
{% if is_first_deep_level %}
6+
{% set toc_size = numOfVisibleTocItems < 10 ? 'md' : numOfVisibleTocItems < 20 ? 'lg' : 'xl' %}
7+
{% endif %}
8+
29
<div class="toctree-wrapper">
310
{% include "toc-level.html.twig" %}
411
</div>

0 commit comments

Comments
 (0)