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:
parent
ddc223846e
commit
750f7fa50a
3 changed files with 94 additions and 33 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue