diff --git a/pages/11ty/markdown.shortcut.js b/pages/11ty/markdown.shortcut.js index 536e89654..2a76bb259 100644 --- a/pages/11ty/markdown.shortcut.js +++ b/pages/11ty/markdown.shortcut.js @@ -5,13 +5,15 @@ const color = require('kleur'); const {JSDOM} = require('jsdom'); const {markdown} = require('./markdown.lib'); -const {github, rewriteRules, urlPrefix} = require('./site.config'); +const {github, rewriteRules, urlPrefix, globalTerms} = require('./site.config'); class MDRenderer { static async render(filePath, startAnchor, endAnchor) { try { const content = await MDRenderer.parseFile(filePath); const {window} = new JSDOM(content); + const localTerms = MDRenderer.generateHeadersIds(window.document.body); + const terms = Object.assign({}, globalTerms, localTerms); // Exclude part before start anchor if (startAnchor) { @@ -27,6 +29,9 @@ class MDRenderer { endAnchorElement.remove(); } + // Add anchors and globally defined terms links + MDRenderer.fillReferenceLinks(window.document, window.document.body, terms); + // Resolve content links MDRenderer.resolveLinks(window.document.body, filePath); @@ -71,6 +76,56 @@ class MDRenderer { } return github.srcUrl + linkPath; } + + static generateHeadersIds(content) { + const headers = [...content.querySelectorAll('h1, h2, h3, h4')]; + const localTerms = {}; + for (const header of headers) { + const text = header.textContent; + const id = MDRenderer.createIDFromText(text) + header.setAttribute('id', id); + const anchor = `#${id}`; + localTerms[text] = anchor; + } + return localTerms; + } + static createIDFromText(text, idLengthLimit = 20) { + return text + .replace(/[^a-zA-Z0-9]+/g, ' ') + .toLowerCase() + .replace(/(?:^|\s)\w/g, (symbol, index) => (index === 0 ? symbol : symbol.toUpperCase())) + .replace(/\s/g, '') + .substring(0, idLengthLimit); + } + + static findTextNodes(root) { + const all = []; + for (let node = root.firstChild; node; node = node.nextSibling) { + if (node.nodeType === 3) all.push(node); + else all.push(...MDRenderer.findTextNodes(node)); + } + return all; + } + + static fillReferenceLinks(document, content, terms) { + const nodes = MDRenderer.findTextNodes(content) + .filter((node) => !node.parentElement.closest('h1, h2, h3, h4, h5, h6')); + + for (const [text, link] of Object.entries(terms)) { + + for (const node of nodes) { + if (node.textContent.includes(text)) { + MDRenderer.wrapTextNode(document, node, text, link); + } + } + } + } + + static wrapTextNode(document, node, text, link) { + const wrapper = document.createElement('span'); + wrapper.innerHTML = node.textContent.replace(text, `${text}`); + node.replaceWith(...wrapper.childNodes); + } } module.exports = (config) => { diff --git a/pages/site.yml b/pages/site.yml index 102039c0d..547ce19d6 100644 --- a/pages/site.yml +++ b/pages/site.yml @@ -17,3 +17,7 @@ rewriteRules: "src/modules/esl-traversing-query/README.md": "/core/esl-traversing-query" "src/modules/esl-trigger/README.md": "/components/esl-trigger" "src/modules/esl-toggleable/README.md": "/components/esl-toggleable" + +globalTerms: + "ESL Base Element": "/core/esl-base-element" + "ESL Mixin Element": "/core/esl-mixin-element"