Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: make header smaller and dropdown click-driven when JS is on #42165

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -7,4 +7,5 @@ tools/icu
tools/lint-md/lint-md.mjs
benchmark/tmp
doc/**/*.js
!doc/api_assets/*.js
!.eslintrc.js
4 changes: 4 additions & 0 deletions doc/api_assets/README.md
@@ -1,5 +1,9 @@
# API Reference Document Assets

## api.js

The main script for API reference documents.

## hljs.css

The syntax theme for code snippets in API reference documents.
Expand Down
141 changes: 141 additions & 0 deletions doc/api_assets/api.js
@@ -0,0 +1,141 @@
'use strict';

{
function setupTheme() {
const kCustomPreference = 'customDarkTheme';
const userSettings = sessionStorage.getItem(kCustomPreference);
const themeToggleButton = document.getElementById('theme-toggle-btn');

if (userSettings === null && window.matchMedia) {
const mq = window.matchMedia('(prefers-color-scheme: dark)');

if ('onchange' in mq) {
function mqChangeListener(e) {
document.documentElement.classList.toggle('dark-mode', e.matches);
}
mq.addEventListener('change', mqChangeListener);
if (themeToggleButton) {
themeToggleButton.addEventListener('click', function() {
mq.removeEventListener('change', mqChangeListener);
}, { once: true });
}
}

if (mq.matches) {
document.documentElement.classList.add('dark-mode');
}
} else if (userSettings === 'true') {
document.documentElement.classList.add('dark-mode');
}

if (themeToggleButton) {
themeToggleButton.hidden = false;
themeToggleButton.addEventListener('click', function() {
sessionStorage.setItem(
kCustomPreference,
document.documentElement.classList.toggle('dark-mode')
);
});
}
}

function setupPickers() {
function closeAllPickers() {
for (const picker of pickers) {
picker.parentNode.classList.remove('expanded');
}

window.removeEventListener('click', closeAllPickers);
window.removeEventListener('keydown', onKeyDown);
}

function onKeyDown(e) {
if (e.key === 'Escape') {
closeAllPickers();
}
}

const pickers = document.querySelectorAll('.picker-header > a');

for (const picker of pickers) {
const parentNode = picker.parentNode;

picker.addEventListener('click', (e) => {
e.preventDefault();

/*
closeAllPickers as window event trigger already closed all the pickers,
if it already closed there is nothing else to do here
*/
if (parentNode.classList.contains('expanded')) {
return;
}

/*
In the next frame reopen the picker if needed and also setup events
to close pickers if needed.
*/

requestAnimationFrame(() => {
parentNode.classList.add('expanded');
window.addEventListener('click', closeAllPickers);
window.addEventListener('keydown', onKeyDown);
});
});
}
}

function setupStickyHeaders() {
const header = document.querySelector('.header');
let ignoreNextIntersection = false;

new IntersectionObserver(
([e]) => {
const currentStatus = header.classList.contains('is-pinned');
const newStatus = e.intersectionRatio < 1;

// Same status, do nothing
if (currentStatus === newStatus) {
return;
} else if (ignoreNextIntersection) {
ignoreNextIntersection = false;
return;
}

/*
To avoid flickering, ignore the next changes event that is triggered
as the visible elements in the header change once we pin it.

The timer is reset anyway after few milliseconds.
*/
ignoreNextIntersection = true;
setTimeout(() => {
ignoreNextIntersection = false;
}, 50);

header.classList.toggle('is-pinned', newStatus);
},
{ threshold: [1] }
).observe(header);
}

function bootstrap() {
// Check if we have JavaScript support
document.documentElement.classList.add('has-js');

// Restore user mode preferences
setupTheme();

// Handle pickers with click/taps rather than hovers
setupPickers();

// Track when the header is in sticky position
setupStickyHeaders();
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
} else {
bootstrap();
}
}
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
47 changes: 42 additions & 5 deletions doc/api_assets/style.css
Expand Up @@ -189,19 +189,23 @@ li.picker-header .expanded-arrow {
display: none;
}

li.picker-header:hover .collapsed-arrow {
li.picker-header.expanded .collapsed-arrow,
:root:not(.has-js) li.picker-header:hover .collapsed-arrow {
display: none;
}

li.picker-header:hover .expanded-arrow {
li.picker-header.expanded .expanded-arrow,
:root:not(.has-js) li.picker-header:hover .expanded-arrow {
display: inline-block;
}

li.picker-header:hover > a {
li.picker-header.expanded > a,
:root:not(.has-js) li.picker-header:hover > a {
border-radius: 2px 2px 0 0;
}

li.picker-header:hover > .picker {
li.picker-header.expanded > .picker,
:root:not(.has-js) li.picker-header:hover > .picker {
display: block;
z-index: 1;
}
Expand Down Expand Up @@ -807,13 +811,38 @@ kbd {
background-color: var(--color-fill-app);
}

@media not screen, (max-height: 1000px) {
@media not screen, (max-width: 600px) {
.header {
position: relative;
top: 0;
}
}

@media not screen, (max-height: 1000px) {
:root:not(.has-js) .header {
position: relative;
top: 0;
}
}

.header .pinned-header {
display: none;
margin-right: 0.4rem;
font-weight: 700;
}

.header.is-pinned .header-container {
display: none;
}

.header.is-pinned .pinned-header {
display: inline;
}

.header.is-pinned #gtoc {
margin: 0;
}

.header-container {
display: flex;
align-items: center;
Expand Down Expand Up @@ -845,6 +874,14 @@ kbd {
padding-right: 0;
}

.header #gtoc > ul > li.pinned-header {
display: none;
}

.header.is-pinned #gtoc > ul > li.pinned-header {
display: inline;
}

#gtoc > ul > li.gtoc-picker-header {
display: none;
}
Expand Down
38 changes: 2 additions & 36 deletions doc/template.html
Expand Up @@ -9,6 +9,7 @@
<link rel="stylesheet" href="assets/style.css">
<link rel="stylesheet" href="assets/hljs.css">
<link rel="canonical" href="https://nodejs.org/api/__FILENAME__.html">
<script async defer src="assets/api.js" type="text/javascript"></script>
</head>
<body class="alt apidoc" id="api-section-__FILENAME__">
<div id="content" class="clearfix">
Expand Down Expand Up @@ -39,6 +40,7 @@ <h1>Node.js __VERSION__ documentation</h1>
</div>
<div id="gtoc">
<ul>
<li class="pinned-header">Node.js __VERSION__</li>
__TOC_PICKER__
__GTOC_PICKER__
__ALTDOCS__
Expand Down Expand Up @@ -73,41 +75,5 @@ <h1>Node.js __VERSION__ documentation</h1>
</div>
</div>
</div>
<script>
'use strict';
{
const kCustomPreference = 'customDarkTheme';
const userSettings = sessionStorage.getItem(kCustomPreference);
const themeToggleButton = document.getElementById('theme-toggle-btn');
if (userSettings === null && window.matchMedia) {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
if ('onchange' in mq) {
function mqChangeListener(e) {
document.documentElement.classList.toggle('dark-mode', e.matches);
}
mq.addEventListener('change', mqChangeListener);
if (themeToggleButton) {
themeToggleButton.addEventListener('click', function() {
mq.removeEventListener('change', mqChangeListener);
}, { once: true });
}
}
if (mq.matches) {
document.documentElement.classList.add('dark-mode');
}
} else if (userSettings === 'true') {
document.documentElement.classList.add('dark-mode');
}
if (themeToggleButton) {
themeToggleButton.hidden = false;
themeToggleButton.addEventListener('click', function() {
sessionStorage.setItem(
kCustomPreference,
document.documentElement.classList.toggle('dark-mode')
);
});
}
}
</script>
</body>
</html>