* feat: Waterfox custom CSS on non-default themes by default * feat: Table of Contents to preferences * feat: DoOH checkbox to privacy preferences * feat panel and menu transparency options * feat: Look & Feel about:preferences item
1364 lines
47 KiB
JavaScript
1364 lines
47 KiB
JavaScript
/******/ (() => {
|
|
// webpackBootstrap
|
|
/******/ var __webpack_modules__ = {
|
|
/***/ "./src/js/build-html.js":
|
|
/*!******************************!*\
|
|
!*** ./src/js/build-html.js ***!
|
|
\******************************/
|
|
/***/ (
|
|
__unused_webpack___webpack_module__,
|
|
__webpack_exports__,
|
|
__webpack_require__
|
|
) => {
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ default: () =>
|
|
/* export default binding */ __WEBPACK_DEFAULT_EXPORT__,
|
|
/* harmony export */
|
|
});
|
|
/* eslint no-var: off */
|
|
|
|
/**
|
|
* This file is responsible for building the DOM and updating DOM state.
|
|
*
|
|
* @author Tim Scanlin
|
|
*/
|
|
|
|
/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(
|
|
options
|
|
) {
|
|
var forEach = [].forEach;
|
|
var some = [].some;
|
|
// if (typeof window === 'undefined') return
|
|
var body = typeof window !== "undefined" && document.body;
|
|
var tocElement;
|
|
var currentlyHighlighting = true;
|
|
var SPACE_CHAR = " ";
|
|
|
|
/**
|
|
* Create link and list elements.
|
|
* @param {Object} d
|
|
* @param {HTMLElement} container
|
|
* @return {HTMLElement}
|
|
*/
|
|
function createEl(d, container) {
|
|
var link = container.appendChild(createLink(d));
|
|
if (d.children.length) {
|
|
var list = createList(d.isCollapsed);
|
|
d.children.forEach((child) => {
|
|
createEl(child, list);
|
|
});
|
|
link.appendChild(list);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render nested heading array data into a given element.
|
|
* @param {HTMLElement} parent Optional. If provided updates the {@see tocElement} to match.
|
|
* @param {Array} data
|
|
* @return {HTMLElement}
|
|
*/
|
|
function render(parent, data) {
|
|
var collapsed = false;
|
|
var container = createList(collapsed);
|
|
|
|
data.forEach((d) => {
|
|
createEl(d, container);
|
|
});
|
|
|
|
// Return if no TOC element is provided or known.
|
|
tocElement = parent || tocElement;
|
|
if (tocElement === null) {
|
|
return;
|
|
}
|
|
|
|
// Remove existing child if it exists.
|
|
if (tocElement.firstChild) {
|
|
tocElement.removeChild(tocElement.firstChild);
|
|
}
|
|
|
|
// Just return the parent and don't append the list if no links are found.
|
|
if (data.length === 0) {
|
|
return tocElement;
|
|
}
|
|
|
|
// Append the Elements that have been created
|
|
return tocElement.appendChild(container);
|
|
}
|
|
|
|
/**
|
|
* Create link element.
|
|
* @param {Object} data
|
|
* @return {HTMLElement}
|
|
*/
|
|
function createLink(data) {
|
|
var item = document.createElement("li");
|
|
var a = document.createElement("a");
|
|
if (options.listItemClass) {
|
|
item.setAttribute("class", options.listItemClass);
|
|
}
|
|
|
|
if (options.onClick) {
|
|
a.onclick = options.onClick;
|
|
}
|
|
|
|
if (options.includeTitleTags) {
|
|
a.setAttribute("title", data.textContent);
|
|
}
|
|
|
|
if (options.includeHtml && data.childNodes.length) {
|
|
forEach.call(data.childNodes, (node) => {
|
|
a.appendChild(node.cloneNode(true));
|
|
});
|
|
} else {
|
|
// Default behavior. Set to textContent to keep tests happy.
|
|
a.textContent = data.textContent;
|
|
}
|
|
a.setAttribute("href", `${options.basePath}#${data.id}`);
|
|
a.setAttribute(
|
|
"class",
|
|
options.linkClass +
|
|
SPACE_CHAR +
|
|
"node-name--" +
|
|
data.nodeName +
|
|
SPACE_CHAR +
|
|
options.extraLinkClasses
|
|
);
|
|
item.appendChild(a);
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Create list element.
|
|
* @param {Boolean} isCollapsed
|
|
* @return {HTMLElement}
|
|
*/
|
|
function createList(isCollapsed) {
|
|
var listElement = options.orderedList ? "ol" : "ul";
|
|
var list = document.createElement(listElement);
|
|
var classes =
|
|
options.listClass + SPACE_CHAR + options.extraListClasses;
|
|
if (isCollapsed) {
|
|
// No plus/equals here fixes compilation issue.
|
|
classes = classes + SPACE_CHAR + options.collapsibleClass;
|
|
classes = classes + SPACE_CHAR + options.isCollapsedClass;
|
|
}
|
|
list.setAttribute("class", classes);
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Update fixed sidebar class.
|
|
* @return {HTMLElement}
|
|
*/
|
|
function updateFixedSidebarClass() {
|
|
if (
|
|
options.scrollContainer &&
|
|
document.querySelector(options.scrollContainer)
|
|
) {
|
|
var top;
|
|
top = document.querySelector(options.scrollContainer).scrollTop;
|
|
} else {
|
|
top = document.documentElement.scrollTop || body.scrollTop;
|
|
}
|
|
var posFixedEl = document.querySelector(
|
|
options.positionFixedSelector
|
|
);
|
|
|
|
if (options.fixedSidebarOffset === "auto") {
|
|
options.fixedSidebarOffset = tocElement.offsetTop;
|
|
}
|
|
|
|
if (top > options.fixedSidebarOffset) {
|
|
if (
|
|
posFixedEl.className.indexOf(options.positionFixedClass) === -1
|
|
) {
|
|
posFixedEl.className += SPACE_CHAR + options.positionFixedClass;
|
|
}
|
|
} else {
|
|
posFixedEl.className = posFixedEl.className.replace(
|
|
SPACE_CHAR + options.positionFixedClass,
|
|
""
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get top position of heading
|
|
* @param {HTMLElement} obj
|
|
* @return {int} position
|
|
*/
|
|
function getHeadingTopPos(obj) {
|
|
var position = 0;
|
|
if (obj !== null) {
|
|
position = obj.offsetTop;
|
|
if (options.hasInnerContainers) {
|
|
position += getHeadingTopPos(obj.offsetParent);
|
|
}
|
|
}
|
|
return position;
|
|
}
|
|
|
|
/**
|
|
* Update className only when changed.
|
|
* @param {HTMLElement} obj
|
|
* @param {string} className
|
|
* @return {HTMLElement} obj
|
|
*/
|
|
function updateClassname(obj, className) {
|
|
if (obj && obj.className !== className) {
|
|
obj.className = className;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Update TOC highlighting and collapsed groupings.
|
|
*/
|
|
function updateToc(headingsArray) {
|
|
// If a fixed content container was set
|
|
if (
|
|
options.scrollContainer &&
|
|
document.querySelector(options.scrollContainer)
|
|
) {
|
|
var top;
|
|
top = document.querySelector(options.scrollContainer).scrollTop;
|
|
} else {
|
|
top = document.documentElement.scrollTop || body.scrollTop;
|
|
}
|
|
|
|
// Add fixed class at offset
|
|
if (options.positionFixedSelector) {
|
|
updateFixedSidebarClass();
|
|
}
|
|
|
|
// Get the top most heading currently visible on the page so we know what to highlight.
|
|
var headings = headingsArray;
|
|
var topHeader;
|
|
// Using some instead of each so that we can escape early.
|
|
if (
|
|
currentlyHighlighting &&
|
|
tocElement !== null &&
|
|
headings.length > 0
|
|
) {
|
|
some.call(headings, (heading, i) => {
|
|
if (
|
|
getHeadingTopPos(heading) >
|
|
top + options.headingsOffset + 10
|
|
) {
|
|
// Don't allow negative index value.
|
|
var index = i === 0 ? i : i - 1;
|
|
topHeader = headings[index];
|
|
return true;
|
|
} else if (i === headings.length - 1) {
|
|
// This allows scrolling for the last heading on the page.
|
|
topHeader = headings[headings.length - 1];
|
|
return true;
|
|
}
|
|
});
|
|
|
|
var oldActiveTocLink = tocElement.querySelector(
|
|
`.${options.activeLinkClass}`
|
|
);
|
|
var activeTocLink = tocElement.querySelector(
|
|
"." +
|
|
options.linkClass +
|
|
".node-name--" +
|
|
CSS.escape(topHeader.nodeName) +
|
|
'[href="' +
|
|
options.basePath +
|
|
"#" +
|
|
topHeader.id.replace(
|
|
/([ #;&,.+*~':"!^$[\]()=>|/\\@])/g,
|
|
"\\$1"
|
|
) +
|
|
'"]'
|
|
);
|
|
// Performance improvement to only change the classes
|
|
// for the toc if a new link should be highlighted.
|
|
if (oldActiveTocLink === activeTocLink) {
|
|
return;
|
|
}
|
|
|
|
// Remove the active class from the other tocLinks.
|
|
var tocLinks = tocElement.querySelectorAll(
|
|
`.${options.linkClass}`
|
|
);
|
|
forEach.call(tocLinks, (tocLink) => {
|
|
updateClassname(
|
|
tocLink,
|
|
tocLink.className.replace(
|
|
SPACE_CHAR + options.activeLinkClass,
|
|
""
|
|
)
|
|
);
|
|
});
|
|
var tocLis = tocElement.querySelectorAll(
|
|
`.${options.listItemClass}`
|
|
);
|
|
forEach.call(tocLis, (tocLi) => {
|
|
updateClassname(
|
|
tocLi,
|
|
tocLi.className.replace(
|
|
SPACE_CHAR + options.activeListItemClass,
|
|
""
|
|
)
|
|
);
|
|
});
|
|
|
|
// Add the active class to the active tocLink.
|
|
if (
|
|
activeTocLink &&
|
|
activeTocLink.className.indexOf(options.activeLinkClass) === -1
|
|
) {
|
|
activeTocLink.className += SPACE_CHAR + options.activeLinkClass;
|
|
}
|
|
var li = activeTocLink?.parentNode;
|
|
if (
|
|
li &&
|
|
li.className.indexOf(options.activeListItemClass) === -1
|
|
) {
|
|
li.className += SPACE_CHAR + options.activeListItemClass;
|
|
}
|
|
|
|
var tocLists = tocElement.querySelectorAll(
|
|
`.${options.listClass}.${options.collapsibleClass}`
|
|
);
|
|
|
|
// Collapse the other collapsible lists.
|
|
forEach.call(tocLists, (list) => {
|
|
if (list.className.indexOf(options.isCollapsedClass) === -1) {
|
|
list.className += SPACE_CHAR + options.isCollapsedClass;
|
|
}
|
|
});
|
|
|
|
// Expand the active link's collapsible list and its sibling if applicable.
|
|
if (
|
|
activeTocLink?.nextSibling &&
|
|
activeTocLink.nextSibling.className.indexOf(
|
|
options.isCollapsedClass
|
|
) !== -1
|
|
) {
|
|
updateClassname(
|
|
activeTocLink.nextSibling,
|
|
activeTocLink.nextSibling.className.replace(
|
|
SPACE_CHAR + options.isCollapsedClass,
|
|
""
|
|
)
|
|
);
|
|
}
|
|
removeCollapsedFromParents(activeTocLink?.parentNode.parentNode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove collapsed class from parent elements.
|
|
* @param {HTMLElement} element
|
|
* @return {HTMLElement}
|
|
*/
|
|
function removeCollapsedFromParents(element) {
|
|
if (
|
|
element &&
|
|
element.className.indexOf(options.collapsibleClass) !== -1 &&
|
|
element.className.indexOf(options.isCollapsedClass) !== -1
|
|
) {
|
|
updateClassname(
|
|
element,
|
|
element.className.replace(
|
|
SPACE_CHAR + options.isCollapsedClass,
|
|
""
|
|
)
|
|
);
|
|
return removeCollapsedFromParents(element.parentNode.parentNode);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* Disable TOC Animation when a link is clicked.
|
|
* @param {Event} event
|
|
*/
|
|
function disableTocAnimation(event) {
|
|
var target = event.target || event.srcElement;
|
|
if (
|
|
typeof target.className !== "string" ||
|
|
target.className.indexOf(options.linkClass) === -1
|
|
) {
|
|
return;
|
|
}
|
|
// Bind to tocLink clicks to temporarily disable highlighting
|
|
// while smoothScroll is animating.
|
|
currentlyHighlighting = false;
|
|
}
|
|
|
|
/**
|
|
* Enable TOC Animation.
|
|
*/
|
|
function enableTocAnimation() {
|
|
currentlyHighlighting = true;
|
|
}
|
|
|
|
return {
|
|
enableTocAnimation,
|
|
disableTocAnimation,
|
|
render,
|
|
updateToc,
|
|
};
|
|
}
|
|
|
|
/***/
|
|
},
|
|
|
|
/***/ "./src/js/default-options.js":
|
|
/*!***********************************!*\
|
|
!*** ./src/js/default-options.js ***!
|
|
\***********************************/
|
|
/***/ (
|
|
__unused_webpack___webpack_module__,
|
|
__webpack_exports__,
|
|
__webpack_require__
|
|
) => {
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
|
|
/* harmony export */
|
|
});
|
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = {
|
|
// Where to render the table of contents.
|
|
tocSelector: ".js-toc",
|
|
// Where to grab the headings to build the table of contents.
|
|
contentSelector: ".js-toc-content",
|
|
// Which headings to grab inside of the contentSelector element.
|
|
headingSelector: "h1, h2, h3",
|
|
// Headings that match the ignoreSelector will be skipped.
|
|
ignoreSelector: ".js-toc-ignore",
|
|
// For headings inside relative or absolute positioned containers within content
|
|
hasInnerContainers: false,
|
|
// Main class to add to links.
|
|
linkClass: "toc-link",
|
|
// Extra classes to add to links.
|
|
extraLinkClasses: "",
|
|
// Class to add to active links,
|
|
// the link corresponding to the top most heading on the page.
|
|
activeLinkClass: "is-active-link",
|
|
// Main class to add to lists.
|
|
listClass: "toc-list",
|
|
// Extra classes to add to lists.
|
|
extraListClasses: "",
|
|
// Class that gets added when a list should be collapsed.
|
|
isCollapsedClass: "is-collapsed",
|
|
// Class that gets added when a list should be able
|
|
// to be collapsed but isn't necessarily collapsed.
|
|
collapsibleClass: "is-collapsible",
|
|
// Class to add to list items.
|
|
listItemClass: "toc-list-item",
|
|
// Class to add to active list items.
|
|
activeListItemClass: "is-active-li",
|
|
// How many heading levels should not be collapsed.
|
|
// For example, number 6 will show everything since
|
|
// there are only 6 heading levels and number 0 will collapse them all.
|
|
// The sections that are hidden will open
|
|
// and close as you scroll to headings within them.
|
|
collapseDepth: 0,
|
|
// Smooth scrolling enabled.
|
|
scrollSmooth: true,
|
|
// Smooth scroll duration.
|
|
scrollSmoothDuration: 420,
|
|
// Smooth scroll offset.
|
|
scrollSmoothOffset: 0,
|
|
// Callback for scroll end.
|
|
scrollEndCallback: (_e) => {},
|
|
// Headings offset between the headings and the top of the document (this is meant for minor adjustments).
|
|
headingsOffset: 1,
|
|
// Timeout between events firing to make sure it's
|
|
// not too rapid (for performance reasons).
|
|
throttleTimeout: 50,
|
|
// Element to add the positionFixedClass to.
|
|
positionFixedSelector: null,
|
|
// Fixed position class to add to make sidebar fixed after scrolling
|
|
// down past the fixedSidebarOffset.
|
|
positionFixedClass: "is-position-fixed",
|
|
// fixedSidebarOffset can be any number but by default is set
|
|
// to auto which sets the fixedSidebarOffset to the sidebar
|
|
// element's offsetTop from the top of the document on init.
|
|
fixedSidebarOffset: "auto",
|
|
// includeHtml can be set to true to include the HTML markup from the
|
|
// heading node instead of just including the innerText.
|
|
includeHtml: false,
|
|
// includeTitleTags automatically sets the html title tag of the link
|
|
// to match the title. This can be useful for SEO purposes or
|
|
// when truncating titles.
|
|
includeTitleTags: false,
|
|
// onclick function to apply to all links in toc. will be called with
|
|
// the event as the first parameter, and this can be used to stop,
|
|
// propagation, prevent default or perform action
|
|
onClick: (_e) => {},
|
|
// orderedList can be set to false to generate unordered lists (ul)
|
|
// instead of ordered lists (ol)
|
|
orderedList: true,
|
|
// If there is a fixed article scroll container, set to calculate titles' offset
|
|
scrollContainer: null,
|
|
// prevent ToC DOM rendering if it's already rendered by an external system
|
|
skipRendering: false,
|
|
// Optional callback to change heading labels.
|
|
// For example it can be used to cut down and put ellipses on multiline headings you deem too long.
|
|
// Called each time a heading is parsed. Expects a string and returns the modified label to display.
|
|
// Additionally, the attribute `data-heading-label` may be used on a heading to specify
|
|
// a shorter string to be used in the TOC.
|
|
// function (string) => string
|
|
headingLabelCallback: false,
|
|
// ignore headings that are hidden in DOM
|
|
ignoreHiddenElements: false,
|
|
// Optional callback to modify properties of parsed headings.
|
|
// The heading element is passed in node parameter and information parsed by default parser is provided in obj parameter.
|
|
// Function has to return the same or modified obj.
|
|
// The heading will be excluded from TOC if nothing is returned.
|
|
// function (object, HTMLElement) => object | void
|
|
headingObjectCallback: null,
|
|
// Set the base path, useful if you use a `base` tag in `head`.
|
|
basePath: "",
|
|
// Only takes affect when `tocSelector` is scrolling,
|
|
// keep the toc scroll position in sync with the content.
|
|
disableTocScrollSync: false,
|
|
// Offset for the toc scroll (top) position when scrolling the page.
|
|
// Only effective if `disableTocScrollSync` is false.
|
|
tocScrollOffset: 0,
|
|
};
|
|
|
|
/***/
|
|
},
|
|
|
|
/***/ "./src/js/index-esm.js":
|
|
/*!*****************************!*\
|
|
!*** ./src/js/index-esm.js ***!
|
|
\*****************************/
|
|
/***/ (
|
|
__unused_webpack___webpack_module__,
|
|
__webpack_exports__,
|
|
__webpack_require__
|
|
) => {
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ _buildHtml: () => /* binding */ _buildHtml,
|
|
/* harmony export */ _headingsArray: () =>
|
|
/* binding */ _headingsArray,
|
|
/* harmony export */ _options: () => /* binding */ _options,
|
|
/* harmony export */ _parseContent: () => /* binding */ _parseContent,
|
|
/* harmony export */ _scrollListener: () =>
|
|
/* binding */ _scrollListener,
|
|
/* harmony export */ destroy: () => /* binding */ destroy,
|
|
/* harmony export */ init: () => /* binding */ init,
|
|
/* harmony export */ refresh: () => /* binding */ refresh,
|
|
/* harmony export */
|
|
});
|
|
/* harmony import */ var _build_html_js__WEBPACK_IMPORTED_MODULE_0__ =
|
|
__webpack_require__(/*! ./build-html.js */ "./src/js/build-html.js");
|
|
/* harmony import */ var _default_options_js__WEBPACK_IMPORTED_MODULE_1__ =
|
|
__webpack_require__(
|
|
/*! ./default-options.js */ "./src/js/default-options.js"
|
|
);
|
|
/* harmony import */ var _parse_content_js__WEBPACK_IMPORTED_MODULE_2__ =
|
|
__webpack_require__(
|
|
/*! ./parse-content.js */ "./src/js/parse-content.js"
|
|
);
|
|
/* harmony import */ var _scroll_smooth_index_js__WEBPACK_IMPORTED_MODULE_3__ =
|
|
__webpack_require__(
|
|
/*! ./scroll-smooth/index.js */ "./src/js/scroll-smooth/index.js"
|
|
);
|
|
/* harmony import */ var _update_toc_scroll_js__WEBPACK_IMPORTED_MODULE_4__ =
|
|
__webpack_require__(
|
|
/*! ./update-toc-scroll.js */ "./src/js/update-toc-scroll.js"
|
|
);
|
|
/* eslint no-var: off */
|
|
/**
|
|
* Tocbot
|
|
* Tocbot creates a table of contents based on HTML headings on a page,
|
|
* this allows users to easily jump to different sections of the document.
|
|
* Tocbot was inspired by tocify (http://gregfranko.com/jquery.tocify.js/).
|
|
* The main differences are that it works natively without any need for jquery or jquery UI).
|
|
*
|
|
* @author Tim Scanlin
|
|
*/
|
|
|
|
// For testing purposes.
|
|
let _options = {}; // Object to store current options.
|
|
let _buildHtml;
|
|
let _parseContent;
|
|
let _headingsArray;
|
|
let _scrollListener;
|
|
|
|
let clickListener;
|
|
|
|
/**
|
|
* Initialize tocbot.
|
|
* @param {object} customOptions
|
|
*/
|
|
function init(customOptions) {
|
|
// Merge defaults with user options.
|
|
// Set to options variable at the top.
|
|
_options = extend(
|
|
_default_options_js__WEBPACK_IMPORTED_MODULE_1__.default,
|
|
customOptions || {}
|
|
);
|
|
|
|
// Init smooth scroll if enabled (default).
|
|
if (_options.scrollSmooth) {
|
|
_options.duration = _options.scrollSmoothDuration;
|
|
_options.offset = _options.scrollSmoothOffset;
|
|
(0, _scroll_smooth_index_js__WEBPACK_IMPORTED_MODULE_3__.default)(
|
|
_options
|
|
);
|
|
}
|
|
|
|
// Pass options to these modules.
|
|
_buildHtml = (0, _build_html_js__WEBPACK_IMPORTED_MODULE_0__.default)(
|
|
_options
|
|
);
|
|
_parseContent = (0,
|
|
_parse_content_js__WEBPACK_IMPORTED_MODULE_2__.default)(_options);
|
|
|
|
// Destroy it if it exists first.
|
|
destroy();
|
|
|
|
const contentElement = getContentElement(_options);
|
|
if (contentElement === null) {
|
|
return;
|
|
}
|
|
|
|
const tocElement = getTocElement(_options);
|
|
if (tocElement === null) {
|
|
return;
|
|
}
|
|
|
|
// Get headings array.
|
|
_headingsArray = _parseContent.selectHeadings(
|
|
contentElement,
|
|
_options.headingSelector
|
|
);
|
|
|
|
// Return if no headings are found.
|
|
if (_headingsArray === null) {
|
|
return;
|
|
}
|
|
|
|
// Build nested headings array.
|
|
const nestedHeadingsObj =
|
|
_parseContent.nestHeadingsArray(_headingsArray);
|
|
const nestedHeadings = nestedHeadingsObj.nest;
|
|
|
|
// Render.
|
|
if (!_options.skipRendering) {
|
|
_buildHtml.render(tocElement, nestedHeadings);
|
|
} else {
|
|
// No need to attach listeners if skipRendering is true, this was causing errors.
|
|
return this;
|
|
}
|
|
|
|
// Update Sidebar and bind listeners.
|
|
_scrollListener = throttle((e) => {
|
|
_buildHtml.updateToc(_headingsArray);
|
|
!_options.disableTocScrollSync &&
|
|
(0, _update_toc_scroll_js__WEBPACK_IMPORTED_MODULE_4__.default)(
|
|
_options
|
|
);
|
|
const isTop =
|
|
e?.target?.scrollingElement &&
|
|
e.target.scrollingElement.scrollTop === 0;
|
|
if (
|
|
(e && (e.eventPhase === 0 || e.currentTarget === null)) ||
|
|
isTop
|
|
) {
|
|
_buildHtml.updateToc(_headingsArray);
|
|
if (_options.scrollEndCallback) {
|
|
_options.scrollEndCallback(e);
|
|
}
|
|
}
|
|
}, _options.throttleTimeout);
|
|
_scrollListener();
|
|
if (
|
|
_options.scrollContainer &&
|
|
document.querySelector(_options.scrollContainer)
|
|
) {
|
|
document
|
|
.querySelector(_options.scrollContainer)
|
|
.addEventListener("scroll", _scrollListener, false);
|
|
document
|
|
.querySelector(_options.scrollContainer)
|
|
.addEventListener("resize", _scrollListener, false);
|
|
} else {
|
|
document.addEventListener("scroll", _scrollListener, false);
|
|
document.addEventListener("resize", _scrollListener, false);
|
|
}
|
|
|
|
// Bind click listeners to disable animation.
|
|
let timeout = null;
|
|
clickListener = throttle((event) => {
|
|
if (_options.scrollSmooth) {
|
|
_buildHtml.disableTocAnimation(event);
|
|
}
|
|
_buildHtml.updateToc(_headingsArray);
|
|
// Timeout to re-enable the animation.
|
|
timeout && clearTimeout(timeout);
|
|
timeout = setTimeout(() => {
|
|
_buildHtml.enableTocAnimation();
|
|
}, _options.scrollSmoothDuration);
|
|
}, _options.throttleTimeout);
|
|
|
|
if (
|
|
_options.scrollContainer &&
|
|
document.querySelector(_options.scrollContainer)
|
|
) {
|
|
document
|
|
.querySelector(_options.scrollContainer)
|
|
.addEventListener("click", clickListener, false);
|
|
} else {
|
|
document.addEventListener("click", clickListener, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy tocbot.
|
|
*/
|
|
function destroy() {
|
|
const tocElement = getTocElement(_options);
|
|
if (tocElement === null) {
|
|
return;
|
|
}
|
|
|
|
if (!_options.skipRendering) {
|
|
// Clear HTML.
|
|
if (tocElement) {
|
|
tocElement.innerHTML = "";
|
|
}
|
|
}
|
|
|
|
// Remove event listeners.
|
|
if (
|
|
_options.scrollContainer &&
|
|
document.querySelector(_options.scrollContainer)
|
|
) {
|
|
document
|
|
.querySelector(_options.scrollContainer)
|
|
.removeEventListener("scroll", _scrollListener, false);
|
|
document
|
|
.querySelector(_options.scrollContainer)
|
|
.removeEventListener("resize", _scrollListener, false);
|
|
if (_buildHtml) {
|
|
document
|
|
.querySelector(_options.scrollContainer)
|
|
.removeEventListener("click", clickListener, false);
|
|
}
|
|
} else {
|
|
document.removeEventListener("scroll", _scrollListener, false);
|
|
document.removeEventListener("resize", _scrollListener, false);
|
|
if (_buildHtml) {
|
|
document.removeEventListener("click", clickListener, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh tocbot.
|
|
*/
|
|
function refresh(customOptions) {
|
|
destroy();
|
|
init(customOptions || _options);
|
|
}
|
|
|
|
// From: https://github.com/Raynos/xtend
|
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
function extend() {
|
|
const target = {};
|
|
for (let i = 0; i < arguments.length; i++) {
|
|
const source = arguments[i];
|
|
for (const key in source) {
|
|
if (hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
// From: https://remysharp.com/2010/07/21/throttling-function-calls
|
|
function throttle(fn, threshold, scope) {
|
|
threshold || (threshold = 250);
|
|
let last;
|
|
let deferTimer;
|
|
return function () {
|
|
const context = scope || this;
|
|
const now = Date.now();
|
|
const args = arguments;
|
|
if (last && now < last + threshold) {
|
|
// hold on to it
|
|
clearTimeout(deferTimer);
|
|
deferTimer = setTimeout(() => {
|
|
last = now;
|
|
fn.apply(context, args);
|
|
}, threshold);
|
|
} else {
|
|
last = now;
|
|
fn.apply(context, args);
|
|
}
|
|
};
|
|
}
|
|
|
|
function getContentElement(options) {
|
|
try {
|
|
return (
|
|
options.contentElement ||
|
|
document.querySelector(options.contentSelector)
|
|
);
|
|
} catch (_e) {
|
|
console.warn(
|
|
`Contents element not found: ${options.contentSelector}`
|
|
); // eslint-disable-line
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getTocElement(options) {
|
|
try {
|
|
return (
|
|
options.tocElement || document.querySelector(options.tocSelector)
|
|
);
|
|
} catch (_e) {
|
|
console.warn(`TOC element not found: ${options.tocSelector}`); // eslint-disable-line
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/***/
|
|
},
|
|
|
|
/***/ "./src/js/parse-content.js":
|
|
/*!*********************************!*\
|
|
!*** ./src/js/parse-content.js ***!
|
|
\*********************************/
|
|
/***/ (
|
|
__unused_webpack___webpack_module__,
|
|
__webpack_exports__,
|
|
__webpack_require__
|
|
) => {
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ default: () => /* binding */ parseContent,
|
|
/* harmony export */
|
|
});
|
|
/* eslint no-var: off */
|
|
/**
|
|
* This file is responsible for parsing the content from the DOM and making
|
|
* sure data is nested properly.
|
|
*
|
|
* @author Tim Scanlin
|
|
*/
|
|
|
|
function parseContent(options) {
|
|
var reduce = [].reduce;
|
|
|
|
/**
|
|
* Get the last item in an array and return a reference to it.
|
|
* @param {Array} array
|
|
* @return {Object}
|
|
*/
|
|
function getLastItem(array) {
|
|
return array[array.length - 1];
|
|
}
|
|
|
|
/**
|
|
* Get heading level for a heading dom node.
|
|
* @param {HTMLElement} heading
|
|
* @return {Number}
|
|
*/
|
|
function getHeadingLevel(heading) {
|
|
return +heading.nodeName
|
|
.replace(/^html:/, "")
|
|
.toUpperCase()
|
|
.replace("H", "");
|
|
}
|
|
|
|
/**
|
|
* Determine whether the object is an HTML Element.
|
|
* Also works inside iframes. HTML Elements might be created by the parent document.
|
|
* @param {Object} maybeElement
|
|
* @return {Number}
|
|
*/
|
|
function isHTMLElement(maybeElement) {
|
|
try {
|
|
return (
|
|
maybeElement instanceof window.HTMLElement ||
|
|
maybeElement instanceof window.parent.HTMLElement
|
|
);
|
|
} catch (_e) {
|
|
return maybeElement instanceof window.HTMLElement;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get important properties from a heading element and store in a plain object.
|
|
* @param {HTMLElement} heading
|
|
* @return {Object}
|
|
*/
|
|
function getHeadingObject(heading) {
|
|
// each node is processed twice by this method because nestHeadingsArray() and addNode() calls it
|
|
// first time heading is real DOM node element, second time it is obj
|
|
// that is causing problem so I am processing only original DOM node
|
|
if (!isHTMLElement(heading)) return heading;
|
|
|
|
if (
|
|
options.ignoreHiddenElements &&
|
|
(!heading.offsetHeight || !heading.offsetParent)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const headingLabel =
|
|
heading.getAttribute("data-heading-label") ||
|
|
(options.headingLabelCallback
|
|
? String(options.headingLabelCallback(heading.innerText))
|
|
: (heading.innerText || heading.textContent).trim());
|
|
var obj = {
|
|
id: heading.id,
|
|
children: [],
|
|
nodeName: heading.nodeName,
|
|
headingLevel: getHeadingLevel(heading),
|
|
textContent: headingLabel,
|
|
};
|
|
|
|
if (options.includeHtml) {
|
|
obj.childNodes = heading.childNodes;
|
|
}
|
|
|
|
if (options.headingObjectCallback) {
|
|
return options.headingObjectCallback(obj, heading);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Add a node to the nested array.
|
|
* @param {Object} node
|
|
* @param {Array} nest
|
|
* @return {Array}
|
|
*/
|
|
function addNode(node, nest) {
|
|
var obj = getHeadingObject(node);
|
|
var level = obj.headingLevel;
|
|
var array = nest;
|
|
var lastItem = getLastItem(array);
|
|
var lastItemLevel = lastItem ? lastItem.headingLevel : 0;
|
|
var counter = level - lastItemLevel;
|
|
|
|
while (counter > 0) {
|
|
lastItem = getLastItem(array);
|
|
// Handle case where there are multiple h5+ in a row.
|
|
if (lastItem && level === lastItem.headingLevel) {
|
|
break;
|
|
} else if (lastItem && lastItem.children !== undefined) {
|
|
array = lastItem.children;
|
|
}
|
|
counter--;
|
|
}
|
|
|
|
if (level >= options.collapseDepth) {
|
|
obj.isCollapsed = true;
|
|
}
|
|
|
|
array.push(obj);
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Select headings in content area, exclude any selector in options.ignoreSelector
|
|
* @param {HTMLElement} contentElement
|
|
* @param {Array} headingSelector
|
|
* @return {Array}
|
|
*/
|
|
function selectHeadings(contentElement, headingSelector) {
|
|
var selectors = headingSelector;
|
|
if (options.ignoreSelector) {
|
|
selectors = headingSelector
|
|
.split(",")
|
|
.map(function mapSelectors(selector) {
|
|
return `${selector.trim()}:not(${options.ignoreSelector})`;
|
|
});
|
|
}
|
|
try {
|
|
return contentElement.querySelectorAll(selectors);
|
|
} catch (_e) {
|
|
console.warn(`Headers not found with selector: ${selectors}`); // eslint-disable-line
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nest headings array into nested arrays with 'children' property.
|
|
* @param {Array} headingsArray
|
|
* @return {Object}
|
|
*/
|
|
function nestHeadingsArray(headingsArray) {
|
|
return reduce.call(
|
|
headingsArray,
|
|
function reducer(prev, curr) {
|
|
var currentHeading = getHeadingObject(curr);
|
|
if (currentHeading) {
|
|
addNode(currentHeading, prev.nest);
|
|
}
|
|
return prev;
|
|
},
|
|
{
|
|
nest: [],
|
|
}
|
|
);
|
|
}
|
|
|
|
return {
|
|
nestHeadingsArray,
|
|
selectHeadings,
|
|
};
|
|
}
|
|
|
|
/***/
|
|
},
|
|
|
|
/***/ "./src/js/scroll-smooth/index.js":
|
|
/*!***************************************!*\
|
|
!*** ./src/js/scroll-smooth/index.js ***!
|
|
\***************************************/
|
|
/***/ (
|
|
__unused_webpack___webpack_module__,
|
|
__webpack_exports__,
|
|
__webpack_require__
|
|
) => {
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ default: () => /* binding */ initSmoothScrolling,
|
|
/* harmony export */
|
|
});
|
|
/* eslint no-var: off */
|
|
/* globals location, requestAnimationFrame */
|
|
|
|
function initSmoothScrolling(options) {
|
|
// if (isCssSmoothSCrollSupported()) { return }
|
|
|
|
var duration = options.duration;
|
|
var offset = options.offset;
|
|
if (typeof window === "undefined" || typeof location === "undefined")
|
|
return;
|
|
|
|
var pageUrl = location.hash
|
|
? stripHash(location.href)
|
|
: location.href;
|
|
|
|
delegatedLinkHijacking();
|
|
|
|
function delegatedLinkHijacking() {
|
|
document.body.addEventListener("click", onClick, false);
|
|
|
|
function onClick(e) {
|
|
if (
|
|
!isInPageLink(e.target) ||
|
|
e.target.className.indexOf("no-smooth-scroll") > -1 ||
|
|
(e.target.href.charAt(e.target.href.length - 2) === "#" &&
|
|
e.target.href.charAt(e.target.href.length - 1) === "!") ||
|
|
e.target.className.indexOf(options.linkClass) === -1
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Don't prevent default or hash doesn't change.
|
|
// e.preventDefault()
|
|
|
|
jump(e.target.hash, {
|
|
duration,
|
|
offset,
|
|
callback: () => {
|
|
setFocus(e.target.hash);
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
function isInPageLink(n) {
|
|
return (
|
|
n.tagName.toLowerCase() === "a" &&
|
|
(n.hash.length > 0 || n.href.charAt(n.href.length - 1) === "#") &&
|
|
(stripHash(n.href) === pageUrl ||
|
|
`${stripHash(n.href)}#` === pageUrl)
|
|
);
|
|
}
|
|
|
|
function stripHash(url) {
|
|
return url.slice(0, url.lastIndexOf("#"));
|
|
}
|
|
|
|
// function isCssSmoothSCrollSupported () {
|
|
// return 'scrollBehavior' in document.documentElement.style
|
|
// }
|
|
|
|
// Adapted from:
|
|
// https://www.nczonline.net/blog/2013/01/15/fixing-skip-to-content-links/
|
|
function setFocus(hash) {
|
|
var element = document.getElementById(hash.substring(1));
|
|
|
|
if (element) {
|
|
if (
|
|
!/^(?:a|select|input|button|textarea)$/i.test(element.tagName)
|
|
) {
|
|
element.tabIndex = -1;
|
|
}
|
|
|
|
element.focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
function jump(target, options) {
|
|
var start = window.pageYOffset;
|
|
var opt = {
|
|
duration: options.duration,
|
|
offset: options.offset || 0,
|
|
callback: options.callback,
|
|
easing: options.easing || easeInOutQuad,
|
|
};
|
|
// This makes ids that start with a number work: ('[id="' + decodeURI(target).split('#').join('') + '"]')
|
|
// DecodeURI for nonASCII hashes, they was encoded, but id was not encoded, it lead to not finding the tgt element by id.
|
|
// And this is for IE: document.body.scrollTop
|
|
// Handle decoded and non-decoded URIs since sometimes URLs automatically transform them (support for internation chars).
|
|
var tgt =
|
|
document.querySelector(
|
|
`[id="${decodeURI(target).split("#").join("")}"]`
|
|
) || document.querySelector(`[id="${target.split("#").join("")}"]`);
|
|
var distance =
|
|
typeof target === "string"
|
|
? opt.offset +
|
|
(target
|
|
? tgt?.getBoundingClientRect().top || 0 // handle non-existent links better.
|
|
: -(
|
|
document.documentElement.scrollTop ||
|
|
document.body.scrollTop
|
|
))
|
|
: target;
|
|
var duration =
|
|
typeof opt.duration === "function"
|
|
? opt.duration(distance)
|
|
: opt.duration;
|
|
var timeStart;
|
|
var timeElapsed;
|
|
|
|
requestAnimationFrame((time) => {
|
|
timeStart = time;
|
|
loop(time);
|
|
});
|
|
function loop(time) {
|
|
timeElapsed = time - timeStart;
|
|
|
|
window.scrollTo(
|
|
0,
|
|
opt.easing(timeElapsed, start, distance, duration)
|
|
);
|
|
|
|
if (timeElapsed < duration) {
|
|
requestAnimationFrame(loop);
|
|
} else {
|
|
end();
|
|
}
|
|
}
|
|
|
|
function end() {
|
|
window.scrollTo(0, start + distance);
|
|
|
|
if (typeof opt.callback === "function") {
|
|
opt.callback();
|
|
}
|
|
}
|
|
|
|
// Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
|
|
function easeInOutQuad(t, b, c, d) {
|
|
t /= d / 2;
|
|
if (t < 1) return (c / 2) * t * t + b;
|
|
t--;
|
|
return (-c / 2) * (t * (t - 2) - 1) + b;
|
|
}
|
|
}
|
|
|
|
/***/
|
|
},
|
|
|
|
/***/ "./src/js/update-toc-scroll.js":
|
|
/*!*************************************!*\
|
|
!*** ./src/js/update-toc-scroll.js ***!
|
|
\*************************************/
|
|
/***/ (
|
|
__unused_webpack___webpack_module__,
|
|
__webpack_exports__,
|
|
__webpack_require__
|
|
) => {
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ default: () => /* binding */ updateTocScroll,
|
|
/* harmony export */
|
|
});
|
|
/* eslint no-var: off */
|
|
|
|
const SCROLL_LEEWAY = 30;
|
|
function updateTocScroll(options) {
|
|
var toc =
|
|
options.tocElement || document.querySelector(options.tocSelector);
|
|
if (toc && toc.scrollHeight > toc.clientHeight) {
|
|
var activeItem = toc.querySelector(
|
|
`.${options.activeListItemClass}`
|
|
);
|
|
if (activeItem) {
|
|
// Determine container top and bottom
|
|
var cTop = toc.scrollTop;
|
|
var cBottom = cTop + toc.clientHeight;
|
|
|
|
// Determine element top and bottom
|
|
var eTop = activeItem.offsetTop;
|
|
var eBottom = eTop + activeItem.clientHeight;
|
|
|
|
// Check if out of view
|
|
// Above scroll view
|
|
if (eTop < cTop + options.tocScrollOffset) {
|
|
toc.scrollTop -= cTop - eTop + options.tocScrollOffset;
|
|
// Below scroll view
|
|
} else if (
|
|
eBottom >
|
|
cBottom - options.tocScrollOffset - SCROLL_LEEWAY
|
|
) {
|
|
toc.scrollTop +=
|
|
eBottom -
|
|
cBottom +
|
|
options.tocScrollOffset +
|
|
2 * SCROLL_LEEWAY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***/
|
|
},
|
|
|
|
/******/
|
|
};
|
|
/************************************************************************/
|
|
/******/ // The module cache
|
|
/******/ var __webpack_module_cache__ = {};
|
|
/******/
|
|
/******/ // The require function
|
|
/******/ function __webpack_require__(moduleId) {
|
|
/******/ // Check if module is in cache
|
|
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
/******/ if (cachedModule !== undefined) {
|
|
/******/ return cachedModule.exports;
|
|
/******/
|
|
}
|
|
/******/ // Create a new module (and put it into the cache)
|
|
/******/ var module = (__webpack_module_cache__[moduleId] = {
|
|
/******/ // no module.id needed
|
|
/******/ // no module.loaded needed
|
|
/******/ exports: {},
|
|
/******/
|
|
});
|
|
/******/
|
|
/******/ // Execute the module function
|
|
/******/ __webpack_modules__[moduleId](
|
|
module,
|
|
module.exports,
|
|
__webpack_require__
|
|
);
|
|
/******/
|
|
/******/ // Return the exports of the module
|
|
/******/ return module.exports;
|
|
/******/
|
|
}
|
|
/******/
|
|
/************************************************************************/
|
|
/******/ /* webpack/runtime/define property getters */
|
|
/******/ (() => {
|
|
/******/ // define getter functions for harmony exports
|
|
/******/ __webpack_require__.d = (exports, definition) => {
|
|
/******/ for (var key in definition) {
|
|
/******/ if (
|
|
__webpack_require__.o(definition, key) &&
|
|
!__webpack_require__.o(exports, key)
|
|
) {
|
|
/******/ Object.defineProperty(exports, key, {
|
|
enumerable: true,
|
|
get: definition[key],
|
|
});
|
|
/******/
|
|
}
|
|
/******/
|
|
}
|
|
/******/
|
|
};
|
|
/******/
|
|
})();
|
|
/******/
|
|
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
/******/ (() => {
|
|
/******/ __webpack_require__.o = (obj, prop) => Object.hasOwn(obj, prop);
|
|
/******/
|
|
})();
|
|
/******/
|
|
/******/ /* webpack/runtime/make namespace object */
|
|
/******/ (() => {
|
|
/******/ // define __esModule on exports
|
|
/******/ __webpack_require__.r = (exports) => {
|
|
/******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
|
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, {
|
|
value: "Module",
|
|
});
|
|
/******/
|
|
}
|
|
/******/ Object.defineProperty(exports, "__esModule", { value: true });
|
|
/******/
|
|
};
|
|
/******/
|
|
})();
|
|
/******/
|
|
/************************************************************************/
|
|
var __webpack_exports__ = {};
|
|
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
|
|
(() => {
|
|
/*!******************************!*\
|
|
!*** ./src/js/index-dist.js ***!
|
|
\******************************/
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony import */ var _index_esm_js__WEBPACK_IMPORTED_MODULE_0__ =
|
|
__webpack_require__(/*! ./index-esm.js */ "./src/js/index-esm.js");
|
|
/* globals define */
|
|
|
|
((root, factory) => {
|
|
if (typeof define === "function" && define.amd) {
|
|
define([], factory(root));
|
|
} else if (typeof exports === "object") {
|
|
module.exports = factory(root);
|
|
} else {
|
|
root.tocbot = factory(root);
|
|
}
|
|
})(typeof global !== "undefined" ? global : window || global, (root) => {
|
|
// Just return if its not a browser.
|
|
const supports =
|
|
!!root &&
|
|
!!root.document &&
|
|
!!root.document.querySelector &&
|
|
!!root.addEventListener; // Feature test
|
|
if (typeof window === "undefined" && !supports) {
|
|
return;
|
|
}
|
|
|
|
// Make tocbot available globally.
|
|
root.tocbot = _index_esm_js__WEBPACK_IMPORTED_MODULE_0__;
|
|
|
|
return _index_esm_js__WEBPACK_IMPORTED_MODULE_0__;
|
|
});
|
|
})();
|
|
|
|
/******/
|
|
})();
|
|
//# sourceMappingURL=main.js.map
|