UNPKG

generatoc

Version:

Automatically generate table of content from heading of HTML document

233 lines (230 loc) 8.54 kB
import { getScrollTop, elementOffset, hideAllTocSubHeading, praseH, lastBranches, lastLeaf, nestNode, getLastHeadingParentOf, throttle, scrollEaseOut, createUl, createLi } from './ultils/index.js'; var tocContent = ""; var tocHeader = ""; var tocSelector = "#toc"; var tocScrollOffset = 0; var tocDuration = 7; var tocFolded = false; var headingList = []; var headingNode; var scrollHistoryConfig = null; function handlePageChange() { var winScrollTop = getScrollTop(); var docHeight = document.body.offsetHeight; var scrollHeight = document.body.scrollHeight; var elem; window.requestAnimationFrame(function () { var closestAnchorDistance = null; var closestAnchorIdx = 0; var anchorText = null; headingNode.forEach(function (hNode, index) { var distance = Math.abs(elementOffset(hNode.nextElementSibling ? hNode.nextElementSibling : hNode).top - winScrollTop - tocScrollOffset); if (closestAnchorDistance == null || distance < closestAnchorDistance) { closestAnchorDistance = distance; closestAnchorIdx = index; } else { return false; } }); if (!headingNode[closestAnchorIdx]) return; anchorText = headingNode[closestAnchorIdx].innerText; var tocA = document.querySelector('a[data-toc-index="' + closestAnchorIdx + '"]'); if (!tocA) { return; } elem = tocA.closest("ul"); if (elem) { triggerShow(elem); } else { return; } activateElement(elem); if (scrollHistoryConfig) { if (typeof scrollHistoryConfig !== "boolean") { var replacePattern = scrollHistoryConfig.replacePattern, replacement = scrollHistoryConfig.replacement, readableSpace = scrollHistoryConfig.readableSpace; if (readableSpace) { anchorText = anchorText.replace(/\s/g, "-"); } else if (replacePattern instanceof RegExp && typeof replacement !== "undefined") { anchorText = anchorText.replace(replacePattern, replacement); } } window.location.replace("#" + anchorText); } }); } function scrollTo(index) { var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; var destination = elementOffset(headingNode[+index]).top - tocScrollOffset; scrollEaseOut(scrollTop, destination, tocDuration); } function scrollByHistory(title) { var decodedTitle = decodeURIComponent(title); var index = Array.prototype.findIndex.call(headingNode, function (i) { return i.innerText === decodedTitle; }); if (index !== -1) { scrollTo(index.toString()); } } function traceParentAndShow(ele) { if (ele.id !== tocSelector.substr(1)) { Array.prototype.forEach.call(ele.children, function (item) { if (item.tagName === "UL") { item.style.transform = "scaleY(1)"; item.style.maxHeight = "200px"; } }); traceParentAndShow(ele.parentElement); } } function showRealUlChildren(element) { if (!element || !element.children || element.children.length === 0) { return undefined; } if (element.tagName === "UL") { Array.prototype.forEach.call(element.children, function (child) { if (child.tagName === "UL") { child.style.transform = "scaleY(1)"; child.style.maxHeight = "200px"; } }); return showRealUlChildren(element.children[0]); } } function showUlChildren(ele) { triggerShow(ele); } function activateElement(element) { if (!Array.prototype.includes.call(element.classList, "active")) { element.querySelector("li").classList.add("active"); } } function triggerShow(element) { if (!element) return; var closestUl = element.tagName === "UL" ? element : element.closest("ul"); if (!closestUl) return; hideAllTocSubHeading(document.querySelector(tocSelector)); showRealUlChildren(closestUl.children[1]); traceParentAndShow(element); activateElement(element); } function constructElements(item) { var ul = createUl(); if (item.ele) { var li = createLi(item.ele.textContent, item.index); ul.append(li); } if (item.children.length > 0) { item.children.forEach(function (subHead) { ul.append(constructElements(subHead)); }); } return ul; } function processNode(node, preNode, heading, index) { var curHeadLevel = praseH(node.localName); var preHeadLevel = preNode ? praseH(preNode.localName) : 0; var item = { index: index, level: curHeadLevel, ele: null, children: [], }; if (curHeadLevel === preHeadLevel) { item.ele = node; item.level = curHeadLevel; lastBranches(heading).push(item); } else if (curHeadLevel > preHeadLevel) { var distance = curHeadLevel - preHeadLevel; lastLeaf(heading).push(nestNode(distance - 1, node, curHeadLevel, index)); } else { item.ele = node; getLastHeadingParentOf(curHeadLevel, heading, index).children.push(item); } } function handleClick(e) { var ele = e.target; if (ele.tagName !== "A") return; var index = ele.getAttribute("data-toc-index") || ""; scrollTo(index); var ul = ele.closest("ul"); if (ul) showUlChildren(ul); } function renderToc() { var tocElement = document.querySelector(tocSelector); if (tocElement === null) { return; } if (!headingList[0]) { return; } headingList[0].index = -1; Array.prototype.forEach.call(headingList[0].children, function (item) { tocElement.appendChild(constructElements(item)); }); tocElement.addEventListener("click", handleClick); if (headingNode.length > 0) { window.addEventListener("scroll", throttle(handlePageChange), false); } } var generatoc = { init: function (_a) { var content = _a.content, _b = _a.heading, heading = _b === void 0 ? ["h2", "h3", "h4", "h5"] : _b, _c = _a.selector, selector = _c === void 0 ? "#toc" : _c, _d = _a.scrollHistory, scrollHistory = _d === void 0 ? null : _d, _e = _a.scrollOffset, scrollOffset = _e === void 0 ? 0 : _e, _f = _a.duration, duration = _f === void 0 ? 7 : _f, _g = _a.fold, fold = _g === void 0 ? false : _g; tocSelector = selector; tocHeader = heading.join(","); tocContent = content; scrollHistoryConfig = scrollHistory; tocScrollOffset = scrollOffset; tocDuration = duration; tocFolded = fold; var postCotent = document.querySelector(tocContent); if (!postCotent) { return; } headingNode = postCotent.querySelectorAll(tocHeader); var previousNode; headingNode.forEach(function (hNode, index) { previousNode = index === 0 ? null : headingNode[index - 1]; processNode(hNode, previousNode, headingList, index); }); renderToc(); if (fold) handlePageChange(); if (typeof scrollHistoryConfig !== 'boolean' && (scrollHistoryConfig === null || scrollHistoryConfig === void 0 ? void 0 : scrollHistoryConfig.scrollToAfterMounted)) { scrollByHistory(window.location.hash.replace('#', '')); } }, destroy: function () { var tocElement = document.querySelector(tocSelector); if (!tocElement) { return; } tocElement.removeEventListener("click", handleClick); headingList = []; tocElement.innerHTML = ""; window.removeEventListener("scroll", handlePageChange); }, refresh: function () { generatoc.destroy(); generatoc.init({ content: tocContent, heading: tocHeader.split(","), selector: tocSelector, scrollOffset: tocScrollOffset, duration: tocDuration, fold: tocFolded, }); }, }; export default generatoc;