@k9n/scully-plugin-toc
Version:
This plugin for scully provides a postRenderer to generate a table of contents for the rendered route content
106 lines • 4.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.tocPlugin = exports.headingLevel = void 0;
const scully_1 = require("@scullyio/scully");
const utils_1 = require("@scullyio/scully/src/lib/utils");
const jsdom_1 = require("jsdom");
const constants_1 = require("./constants");
const headingLevel = (tag) => {
const match = tag.match(/(?!h)[123456]/g);
return match && match.length ? Number(match[0]) : null;
};
exports.headingLevel = headingLevel;
const tocPlugin = async (html, routeData) => {
const tocConfig = (0, scully_1.getPluginConfig)(constants_1.TocPluginName);
const route = routeData.route;
try {
const dom = new jsdom_1.JSDOM(html);
const { window } = dom;
/**
* define insert point
*/
let tocInsertPointSelector = '#toc';
if (!tocConfig.insertSelector) {
(0, scully_1.logWarn)(`No "insertSelector" for "toc" provided, using default: "#id".`);
}
else {
tocInsertPointSelector = tocConfig.insertSelector;
}
/**
* search for insert point
*/
const insertPoint = window.document.querySelector(tocInsertPointSelector);
// in case <div id="toc"></div> is not on the site
if (!insertPoint) {
(0, scully_1.logWarn)(`Insert point with selector ${tocInsertPointSelector} not found. Skipping toc generation for route ${route}.`);
return html;
}
/**
* get headings for toc generation
*/
let levels = ['h2', 'h3'];
if (!tocConfig.level) {
(0, scully_1.logWarn)(`Option "level" for "toc" not set, using default: "['h2', 'h3']".`);
}
else {
levels = tocConfig.level;
}
const possibleValues = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
let selector = '';
levels.forEach((level) => {
const lowerCased = level.toLowerCase();
if (possibleValues.indexOf(lowerCased) === -1) {
(0, utils_1.logWarnOnce)(`Level "${level}" is not valid. It should be one of ${JSON.stringify(possibleValues)}.`);
}
else {
selector += tocConfig.blogAreaSelector
? `${tocConfig.blogAreaSelector} ${lowerCased},`
: `${lowerCased},`;
}
});
// remove leading and trailing comma
selector = selector.replace(/(^,)|(,$)/g, '');
const headers = window.document.querySelectorAll(selector);
if (!headers.length) {
(0, utils_1.logWarnOnce)(`No selector match found for ${selector}.`);
}
/**
* build nested ul, li list
*/
let previousTag;
let toc = '';
headers.forEach((c) => {
const level = (0, exports.headingLevel)(c.tagName);
const trailingSlash = tocConfig.trailingSlash ? '/' : '';
const onClickScrollIntoViewString = tocConfig.scrollIntoViewOnClick
? ` onclick="document.getElementById('${c.id}').scrollIntoView();"`
: '';
const baseLiEl = `<li${onClickScrollIntoViewString}><a href="${route}${trailingSlash}#${c.id}">${c.textContent}</a></li>`;
if (previousTag && level && level > previousTag) {
toc += '<ul style="margin-bottom: 0px">';
}
if (previousTag && level && level < previousTag) {
toc += '</ul>';
}
toc += baseLiEl;
previousTag = level;
});
/**
* append toc as child
*/
const list = window.document.createElement('ul');
list.innerHTML = toc;
insertPoint.appendChild(list);
/**
* return new serialized HTML
*/
return dom.serialize();
}
catch (e) {
(0, scully_1.logWarn)(`error in tocPlugin, didn't parse for route '${(0, scully_1.yellow)(route)}'`);
}
// in case of failure return unchanged HTML to keep flow going
return Promise.resolve(html);
};
exports.tocPlugin = tocPlugin;
//# sourceMappingURL=toc.js.map