generatoc
Version:
Automatically generate table of content from heading of HTML document
233 lines (230 loc) • 8.54 kB
JavaScript
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;