';
+ }
+ // Skip smaller headings (if there are any) to avoid making list too long
+ });
+ var ToCList = document.getElementById('ToCList');
+ ToCList.style.maxHeight = ~~(window.innerHeight * 0.75) + 'px';
+ ToCList.innerHTML = dropupHtml;
+ Array.from(ToCList.getElementsByTagName('a'))
+ .forEach(function (listElement) {
+ listElement.addEventListener('click', function () {
+ var sectionEle = innerDoc.getElementById(this.dataset.headingId)
+
+ var sectionsToOpen = getParentSections(sectionEle); // get all parents which are 'section' or 'details'
+ openSection(sectionsToOpen); // open all parents
+ // why..? because if the section is inside a details element, it will be closed by default
+
+ sectionEle.scrollIntoView();
+
+ // highlighting the section
+ sectionEle.style.backgroundColor = '#bdd1e5';
+ setTimeout(function () {
+ sectionEle.style.backgroundColor = '';
+ }, 400);
+ sectionEle.style.transition = 'background-color 300ms ease-out';
+ });
+ });
+}
+
+// get all parent elements which are 'section' or 'details'
+function getParentSections (element) {
+ const parents = [];
+ let currentElement = element;
+ while (currentElement) {
+ if (currentElement.matches('section, details')) {
+ parents.push(currentElement);
+ }
+ currentElement = currentElement.parentElement;
+ }
+ return parents;
+};
+
+// Function to open a specific section and all its parent sections
+function openSection (sectionsToOpen) {
+ if (!sectionsToOpen) return;
+ sectionsToOpen.forEach(section => {
+ if (section.tagName === 'DETAILS') {
+ section.setAttribute('open', '');
+ } else if (section.tagName === 'SECTION') {
+ section.style.display = '';
+ Array.from(section.children).forEach(child => {
+ if (!/SUMMARY|H\d/.test(child.tagName)) {
+ child.style.display = '';
+ }
+ });
+ }
+ });
+};
+
/**
* Extracts the content of the given article pathname, or a downloadable file, from the ZIM
*
diff --git a/www/js/lib/uiUtil.js b/www/js/lib/uiUtil.js
index 007054117..12bb5a7fc 100644
--- a/www/js/lib/uiUtil.js
+++ b/www/js/lib/uiUtil.js
@@ -172,6 +172,43 @@ function slideAway (e) {
}
}
+/*
+ * Returns a list of headings from an article
+ * @param {String} the page for which table of cotents needs to be listed
+ * @returns {List} a list of all headings as objects
+*/
+function TableOfContents (articleDoc) {
+ this.doc = articleDoc;
+ this.headings = this.doc.querySelectorAll('h1, h2, h3, h4, h5, h6');
+
+ this.getHeadingObjects = function () {
+ var headings = [];
+ for (var i = 0; i < this.headings.length; i++) {
+ var element = this.headings[i];
+ var obj = {};
+
+ if (element.id) {
+ obj.id = element.id;
+ } else {
+ // generating custom id if id attribute is not present in element
+ var generatedId = element.textContent
+ .toLowerCase()
+ .trim()
+ .replace(/[^\w\s-]/g, '')
+ .replace(/\s+/g, '-')
+ .replace(/-+/g, '-');
+ obj.id = 'pph-' + i + '-' + generatedId;
+ element.id = obj.id; // to target the element
+ }
+ obj.index = i;
+ obj.textContent = element.textContent;
+ obj.tagName = element.tagName;
+ headings.push(obj);
+ }
+ return headings;
+ };
+}
+
/**
* Displays a Bootstrap alert or confirm dialog box depending on the options provided
*
@@ -1083,6 +1120,7 @@ export default {
determineCanvasElementsWorkaround: determineCanvasElementsWorkaround,
replaceCSSLinkWithInlineCSS: replaceCSSLinkWithInlineCSS,
deriveZimUrlFromRelativeUrl: deriveZimUrlFromRelativeUrl,
+ TOC: TableOfContents,
removeUrlParameters: removeUrlParameters,
displayActiveContentWarning: displayActiveContentWarning,
displayFileDownloadAlert: displayFileDownloadAlert,