240 lines
No EOL
8.3 KiB
JavaScript
240 lines
No EOL
8.3 KiB
JavaScript
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 ? `*<a class="metadata" href="?metadata">status: ${frontmatter.status}</a>*\n\n---\n\n` : "";
|
|
|
|
let metadata = (frontmatter.nobreak ? "" : "\n---") + `\n\n*<a class="metadata" href="?metadata"><p>`;
|
|
if (frontmatter.last_tended_to) {
|
|
metadata += `last tended to: ${frontmatter.last_tended_to}`;
|
|
}
|
|
|
|
if (frontmatter.repotted) {
|
|
if(!metadata.endsWith("<p>")) { // not the first metadata item
|
|
metadata += "<br>";
|
|
}
|
|
metadata += `repotted: ${frontmatter.repotted}`;
|
|
}
|
|
if (frontmatter.planted) {
|
|
if(!metadata.endsWith("<p>")) { // not the first metadata item
|
|
metadata += "<br>";
|
|
}
|
|
metadata += `planted: ${frontmatter.planted}`;
|
|
}
|
|
metadata += "</p></a>*";
|
|
|
|
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}]]`,
|
|
`<p id="placeholder-${match}"></p>`
|
|
)
|
|
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 += `<p>*this page has been passed through ${steps}`
|
|
markdown += steps === "1" ? " time" : " times"
|
|
markdown += "*<p>"
|
|
}
|
|
|
|
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 = <top left X value>, y = <top left y value>, width = <modified container 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(); |