//#region Helpers function getAbsoluteRootPath() { if (typeof rootPath == 'undefined') setupRootPath(document); return new URL(window.location.href + "/../" + rootPath).pathname; } function getURLPath(url = window.location.pathname) { let absoluteRoot = getAbsoluteRootPath(); let pathname = url.substring(absoluteRoot.length); return pathname; } function getURLRootPath(url = window.location.pathname) { let path = getURLPath(url); let splitPath = path.split("/"); let rootPath = ""; for (let i = 0; i < splitPath.length - 1; i++) { rootPath += "../"; } return rootPath; } async function setTreeCollapsed(element, collapsed, animate = true) { if (!element || !element.classList.contains("mod-collapsible")) return; let children = element.querySelector(".tree-item-children"); if (collapsed) { element.classList.add("is-collapsed"); if(animate) slideUp(children, 100); else children.style.display = "none"; } else { element.classList.remove("is-collapsed"); if(animate) slideDown(children, 100); else children.style.removeProperty("display"); } } async function setTreeCollapsedAll(elements, collapsed, animate = true) { let childrenList = []; elements.forEach(async element => { if (!element || !element.classList.contains("mod-collapsible")) return; let children = element.querySelector(".tree-item-children"); if (collapsed) { element.classList.add("is-collapsed"); } else { element.classList.remove("is-collapsed"); } childrenList.push(children); }); if (collapsed) { if(animate) slideUpAll(childrenList, 100); else childrenList.forEach(async children => children.style.display = "none"); } else { if(animate) slideDownAll(childrenList, 100); else childrenList.forEach(async children => children.style.removeProperty("display")); } } function toggleTreeCollapsed(element) { if (!element) return; setTreeCollapsed(element, !element.classList.contains("is-collapsed")); } function toggleTreeCollapsedAll(elements) { if (!elements) return; setTreeCollapsedAll(elements, !elements[0].classList.contains("is-collapsed")); } function getHeaderEl(headerDiv) { let possibleChildHeader = headerDiv.firstChild; let isHeader = false; while (possibleChildHeader != null) { isHeader = possibleChildHeader ? /[Hh][1-6]/g.test(possibleChildHeader.tagName) : false; if (isHeader) break; possibleChildHeader = possibleChildHeader.nextElementSibling; } return possibleChildHeader; } function getPreviousHeader(headerDiv) { let possibleParent = headerDiv.previousElementSibling; let isHeader = false; while (possibleParent != null) { let possibleChildHeader = getHeaderEl(possibleParent); isHeader = possibleChildHeader ? /[Hh][1-6]/g.test(possibleChildHeader.tagName) : false; if (isHeader) break; possibleParent = possibleParent.previousElementSibling; } return possibleParent; } function setHeaderOpen(headerDiv, open, openParents = true) { if(headerDiv.tagName != "DIV" || !getHeaderEl(headerDiv)) { console.error("setHeaderOpen() must be called with a header div"); return; } // let selector = getHeadingContentsSelector(header); if (open) { headerDiv.classList.remove("is-collapsed"); headerDiv.style.display = ""; } if (!open) { headerDiv.classList.add("is-collapsed"); } let headerEl = getHeaderEl(headerDiv); let childHeaders = []; let possibleChild = headerDiv.nextElementSibling; // loop through next siblings showing/ hiding children until we reach a header of the same or lower level while (possibleChild != null) { let possibleChildHeader = getHeaderEl(possibleChild); if(possibleChildHeader) { // if header is a sibling of this header then break if (possibleChildHeader.tagName <= headerEl.tagName) break; // save child headers to be re closed afterwards childHeaders.push(possibleChild); } if (!open) possibleChild.style.display = "none"; else possibleChild.style.display = ""; possibleChild = possibleChild.nextElementSibling; } if(open) { // if we are opening the header then we need to make sure that all closed child headers stay closed childHeaders.forEach(function(item) { if (item.classList.contains("is-collapsed")) { setHeaderOpen(item, false); } }); // if we are opening the header then we need to make sure that all parent headers are open if (openParents) { let previousHeader = getPreviousHeader(headerDiv); while (previousHeader != null) { let previousHeaderEl = getHeaderEl(previousHeader); if (previousHeaderEl.tagName < headerEl.tagName) { // if header is a parent of this header then unhide setHeaderOpen(previousHeader, true); break; } previousHeader = getPreviousHeader(previousHeader); } } } } let slideUp = (target, duration=500) => { target.style.transitionProperty = 'height, margin, padding'; target.style.transitionDuration = duration + 'ms'; target.style.boxSizing = 'border-box'; target.style.height = target.offsetHeight + 'px'; target.offsetHeight; target.style.overflow = 'hidden'; target.style.height = 0; target.style.paddingTop = 0; target.style.paddingBottom = 0; target.style.marginTop = 0; target.style.marginBottom = 0; window.setTimeout(async () => { target.style.display = 'none'; target.style.removeProperty('height'); target.style.removeProperty('padding-top'); target.style.removeProperty('padding-bottom'); target.style.removeProperty('margin-top'); target.style.removeProperty('margin-bottom'); target.style.removeProperty('overflow'); target.style.removeProperty('transition-duration'); target.style.removeProperty('transition-property'); }, duration); } let slideUpAll = (targets, duration=500) => { targets.forEach(async target => { target.style.transitionProperty = 'height, margin, padding'; target.style.transitionDuration = duration + 'ms'; target.style.boxSizing = 'border-box'; target.style.height = target.offsetHeight + 'px'; target.offsetHeight; target.style.overflow = 'hidden'; target.style.height = 0; target.style.paddingTop = 0; target.style.paddingBottom = 0; target.style.marginTop = 0; target.style.marginBottom = 0; }); window.setTimeout(async () => { targets.forEach(async target => { target.style.display = 'none'; target.style.removeProperty('height'); target.style.removeProperty('padding-top'); target.style.removeProperty('padding-bottom'); target.style.removeProperty('margin-top'); target.style.removeProperty('margin-bottom'); target.style.removeProperty('overflow'); target.style.removeProperty('transition-duration'); target.style.removeProperty('transition-property'); }); }, duration); } let slideDown = (target, duration=500) => { target.style.removeProperty('display'); let display = window.getComputedStyle(target).display; if (display === 'none') display = 'block'; target.style.display = display; let height = target.offsetHeight; target.style.overflow = 'hidden'; target.style.height = 0; target.style.paddingTop = 0; target.style.paddingBottom = 0; target.style.marginTop = 0; target.style.marginBottom = 0; target.offsetHeight; target.style.boxSizing = 'border-box'; target.style.transitionProperty = "height, margin, padding"; target.style.transitionDuration = duration + 'ms'; target.style.height = height + 'px'; target.style.removeProperty('padding-top'); target.style.removeProperty('padding-bottom'); target.style.removeProperty('margin-top'); target.style.removeProperty('margin-bottom'); window.setTimeout(async () => { target.style.removeProperty('height'); target.style.removeProperty('overflow'); target.style.removeProperty('transition-duration'); target.style.removeProperty('transition-property'); }, duration); } let slideDownAll = (targets, duration=500) => { targets.forEach(async target => { target.style.removeProperty('display'); let display = window.getComputedStyle(target).display; if (display === 'none') display = 'block'; target.style.display = display; let height = target.offsetHeight; target.style.overflow = 'hidden'; target.style.height = 0; target.style.paddingTop = 0; target.style.paddingBottom = 0; target.style.marginTop = 0; target.style.marginBottom = 0; target.offsetHeight; target.style.boxSizing = 'border-box'; target.style.transitionProperty = "height, margin, padding"; target.style.transitionDuration = duration + 'ms'; target.style.height = height + 'px'; target.style.removeProperty('padding-top'); target.style.removeProperty('padding-bottom'); target.style.removeProperty('margin-top'); target.style.removeProperty('margin-bottom'); }); window.setTimeout( async () => { targets.forEach(async target => { target.style.removeProperty('height'); target.style.removeProperty('overflow'); target.style.removeProperty('transition-duration'); target.style.removeProperty('transition-property'); }); }, duration); } var slideToggle = (target, duration = 500) => { if (window.getComputedStyle(target).display === 'none') { return slideDown(target, duration); } else { return slideUp(target, duration); } } var slideToggleAll = (targets, duration = 500) => { if (window.getComputedStyle(targets[0]).display === 'none') { return slideDownAll(targets, duration); } else { return slideUpAll(targets, duration); } } //#endregion async function loadDocument(url, pushHistory = true, scrollTo = true) { console.log("Loading document: " + url); // change the active file setActiveDocument(url, scrollTo, pushHistory); let response; // if(typeof embeddedDocuments == 'undefined') // { try { response = await fetch(url); } catch (error) { console.log("Cannot use fetch API (likely due to CORS), just loading the page normally."); window.location.assign(url); return; } // } // else // { // response = new Response(embeddedDocuments[url], {status: 200, statusText: "OK"}); // } let doc = document.implementation.createHTMLDocument(); if (response.ok) { let html = (await response.text()).replaceAll("", "").replaceAll("", "").replaceAll("", ""); doc.documentElement.innerHTML = html; // copy document content and outline tree document.querySelector(".document-container").innerHTML = doc.querySelector(".document-container").innerHTML; document.querySelector(".outline-tree").innerHTML = doc.querySelector(".outline-tree").innerHTML; // if the url has a heading, scroll to it let splitURL = url.split("#"); let pathnameTarget = splitURL[0] ?? url; let headingTarget = splitURL.length > 1 ? splitURL[1] : null; if (headingTarget) document.getElementById(headingTarget).scrollIntoView(); // Change the root path to match the match from the new page setupRootPath(doc); // initialize events on the new page initializePage(document.querySelector(".document-container")); initializePage(document.querySelector(".outline-tree")); document.title = doc.title; } else { // if the page is not able to load instead add a header saying the page doesn't exist document.querySelector(".markdown-preview-view").innerHTML = `

Page Not Found

`; document.querySelector(".outline-tree").innerHTML = ""; console.log("Page not found: " + getAbsoluteRootPath() + url); let newRootPath = getURLRootPath(getAbsoluteRootPath() + url); rootPath = newRootPath; document.querySelector("base").href = newRootPath; document.title = "Page Not Found"; } return doc; } function setActiveDocument(url, scrollTo = true, pushHistory = true) { let pathnameTarget = url.split("#")[0] ?? url; // path with no header // switch active file in file tree document.querySelector(".tree-item.mod-active")?.classList.remove("mod-active"); let treeItems = Array.from(document.querySelectorAll(".tree-item > .tree-item-contents > .tree-item-link")); let treeItem = undefined; for (let item of treeItems) { if (item.getAttribute("href") == url) { let parent = item.parentElement.parentElement; parent.classList.add("mod-active"); treeItem = parent; while (parent.hasAttribute("data-depth")) { setTreeCollapsed(parent, false, false); parent = parent.parentElement.parentElement; } continue; } } if(scrollTo) treeItem?.scrollIntoView({block: "center", inline: "nearest"}); // set the active file in th graph view if(typeof nodes != 'undefined' && window.renderWorker) { let activeNode = nodes?.paths.findIndex(function(item) { return item.endsWith(pathnameTarget); }) ?? -1; if(activeNode >= 0) { window.renderWorker.activeNode = activeNode; } } if(pushHistory && window.location.protocol != "file:") window.history.pushState({ path: pathnameTarget }, '', pathnameTarget); } //#region Initialization function setupThemeToggle(setupOnNode) { if (localStorage.getItem("theme_toggle") != null) { setThemeToggle(localStorage.getItem("theme_toggle") == "true"); } var lastScheme = "theme-dark"; // change theme to match current system theme if (localStorage.getItem("theme_toggle") == null && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { setThemeToggle(true); lastScheme = "theme-dark"; } if (localStorage.getItem("theme_toggle") == null && window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { setThemeToggle(true); lastScheme = "theme-light"; } // set initial toggle state based on body theme class if (document.body.classList.contains("theme-light")) { setThemeToggle(false); } else { setThemeToggle(true); } function setThemeToggle(state, instant = false) { let toggle = document.querySelector(".theme-toggle-input"); toggle.checked = state; if (instant) { var oldTransition = document.body.style.transition; document.body.style.transition = "none"; } if(!toggle.classList.contains("is-checked") && state) { toggle.classList.add("is-checked"); } else if (toggle.classList.contains("is-checked") && !state) { toggle.classList.remove("is-checked"); } if(!state) { if (document.body.classList.contains("theme-dark")) { document.body.classList.remove("theme-dark"); } if (!document.body.classList.contains("theme-light")) { document.body.classList.add("theme-light"); } } else { if (document.body.classList.contains("theme-light")) { document.body.classList.remove("theme-light"); } if (!document.body.classList.contains("theme-dark")) { document.body.classList.add("theme-dark"); } } if (instant) { setTimeout(function() { document.body.style.transition = oldTransition; }, 100); } localStorage.setItem("theme_toggle", state ? "true" : "false"); } setupOnNode.querySelector(".theme-toggle-input")?.addEventListener("change", event => { setThemeToggle(!(localStorage.getItem("theme_toggle") == "true")); }); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { // return if we are printing if (window.matchMedia('print').matches) { printing = true; return; } let newColorScheme = event.matches ? "theme-dark" : "theme-light"; if (newColorScheme == lastScheme) return; if (newColorScheme == "theme-dark") { setThemeToggle(true); } if (newColorScheme == "theme-light") { setThemeToggle(false); } lastScheme = newColorScheme; }); } function setupHeaders(setupOnNode) { // MAKE HEADERS COLLAPSIBLE setupOnNode.querySelectorAll(".heading-collapse-indicator").forEach(function (element) { element.addEventListener("click", function () { var isOpen = !this.parentElement.parentElement.classList.contains("is-collapsed"); setHeaderOpen(this.parentElement.parentElement, !isOpen); }); }); // unfold header when an internal link that points to that header is clicked setupOnNode.querySelectorAll("a.internal-link, a.tree-item-link").forEach(function (element) { element.addEventListener("click", function (event) { event.preventDefault(); let target = this.getAttribute("href"); // if the target is a header uncollapse it if (target.startsWith("#")) { console.log("Uncollapsing header: " + target); let header = document.getElementById(target.substring(1)); setHeaderOpen(header.parentElement, true); } }); }); } function setupTrees(setupOnNode) { const fileTreeItems = Array.from(setupOnNode.querySelectorAll(".tree-container.file-tree .tree-item")); setupOnNode.querySelectorAll(".tree-item-contents > .collapse-icon").forEach(function(item) { item.addEventListener("click", function() { toggleTreeCollapsed(item.parentElement.parentElement); }); }); let fileTreeCollapse = setupOnNode.querySelector(".tree-container.file-tree .collapse-tree-button"); if (fileTreeCollapse) fileTreeCollapse.addEventListener("click", async function() { let fileTreeIsCollapsed = fileTreeCollapse.classList.contains("is-collapsed"); setTreeCollapsedAll(fileTreeItems, !fileTreeIsCollapsed, fileTreeItems.length < 100); fileTreeCollapse.classList.toggle("is-collapsed"); fileTreeCollapse.querySelector("iconify-icon").setAttribute("icon", fileTreeIsCollapsed ? "ph:arrows-out-line-horizontal-bold" : "ph:arrows-in-line-horizontal-bold"); }); let outlineTreeCollapse = setupOnNode.querySelector(".tree-container.outline-tree .collapse-tree-button"); if(outlineTreeCollapse) outlineTreeCollapse.addEventListener("click", async function() { let outlineTreeIsCollapsed = outlineTreeCollapse.classList.contains("is-collapsed"); let items = Array.from(outlineTreeCollapse.parentElement.parentElement.querySelectorAll(".tree-item")); setTreeCollapsedAll(items, !outlineTreeIsCollapsed, items.length < 100); outlineTreeCollapse.classList.toggle("is-collapsed"); outlineTreeCollapse.querySelector("iconify-icon").setAttribute("icon", outlineTreeIsCollapsed ? "ph:arrows-out-line-horizontal-bold" : "ph:arrows-in-line-horizontal-bold"); }); // start with all closed setupOnNode.querySelectorAll(".tree-container .tree-item").forEach(function(item) { if (item.classList.contains("is-collapsed")) setTreeCollapsed(item, true, false); }); // make sure the icons match their starting collaped state setupOnNode.querySelectorAll(".tree-container > .tree-header > .collapse-tree-button").forEach(function(item) { if (item.classList.contains("is-collapsed")) { item.querySelector("iconify-icon").setAttribute("icon", "ph:arrows-out-line-horizontal-bold"); } else { item.querySelector("iconify-icon").setAttribute("icon", "ph:arrows-in-line-horizontal-bold"); } }); } function setupCallouts(setupOnNode) { // MAKE CALLOUTS COLLAPSIBLE // if the callout title is clicked, toggle the display of .callout-content setupOnNode.querySelectorAll(".callout.is-collapsible .callout-title").forEach(function (element) { element.addEventListener("click", function () { var parent = this.parentElement; var isCollapsed = parent.classList.contains("is-collapsed"); parent.classList.toggle("is-collapsed"); element.querySelector(".callout-fold").classList.toggle("is-collapsed"); slideToggle(parent.querySelector(".callout-content"), 100); }); }); } function setupCheckboxes(setupOnNode) { // Fix checkboxed toggling .is-checked setupOnNode.querySelectorAll(".task-list-item-checkbox").forEach(function (element) { element.addEventListener("click", function () { var parent = this.parentElement; parent.classList.toggle("is-checked"); parent.setAttribute("data-task", parent.classList.contains("is-checked") ? "x" : " "); }); }); setupOnNode.querySelectorAll(`.plugin-tasks-list-item input[type="checkbox"]`).forEach(function(checkbox) { checkbox.checked = checkbox.parentElement.classList.contains("is-checked"); }); setupOnNode.querySelectorAll('.kanban-plugin__item.is-complete').forEach(function(checkbox) { checkbox.querySelector('input[type="checkbox"]').checked = true; }); } function setupCanvas(setupOnNode) { let focusedNode = null; // make canvas nodes selectable setupOnNode.querySelectorAll(".canvas-node-content-blocker").forEach(function (element) { element.addEventListener("click", function () { var parent = this.parentElement.parentElement; parent.classList.toggle("is-focused"); this.style.display = "none"; if (focusedNode) { focusedNode.classList.remove("is-focused"); focusedNode.querySelector(".canvas-node-content-blocker").style.display = ""; } focusedNode = parent; }); }); // make canvas node deselect when clicking outside // document.addEventListener("click", function (event) // { // if (!event.target.closest(".canvas-node")) // { // document.querySelectorAll(".canvas-node").forEach(function (node) // { // node.classList.remove("is-focused"); // node.querySelector(".canvas-node-content-blocker").style.display = ""; // }); // } // }); } function setupCodeblocks(setupOnNode) { // make code snippet block copy button copy the code to the clipboard setupOnNode.querySelectorAll(".copy-code-button").forEach(function (element) { element.addEventListener("click", function () { var code = this.parentElement.querySelector("code").textContent; navigator.clipboard.writeText(code); this.textContent = "Copied!"; // set a timeout to change the text back setTimeout(function () { setupOnNode.querySelectorAll(".copy-code-button").forEach(function (button) { button.textContent = "Copy"; }); }, 2000); }); }); } function setupLinks(setupOnNode) { setupOnNode.querySelectorAll(".internal-link, .footnote-link, .tree-item-link").forEach(function(link) { link.addEventListener("click", function(event) { let target = link.getAttribute("href"); event.preventDefault(); // this is linking to a different page if (!target.startsWith("#")) { // load doc, if it is a tree link then don't scroll to the active doc in the file tree loadDocument(target, true, !link.classList.contains("tree-item-link")); return; } else { let headerTarget = document.getElementById(target.substring(1)); setHeaderOpen(headerTarget.parentElement, true); headerTarget.scrollIntoView(); } }); }); window.onpopstate = function(event) { loadDocument(getURLPath(), false); } } // What the FUCK is this abomination??? - Endermanch, 2023 /* let sidebarWidth = undefined; let lineWidth = undefined; function setupResize(setupOnNode) { if (setupOnNode != document) return; function updateSidebars() { let rightSidebar = document.querySelector(".sidebar-right"); let leftSidebar = document.querySelector(".sidebar-left"); let sidebarCount = (rightSidebar ? 1 : 0) + (leftSidebar ? 1 : 0); if (sidebarCount == 0) return; if(!sidebarWidth) sidebarWidth = Math.max(rightSidebar?.clientWidth, leftSidebar?.clientWidth); if (!lineWidth) { let docWidthTestEl = document.createElement("div"); document.querySelector(".markdown-preview-view").appendChild(docWidthTestEl); docWidthTestEl.style.width = "var(--line-width)"; docWidthTestEl.style.minWidth = "var(--line-width)"; docWidthTestEl.style.maxWidth = "var(--line-width)"; lineWidth = docWidthTestEl.clientWidth; docWidthTestEl.remove(); } let letHideRightThreshold = sidebarWidth * sidebarCount + lineWidth / 2; if (window.innerWidth < letHideRightThreshold) { rightSidebar.style.display = "none"; } else { rightSidebar.style.display = ""; } let letHideLeftThreshold = lineWidth / 2 + sidebarWidth; if (window.innerWidth < letHideLeftThreshold) { leftSidebar.style.display = "none"; } else { leftSidebar.style.display = ""; } } window.addEventListener("resize", function() { updateSidebars(); }); updateSidebars(); } */ function setupRootPath(fromDocument) { let basePath = fromDocument.querySelector("#root-path").getAttribute("root-path"); document.querySelector("base").href = basePath; document.querySelector("#root-path").setAttribute("root-path", basePath); rootPath = basePath; } let touchDrag = false; function initializePage(setupOnNode) { setupThemeToggle(setupOnNode); setupHeaders(setupOnNode); setupTrees(setupOnNode); setupCallouts(setupOnNode); setupCheckboxes(setupOnNode); setupCanvas(setupOnNode); setupCodeblocks(setupOnNode); setupLinks(setupOnNode); setupOnNode.querySelectorAll("*").forEach(function(element) { element.addEventListener("touchend", function(event) { if (touchDrag) { touchDrag = false; event.stopPropagation(); return; } if (element instanceof HTMLElement) element.click(); }); }); if(setupOnNode == document) { document.body.addEventListener("touchmove", function(event) { event.stopImmediatePropagation(); touchDrag = true; }); setupRootPath(document); setActiveDocument(getURLPath()); } } function initializeForFileProtocol() { let graphEl = document.querySelector(".graph-view-placeholder"); if(graphEl) { console.log("Running locally, skipping graph view initialization and hiding graph."); graphEl.style.display = "none"; graphEl.previousElementSibling.style.display = "none"; // hide the graph's header } } //#endregion window.onload = function() { if (window.location.protocol == "file:") initializeForFileProtocol(); initializePage(document); }