Mercurial
diff mrjunejune/src/jrpg/jrpg.js @ 273:e02e2036ef84 default tip
add Layer 2 JRPG component system
Add reusable content and window modals, an isolated component sandbox, shared cyberpunk scroll areas, production-safe cache freshness, and server-rendered JRPG panel state.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 08 Aug 2026 02:08:08 -0700 |
| parents | 41a49c29a28f |
| children |
line wrap: on
line diff
--- a/mrjunejune/src/jrpg/jrpg.js Fri Aug 07 16:05:29 2026 -0700 +++ b/mrjunejune/src/jrpg/jrpg.js Sat Aug 08 02:08:08 2026 -0700 @@ -543,6 +543,7 @@ class MjjJrpgPreview extends HTMLElement { connectedCallback() { + this._modalOwner = this.querySelector("[data-detail-modal-owner]"); this._dialog = this.querySelector("dialog"); this._onDialogClose = () => { this._toolRequest = (this._toolRequest || 0) + 1; @@ -570,30 +571,32 @@ const latestBlog = event.target.closest("[data-latest-blog]"); if (latestBlog && !event.metaKey && !event.ctrlKey && !event.shiftKey) { event.preventDefault(); - this.querySelector("zen-dialog").open(); + this.show("blog"); + this._modalOwner?.open(); void this.loadBlogDetail(latestBlog.dataset.blogUrl); return; } const blogArchive = event.target.closest("[data-blog-archive]"); if (blogArchive) { event.preventDefault(); - this.querySelector("zen-dialog").open(); this.show("blog"); + this._modalOwner?.open(); void this.loadBlogs(); return; } const resumeModal = event.target.closest("[data-resume-modal]"); if (resumeModal) { event.preventDefault(); - this.querySelector("zen-dialog").open(); this.show("resume"); + this._modalOwner?.open(); void this.loadResume(); return; } const latestTool = event.target.closest("[data-latest-tool]"); if (latestTool && !event.metaKey && !event.ctrlKey && !event.shiftKey) { event.preventDefault(); - this.querySelector("zen-dialog").open(); + this.show("tools"); + this._modalOwner?.open(); void this.loadToolDetail(latestTool.dataset.toolUrl); } }; @@ -608,6 +611,18 @@ this.cleanupTool(); } + _setDetailModalType(useWindow) { + const targetName = useWindow ? "mjj-window-modal" : "mjj-content-modal"; + if (!this._modalOwner || this._modalOwner.localName === targetName) return; + const replacement = document.createElement(targetName); + for (const attribute of [...this._modalOwner.attributes]) { + replacement.setAttribute(attribute.name, attribute.value); + } + replacement.append(...this._modalOwner.childNodes); + this._modalOwner.replaceWith(replacement); + this._modalOwner = replacement; + } + show(selection) { this.cleanupTool(); const preview = PREVIEWS[selection] || PREVIEWS.resume; @@ -617,9 +632,6 @@ this.querySelector("[data-dialog-title]").textContent = preview.title; this.querySelector("[data-dialog-copy]").textContent = preview.copy; this.renderShowcase(preview.works); - const link = this.querySelector("[data-preview-link]"); - link.href = preview.url; - link.setAttribute("aria-label", `Open ${preview.title}`); const resumeDossier = this.querySelector("[data-resume-dossier]"); const resumeDownload = this.querySelector("[data-resume-download]"); const blogBrowser = this.querySelector("[data-blog-browser]"); @@ -627,10 +639,10 @@ const isResume = selection === "resume"; const isBlog = selection === "blog"; const isTools = selection === "tools"; + this._setDetailModalType(isTools); this._dialog.dataset.detailMode = isTools ? "tools" : "content"; resumeDossier.hidden = !isResume; resumeDownload.hidden = !isResume; - this.querySelector("[data-dialog-actions]").hidden = !isResume; blogBrowser.hidden = !isBlog; toolBrowser.hidden = !isTools; this.querySelector("[data-dialog-copy]").hidden = @@ -711,6 +723,12 @@ const resume = documentCopy.querySelector("main"); if (!resume) throw new Error("Resume content is unavailable"); sanitizeFetchedMain(resume); + for (const duplicateDownload of resume.querySelectorAll( + 'a[href="/public/resume.pdf"]', + )) { + duplicateDownload.closest("zen-button")?.remove(); + duplicateDownload.remove(); + } content.replaceChildren(...resume.childNodes); status.hidden = true; this._resumeLoaded = true; @@ -823,9 +841,6 @@ status.textContent = `Loading ${blog.label}...`; content.replaceChildren(); this.querySelector("[data-dialog-title]").textContent = blog.label; - const fullPage = this.querySelector("[data-preview-link]"); - fullPage.href = blog.url; - fullPage.setAttribute("aria-label", `Open ${blog.label}`); for (const button of this.querySelectorAll("[data-blog-entry]")) { button.setAttribute( "aria-pressed", @@ -928,9 +943,6 @@ this.cleanupTool(); content.replaceChildren(); this.querySelector("[data-dialog-title]").textContent = tool.label; - const fullPage = this.querySelector("[data-preview-link]"); - fullPage.href = tool.url; - fullPage.setAttribute("aria-label", `Open ${tool.label}`); if (tool.url === "/tools/markdown_to_html") { await this.renderMarkdownTool(content, status, request); return; @@ -1426,10 +1438,12 @@ this._loadMoreButton = this.querySelector("[data-archive-load-more]"); this._newButton = this.querySelector("[data-archive-new]"); this._closeButton = this.querySelector("[data-archive-close]"); + this._deleteModal = this.querySelector("[data-archive-delete-owner]"); this._deleteDialog = this.querySelector("[data-archive-delete-dialog]"); this._deleteNameEl = this.querySelector("[data-archive-delete-name]"); this._deleteCancelButton = this.querySelector("[data-archive-delete-cancel]"); this._deleteConfirmButton = this.querySelector("[data-archive-delete-confirm]"); + this._renameModal = this.querySelector("[data-archive-rename-owner]"); this._renameDialog = this.querySelector("[data-archive-rename-dialog]"); this._renameInput = this.querySelector("[data-archive-rename-input]"); this._renameForm = this.querySelector("[data-archive-rename-form]"); @@ -1452,7 +1466,7 @@ }); this._deleteCancelButton?.addEventListener("click", () => { - this._deleteDialog?.close(); + this._deleteModal?.close(); const t = this._pendingDeleteTrigger; this._pendingDeleteId = null; this._pendingDeleteTrigger = null; @@ -1462,7 +1476,7 @@ this._deleteConfirmButton?.addEventListener("click", () => { const id = this._pendingDeleteId; const trigger = this._pendingDeleteTrigger; - this._deleteDialog?.close(); + this._deleteModal?.close(); this._pendingDeleteId = null; this._pendingDeleteTrigger = null; if (id) { @@ -1481,7 +1495,7 @@ }); this._renameCancelButton?.addEventListener("click", () => { - this._renameDialog?.close(); + this._renameModal?.close(); const t = this._pendingRenameTrigger; this._pendingRenameId = null; this._pendingRenameTrigger = null; @@ -1494,7 +1508,7 @@ const trigger = this._pendingRenameTrigger; const newTitle = this._renameInput?.value.trim(); if (!id || !newTitle) return; - this._renameDialog?.close(); + this._renameModal?.close(); this._pendingRenameId = null; this._pendingRenameTrigger = null; this.dispatchEvent(new CustomEvent("mjj-archive-rename", { @@ -1529,7 +1543,7 @@ this._pendingRenameId = item._convId; this._pendingRenameTrigger = renameButton; if (this._renameInput) this._renameInput.value = item._convTitle || ""; - this._renameDialog?.showModal(); + this._renameModal?.open(); requestAnimationFrame(() => { this._renameInput?.select(); }); } return; @@ -1541,7 +1555,7 @@ this._pendingDeleteId = item._convId; this._pendingDeleteTrigger = deleteButton; if (this._deleteNameEl) this._deleteNameEl.textContent = item._convTitle || ""; - this._deleteDialog?.showModal(); + this._deleteModal?.open(); requestAnimationFrame(() => { this._deleteConfirmButton?.focus(); }); } return; @@ -2186,7 +2200,10 @@ let characterTimer = 0; let currentConversationId = null; - let currentPanel = "resume"; + const serverInitialPanel = VALID_PANELS.includes(shell?.dataset.initialPanel) + ? shell.dataset.initialPanel + : "resume"; + let currentPanel = serverInitialPanel; let archiveCursor = null; let activeController = null; let archiveLoadInFlight = false; /* serialize pagination requests */ @@ -2799,7 +2816,7 @@ normalUrl, ); } - currentPanel = validPanel || "resume"; + currentPanel = validPanel || serverInitialPanel; } /* ---- Load archive; only an explicit URL restores a conversation ---- */