Skip to content

Commit e820a08

Browse files
ShogunPandaguangwong
authored andcommittedOct 10, 2022
doc: make header smaller and dropdown click-driven when JS is on
PR-URL: nodejs/node#42165 Fixes: nodejs/node#42286 Reviewed-By: Antoine du Hamel <[email protected]>

File tree

5 files changed

+190
-41
lines changed

5 files changed

+190
-41
lines changed
 

‎.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ tools/icu
77
tools/lint-md/lint-md.mjs
88
benchmark/tmp
99
doc/**/*.js
10+
!doc/api_assets/*.js
1011
!.eslintrc.js

‎doc/api_assets/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# API Reference Document Assets
22

3+
## api.js
4+
5+
The main script for API reference documents.
6+
37
## hljs.css
48

59
The syntax theme for code snippets in API reference documents.

‎doc/api_assets/api.js

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use strict';
2+
3+
{
4+
function setupTheme() {
5+
const kCustomPreference = 'customDarkTheme';
6+
const userSettings = sessionStorage.getItem(kCustomPreference);
7+
const themeToggleButton = document.getElementById('theme-toggle-btn');
8+
9+
if (userSettings === null && window.matchMedia) {
10+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
11+
12+
if ('onchange' in mq) {
13+
function mqChangeListener(e) {
14+
document.documentElement.classList.toggle('dark-mode', e.matches);
15+
}
16+
mq.addEventListener('change', mqChangeListener);
17+
if (themeToggleButton) {
18+
themeToggleButton.addEventListener('click', function() {
19+
mq.removeEventListener('change', mqChangeListener);
20+
}, { once: true });
21+
}
22+
}
23+
24+
if (mq.matches) {
25+
document.documentElement.classList.add('dark-mode');
26+
}
27+
} else if (userSettings === 'true') {
28+
document.documentElement.classList.add('dark-mode');
29+
}
30+
31+
if (themeToggleButton) {
32+
themeToggleButton.hidden = false;
33+
themeToggleButton.addEventListener('click', function() {
34+
sessionStorage.setItem(
35+
kCustomPreference,
36+
document.documentElement.classList.toggle('dark-mode')
37+
);
38+
});
39+
}
40+
}
41+
42+
function setupPickers() {
43+
function closeAllPickers() {
44+
for (const picker of pickers) {
45+
picker.parentNode.classList.remove('expanded');
46+
}
47+
48+
window.removeEventListener('click', closeAllPickers);
49+
window.removeEventListener('keydown', onKeyDown);
50+
}
51+
52+
function onKeyDown(e) {
53+
if (e.key === 'Escape') {
54+
closeAllPickers();
55+
}
56+
}
57+
58+
const pickers = document.querySelectorAll('.picker-header > a');
59+
60+
for (const picker of pickers) {
61+
const parentNode = picker.parentNode;
62+
63+
picker.addEventListener('click', (e) => {
64+
e.preventDefault();
65+
66+
/*
67+
closeAllPickers as window event trigger already closed all the pickers,
68+
if it already closed there is nothing else to do here
69+
*/
70+
if (parentNode.classList.contains('expanded')) {
71+
return;
72+
}
73+
74+
/*
75+
In the next frame reopen the picker if needed and also setup events
76+
to close pickers if needed.
77+
*/
78+
79+
requestAnimationFrame(() => {
80+
parentNode.classList.add('expanded');
81+
window.addEventListener('click', closeAllPickers);
82+
window.addEventListener('keydown', onKeyDown);
83+
});
84+
});
85+
}
86+
}
87+
88+
function setupStickyHeaders() {
89+
const header = document.querySelector('.header');
90+
let ignoreNextIntersection = false;
91+
92+
new IntersectionObserver(
93+
([e]) => {
94+
const currentStatus = header.classList.contains('is-pinned');
95+
const newStatus = e.intersectionRatio < 1;
96+
97+
// Same status, do nothing
98+
if (currentStatus === newStatus) {
99+
return;
100+
} else if (ignoreNextIntersection) {
101+
ignoreNextIntersection = false;
102+
return;
103+
}
104+
105+
/*
106+
To avoid flickering, ignore the next changes event that is triggered
107+
as the visible elements in the header change once we pin it.
108+
109+
The timer is reset anyway after few milliseconds.
110+
*/
111+
ignoreNextIntersection = true;
112+
setTimeout(() => {
113+
ignoreNextIntersection = false;
114+
}, 50);
115+
116+
header.classList.toggle('is-pinned', newStatus);
117+
},
118+
{ threshold: [1] }
119+
).observe(header);
120+
}
121+
122+
function bootstrap() {
123+
// Check if we have JavaScript support
124+
document.documentElement.classList.add('has-js');
125+
126+
// Restore user mode preferences
127+
setupTheme();
128+
129+
// Handle pickers with click/taps rather than hovers
130+
setupPickers();
131+
132+
// Track when the header is in sticky position
133+
setupStickyHeaders();
134+
}
135+
136+
if (document.readyState === 'loading') {
137+
document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
138+
} else {
139+
bootstrap();
140+
}
141+
}

‎doc/api_assets/style.css

+42-5
Original file line numberDiff line numberDiff line change
@@ -189,19 +189,23 @@ li.picker-header .expanded-arrow {
189189
display: none;
190190
}
191191

192-
li.picker-header:hover .collapsed-arrow {
192+
li.picker-header.expanded .collapsed-arrow,
193+
:root:not(.has-js) li.picker-header:hover .collapsed-arrow {
193194
display: none;
194195
}
195196

196-
li.picker-header:hover .expanded-arrow {
197+
li.picker-header.expanded .expanded-arrow,
198+
:root:not(.has-js) li.picker-header:hover .expanded-arrow {
197199
display: inline-block;
198200
}
199201

200-
li.picker-header:hover > a {
202+
li.picker-header.expanded > a,
203+
:root:not(.has-js) li.picker-header:hover > a {
201204
border-radius: 2px 2px 0 0;
202205
}
203206

204-
li.picker-header:hover > .picker {
207+
li.picker-header.expanded > .picker,
208+
:root:not(.has-js) li.picker-header:hover > .picker {
205209
display: block;
206210
z-index: 1;
207211
}
@@ -807,13 +811,38 @@ kbd {
807811
background-color: var(--color-fill-app);
808812
}
809813

810-
@media not screen, (max-height: 1000px) {
814+
@media not screen, (max-width: 600px) {
811815
.header {
812816
position: relative;
813817
top: 0;
814818
}
815819
}
816820

821+
@media not screen, (max-height: 1000px) {
822+
:root:not(.has-js) .header {
823+
position: relative;
824+
top: 0;
825+
}
826+
}
827+
828+
.header .pinned-header {
829+
display: none;
830+
margin-right: 0.4rem;
831+
font-weight: 700;
832+
}
833+
834+
.header.is-pinned .header-container {
835+
display: none;
836+
}
837+
838+
.header.is-pinned .pinned-header {
839+
display: inline;
840+
}
841+
842+
.header.is-pinned #gtoc {
843+
margin: 0;
844+
}
845+
817846
.header-container {
818847
display: flex;
819848
align-items: center;
@@ -845,6 +874,14 @@ kbd {
845874
padding-right: 0;
846875
}
847876

877+
.header #gtoc > ul > li.pinned-header {
878+
display: none;
879+
}
880+
881+
.header.is-pinned #gtoc > ul > li.pinned-header {
882+
display: inline;
883+
}
884+
848885
#gtoc > ul > li.gtoc-picker-header {
849886
display: none;
850887
}

‎doc/template.html

+2-36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<link rel="stylesheet" href="assets/style.css">
1010
<link rel="stylesheet" href="assets/hljs.css">
1111
<link rel="canonical" href="https://nodejs.org/api/__FILENAME__.html">
12+
<script async defer src="assets/api.js" type="text/javascript"></script>
1213
</head>
1314
<body class="alt apidoc" id="api-section-__FILENAME__">
1415
<div id="content" class="clearfix">
@@ -39,6 +40,7 @@ <h1>Node.js __VERSION__ documentation</h1>
3940
</div>
4041
<div id="gtoc">
4142
<ul>
43+
<li class="pinned-header">Node.js __VERSION__</li>
4244
__TOC_PICKER__
4345
__GTOC_PICKER__
4446
__ALTDOCS__
@@ -73,41 +75,5 @@ <h1>Node.js __VERSION__ documentation</h1>
7375
</div>
7476
</div>
7577
</div>
76-
<script>
77-
'use strict';
78-
{
79-
const kCustomPreference = 'customDarkTheme';
80-
const userSettings = sessionStorage.getItem(kCustomPreference);
81-
const themeToggleButton = document.getElementById('theme-toggle-btn');
82-
if (userSettings === null && window.matchMedia) {
83-
const mq = window.matchMedia('(prefers-color-scheme: dark)');
84-
if ('onchange' in mq) {
85-
function mqChangeListener(e) {
86-
document.documentElement.classList.toggle('dark-mode', e.matches);
87-
}
88-
mq.addEventListener('change', mqChangeListener);
89-
if (themeToggleButton) {
90-
themeToggleButton.addEventListener('click', function() {
91-
mq.removeEventListener('change', mqChangeListener);
92-
}, { once: true });
93-
}
94-
}
95-
if (mq.matches) {
96-
document.documentElement.classList.add('dark-mode');
97-
}
98-
} else if (userSettings === 'true') {
99-
document.documentElement.classList.add('dark-mode');
100-
}
101-
if (themeToggleButton) {
102-
themeToggleButton.hidden = false;
103-
themeToggleButton.addEventListener('click', function() {
104-
sessionStorage.setItem(
105-
kCustomPreference,
106-
document.documentElement.classList.toggle('dark-mode')
107-
);
108-
});
109-
}
110-
}
111-
</script>
11278
</body>
11379
</html>

0 commit comments

Comments
 (0)
Please sign in to comment.