<%* /* Author: TfTHacker - more info https://tfthacker.com/ Date: 2023-07-16 LICENSE: Copyright © 2023 TfThacker (https://tfthacker.com/) You are granted a non-exclusive, non-transferable, and non-sublicensable license to use and modify this file for your personal use only, and are prohibited from distributing, sublicensing, using for commercial purposes. This file remain the property of TfTHacker, and any unauthorized use or infringement will result in termination of this License. This file are provided "AS IS" without warranty of any kind, and the Licensor shall not be liable for any damages arising from the use or distribution of this file. By using this file, you acknowledge that you have read, understand, and agree to be bound by this License Agreement. */ /* Notes: - TABLES: Work in tables, but links are buggy - LISTS: footnotes are only supported in the first level of list, no in child nodes - Sidenotes will overlap with footnotes from other document sections - create a template to add Tufte footnote to cssclass - no support for embedded content - sidenotes can disappear while editing. they usually appear after reopening file or continuing to edit document outside of current section. This is due to waiting for Obsidian to update the footer section. */ console.log("Loading Tufte Sidenotes Processor"); const templaterPlugin = app.plugins.getPlugin('templater-obsidian').templater; //if script is reloaded, unregister the Markdown procesor try { templaterPlugin.current_functions_object.obsidian.MarkdownPreviewRenderer.unregisterPostProcessor(window.TufteSidenote.MarkdownPostProcessor); } catch (error) { } window.TufteSidenote = {}; window.TufteSidenote.footnotes = {}; window.TufteSidenote.sidenotes = {}; // The Obsidian base class class Component { load() {} onload() {} unload() {} } class SidenoteRenderer extends Component { constructor(el, ctx) { super(el); this.ctx = ctx; } // Adds each footnote by section into a container with the class name tufte-sidenotes-collection // this class will align the footnotes to the right and stack them using a flex grid processFootnotes(el, docId, exportToPDF=false) { for (const fRef of Array.from(el.querySelectorAll("sup.footnote-ref"))) { const footnoteId = fRef.id.replace("fnref", "fn"); // Deetermine what should be the parent node for the sidenotes container let collectionParentNode; if(fRef.parentNode.nodeName==="LI") { //if in a list, only support the first level if(fRef.parentNode.parentNode.parentNode.nodeName==="DIV") collectionParentNode = fRef.parentNode; } else if(fRef.parentNode.nodeName==="TD") //if a table collectionParentNode = fRef.parentNode; else // default paragraph text flow collectionParentNode = exportToPDF ? fRef.parentNode : fRef.parentNode.parentNode; // Collection of footnotes let sidenotesCollectionEl; if(collectionParentNode?.querySelector(".tufte-sidenotes-collection")) { sidenotesCollectionEl = collectionParentNode.querySelector(".tufte-sidenotes-collection") } else { sidenotesCollectionEl = document.createElement("div"); sidenotesCollectionEl.classList.add("tufte-sidenotes-collection"); collectionParentNode?.insertBefore(sidenotesCollectionEl, collectionParentNode.firstChild); } const sidenoteEl = document.createElement("div"); sidenoteEl.classList.add("tufte-sidenote"); sidenoteEl.setAttribute("tufte-fn-Id", footnoteId); sidenotesCollectionEl.appendChild(sidenoteEl); if(exportToPDF===false) (window.TufteSidenote.sidenotes[docId] ??= []).push(sidenoteEl); else { formatFootnote(sidenoteEl, footnoteId, window.TufteSidenote.footnotes[docId][footnoteId].html); } } } load() { super.load(); this.processFootnotes(this.ctx.el, this.ctx.docId, this.ctx.el.classList.contains("markdown-rendered")); } onunload() { super.onunload() } unload() { super.unload(); const docId = this.ctx.docId; const rs = window.TufteSidenote.sidenotes[docId] ??= []; rs.remove(this) if (rs.length <= 0) { delete window.TufteSidenote.sidenotes[docId]; delete window.TufteSidenote.footnotes[docId]; } } } //END: SidenoteRenderer const formatFootnote = (el, footnoteId, innerHTML) => { const footnoteNumber = footnoteId.match(/(?<=fn-)\d+(?=-)/)[0]; el.innerHTML = `<span class="tufte-footnote-number">${footnoteNumber}</span>` + innerHTML; el.querySelector("a.footnote-backref.footnote-link").remove(); } window.TufteSidenote.MarkdownPostProcessor = async (el, ctx)=> { // Test if the document has the proper css classed defined. It needs: // - tufte-sidenotes // - either cornell-left or cornell-right if(ctx.frontmatter===undefined) return; const cssclass = ctx.frontmatter["cssclass"]; if(!cssclass?.includes("tufte-sidenotes")) return; if(!cssclass?.includes("cornell-left") && !cssclass?.includes("cornell-right")) return; // Detect if there are footnotes. // If using Export To PDF, the el element has a class called markdown-rendered // - This means el is the entire document, not section by section as normally handled by MarkdownPostProcessor // if this class doesn't exist, the footnotes are not process until all other sections are process // - for this reason we add an empty div for the footnote, then after footnotes section is processed, the // empty food note divs are updated with the html of the footnote // Long story short, we have to process "screen" and "Export to PDF" differently in rendering const footnotes = el.querySelectorAll(".footnotes li"); if(footnotes.length>0) { window.TufteSidenote.footnotes[ctx.docId]={} for(const footnote of footnotes) { // Save footnotes for later processing window.TufteSidenote.footnotes[ctx.docId][footnote.id] = { html: footnote.innerHTML } // if NOT exporting to PDF, update the footnotes if(!el.classList.contains("markdown-rendered")) for(sidenoteEl of window.TufteSidenote.sidenotes[ctx.docId]) if(sidenoteEl.getAttribute("tufte-fn-Id")===footnote.id) formatFootnote(sidenoteEl, footnote.id, footnote.innerHTML); } // END: for } // END: if ctx.addChild(new SidenoteRenderer(el, ctx)); } templaterPlugin.plugin.registerMarkdownPostProcessor(window.TufteSidenote.MarkdownPostProcessor); %>