diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/init.ts | 118 | ||||
-rw-r--r-- | js/main.ts | 36 | ||||
-rw-r--r-- | js/navigation.ts | 174 | ||||
-rw-r--r-- | js/theme.ts | 70 |
4 files changed, 246 insertions, 152 deletions
@@ -1,12 +1,114 @@ -/// <reference path="navigation.ts" /> -/// <reference path="theme.ts" /> +namespace Ach { + export async function init() { + Ach.loadTheme(); -function init() { - window.addEventListener("popstate", (_e) => { - location.reload(); - }); + Ach.initImages(); + Ach.initLinks(); - loadTheme(); + window.addEventListener("popstate", (_e) => { + location.reload(); + }); + } - initImages(); + export async function initImages() { + let page = Ach.getOnlyElement(document, "page"); + + let imageList = Array.from(page.getElementsByTagName("x-image")); + + for (let image of imageList) { + let alt = image.getAttribute("alt"); + let file = image.getAttribute("data-file"); + + if (!alt) { + alert("stupid superuser forgot to add image caption, please notify"); + alt = ""; + } + + if (!file) { + throw new Error("file not set for image element"); + } + + console.log("initialising image that links to \"" + file + "\""); + + let sourceUrl = "/image/source/" + file + ".webp"; + let thumbnailUrl = "/image/thumbnail/" + file + ".avif"; + + let blurElement = document.createElement("img"); + blurElement.setAttribute("class", "blur"); + blurElement.setAttribute("src", thumbnailUrl); + + let hyperlinkElement = document.createElement("a"); + hyperlinkElement.setAttribute("href", sourceUrl); + hyperlinkElement.setAttribute("rel", "noopener noreferrer"); + hyperlinkElement.setAttribute("target", "_blank"); + + let image_element = document.createElement("img"); + image_element.setAttribute("alt", alt); + image_element.setAttribute("src", thumbnailUrl); + + hyperlinkElement.appendChild(image_element); + + image.appendChild(blurElement); + image.appendChild(hyperlinkElement); + } + } + + export async function initLinks() { + console.log("initialising links"); + + let stats = { + total: 0x0, + overrideCount: 0x0, + currentCount: 0x0, + }; + + for (let link of document.getElementsByTagName("a")) { + ++stats.total; + + let address = link.getAttribute("href"); + + if (!address) { + console.log("note: skipping override of empty link"); + continue; + } + + let pageAnchor = Ach.parseInternalLink(address); + + if (!pageAnchor) { + console.log(`note: skipping override of link to "${address}"`); + continue; + } + + console.log(`note: overriding link to "${address}"`); + ++stats.overrideCount; + + let command = `Ach.loadPage(${JSON.stringify(pageAnchor[0x0])}, ${JSON.stringify(pageAnchor[0x1])})` + + link.removeAttribute("href"); + link.setAttribute("data-page", pageAnchor[0x0]); + link.setAttribute("onclick", command); + + if (pageAnchor[0x1]) { + link.setAttribute("data-anchor", pageAnchor[0x1]); + } + } + + let currentPage = Ach.currentPage(); + + for (let link of document.getElementsByTagName("a")) { + let page = link.getAttribute("data-page"); + let anchor = link.getAttribute("data-anchor"); + + if (page == currentPage && !anchor) { + ++stats.currentCount; + link.setAttribute("aria-current", "page"); + } else { + link.setAttribute("aria-current", "false"); + } + } + + console.log(`note: initialised (${stats.total}) links`); + console.log(`note: of which (${stats.overrideCount}) overrides were done`); + console.log(`note: and of which (${stats.currentCount}) were marked as current`); + } } diff --git a/js/main.ts b/js/main.ts new file mode 100644 index 0000000..44d28ad --- /dev/null +++ b/js/main.ts @@ -0,0 +1,36 @@ +/// <reference path="init.ts" /> +/// <reference path="navigation.ts" /> +/// <reference path="theme.ts" /> + +namespace Ach { + export function currentPage() { + let body = Ach.getFirstElement(document, "body"); + let page = body.getAttribute("data-page"); + + if (!page) { + throw new Error("body does not specify page"); + } + + return page; + } + + export function getFirstElement(dom: Document, tag: string): Element { + let elements = dom.getElementsByTagName(tag); + + if (elements.length < 0x0) { + throw new Error(`unable to find <${tag}> element`); + } + + return elements[0x0]; + } + + export function getOnlyElement(dom: Document, id: string): Element { + let element = dom.getElementById(id); + + if (!element) { + throw new Error(`unable to find #${id} element`); + } + + return element; + } +} diff --git a/js/navigation.ts b/js/navigation.ts index 4a83be6..c1af99f 100644 --- a/js/navigation.ts +++ b/js/navigation.ts @@ -1,145 +1,97 @@ -async function loadPage(page_name: string, anchor?: string) { - console.log(`loading page \`${page_name}\``); +namespace Ach { + export async function loadPage(page_name: string, anchor?: string) { + console.log(`loading page \`${page_name}\``); - let url = `/html/${page_name}.html`; - console.log(`note: page is at "${url}"`); + window.scrollTo({ + top: 0.0, + left: undefined, + behavior: "smooth", + }); - window.history.pushState(page_name, "", url); + let url = `/html/${page_name}.html`; + console.log(`note: page is at "${url}"`); - let response = await fetch(url); + window.history.pushState(page_name, "", url); - if (!response.ok) { - throw new Error(`unable to load page: \"${response.status}\"`); - } + let response = await fetch(url); - let markup = await response.text(); + if (!response.ok) { + throw new Error(`unable to load page: \"${response.status}\"`); + } - let parser = new DOMParser(); - let dom = parser.parseFromString(markup, "text/html"); + let markup = await response.text(); - let titles = document.getElementsByTagName("title"); - let bodies = document.getElementsByTagName("body"); - let page = document.getElementById("page")!; + let parser = new DOMParser(); + let dom = parser.parseFromString(markup, "text/html"); - if (titles.length < 0x1) { - throw new Error("unable to find title"); - } + let body = Ach.getFirstElement(document, "body"); - if (bodies.length < 0x1) { - throw new Error("unable to find body"); - } + let title = Ach.getFirstElement(document, "title"); + let page = Ach.getOnlyElement(document, "page"); - if (!page) { - throw new Error("unable to find page element"); - } + let newTitle = Ach.getFirstElement(dom, "title"); + let newPage = Ach.getOnlyElement(dom, "page"); - let newTitles = dom.getElementsByTagName("title"); - let newPage = dom.getElementById("page"); + title.replaceWith(newTitle); + body.setAttribute("data-page", page_name); + page.replaceWith(newPage); - if (newTitles.length < 0x1) { - throw new Error("unable to find new title"); - } + initImages(); + initLinks(); - if (!newPage) { - throw new Error("unable to find new page element"); - } + if (anchor) { + console.log(`going to anchor \`${anchor}\``); - titles[0x0].replaceWith(newTitles[0x0]); - bodies[0x0].setAttribute("data-page", page_name); - page.replaceWith(newPage); + anchor = `anchor.${anchor}`; - initImages(); + console.log(`note: anchor has id "${anchor}"`); - if (anchor) { - console.log(`going to anchor \`${anchor}\``); + let anchor_element = document.getElementById(anchor); - anchor = `anchor.${anchor}`; + if (!anchor_element) { + throw new Error(`unable to find anchor "${anchor}"`); + } - console.log(`note: anchor has id "${anchor}"`); - - let anchor_element = document.getElementById(anchor); - - if (!anchor_element) { - throw new Error(`unable to find anchor "${anchor}"`); + anchor_element.scrollIntoView({ + behavior: "smooth", + }); } + } - anchor_element.scrollIntoView({ + export function toggleSideMenu() { + window.scrollTo({ + top: 0.0, + left: undefined, behavior: "smooth", }); - } -} - -function toggleSideMenu() { - scrollToTop(); - - let sideMenu = document.getElementById("sideMenu"); - let navBar = document.getElementById("navBar"); - let glyph = document.getElementById("glyph"); - - if (!sideMenu) { - throw new Error("unable to find sideMenu"); - } - - if (!navBar) { - throw new Error("unable to find navigation bar"); - } - - if (!glyph) { - throw new Error("unable to find glyph"); - } - sideMenu.classList.toggle("visible"); - glyph.classList.toggle("hidden"); + let sideMenu = Ach.getOnlyElement(document, "sideMenu"); + let navBar = Ach.getOnlyElement(document, "navBar"); + let glyph = Ach.getOnlyElement(document, "glyph"); - for (let link of navBar.getElementsByTagName("a")) { - link.classList.toggle("hidden"); - } -} - -async function scrollToTop() { - window.scroll({ - top: 0, - left: 0, - behavior: "smooth", - }); -} + sideMenu.classList.toggle("visible"); + glyph.classList.toggle("hidden"); -function initImages() { - let page = document.getElementById("page"); - - if (!page) { - throw new Error("unable to find page"); + for (let link of navBar.getElementsByTagName("a")) { + link.classList.toggle("hidden"); + } } - let imageList = Array.from(page.getElementsByTagName("x-image")); + export function parseInternalLink(address: string): [string, string | undefined] | undefined { + let regex = /\/html\/([A-Za-z0-9]+)\.html(?:#([A-Za-z0-9]+)){0,1}/; + let regex_result = regex.exec(address); - for (let image of imageList) { - let file = image.getAttribute("data-file"); - - if (!file) { - throw new Error("file not set for image element") + if (!regex_result) { + return; } - console.log("initialising image that links to \"" + file + "\""); - - let sourceUrl = "/image/source/" + file + ".webp"; - let thumbnailUrl = "/image/thumbnail/" + file + ".avif"; + let page = regex_result[0x1]; + let anchor = regex_result[0x2]; - let blurElement = document.createElement("img"); - blurElement.setAttribute("class", "blur"); - blurElement.setAttribute("src", thumbnailUrl); - - let hyperlinkElement = document.createElement("a"); - hyperlinkElement.setAttribute("href", sourceUrl); - hyperlinkElement.setAttribute("rel", "noopener noreferrer"); - hyperlinkElement.setAttribute("target", "_blank"); - - let image_element = document.createElement("img"); - image_element.setAttribute("src", thumbnailUrl); - - hyperlinkElement.appendChild(image_element); + if (!page) { + return; + } - image.appendChild(blurElement); - image.appendChild(hyperlinkElement); + return [page, anchor]; } } diff --git a/js/theme.ts b/js/theme.ts index 36959bc..45101da 100644 --- a/js/theme.ts +++ b/js/theme.ts @@ -1,51 +1,55 @@ -const DEFAULT_THEME = "dark"; +namespace Ach { + export function toggleTheme() { + let body = Ach.getFirstElement(document, "body"); -function toggleTheme() { - let bodies = document.getElementsByTagName("body"); + let theme = "light"; - if (!bodies) { - throw new Error("unable to find body"); - } + if (body.classList.contains("light")) { + theme = "dark"; + } - let theme = "light"; + console.log("setting theme to `" + theme + "`"); - if (bodies[0x0].classList.contains("light")) { - theme = "dark"; + body.classList.toggle("light"); + sessionStorage.setItem("theme", theme); } - console.log("setting theme to `" + theme + "`"); + export function loadTheme() { + let theme = sessionStorage.getItem("theme"); - bodies[0x0].classList.toggle("light"); - localStorage.setItem("theme", theme); -} + if (!theme) { + console.log("theme not set, using default"); + theme = defaultTheme(); + } -function loadTheme() { - let theme = localStorage.getItem("theme"); + switch (theme) { + case "dark": + // We assume this theme in our stylesheets. + break; - if (!theme) { - console.log("theme not set, using default"); - theme = DEFAULT_THEME; - } + case "light": + let body = Ach.getFirstElement(document, "body"); + body.classList.add("light"); - switch (theme) { - case "dark": - // We assume this theme in our stylesheets. - break; + break; - case "light": - let bodies = document.getElementsByTagName("body"); + default: + console.log(`invalid theme \"${theme}\", using default`); + //theme = DEFAULT_THEME; // Redundant now. - if (!bodies) { - throw new Error("unable to find body"); + break; } - bodies[0x0].classList.add("light"); - break; + console.log(`note: theme is now \`${theme}\``); + } - default: - console.log(`invalid theme \"${theme}\", using default`); - //theme = DEFAULT_THEME; // Redundant now. + function defaultTheme(): string { + let hour = new Date().getHours(); - break; + if (hour >= 0x6 && hour <= 0x12) { + return "light"; + } else { + return "dark"; + } } } |