documentation/extensions/odoo_theme/static/js/page_toc.js
Elisabeth Dickinson 07e943f85c [IMP] odoo_theme: show the "On this page" section in mobile
When the page gets too small for the "On this page" section (local tree
of content) to fit on the page, the section is now moved above the menu
(global tree of content) rather than being hidden.

task-2800970

closes odoo/documentation#2194

X-original-commit: c0040fa532
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
Co-authored-by: Antoine Vandevenne (anv) <anv@odoo.com>
2022-06-14 07:42:03 +02:00

138 lines
5.6 KiB
JavaScript

/* global _prepareAccordion */ //see utils.js
(function ($) {
// Customize the page TOC
document.addEventListener('DOMContentLoaded', () => {
// Loop on all tree of content of the page. There may be from 0 to 2 depending on the page.
document.querySelectorAll('.o_page_toc').forEach(pageToc => {
const headingRefs = pageToc.querySelectorAll('a'); // The references to all headings.
// If the page TOC has less than 2 headings, in addition to the title, hide it entirely.
if (headingRefs.length <= 2) {
_hidePageToc(pageToc);
return;
}
// Allow to automatically collapse and expand TOC entries
_prepareAccordion(pageToc);
// Allow to respectively highlight and expand the TOC entries and their related TOC
// entry list whose section is focused.
_flagActiveTocEntriesAndLists(pageToc, headingRefs);
// Allow to hide the TOC entry referring the title (<h1> heading)
_flagFirstHeadingRef(headingRefs);
// Show hidden menu when the css classes have been properly specified
pageToc.removeAttribute('hidden');
});
});
/**
* Entirely hide the local tree of contents.
*
* @param {HTMLElement} pageToc - The tree of content of the page.
*/
const _hidePageToc = pageToc => pageToc.style.display = 'none';
/**
* Add the relevant classes on the TOC entries (and lists) whose section is focused.
*
* TOC entries whose section is focused (<li> elements) receive the `o_active_toc_entry` class
* and their related TOC entry list (<ul> elements) receive the `show` (Bootstrap) class.
*
* @param {HTMLElement} pageToc - The tree of content of the page.
* @param {NodeList} headingRefs - The references to all headings.
*/
const _flagActiveTocEntriesAndLists = (pageToc, headingRefs) => {
const _updateFlags = () => {
const activeHeadingRef = clickedHeadingRef || _findActiveHeadingRef();
if (
lastActiveHeadingRef // `undefined` on the first update
&& activeHeadingRef.href === lastActiveHeadingRef.href
) {
return; // The focus didn't change
}
_unflagAll();
_flagActiveHierarchy(activeHeadingRef);
// Store to avoid updating later if the focus didn't change
lastActiveHeadingRef = activeHeadingRef;
};
const _findActiveHeadingRef = () => {
let activeHeadingRef = headingRefs[0];
headingRefs.forEach(headingRef => {
const href = headingRef.getAttribute('href');
if (href !== '#') {
const sectionId = href.replace('#', '');
// The DOM is searched with querySelector rather than getElementById because
// auto-documented modules generate ids containing a '.' which would make the
// search fail.
const section = document.querySelector(`section[id="${sectionId}"]`);
if (window.pageYOffset >= section.offsetTop) {
// The focused section is the last one with a smaller offset from top than
// the current user scrolling offset.
activeHeadingRef = headingRef;
} else {
return activeHeadingRef; // Break
}
}
});
return activeHeadingRef;
};
const _unflagAll = () => {
pageToc.querySelectorAll('li,ul').forEach(element => {
element.classList.remove('o_active_toc_entry', 'show');
});
pageToc.querySelectorAll('i').forEach(element => {
element.setAttribute('aria-expanded', false);
});
};
const _flagActiveHierarchy = (headingRef) => {
let tocEntry = headingRef.parentElement;
while (tocEntry !== pageToc) {
if (tocEntry.tagName === 'LI') {
// Highlight all <li> in the active hierarchy
tocEntry.classList.add('o_active_toc_entry');
// Expand all related <ul>
const relatedTocEntryList = tocEntry.querySelector('ul');
if (relatedTocEntryList) {
relatedTocEntryList.classList.add('show');
}
}
tocEntry = tocEntry.parentElement;
}
};
let clickedHeadingRef = undefined;
pageToc.addEventListener('click', ev => {
clickedHeadingRef = ev.target.closest('a[href^="#"]'); // Highlight the clicked ref
});
let timeoutId = undefined;
document.addEventListener('scroll', () => {
clearTimeout(timeoutId); // For each scroll event, cancel the previous timeout callback
timeoutId = setTimeout(() => {
clickedHeadingRef = undefined; // Go back to highlighting the heading ref in view
}, 100);
_updateFlags();
});
let lastActiveHeadingRef = undefined; // Init as `undefined` to allow an initial update
_updateFlags(); // Flag initially active sections before the first scroll event
};
/**
* Add the class `o_page_toc_title` on the first heading reference.
*
* @param {NodeList} headingRefs - The references to all headings.
*/
const _flagFirstHeadingRef = headingRefs => {
headingRefs[0].parentNode.classList.add('o_page_toc_title');
}
})();