Skip to content

Commit d412153

Browse files
authored
Merge pull request #8461 from marmelab/documentation-sidebar
[Doc] Show nav sidebar on main navigation page
2 parents 236992b + be32022 commit d412153

File tree

5 files changed

+209
-133
lines changed

5 files changed

+209
-133
lines changed

docs/_layouts/default.html

+16-44
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
/>
2121
<link
2222
rel="stylesheet"
23-
href="{{ '/css/style-v12.css' | relative_url }}"
23+
href="{{ '/css/style-v13.css' | relative_url }}"
2424
/>
2525
<link rel="stylesheet" href="{{ '/css/syntax.css' | relative_url }}" />
2626
<link rel="stylesheet" href="{{ '/css/prism.css' | relative_url }}" />
@@ -36,6 +36,9 @@
3636
<link
3737
rel="stylesheet"
3838
href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.css"
39+
integrity="sha512-2kqK5bWDwdrluTn6Wkj+jgiwyzvAHLirVp08Kz7kN5I8PWQ0tUtEMxfhyWDCrnws91BJ9PfrUy6ImfA7Qb22TA=="
40+
crossorigin="anonymous"
41+
referrerpolicy="no-referrer"
3942
/>
4043
<link
4144
rel="stylesheet"
@@ -76,46 +79,18 @@
7679

7780
<script src="{{ '/js/materialize.min.js' | relative_url }}"></script>
7881
<script src="{{ 'js/prism.js' | relative_url }}"></script>
79-
<script>
80-
function slugify(text) {
81-
return text
82-
.toString()
83-
.toLowerCase()
84-
.replace(/\s+/g, '-') // Replace spaces with -
85-
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
86-
.replace(/\-\-+/g, '-') // Replace multiple - with single -
87-
.replace(/^-+/, '') // Trim - from start of text
88-
.replace(/-+$/, ''); // Trim - from end of text
89-
}
90-
function buildPageToC() {
91-
M.Sidenav.init(document.querySelectorAll('.sidenav'));
92-
// Initialize version selector
93-
M.Dropdown.init(document.querySelectorAll('.dropdown-trigger'));
94-
/* Generate table of contents */
95-
tocbot.init({
96-
// Where to render the table of contents
97-
tocSelector: '.toc',
98-
positionFixedSelector: '.toc',
99-
// Where to grab the headings to build the table of contents
100-
contentSelector: '.toc-content',
101-
// More options
102-
headingSelector: 'h2, h3, h4',
103-
includeHtml: true,
104-
collapseDepth: 2,
105-
hasInnerContainers: true,
106-
});
107-
}
108-
// build ToC on page load
109-
document.addEventListener('DOMContentLoaded', function () {
110-
buildPageToC();
111-
});
112-
</script>
82+
<script
83+
src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.min.js"
84+
defer
85+
integrity="sha512-oD3xGN8YTxenx6tdS4D/RKqO4OtORBQtAb2/FseM17GGMIi6jMwKUBc8duX4A5RwMOGGXoFBZrsqbOk8GpXFgQ=="
86+
crossorigin="anonymous"
87+
referrerpolicy="no-referrer"
88+
></script>
89+
<script src="{{ 'js/ra-doc-exec.js' | relative_url }}"></script>
90+
11391
<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@3"></script>
114-
{% if page.dir contains "doc" %}
115-
{% assign version = page.dir | split: '/' | last %}
116-
{% else %}
117-
{% assign version = "latest" %}
118-
{% endif %}
92+
{% if page.dir contains "doc" %} {% assign version = page.dir | split:
93+
'/' | last %} {% else %} {% assign version = "latest" %} {% endif %}
11994
<script type="text/javascript">
12095
docsearch({
12196
container: '#docsearch',
@@ -125,10 +100,7 @@
125100
searchParameters: { facetFilters: ['version: {{ version }}'] },
126101
});
127102
</script>
128-
<script
129-
src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.min.js"
130-
defer
131-
></script>
103+
132104
<!--
133105
React-admin protects your privacy!
134106
We use our own self-hosted tracking solution to collect some raw metrics,

docs/css/style-v12.css docs/css/style-v13.css

+1
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ ul.sidenav code {
484484
.docBlocks a:hover {
485485
border: #c0c0c0 1px solid;
486486
box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.1);
487+
text-decoration: none;
487488
}
488489

489490
.docBlocks a .docBlock {

docs/documentation.html

+37-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/>
2020
<link
2121
rel="stylesheet"
22-
href="{{ '/css/style-v12.css' | relative_url }}"
22+
href="{{ '/css/style-v13.css' | relative_url }}"
2323
/>
2424
<link rel="stylesheet" href="{{ '/css/syntax.css' | relative_url }}" />
2525
<link rel="stylesheet" href="{{ '/css/prism.css' | relative_url }}" />
@@ -39,8 +39,20 @@
3939
<link rel="stylesheet" href="css/docsearch.css" />
4040
</head>
4141

42-
<body class="no-sidebar">
43-
{% include nav.html %}
42+
<body>
43+
<header>
44+
{% include nav.html %}
45+
<ul id="slide-out" class="sidenav sidenav-fixed">
46+
<li class="logo">
47+
<a href="{{ site.url }}"
48+
><img src="{{ site.url }}/assets/logo_white.png"
49+
/></a>
50+
</li>
51+
<li class="version">{% include versions.html %}</li>
52+
{% include_relative navigation.html %}
53+
</ul>
54+
</header>
55+
4456
<main>
4557
<div class="container">
4658
<div class="row nav_root">
@@ -129,7 +141,7 @@ <h2>Fields</h2>
129141
<div class="material-icons">&#xe8f4;</div>
130142
</a>
131143

132-
<a href="./Inputs.html">
144+
<a class="nav-link" href="./Inputs.html">
133145
<div class="docBlock">
134146
<h2>Inputs</h2>
135147
<code>&lt;TextInput&gt;</code>,
@@ -181,17 +193,32 @@ <h2>Recipes</h2>
181193
<div class="material-icons">&#xe1bd;</div>
182194
</a>
183195
</div>
196+
<div class="col s12 m9">
197+
<div
198+
class="markdown-section DocSearch-content toc-content"
199+
></div>
200+
</div>
201+
<div class="toc-container">
202+
<div class="toc"></div>
203+
</div>
184204
</div>
185205
</div>
186206
</main>
187207

188208
<script src="js/materialize.min.js"></script>
209+
<script src="js/prism.js"></script>
210+
<script
211+
src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.min.js"
212+
defer
213+
integrity="sha512-oD3xGN8YTxenx6tdS4D/RKqO4OtORBQtAb2/FseM17GGMIi6jMwKUBc8duX4A5RwMOGGXoFBZrsqbOk8GpXFgQ=="
214+
crossorigin="anonymous"
215+
referrerpolicy="no-referrer"
216+
></script>
217+
<script src="js/ra-doc-exec.js"></script>
218+
189219
<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@3"></script>
190-
{% if page.dir contains "doc" %}
191-
{% assign version = page.dir | split: '/' | last %}
192-
{% else %}
193-
{% assign version = "latest" %}
194-
{% endif %}
220+
{% if page.dir contains "doc" %} {% assign version = page.dir | split:
221+
'/' | last %} {% else %} {% assign version = "latest" %} {% endif %}
195222
<script type="text/javascript">
196223
docsearch({
197224
container: '#docsearch',
@@ -201,6 +228,7 @@ <h2>Recipes</h2>
201228
searchParameters: { facetFilters: ['version: {{ version }}'] },
202229
});
203230
</script>
231+
204232
<!--
205233
React-admin protects your privacy!
206234
We use our own self-hosted tracking solution to collect some raw metrics,

docs/js/ra-doc-exec.js

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
var allMenus, navLinks, versionsLinks;
2+
3+
// eslint-disable-next-line no-unused-vars
4+
function slugify(text) {
5+
return text
6+
.toString()
7+
.toLowerCase()
8+
.replace(/\s+/g, '-') // Replace spaces with -
9+
.replace(/[^\w-]+/g, '') // Remove all non-word chars
10+
.replace(/--+/g, '-') // Replace multiple - with single -
11+
.replace(/^-+/, '') // Trim - from start of text
12+
.replace(/-+$/, ''); // Trim - from end of text
13+
}
14+
15+
function navigationFitScroll() {
16+
var scrollIntoView = window.sessionStorage.getItem('scrollIntoView');
17+
if (true || scrollIntoView !== 'false') {
18+
var activeMenu = document.querySelector('.sidenav li.active');
19+
if (activeMenu) activeMenu.parentNode.scrollIntoView();
20+
}
21+
window.sessionStorage.removeItem('scrollIntoView');
22+
}
23+
24+
function buildPageToC() {
25+
var M = window.M;
26+
M.Sidenav.init(document.querySelectorAll('.sidenav'));
27+
// Initialize version selector
28+
M.Dropdown.init(document.querySelectorAll('.dropdown-trigger'));
29+
30+
var Prism = window.Prism;
31+
Prism.highlightAll();
32+
33+
/* Generate table of contents */
34+
if (document.querySelector('.toc') != null) {
35+
var tocbot = window.tocbot;
36+
tocbot.init({
37+
// Where to render the table of contents
38+
tocSelector: '.toc',
39+
positionFixedSelector: '.toc',
40+
// Where to grab the headings to build the table of contents
41+
contentSelector: '.toc-content',
42+
// More options
43+
headingSelector: 'h2, h3, h4',
44+
includeHtml: true,
45+
collapseDepth: 2,
46+
hasInnerContainers: true,
47+
});
48+
}
49+
}
50+
51+
function replaceContent(text) {
52+
var tocContainer = document.querySelector('.toc-container');
53+
tocContainer.className =
54+
text.trim() !== ''
55+
? 'toc-container col hide-on-small-only m3'
56+
: 'toc-container';
57+
58+
var tmpElement = document.createElement('div');
59+
tmpElement.innerHTML = text;
60+
61+
toggleDockBlocks(false);
62+
63+
var content = document.querySelector('.DocSearch-content');
64+
content.innerHTML = tmpElement.querySelector(
65+
'.DocSearch-content'
66+
).innerHTML;
67+
68+
window.scrollTo(0, 0);
69+
70+
buildPageToC();
71+
72+
navigationFitScroll();
73+
}
74+
75+
function changeSelectedMenu() {
76+
var activeMenu = document.querySelector(`.sidenav li.active`);
77+
activeMenu && activeMenu.classList.remove('active');
78+
allMenus
79+
.find(menuEl => menuEl.href === window.location.href)
80+
.parentNode.classList.add('active');
81+
}
82+
83+
function toggleDockBlocks(status) {
84+
var docBlock = document.querySelector('.docBlocks');
85+
if (!docBlock) return;
86+
docBlock.style.display = status ? 'grid' : 'none';
87+
}
88+
89+
// Replace full page reloads by a fill of the content area
90+
// so that the side navigation keeps its state
91+
// use a global event listener to also catch links inside the content area
92+
document.addEventListener('click', event => {
93+
var link = event.target.closest('a');
94+
if (!link) {
95+
return; // click not on a link
96+
}
97+
var location = document.location.href.split('#')[0];
98+
var href = link.href;
99+
if (href.indexOf(`${location}#`) === 0) {
100+
return; // click on an anchor in the current page
101+
}
102+
if (!navLinks.includes(href)) {
103+
return; // not a navigation link
104+
}
105+
window.sessionStorage.setItem(
106+
'scrollIntoView',
107+
link.closest('.sidenav') ? 'false' : 'true'
108+
);
109+
// now we're sure it's an internal navigation link
110+
// transform it to an AJAX call
111+
event.preventDefault();
112+
// update versions links
113+
var currentPage = href.split('/').pop();
114+
versionsLinks.forEach(link => {
115+
link.href =
116+
link.href.substr(0, link.href.lastIndexOf('/') + 1) + currentPage;
117+
});
118+
// fetch the new content
119+
fetch(href)
120+
.then(res => res.text())
121+
.then(replaceContent);
122+
// change the URL
123+
window.history.pushState(null, null, href);
124+
changeSelectedMenu();
125+
});
126+
127+
// make back button work again
128+
window.addEventListener('popstate', event => {
129+
if (document.location.href.indexOf('#') !== -1) {
130+
// popstate triggered by a click on an anchor, not back button
131+
return;
132+
}
133+
if (window.location.pathname === '/documentation.html') {
134+
document.querySelector('.DocSearch-content').innerHTML = '';
135+
toggleDockBlocks(true);
136+
} else {
137+
// fetch the new content
138+
fetch(window.location.pathname)
139+
.then(res => res.text())
140+
.then(replaceContent);
141+
}
142+
changeSelectedMenu();
143+
});
144+
145+
window.addEventListener('DOMContentLoaded', () => {
146+
allMenus = Array.from(document.querySelectorAll(`.sidenav a.nav-link`));
147+
navLinks = allMenus
148+
.filter(link => !link.classList.contains('external'))
149+
.map(link => link.href);
150+
versionsLinks = Array.from(document.querySelectorAll('#versions > li > a'));
151+
152+
buildPageToC();
153+
154+
navigationFitScroll();
155+
});

0 commit comments

Comments
 (0)