Initial commit (after 2 months)
This commit is contained in:
@@ -0,0 +1,937 @@
|
||||
|
||||
//#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("<!DOCTYPE html>", "").replaceAll("<html>", "").replaceAll("</html>", "");
|
||||
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 =
|
||||
`
|
||||
<div>
|
||||
<center style='position: relative; transform: translateY(20vh); width: 100%; text-align: center;'>
|
||||
<h1 style>Page Not Found</h1>
|
||||
</center>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user