import { marked } from 'https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js' const body = document.body async function loadPage(x = 0, y = 0) { const urlString = window.location.search; let pageName = urlString.split('?')[1]; if(!pageName || !(/^[A-Za-z0-9_]+$/.test(pageName))) { pageName = "home_page" window.location.search = "?home_page" window.history.pushState("home_page", "", window.location) } const text = await requestPage(pageName); const steps = await stepThroughPage(pageName); renderPage(pageName, text, steps, x, y); } async function stepThroughPage(pageName) { const step = await fetch(`/loam/step/${pageName}.txt`, { method: 'post' }); if(!step.ok) { console.log("Unable to leave footprints here :("); } const getSteps = await fetch(`/hidden/footsteps/${pageName}.txt`); if(!getSteps.ok) { console.log("Unable to figure out how many steps are here :("); return "an unknown number of " } return getSteps.text(); } async function requestPage(pageName) { const fileName = pageName.replaceAll("__", "/") + ".md"; const response = await fetch(fileName); if(!response.ok) { return "This page does not exist!" } return response.text(); } async function preprocessMarkdown(markdown) { // parse the initial {{{ frontmatter }}} let frontmatter = null; markdown = markdown.trimStart(); if (markdown.startsWith("{{{")) { let begin = markdown.indexOf("{{{") + 2; let end = markdown.indexOf("}}}") + 1; // console.log(`begin: ${begin} end: ${end}`); // console.log(markdown.slice(begin, end)); // parse frontmatter JSON frontmatter = JSON.parse(markdown.slice(begin, end)); // remove frontmatter markdown = markdown.slice(end + 2); } // use frontmatter to add page stylings if (frontmatter) { const title = frontmatter.title ? `# ${frontmatter.title}\n---\n\n` : ""; const status = frontmatter.status ? `*status: ${frontmatter.status}*\n\n---\n\n` : ""; let metadata = (frontmatter.nobreak ? "" : "\n---") + `\n\n*

`; if (frontmatter.last_tended_to) { metadata += `last tended to: ${frontmatter.last_tended_to}`; } if (frontmatter.repotted) { if(!metadata.endsWith("

")) { // not the first metadata item metadata += "
"; } metadata += `repotted: ${frontmatter.repotted}`; } if (frontmatter.planted) { if(!metadata.endsWith("

")) { // not the first metadata item metadata += "
"; } metadata += `planted: ${frontmatter.planted}`; } metadata += "

*"; if (frontmatter.allow_inline) { const matches = markdown.match(/\[\[.+\]\]/g); let i = 0; for(let match of matches) { match = match.slice(2, -2); if(i < 2) { // synchronously load the first few inlines let subpage = await requestPage(`${match}`); subpage = await preprocessMarkdown(subpage); markdown = markdown.replace(`[[${match}]]`, subpage); } else { // replace the rest with placeholders to be loaded slowly markdown = markdown.replace(`[[${match}]]`, `

` ) requestPage(match) .then((text) => preprocessMarkdown(text)) .then((md) => { const placeholder = document.getElementById(`placeholder-${match}`); placeholder.outerHTML = marked.parse(md); }); } i++; } } markdown = title + status + markdown + metadata; } return markdown; } async function renderPage(title, markdown, steps, x = 0, y = 0) { let clientWidth = window.innerWidth; // make sure a page isn't wider than the screen const pageWidth = Math.min(400, clientWidth - 20); // make sure that small screens can still get the corkboard effect if(clientWidth < pageWidth * 2) { clientWidth = pageWidth * 2; } if(x + pageWidth + 20 > clientWidth) { // offset the page by the amount it would've been pushed off the page. x -= ((x + pageWidth + 20) - clientWidth) * 2; } x = Math.max(0, Math.min(x, clientWidth - pageWidth - 20)) let page = document.createElement("div"); page.id = title; page.className = "page"; page.style = `width: ${pageWidth}px; left: ${x}px; top: ${y}px`; markdown = await preprocessMarkdown(markdown); if(steps) { markdown += `

*this page has been passed through ${steps}` markdown += steps === "1" ? " time" : " times" markdown += "*

" } page.innerHTML = marked.parse(markdown); body.appendChild(page); } // satisfies the following constraints for a container: // 1. The container cannot be *outside the screen bounds* // a. If this means resizing the container width, do so // 2. The container must be underneath the mouse pointer (with the container top placed aligned with it) // returns { x = , y = , width = } function placeContainer(width, mouseX, mouseY) { // TODO ( i got lazy ) } async function renderImageDetail(source, width, x = 0, y = 0) { let clientWidth = window.visualViewport.width; if (width < 400) width = 400; // make sure a page isn't wider than the screen const pageWidth = Math.min(width, clientWidth - 20); // start by putting the image in the center of the screen let newX = window.visualViewport.offsetLeft + clientWidth / 2 - width / 2; // make sure the mouse pointer is past the *left edge* of the image if(x < newX) newX = x; // make sure the mouse pointer is before the *right edge* of the image if(newX + pageWidth < x) newX = x - pageWidth; // make sure the page is not off screen either direction if(newX < window.visualViewport.offsetLeft) newX = 0; if(newX + pageWidth > window.visualViewport.offsetLeft + clientWidth) newX = clientWidth - pageWidth; let page = document.createElement("div"); page.id = source; page.className = "page"; page.style = `width: ${pageWidth}px; left: ${newX}px; top: ${y}px`; let img = document.createElement("img"); page.appendChild(img); img.src = source; img.className = "imgDetail"; body.appendChild(page); } window.addEventListener('popstate', (event) => { if(event.state) { const name = event.state; const matches = document.querySelectorAll(`#${name}`); const keep = matches[matches.length - 1]; if(keep) { while(keep.nextElementSibling) { keep.nextElementSibling.remove(); } } else { // no pages remaining, just reload the one requested. loadPage(); } } }); window.addEventListener('click', (event) => { // console.log(event.target.tagName); if(event.target.tagName === "A") { const url = event.target.href; if(!url.includes('hotn.gay') || url.includes('mailto')) { return; } event.preventDefault(); let page = url.split("?")[1] if(page != window.history.state) window.history.pushState(page, "", url); loadPage(event.pageX, event.pageY); } else if(event.target.tagName === "IMG") { if(event.target.className === "imgDetail") { event.target.parentElement.remove(); return; } let src = event.target.src; if(document.getElementById(src)) { document.getElementById(src).remove(); } let width = event.target.naturalWidth + 20; renderImageDetail(src, width, event.pageX, event.pageY); } else { let keep = event.target.closest('.page'); if(keep) { window.history.pushState(keep.id, "", `?${keep.id}`) while(keep.nextElementSibling) { keep.nextElementSibling.remove(); } } } }) loadPage();