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

View file

@ -1,21 +1,32 @@
(function () { (function () {
"use strict"; "use strict";
function syncWindow(container) { function injectShare(container, url) {
container.scrollTop = 0; var parts = url.replace(/\/$/, "").split("/").filter(Boolean);
var win = container.closest(".window"); var type = parts[1]; // "blog" or "projects"
if (win && win._syncMore) win._syncMore(); var slug = parts[2];
} if (type !== "blog" && type !== "projects") return;
function openSubPage(url, container, backUrl) { var shareUrl = window.location.origin + "/#" + type + "/" + slug;
WM.loadFragment(url, container, function () { var btn = document.createElement("button");
var back = document.createElement("button"); btn.className = "win-share";
back.className = "blog-back"; btn.textContent = "share";
back.dataset.backUrl = backUrl; btn.addEventListener("click", function () {
back.textContent = "\u2190 back"; navigator.clipboard.writeText(shareUrl).then(function () {
container.insertBefore(back, container.firstChild); btn.textContent = "copied!";
syncWindow(container); 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) { document.addEventListener("click", function (e) {
@ -24,8 +35,9 @@
var postLink = e.target.closest(".blog-open-post"); var postLink = e.target.closest(".blog-open-post");
if (postLink) { if (postLink) {
e.preventDefault(); e.preventDefault();
var container = postLink.closest(".window-content"); WM.navigate(postLink.closest(".window-content"), postLink.dataset.postUrl, "/fragments/blog/", function (c) {
openSubPage(postLink.dataset.postUrl, container, "/fragments/blog/"); injectShare(c, postLink.dataset.postUrl);
});
return; return;
} }
@ -33,17 +45,8 @@
var projLink = e.target.closest(".project-open-item"); var projLink = e.target.closest(".project-open-item");
if (projLink) { if (projLink) {
e.preventDefault(); e.preventDefault();
var container = projLink.closest(".window-content"); WM.navigate(projLink.closest(".window-content"), projLink.dataset.projectUrl, "/fragments/projects/", function (c) {
openSubPage(projLink.dataset.projectUrl, container, "/fragments/projects/"); injectShare(c, projLink.dataset.projectUrl);
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);
}); });
return; 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; 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 */ /* Init */
function init() { function init() {
@ -42,9 +75,16 @@ const WM = (() => {
setupIcons(); setupIcons();
document.addEventListener("mousemove", onMove); document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp); 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 // Focus topmost visible window on load
const visible = [...state.values()].filter(s => !s.hidden); const visible = [...state.values()].filter(s => !s.hidden);
if (visible.length) focusEl(visible[visible.length - 1].el); if (visible.length) focusEl(visible[visible.length - 1].el);
document.dispatchEvent(new CustomEvent("wm:ready"));
} }
/* Build and inject window chrome */ /* Build and inject window chrome */
@ -302,5 +342,5 @@ const WM = (() => {
document.addEventListener("DOMContentLoaded", init); document.addEventListener("DOMContentLoaded", init);
return { show, hide, toggle, focus, loadFragment }; return { show, hide, toggle, focus, loadFragment, navigate, showAt };
})(); })();