/** * ghost-dynamic-footnotes 1.0.0 (https://github.com/Inoryum-Ltd/ghost-dynamic-footnotes) * A simple script for creating dynamic footnotes for Ghost CMS. * Copyright 2024 Inoryum Ltd (https://inoryum.com) * Released under MIT License * Released on: May 11, 2024 */ function createDynamicFootnotes(customOptions = {}) { // Default configuration const defaultOptions = { backArrowSVG: '', postContentSelector: '.gh-content', footnotesDiv: 'footnotes', footnotesList: 'footnotes-list', footnoteItem: 'footnote', footnoteLink: 'footnote-link', }; // Merge with custom options (overriding defaults) const options = { ...defaultOptions, ...customOptions }; const regexRef = /\[\[([0-9]+)\]\](?!:)/g; const regexDef = /\[\[([0-9]+)\]\]:/g; // Get post content elements const postContentElements = document.querySelectorAll(options.postContentSelector); // Footnote reference handling postContentElements.forEach(postContent => { postContent.querySelectorAll('p, li, figcaption, .kg-callout-text, blockquote em, blockquote, div').forEach(element => { for (let i = 0; i < element.childNodes.length; i++) { const node = element.childNodes[i]; if (node.nodeName === "#text") { let textchunk = node.nodeValue; const matches = textchunk.matchAll(regexRef); for (const match of matches) { const firstPiece = textchunk.slice(0, match.index); const lastPiece = textchunk.slice(match.index + match[0].length); const linkPiece = `${match[1]}`; const newPiece = firstPiece + linkPiece + lastPiece; const tmp = document.createRange().createContextualFragment(newPiece); node.replaceWith(tmp); } } } }); }); // Footnote definition handling postContentElements.forEach(postContent => { const footnotesDiv = document.createElement('div'); footnotesDiv.classList.add(options.footnotesDiv); const footnotesList = document.createElement('ol'); footnotesList.classList.add(options.footnotesList); postContent.querySelectorAll('p').forEach(node => { let textchunk = node.innerHTML; const matches = textchunk.matchAll(regexDef); for (const match of matches) { let listItem = document.createElement('li'); listItem.classList.add(options.footnoteItem); listItem.id = `fn:${match[1]}`; let referredContent = textchunk.slice(match.index + match[0].length); textchunk = textchunk.replace(match[0] + referredContent, ''); listItem.innerHTML = `

${referredContent} ${options.backArrowSVG}

`; // (SVG code omitted) listItem.setAttribute('data-reference-content', referredContent); footnotesList.appendChild(listItem); } node.innerHTML = textchunk; if (node.textContent.trim() === '') { node.remove(); } }); if (footnotesList.childNodes.length > 0) { footnotesDiv.appendChild(footnotesList); postContent.appendChild(footnotesDiv); } }); // Tooltip const footnotes = document.querySelectorAll(`li.${options.footnoteItem}`); footnotes.forEach(footnote => { const referenceContent = footnote.getAttribute('data-reference-content'); let tempElement = document.createElement('div'); tempElement.innerHTML = referenceContent; const plainTextContent = tempElement.textContent || tempElement.innerText; const link = document.querySelector(`a.${options.footnoteLink}[href="#${footnote.id}"]`); if (link) { link.setAttribute('title', plainTextContent); } }); }