Reworked the back button to be strictly owned/injected by the WM. Back

buttons are now tied to callback functions per the window upon
registration of the child.

Also, added a share button. This one belongs to the blog.
This commit is contained in:
lordtet 2026-03-30 21:22:41 -04:00
parent ddc223846e
commit 750f7fa50a
3 changed files with 94 additions and 33 deletions

View file

@ -487,14 +487,13 @@ body { animation: flicker 12s infinite; }
.project-tag.active { background: var(--p); border-color: var(--p); color: #000; }
.tag-count { opacity: 0.65; font-size: 10px; }
/* Back button inside post view */
.blog-back,
.project-back {
/* Back / share buttons injected into sub-page views */
.win-back,
.win-share {
appearance: none;
-webkit-appearance: none;
border: 1px solid var(--p-dim);
background: transparent;
color: var(--p);
font-family: var(--font-body);
font-size: 13px;
padding: 2px 12px;
@ -503,8 +502,11 @@ body { animation: flicker 12s infinite; }
display: inline-block;
margin-bottom: 4px;
}
.blog-back:hover,
.project-back:hover { background: var(--p); color: #000; border-color: var(--p); }
.win-back { color: var(--p); margin-right: 6px; }
.win-back:hover { background: var(--p); color: #000; border-color: var(--p); }
.win-share { color: var(--p-dim); }
.win-share:hover,
.win-share.copied { border-color: var(--p); color: var(--p); }
/* Post metadata line */
.post-meta {

View file

@ -1,21 +1,32 @@
(function () {
"use strict";
function syncWindow(container) {
container.scrollTop = 0;
var win = container.closest(".window");
if (win && win._syncMore) win._syncMore();
}
function injectShare(container, url) {
var parts = url.replace(/\/$/, "").split("/").filter(Boolean);
var type = parts[1]; // "blog" or "projects"
var slug = parts[2];
if (type !== "blog" && type !== "projects") return;
function openSubPage(url, container, backUrl) {
WM.loadFragment(url, container, function () {
var back = document.createElement("button");
back.className = "blog-back";
back.dataset.backUrl = backUrl;
back.textContent = "\u2190 back";
container.insertBefore(back, container.firstChild);
syncWindow(container);
var shareUrl = window.location.origin + "/#" + type + "/" + slug;
var btn = document.createElement("button");
btn.className = "win-share";
btn.textContent = "share";
btn.addEventListener("click", function () {
navigator.clipboard.writeText(shareUrl).then(function () {
btn.textContent = "copied!";
btn.classList.add("copied");
setTimeout(function () {
btn.textContent = "share";
btn.classList.remove("copied");
}, 2000);
}).catch(function () {
window.prompt("Copy this link:", shareUrl);
});
});
var back = container.querySelector(".win-back");
if (back) back.insertAdjacentElement("afterend", btn);
else container.insertBefore(btn, container.firstChild);
}
document.addEventListener("click", function (e) {
@ -24,8 +35,9 @@
var postLink = e.target.closest(".blog-open-post");
if (postLink) {
e.preventDefault();
var container = postLink.closest(".window-content");
openSubPage(postLink.dataset.postUrl, container, "/fragments/blog/");
WM.navigate(postLink.closest(".window-content"), postLink.dataset.postUrl, "/fragments/blog/", function (c) {
injectShare(c, postLink.dataset.postUrl);
});
return;
}
@ -33,17 +45,8 @@
var projLink = e.target.closest(".project-open-item");
if (projLink) {
e.preventDefault();
var container = projLink.closest(".window-content");
openSubPage(projLink.dataset.projectUrl, container, "/fragments/projects/");
return;
}
// Back button (shared by blog and projects)
var backBtn = e.target.closest(".blog-back");
if (backBtn) {
var container = backBtn.closest(".window-content");
WM.loadFragment(backBtn.dataset.backUrl, container, function () {
syncWindow(container);
WM.navigate(projLink.closest(".window-content"), projLink.dataset.projectUrl, "/fragments/projects/", function (c) {
injectShare(c, projLink.dataset.projectUrl);
});
return;
}
@ -77,4 +80,20 @@
}
});
// Hash-based deep linking: /#blog/<slug> or /#projects/<slug>
document.addEventListener("wm:ready", function () {
var hash = window.location.hash.slice(1);
if (!hash) return;
var slash = hash.indexOf("/");
if (slash === -1) return;
var type = hash.slice(0, slash);
var slug = hash.slice(slash + 1);
if (!slug || (type !== "blog" && type !== "projects")) return;
var fragUrl = "/fragments/" + type + "/" + slug + "/";
WM.showAt("win-" + type, fragUrl, "/fragments/" + type + "/", function (c) {
injectShare(c, fragUrl);
});
});
}());

View file

@ -35,6 +35,39 @@ const WM = (() => {
const mobile = () => window.matchMedia("(pointer: coarse)").matches;
/* Sub-page navigation */
function navigate(container, url, backUrl, onDone) {
loadFragment(url, container, () => {
if (backUrl) {
const back = document.createElement("button");
back.className = "win-back";
back.dataset.backUrl = backUrl;
back.textContent = "\u2190 back";
container.insertBefore(back, container.firstChild);
}
if (onDone) onDone(container);
container.scrollTop = 0;
const win = container.closest(".window");
if (win && win._syncMore) win._syncMore();
});
}
function showAt(id, url, backUrl, onDone) {
const s = state.get(id);
if (!s) return;
s.contentLoaded = true;
s.el.classList.remove("hidden");
s.hidden = false;
focusEl(s.el);
const contentEl = s.el.querySelector(".window-content");
if (contentEl) navigate(contentEl, url, backUrl, onDone);
requestAnimationFrame(() => {
clampToArea(s.el);
if (s.el._syncMore) s.el._syncMore();
});
}
/* Init */
function init() {
@ -42,9 +75,16 @@ const WM = (() => {
setupIcons();
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
document.addEventListener("click", e => {
const btn = e.target.closest(".win-back");
if (!btn) return;
const container = btn.closest(".window-content");
if (container) navigate(container, btn.dataset.backUrl, null);
});
// Focus topmost visible window on load
const visible = [...state.values()].filter(s => !s.hidden);
if (visible.length) focusEl(visible[visible.length - 1].el);
document.dispatchEvent(new CustomEvent("wm:ready"));
}
/* Build and inject window chrome */
@ -302,5 +342,5 @@ const WM = (() => {
document.addEventListener("DOMContentLoaded", init);
return { show, hide, toggle, focus, loadFragment };
return { show, hide, toggle, focus, loadFragment, navigate, showAt };
})();