UNPKG

@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
"use strict"; 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