From f996111ff0a1264487cb24d635487092e1cae34b Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 24 Aug 2026 05:55:56 +0100 Subject: [PATCH] refactor: semantically port to AffineScript --- src/WikiMain.affine | 211 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 3 deletions(-) diff --git a/src/WikiMain.affine b/src/WikiMain.affine index 04dc118..f4310e5 100644 --- a/src/WikiMain.affine +++ b/src/WikiMain.affine @@ -1,7 +1,212 @@ // SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// Ported via Harvard Engine bulk-processor +// Ported via Harvard Engine (Semantic pass) module WikiMain; -// TODO: Complete semantic implementation +// Qubes SDP Wiki - ReScript + +// DOM bindings +@val external document: Dom.document = "document" +@val external window: Dom.window = "window" +@send external addEventListener: (Dom.document, string, unit => unit) => unit = "addEventListener" +@send external querySelector: (Dom.document, string) => Nullable.t = "querySelector" +@send external querySelectorAll: (Dom.document, string) => array = "querySelectorAll" +@send external querySelectorEl: (Dom.element, string) => Nullable.t = "querySelector" +@send external createElement: (Dom.document, string) => Dom.element = "createElement" +@send external appendChild: (Dom.element, Dom.element) => unit = "appendChild" +@send external insertBefore: (Dom.element, Dom.element, Nullable.t) => unit = "insertBefore" +@send external getAttribute: (Dom.element, string) => Nullable.t = "getAttribute" +@send external setAttribute: (Dom.element, string, string) => unit = "setAttribute" +@send external addEventListenerEl: (Dom.element, string, Dom.event => unit) => unit = "addEventListener" +@send external preventDefault: Dom.event => unit = "preventDefault" +@send external scrollIntoView: (Dom.element, {"behavior": string, "block": string}) => unit = "scrollIntoView" +@get external textContent: Dom.element => string = "textContent" +@set external setTextContent: (Dom.element, string) => unit = "textContent" +@set external setClassName: (Dom.element, string) => unit = "className" +@set external setHref: (Dom.element, string) => unit = "href" +@set external setId: (Dom.element, string) => unit = "id" +@get external id: Dom.element => string = "id" +@get external tagName: Dom.element => string = "tagName" +@get external parentElement: Dom.element => Nullable.t = "parentElement" +@get external nextSibling: Dom.element => Nullable.t = "nextSibling" +@get external style: Dom.element => {..} = "style" +@set external setStyleProp: ({..}, string, string) => unit = "" +@get external pathname: Dom.window => string = "location.pathname" +@get external target: Dom.event => Dom.element = "target" +@get external value: Dom.element => string = "value" + +// Clipboard API +@val @scope(("navigator", "clipboard")) external writeText: string => promise = "writeText" + +// Console +@val @scope("console") external log: string => unit = "log" + +// Timers +@val external setTimeout: (unit => unit, int) => unit = "setTimeout" + +// String helpers +module Str = { + @send external split: (string, string) => array = "split" + @send external pop: array => option = "pop" + @send external toLowerCase: string => string = "toLowerCase" + @send external replace: (string, Js.Re.t, string) => string = "replace" + @send external includes: (string, string) => bool = "includes" +} + +// Highlight current page in sidebar navigation +fn highlightCurrentPage = () => { + fn path = window->pathname + fn parts = path->Str.split("/") + fn currentPage = parts->Str.pop->Option.getOr("index.html") + + document->querySelectorAll(".nav-menu a")->Array.forEach(link => { + link->getAttribute("href")->Nullable.toOption->Option.forEach(href => { + if href == currentPage { + fn s = link->style + s->setStyleProp("background", "rgba(255,255,255,0.1)") + s->setStyleProp("borderLeftColor", "#3874D8") + s->setStyleProp("color", "white") + } + }) + }) +} + +// Add copy buttons to code blocks +fn addCopyButtons = () => { + document->querySelectorAll("pre code")->Array.forEach(block => { + fn button = document->createElement("button") + button->setClassName("copy-button") + button->setTextContent("Copy") + + button->addEventListenerEl("click", _ => { + fn code = block->textContent + writeText(code)->Promise.then(_ => { + button->setTextContent("Copied!") + setTimeout(() => button->setTextContent("Copy"), 2000) + Promise.resolve() + })->ignore + }) + + block->parentElement->Nullable.toOption->Option.forEach(pre => { + fn s = pre->style + s->setStyleProp("position", "relative") + pre->appendChild(button) + }) + }) +} + +// Add anchor links to headings +fn addAnchorLinks = () => { + document->querySelectorAll("h2, h3, h4")->Array.forEach(heading => { + fn text = heading->textContent + fn id = text + ->Str.toLowerCase + ->Str.replace(%re("/[^a-z0-9]+/g"), "-") + ->Str.replace(%re("/(^-|-$)/g"), "") + + heading->setId(id) + + fn anchor = document->createElement("a") + anchor->setClassName("anchor-link") + anchor->setHref("#" ++ id) + anchor->setTextContent("#") + + fn s = anchor->style + s->setStyleProp("marginLeft", "0.5rem") + s->setStyleProp("color", "#ccc") + s->setStyleProp("textDecoration", "none") + s->setStyleProp("display", "none") + + heading->appendChild(anchor) + + heading->addEventListenerEl("mouseenter", _ => { + fn s = anchor->style + s->setStyleProp("display", "inline") + }) + + heading->addEventListenerEl("mouseleave", _ => { + fn s = anchor->style + s->setStyleProp("display", "none") + }) + }) +} + +// Simple search functionality +fn initSearch = () => { + document->querySelector("#wiki-search")->Nullable.toOption->Option.forEach(searchBox => { + searchBox->addEventListenerEl("input", e => { + fn query = (e->target)->value->Str.toLowerCase + document->querySelector(".container")->Nullable.toOption->Option.forEach(content => { + fn text = content->textContent->Str.toLowerCase + if query != "" && text->Str.includes(query) { + log("Found: " ++ query) + } + }) + }) + }) +} + +// Smooth scrolling for anchor links +fn initSmoothScrolling = () => { + document->querySelectorAll("a[href^=\"#\"]")->Array.forEach(anchor => { + anchor->addEventListenerEl("click", e => { + e->preventDefault + anchor->getAttribute("href")->Nullable.toOption->Option.forEach(href => { + document->querySelector(href)->Nullable.toOption->Option.forEach(target => { + target->scrollIntoView({"behavior": "smooth", "block": "start"}) + }) + }) + }) + }) +} + +// Generate table of contents for long pages +fn generateTableOfContents = () => { + fn headings = document->querySelectorAll("h2, h3") + if Array.length(headings) >= 3 { + fn toc = document->createElement("div") + toc->setClassName("table-of-contents") + + fn h3 = document->createElement("h3") + h3->setTextContent("Table of Contents") + toc->appendChild(h3) + + fn list = document->createElement("ul") + + headings->Array.forEach(heading => { + fn li = document->createElement("li") + fn a = document->createElement("a") + a->setHref("#" ++ heading->id) + a->setTextContent(heading->textContent->Str.replace(%re("/#/g"), "")) + + if heading->tagName == "H3" { + fn s = li->style + s->setStyleProp("marginLeft", "1rem") + } + + li->appendChild(a) + list->appendChild(li) + }) + + toc->appendChild(list) + + document->querySelector("h1")->Nullable.toOption->Option.forEach(firstHeading => { + firstHeading->parentElement->Nullable.toOption->Option.forEach(parent => { + parent->insertBefore(toc, firstHeading->nextSibling) + }) + }) + } +} + +// Initialize on DOM ready +fn init = () => { + highlightCurrentPage() + addCopyButtons() + addAnchorLinks() + initSearch() + initSmoothScrolling() + generateTableOfContents() +} + +document->addEventListener("DOMContentLoaded", init) +