annotate mrjunejune/src/jrpg/jrpg.js @ 267:a78f5986011f

discover bundled production runtimes Locate Bazel-packaged Copilot and Python binaries by stable suffix instead of hardcoding an external repository prefix. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 12:58:55 -0700
parents 056790c4fb0d
children 41a49c29a28f
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1 const CHARACTER_STATES = Object.freeze({
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2 default: {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3 alt: "Epi the Shiba Inu standing happily",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
4 src: "/public/jrpg/shiba-default.webp",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
5 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
6 sad: {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
7 alt: "Epi the Shiba Inu sitting sadly",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
8 src: "/public/jrpg/shiba-sad.webp",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
9 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
10 thinking: {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
11 alt: "Epi the Shiba Inu tilting her head in thought",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
12 src: "/public/jrpg/shiba-thinking.webp",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
13 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
14 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
15
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
16 const PREVIEWS = Object.freeze({
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
17 resume: {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
18 copy: "Member of Technical Staff and engineering leader with 10+ years building AI agent platforms and production systems across Microsoft, Meta, Google, and growth-stage companies.",
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
19 title: "Resume",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
20 url: "/resume",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
21 works: [
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
22 {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
23 detail: "Career dossier",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
24 label: "Full resume",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
25 modal: "resume",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
26 url: "/resume",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
27 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
28 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
29 detail: "Led engineering",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
30 label: "Copilot Tasks",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
31 url: "https://www.microsoft.com/en-us/microsoft-copilot/blog/2026/02/26/copilot-tasks-from-answers-to-actions/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
32 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
33 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
34 detail: "Foundational platform",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
35 label: "AIX Harness / Copilot",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
36 url: "https://www.cio.com/article/3977098/microsoft-doubles-down-on-multi-model-ai-as-it-builds-a-copilot-super-app.html",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
37 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
38 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
39 detail: "Build 2026 products",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
40 label: "Code & Autopilot",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
41 url: "https://news.microsoft.com/build-2026/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
42 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
43 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
44 detail: "Full-stack ads systems",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
45 label: "Meta",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
46 url: "https://www.meta.com/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
47 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
48 {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
49 detail: "Workspace platform",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
50 label: "Google",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
51 url: "https://www.google.com/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
52 },
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
53 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
54 detail: "Technical lead",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
55 label: "Warner Music Group",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
56 url: "https://www.wmg.com/",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
57 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
58 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
59 detail: "Personal agent launch",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
60 label: "Microsoft Scout",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
61 url: "https://www.microsoft.com/en-us/microsoft-365/blog/2026/06/02/introducing-microsoft-scout-your-always-on-personal-agent/",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
62 },
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
63 ],
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
64 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
65 tools: {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
66 copy: "Useful browser tools backed by first-party C, WASM, media, and document-processing systems.",
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
67 title: "Tools",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
68 url: "/tools",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
69 works: [
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
70 { detail: "Writing", label: "Markdown", url: "/tools/markdown_to_html" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
71 { detail: "Media", label: "Converter", url: "/tools/file_converter" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
72 { detail: "Streaming", label: "HLS Player", url: "/tools/hls_player" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
73 ],
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
74 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
75 blog: {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
76 copy: "Technical writing about networking, rendering, performance, developer tooling, and experiments.",
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
77 title: "Blogs",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
78 url: "/blog",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
79 works: [
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
80 { detail: "Archive", label: "All posts", url: "/blog" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
81 ],
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
82 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
83 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
84
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
85 const CONVERSATION_STORAGE_KEY = "mjj-jrpg-conversation-id";
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
86 const CONVERSATION_LEGACY_KEY = "mjj-jrpg-legacy-claim";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
87 const ARCHIVE_PAGE_SIZE = 20;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
88 const TYPING_INTERVAL_MS = 18;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
89 const SCRIPT_LOADS = new Map();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
90 const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
91 const VALID_PANELS = Object.freeze(["resume", "tools", "blog", "conversations"]);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
92 const ROLE_GREETINGS = Object.freeze({
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
93 guest: "Hi, my name is Epi. June usually calls me Epi-chan. I’m here to help you navigate his personal website and learn about his work, projects, and writing.",
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
94 invited: "Hi! I’m Epi, but June usually calls me Epi-chan. Since June invited you, I can be a little more casual. I can help you explore his site, talk about his projects, or brainstorm simple ideas with you.",
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
95 admin: "Hi June! I’m Epi-chan. I’m here to help you review your personal website, think through your projects, and draft or refine public-facing ideas.",
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
96 });
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
97 const TOOL_ICON_NAMES = Object.freeze({
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
98 "/notes": "repository",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
99 "/tools/file_converter": "retry",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
100 "/tools/hls_player": "play",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
101 "/tools/latex_editor": "file",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
102 "/tools/markdown_to_html": "code",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
103 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
104
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
105 function loadScript(source) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
106 if (SCRIPT_LOADS.has(source)) return SCRIPT_LOADS.get(source);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
107 const loading = new Promise((resolve, reject) => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
108 const script = document.createElement("script");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
109 script.src = source;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
110 script.addEventListener("load", resolve, { once: true });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
111 script.addEventListener("error", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
112 SCRIPT_LOADS.delete(source);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
113 script.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
114 reject(new Error(`Unable to load ${source}`));
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
115 }, { once: true });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
116 document.head.append(script);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
117 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
118 SCRIPT_LOADS.set(source, loading);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
119 return loading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
120 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
121
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
122 function greetingForSession(sessionData) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
123 if (sessionData?.kind !== "user") return ROLE_GREETINGS.guest;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
124 return sessionData.role === "admin"
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
125 ? ROLE_GREETINGS.admin
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
126 : ROLE_GREETINGS.invited;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
127 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
128
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
129 class StreamingTextAnimator {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
130 constructor(render) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
131 this._render = render;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
132 this._displayed = "";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
133 this._target = "";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
134 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
135 this._waiters = [];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
136 this._reduceMotion = window.matchMedia(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
137 "(prefers-reduced-motion: reduce)",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
138 ).matches;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
139 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
140
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
141 append(text) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
142 if (typeof text !== "string" || !text) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
143 this._target += text;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
144 this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
145 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
146
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
147 complete(text) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
148 if (typeof text === "string") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
149 this._target = text;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
150 if (!this._target.startsWith(this._displayed)) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
151 let shared = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
152 while (
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
153 shared < this._target.length &&
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
154 shared < this._displayed.length &&
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
155 this._target[shared] === this._displayed[shared]
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
156 ) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
157 shared++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
158 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
159 this._displayed = this._displayed.slice(0, shared);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
160 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
161 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
162 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
163 this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
164 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
165
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
166 waitForIdle() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
167 if (!this._timer && this._displayed === this._target) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
168 return Promise.resolve();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
169 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
170 return new Promise(resolve => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
171 this._waiters.push(resolve);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
172 });
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
173 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
174
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
175 cancel() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
176 window.clearTimeout(this._timer);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
177 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
178 this._target = this._displayed;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
179 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
180 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
181
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
182 _schedule() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
183 if (this._reduceMotion) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
184 this._displayed = this._target;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
185 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
186 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
187 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
188 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
189 if (this._timer || this._displayed === this._target) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
190 this._timer = window.setTimeout(() => this._tick(), TYPING_INTERVAL_MS);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
191 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
192
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
193 _tick() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
194 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
195 const remaining = this._target.length - this._displayed.length;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
196 if (remaining <= 0) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
197 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
198 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
199 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
200 const amount = Math.max(1, Math.ceil(remaining / 80));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
201 this._displayed = this._target.slice(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
202 0,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
203 this._displayed.length + amount,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
204 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
205 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
206 if (this._displayed === this._target) this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
207 else this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
208 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
209
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
210 _settle() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
211 if (this._timer || this._displayed !== this._target) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
212 const waiters = this._waiters.splice(0);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
213 for (const resolve of waiters) resolve();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
214 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
215 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
216
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
217 class MjjJrpgCharacter extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
218 static get observedAttributes() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
219 return ["state"];
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
220 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
221
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
222 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
223 this.render();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
224 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
225
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
226 attributeChangedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
227 if (this.isConnected) this.render();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
228 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
229
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
230 render() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
231 const image = this.querySelector("[data-character]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
232 if (!image) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
233 const state = CHARACTER_STATES[this.getAttribute("state")] ||
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
234 CHARACTER_STATES.default;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
235 image.src = state.src;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
236 image.alt = state.alt;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
237 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
238 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
239
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
240 class MjjJrpgChat extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
241 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
242 this._messages = this.querySelector("[data-messages]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
243 this._viewport = this.querySelector("[data-zen-viewport]");
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
244 this._previousButton = this.querySelector("[data-turn-previous]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
245 this._nextButton = this.querySelector("[data-turn-next]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
246 this._position = this.querySelector("[data-turn-position]");
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
247 this._greetingText = ROLE_GREETINGS.guest;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
248 this._greetingAnimator = null;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
249 this._turnSequence = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
250 this._activeGroup = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
251 this._onNavigationClick = event => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
252 const button = event.target.closest("button");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
253 if (!button || !this.contains(button) || button.disabled) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
254 if (button === this._previousButton) this._moveTurn(-1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
255 else if (button === this._nextButton) this._moveTurn(1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
256 };
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
257 this.addEventListener("click", this._onNavigationClick);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
258 this._refreshTurnGroups("start");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
259 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
260
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
261 disconnectedCallback() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
262 this.removeEventListener("click", this._onNavigationClick);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
263 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
264
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
265 appendMessage(speaker, text) {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
266 let group = this._activeGroup || "start";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
267 if (speaker === "June") {
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
268 this._greetingAnimator?.cancel();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
269 this._greetingAnimator = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
270 const greeting = this._messages?.querySelector("[data-greeting]");
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
271 if (greeting) this.setMessageStreaming(greeting, false);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
272 this._turnSequence++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
273 group = `turn-${this._turnSequence}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
274 this._activeGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
275 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
276 return this._createMessage(speaker, text, group, true);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
277 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
278
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
279 setGreeting(text, animate = true) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
280 this._greetingText = typeof text === "string" && text
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
281 ? text
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
282 : ROLE_GREETINGS.guest;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
283 this._showGreeting(animate);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
284 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
285
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
286 _showGreeting(animate) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
287 if (!this._messages) return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
288 this._greetingAnimator?.cancel();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
289 this._greetingAnimator = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
290 this._messages.replaceChildren();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
291 this._turnSequence = 0;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
292 this._activeGroup = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
293 const item = this._createMessage("Epi", "", "start", false);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
294 if (!item) return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
295 item.dataset.greeting = "true";
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
296 this._refreshTurnGroups("start");
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
297 if (!animate) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
298 this.setMessageText(item, this._greetingText);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
299 this.setMessageStreaming(item, false);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
300 return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
301 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
302 this.setMessageStreaming(item, true);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
303 const animator = new StreamingTextAnimator(value => {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
304 this.setMessageText(item, value);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
305 });
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
306 this._greetingAnimator = animator;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
307 animator.complete(this._greetingText);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
308 void animator.waitForIdle().then(() => {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
309 if (this._greetingAnimator !== animator) return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
310 this.setMessageStreaming(item, false);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
311 this._greetingAnimator = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
312 });
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
313 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
314
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
315 _createMessage(speaker, text, group, select) {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
316 if (!this._messages) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
317 const item = document.createElement("li");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
318 const name = document.createElement("strong");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
319 const copy = document.createElement("p");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
320 item.className = "jrpg-message";
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
321 item.dataset.speaker = speaker;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
322 item.dataset.turnGroup = group;
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
323 name.textContent = speaker;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
324 copy.textContent = text;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
325 item.append(name, copy);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
326 this._messages.append(item);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
327 this._refreshTurnGroups(select ? group : this._currentGroup);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
328 return item;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
329 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
330
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
331 replaceMessages(turns) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
332 if (!this._messages) return;
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
333 this._greetingAnimator?.cancel();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
334 this._greetingAnimator = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
335 this._messages.replaceChildren();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
336 this._turnSequence = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
337 this._activeGroup = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
338 const visibleTurns = turns.filter(turn =>
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
339 ["user", "assistant"].includes(turn.role)
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
340 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
341 if (!visibleTurns.length) {
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
342 this._showGreeting(true);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
343 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
344 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
345 let group = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
346 for (const turn of visibleTurns) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
347 if (turn.role === "user" || !group) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
348 this._turnSequence++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
349 group = `turn-${this._turnSequence}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
350 this._activeGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
351 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
352 this._createMessage(
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
353 turn.role === "user" ? "June" : "Epi",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
354 turn.content ||
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
355 (turn.status === "aborted"
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
356 ? "Quest cancelled."
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
357 : turn.status === "failed"
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
358 ? "The quest failed."
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
359 : ""),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
360 group,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
361 false,
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
362 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
363 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
364 this._refreshTurnGroups(group);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
365 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
366
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
367 startAssistantMessage() {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
368 return this.appendMessage("Epi", "");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
369 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
370
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
371 setMessageText(item, text) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
372 const copy = item?.querySelector("p");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
373 if (copy) copy.textContent = text;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
374 this.scrollEnd();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
375 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
376
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
377 setMessageStreaming(item, streaming) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
378 if (!item) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
379 if (streaming) item.dataset.streaming = "true";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
380 else delete item.dataset.streaming;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
381 this._viewport?.setAttribute("aria-busy", String(streaming));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
382 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
383
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
384 _refreshTurnGroups(preferredGroup) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
385 const groups = [];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
386 for (const item of this._messages?.children || []) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
387 const group = item.dataset.turnGroup || "start";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
388 item.dataset.turnGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
389 if (!groups.includes(group)) groups.push(group);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
390 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
391 this._turnGroups = groups;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
392 const preferredIndex = groups.indexOf(preferredGroup);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
393 const currentIndex = groups.indexOf(this._currentGroup);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
394 this._turnIndex = preferredIndex >= 0
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
395 ? preferredIndex
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
396 : currentIndex >= 0
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
397 ? currentIndex
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
398 : Math.max(0, groups.length - 1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
399 this._showTurn();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
400 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
401
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
402 _moveTurn(offset) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
403 const nextIndex = Math.max(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
404 0,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
405 Math.min(this._turnGroups.length - 1, this._turnIndex + offset),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
406 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
407 if (nextIndex === this._turnIndex) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
408 this._turnIndex = nextIndex;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
409 this._showTurn();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
410 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
411
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
412 _showTurn() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
413 const group = this._turnGroups?.[this._turnIndex];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
414 this._currentGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
415 for (const item of this._messages?.children || []) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
416 item.hidden = item.dataset.turnGroup !== group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
417 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
418 if (this._previousButton) this._previousButton.disabled = this._turnIndex <= 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
419 if (this._nextButton) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
420 this._nextButton.disabled =
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
421 this._turnIndex >= (this._turnGroups?.length || 0) - 1;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
422 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
423 if (this._position) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
424 if (group === "start") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
425 this._position.textContent = "START";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
426 } else {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
427 const turns = this._turnGroups.filter(item => item !== "start");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
428 this._position.textContent =
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
429 `TURN ${turns.indexOf(group) + 1} / ${turns.length}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
430 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
431 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
432 this.scrollEnd();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
433 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
434
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
435 scrollEnd() {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
436 requestAnimationFrame(() => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
437 if (this._viewport) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
438 this._viewport.scrollTop = this._viewport.scrollHeight;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
439 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
440 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
441 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
442 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
443
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
444 class MjjJrpgComposer extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
445 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
446 this._form = this.querySelector("[data-composer]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
447 this._textarea = this.querySelector("textarea");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
448 this._button = this.querySelector('button[type="submit"]');
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
449 this._cancel = this.querySelector("[data-cancel]");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
450 this._cancelControl = this.querySelector("[data-cancel-control]");
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
451 this._onSubmit = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
452 event.preventDefault();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
453 const message = this._textarea?.value.trim();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
454 if (!message || this._button?.disabled) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
455 this.dispatchEvent(new CustomEvent("mjj-jrpg-submit", {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
456 bubbles: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
457 detail: { message },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
458 }));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
459 this._form.reset();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
460 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
461 this._onKeydown = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
462 if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
463 event.preventDefault();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
464 this._form?.requestSubmit();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
465 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
466 this._form?.addEventListener("submit", this._onSubmit);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
467 this._textarea?.addEventListener("keydown", this._onKeydown);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
468 this._onCancel = () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
469 this.dispatchEvent(new CustomEvent("mjj-jrpg-cancel", {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
470 bubbles: true,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
471 }));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
472 };
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
473 this._cancel?.addEventListener("click", this._onCancel);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
474 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
475
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
476 disconnectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
477 this._form?.removeEventListener("submit", this._onSubmit);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
478 this._textarea?.removeEventListener("keydown", this._onKeydown);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
479 this._cancel?.removeEventListener("click", this._onCancel);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
480 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
481
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
482 setBusy(busy) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
483 if (this._textarea) this._textarea.disabled = busy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
484 if (this._button) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
485 this._button.disabled = busy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
486 this._button.setAttribute("aria-busy", String(busy));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
487 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
488 if (this._cancelControl) this._cancelControl.hidden = !busy;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
489 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
490
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
491 setCancellable(cancellable) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
492 if (this._cancelControl) this._cancelControl.hidden = !cancellable;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
493 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
494
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
495 setDisabled(disabled) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
496 if (this._textarea) this._textarea.disabled = disabled;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
497 if (this._button) this._button.disabled = disabled;
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
498 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
499 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
500
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
501 class MjjJrpgMenu extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
502 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
503 this._onClick = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
504 const button = event.target.closest("button[data-preview]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
505 if (!button || !this.contains(button)) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
506 for (const item of this.querySelectorAll("button[data-preview]")) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
507 item.setAttribute("aria-pressed", String(item === button));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
508 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
509 this.dispatchEvent(new CustomEvent("mjj-jrpg-preview-change", {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
510 bubbles: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
511 detail: { selection: button.dataset.preview },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
512 }));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
513 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
514 this.addEventListener("click", this._onClick);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
515 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
516
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
517 disconnectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
518 this.removeEventListener("click", this._onClick);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
519 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
520 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
521
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
522 function sanitizeFetchedMain(main) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
523 for (const unsafe of main.querySelectorAll(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
524 "embed, form, iframe, object, script, style, svg",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
525 )) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
526 unsafe.remove();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
527 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
528 for (const element of main.querySelectorAll("*")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
529 for (const attribute of [...element.attributes]) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
530 const name = attribute.name.toLowerCase();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
531 if (name.startsWith("on") || name === "id" || name === "style") {
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
532 element.removeAttribute(attribute.name);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
533 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
534 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
535 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
536 for (const anchor of main.querySelectorAll("a")) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
537 const href = anchor.getAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
538 if (!href || anchor.getAttribute("aria-disabled") === "true") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
539 anchor.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
540 continue;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
541 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
542 const target = new URL(
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
543 href,
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
544 window.location.href,
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
545 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
546 anchor.href = target.href;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
547 if (target.origin !== window.location.origin) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
548 anchor.target = "_blank";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
549 anchor.rel = "noreferrer";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
550 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
551 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
552 for (const image of main.querySelectorAll("img")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
553 const source = image.getAttribute("src") || "";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
554 if (source.startsWith("/public/") && source.endsWith(".png")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
555 image.src = source.replace(/\.png$/i, ".webp");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
556 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
557 image.loading = "lazy";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
558 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
559 return main;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
560 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
561
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
562 function sanitizeToolDetail(main) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
563 sanitizeFetchedMain(main);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
564 for (const textarea of main.querySelectorAll("textarea")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
565 const sample = document.createElement("pre");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
566 sample.className = "jrpg-tool-source-sample";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
567 sample.textContent = textarea.value || textarea.textContent;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
568 textarea.replaceWith(sample);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
569 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
570 for (const inactive of main.querySelectorAll(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
571 "button, canvas, dialog, input, select, video, [hidden]",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
572 )) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
573 inactive.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
574 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
575 for (const owner of main.querySelectorAll(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
576 "zen-button, zen-checkbox, zen-field, zen-input, zen-textarea",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
577 )) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
578 owner.replaceWith(...owner.childNodes);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
579 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
580 for (const label of main.querySelectorAll("label[for]")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
581 label.removeAttribute("for");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
582 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
583 return main;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
584 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
585
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
586 class MjjJrpgPreview extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
587 connectedCallback() {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
588 this._dialog = this.querySelector("dialog");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
589 this._onDialogClose = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
590 this._toolRequest = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
591 this.cleanupTool();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
592 };
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
593 this._onClick = event => {
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
594 const trigger = event.target.closest("[data-zen-trigger]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
595 if (trigger) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
596 if (this.dataset.selection === "resume") void this.loadResume();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
597 if (this.dataset.selection === "blog") {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
598 this.show("blog");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
599 void this.loadBlogs();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
600 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
601 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
602 this.show("tools");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
603 void this.openToolWorkspace();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
604 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
605 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
606 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
607 const blogEntry = event.target.closest("[data-blog-entry]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
608 if (blogEntry) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
609 void this.loadBlogDetail(blogEntry.dataset.blogUrl);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
610 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
611 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
612 const latestBlog = event.target.closest("[data-latest-blog]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
613 if (latestBlog && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
614 event.preventDefault();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
615 this.querySelector("zen-dialog").open();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
616 void this.loadBlogDetail(latestBlog.dataset.blogUrl);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
617 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
618 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
619 const blogArchive = event.target.closest("[data-blog-archive]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
620 if (blogArchive) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
621 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
622 this.querySelector("zen-dialog").open();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
623 this.show("blog");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
624 void this.loadBlogs();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
625 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
626 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
627 const resumeModal = event.target.closest("[data-resume-modal]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
628 if (resumeModal) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
629 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
630 this.querySelector("zen-dialog").open();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
631 this.show("resume");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
632 void this.loadResume();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
633 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
634 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
635 const latestTool = event.target.closest("[data-latest-tool]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
636 if (latestTool && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
637 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
638 this.querySelector("zen-dialog").open();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
639 void this.loadToolDetail(latestTool.dataset.toolUrl);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
640 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
641 };
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
642 this.addEventListener("click", this._onClick);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
643 this._dialog.addEventListener("close", this._onDialogClose);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
644 this.show(this.dataset.selection || "resume");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
645 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
646
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
647 disconnectedCallback() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
648 this.removeEventListener("click", this._onClick);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
649 this._dialog?.removeEventListener("close", this._onDialogClose);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
650 this.cleanupTool();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
651 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
652
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
653 show(selection) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
654 this.cleanupTool();
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
655 const preview = PREVIEWS[selection] || PREVIEWS.resume;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
656 this.dataset.selection = selection;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
657 this.querySelector("[data-preview-title]").textContent = preview.title;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
658 this.querySelector("[data-preview-copy]").textContent = preview.copy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
659 this.querySelector("[data-dialog-title]").textContent = preview.title;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
660 this.querySelector("[data-dialog-copy]").textContent = preview.copy;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
661 this.renderShowcase(preview.works);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
662 const link = this.querySelector("[data-preview-link]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
663 link.href = preview.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
664 link.setAttribute("aria-label", `Open ${preview.title}`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
665 const resumeDossier = this.querySelector("[data-resume-dossier]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
666 const resumeDownload = this.querySelector("[data-resume-download]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
667 const blogBrowser = this.querySelector("[data-blog-browser]");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
668 const toolBrowser = this.querySelector("[data-tool-browser]");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
669 const isResume = selection === "resume";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
670 const isBlog = selection === "blog";
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
671 const isTools = selection === "tools";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
672 this._dialog.dataset.detailMode = isTools ? "tools" : "content";
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
673 resumeDossier.hidden = !isResume;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
674 resumeDownload.hidden = !isResume;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
675 this.querySelector("[data-dialog-actions]").hidden = !isResume;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
676 blogBrowser.hidden = !isBlog;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
677 toolBrowser.hidden = !isTools;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
678 this.querySelector("[data-dialog-copy]").hidden =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
679 isResume || isBlog || isTools;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
680 if (!isBlog) this._blogRequest = (this._blogRequest || 0) + 1;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
681 if (isBlog) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
682 const status = this.querySelector("[data-blog-status]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
683 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
684 status.textContent = "Select a blog to inspect.";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
685 this.querySelector("[data-blog-content]").replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
686 for (const button of this.querySelectorAll("[data-blog-entry]")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
687 button.setAttribute("aria-pressed", "false");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
688 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
689 void this.loadBlogs();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
690 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
691 if (!isTools) this._toolRequest = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
692 if (isTools) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
693 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
694 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
695 status.textContent = "Select a tool to inspect.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
696 this.querySelector("[data-tool-content]").replaceChildren();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
697 void this.loadTools();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
698 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
699 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
700
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
701 renderShowcase(works, latestBlogs = false, latestKind = null) {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
702 const showcase = this.querySelector("[data-work-showcase]");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
703 showcase.replaceChildren(...works.map(work => {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
704 const item = document.createElement("li");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
705 const link = document.createElement("a");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
706 const label = document.createElement("span");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
707 const detail = document.createElement("small");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
708 link.href = work.url;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
709 if (latestBlogs) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
710 if (work.modal === "blog") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
711 link.dataset.blogArchive = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
712 } else {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
713 link.dataset.latestBlog = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
714 link.dataset.blogUrl = work.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
715 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
716 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
717 if (work.modal === "resume") link.dataset.resumeModal = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
718 if (latestKind === "tool") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
719 link.dataset.latestTool = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
720 link.dataset.toolUrl = work.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
721 const icon = document.createElement("zen-icon");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
722 icon.setAttribute("name", TOOL_ICON_NAMES[work.url] || "settings");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
723 link.prepend(icon);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
724 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
725 if (new URL(work.url, window.location.href).origin !== window.location.origin) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
726 link.target = "_blank";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
727 link.rel = "noreferrer";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
728 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
729 label.textContent = work.label;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
730 detail.textContent = work.detail;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
731 link.append(label, detail);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
732 item.append(link);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
733 return item;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
734 }));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
735 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
736
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
737 async loadResume() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
738 if (this._resumeLoaded || this._resumeLoading) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
739 this._resumeLoading = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
740 const status = this.querySelector("[data-resume-status]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
741 const content = this.querySelector("[data-resume-content]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
742 try {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
743 const response = await fetch("/resume", {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
744 headers: { Accept: "text/html" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
745 });
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
746 if (!response.ok) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
747 throw new Error(`Resume request failed (${response.status})`);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
748 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
749 const documentCopy = new DOMParser().parseFromString(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
750 await response.text(),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
751 "text/html",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
752 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
753 const resume = documentCopy.querySelector("main");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
754 if (!resume) throw new Error("Resume content is unavailable");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
755 sanitizeFetchedMain(resume);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
756 content.replaceChildren(...resume.childNodes);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
757 status.hidden = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
758 this._resumeLoaded = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
759 } catch (error) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
760 status.textContent = `Unable to load resume: ${error.message}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
761 } finally {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
762 this._resumeLoading = false;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
763 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
764 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
765
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
766 async loadBlogs() {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
767 if (this._blogs) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
768 if (this.dataset.selection === "blog") {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
769 this.renderShowcase([
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
770 ...this._blogs.slice(0, 4),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
771 {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
772 detail: "Full archive",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
773 label: "All blogs",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
774 modal: "blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
775 url: "/blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
776 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
777 ], true);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
778 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
779 return true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
780 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
781 if (this._blogsLoading) return this._blogsLoading;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
782 const list = this.querySelector("[data-blog-list]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
783 this._blogsLoading = (async () => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
784 const response = await fetch("/blog", {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
785 headers: { Accept: "text/html" },
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
786 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
787 if (!response.ok) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
788 throw new Error(`Blog archive request failed (${response.status})`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
789 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
790 const documentCopy = new DOMParser().parseFromString(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
791 await response.text(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
792 "text/html",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
793 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
794 this._blogs = [...documentCopy.querySelectorAll(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
795 'main li a[href^="/blog/"]',
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
796 )].map(anchor => ({
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
797 date: anchor.closest("li")?.querySelector("span")?.textContent.trim() ||
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
798 "Archive",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
799 detail: anchor.closest("li")?.querySelector("span")?.textContent.trim() ||
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
800 "Archive",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
801 label: anchor.textContent.trim(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
802 url: anchor.getAttribute("href"),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
803 }));
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
804 if (!this._blogs.length) throw new Error("No blog entries were found");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
805 this.renderBlogList();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
806 if (this.dataset.selection === "blog") {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
807 this.renderShowcase([
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
808 ...this._blogs.slice(0, 4),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
809 {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
810 detail: "Full archive",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
811 label: "All blogs",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
812 modal: "blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
813 url: "/blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
814 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
815 ], true);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
816 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
817 return true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
818 })().catch(error => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
819 list.replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
820 const item = document.createElement("li");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
821 item.textContent = `Unable to load blogs: ${error.message}`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
822 list.append(item);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
823 return false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
824 }).finally(() => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
825 this._blogsLoading = null;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
826 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
827 return this._blogsLoading;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
828 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
829
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
830 renderBlogList() {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
831 const list = this.querySelector("[data-blog-list]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
832 list.replaceChildren(...this._blogs.map(blog => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
833 const item = document.createElement("li");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
834 const owner = document.createElement("zen-button");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
835 const button = document.createElement("button");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
836 const title = document.createElement("span");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
837 const date = document.createElement("small");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
838 owner.setAttribute("appearance", "plain");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
839 owner.setAttribute("size", "sm");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
840 button.type = "button";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
841 button.dataset.blogEntry = "";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
842 button.dataset.blogUrl = blog.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
843 title.textContent = blog.label;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
844 date.textContent = blog.date;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
845 button.append(title, date);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
846 owner.append(button);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
847 item.append(owner);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
848 return item;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
849 }));
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
850 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
851
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
852 async loadBlogDetail(url) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
853 if (!await this.loadBlogs()) return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
854 const blog = this._blogs.find(item => item.url === url);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
855 const status = this.querySelector("[data-blog-status]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
856 const content = this.querySelector("[data-blog-content]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
857 if (!blog) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
858 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
859 status.textContent = "Unable to load blog: unknown entry.";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
860 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
861 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
862 const request = (this._blogRequest || 0) + 1;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
863 this._blogRequest = request;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
864 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
865 status.textContent = `Loading ${blog.label}...`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
866 content.replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
867 this.querySelector("[data-dialog-title]").textContent = blog.label;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
868 const fullPage = this.querySelector("[data-preview-link]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
869 fullPage.href = blog.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
870 fullPage.setAttribute("aria-label", `Open ${blog.label}`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
871 for (const button of this.querySelectorAll("[data-blog-entry]")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
872 button.setAttribute(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
873 "aria-pressed",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
874 String(button.dataset.blogUrl === blog.url),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
875 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
876 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
877 try {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
878 const response = await fetch(blog.url, {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
879 headers: { Accept: "text/html" },
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
880 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
881 if (!response.ok) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
882 throw new Error(`Blog request failed (${response.status})`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
883 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
884 const documentCopy = new DOMParser().parseFromString(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
885 await response.text(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
886 "text/html",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
887 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
888 const article = documentCopy.querySelector("main");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
889 if (!article) throw new Error("Blog content is unavailable");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
890 sanitizeFetchedMain(article);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
891 if (request !== this._blogRequest || this.dataset.selection !== "blog") {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
892 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
893 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
894 content.replaceChildren(...article.childNodes);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
895 status.hidden = true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
896 } catch (error) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
897 if (request !== this._blogRequest) return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
898 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
899 status.textContent = `Unable to load blog: ${error.message}`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
900 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
901 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
902
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
903 async loadTools() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
904 if (this._tools) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
905 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
906 this.renderShowcase(this._tools, false, "tool");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
907 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
908 return true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
909 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
910 if (this._toolsLoading) return this._toolsLoading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
911 this._toolsLoading = (async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
912 const response = await fetch("/tools", {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
913 headers: { Accept: "text/html" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
914 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
915 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
916 throw new Error(`Tools request failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
917 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
918 const documentCopy = new DOMParser().parseFromString(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
919 await response.text(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
920 "text/html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
921 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
922 this._tools = [...documentCopy.querySelectorAll("main li a[href]")].map(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
923 anchor => ({
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
924 detail: anchor.getAttribute("href").startsWith("/notes")
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
925 ? "Writing"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
926 : "Utility",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
927 label: anchor.textContent.trim(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
928 url: anchor.getAttribute("href"),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
929 }),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
930 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
931 if (!this._tools.length) throw new Error("No tools were found");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
932 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
933 this.renderShowcase(this._tools, false, "tool");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
934 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
935 return true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
936 })().catch(error => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
937 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
938 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
939 status.textContent = `Unable to load tools: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
940 return false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
941 }).finally(() => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
942 this._toolsLoading = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
943 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
944 return this._toolsLoading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
945 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
946
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
947 async openToolWorkspace() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
948 if (!await this.loadTools() || this.dataset.selection !== "tools") return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
949 await this.loadToolDetail(this._activeToolUrl || this._tools[0].url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
950 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
951
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
952 async loadToolDetail(url) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
953 const request = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
954 this._toolRequest = request;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
955 if (!await this.loadTools()) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
956 if (request !== this._toolRequest || this.dataset.selection !== "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
957 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
958 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
959 const tool = this._tools.find(item => item.url === url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
960 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
961 const content = this.querySelector("[data-tool-content]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
962 if (!tool) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
963 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
964 status.textContent = "Unable to load tool: unknown entry.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
965 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
966 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
967 this._activeToolUrl = tool.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
968 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
969 status.textContent = `Loading ${tool.label}...`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
970 this.cleanupTool();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
971 content.replaceChildren();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
972 this.querySelector("[data-dialog-title]").textContent = tool.label;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
973 const fullPage = this.querySelector("[data-preview-link]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
974 fullPage.href = tool.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
975 fullPage.setAttribute("aria-label", `Open ${tool.label}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
976 if (tool.url === "/tools/markdown_to_html") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
977 await this.renderMarkdownTool(content, status, request);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
978 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
979 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
980 if (tool.url === "/tools/file_converter") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
981 this.renderFileConverterTool(content, status);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
982 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
983 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
984 if (tool.url === "/tools/hls_player") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
985 await this.renderHlsTool(content, status, request);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
986 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
987 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
988 if (tool.url === "/tools/latex_editor") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
989 this.renderLatexTool(content, status);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
990 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
991 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
992 if (tool.url === "/notes") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
993 const heading = document.createElement("h1");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
994 const description = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
995 const privacy = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
996 heading.textContent = "Personal Notes";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
997 description.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
998 "A private browser-based writing workspace with rich-text editing and server-backed persistence.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
999 privacy.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1000 "The interactive editor and authentication flow are available on the full Notes page.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1001 content.replaceChildren(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1002 this.createReadonlyToolNote(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1003 heading,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1004 description,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1005 privacy,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1006 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1007 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1008 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1009 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1010 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1011 const response = await fetch(tool.url, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1012 headers: { Accept: "text/html" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1013 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1014 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1015 throw new Error(`Tool request failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1016 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1017 const documentCopy = new DOMParser().parseFromString(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1018 await response.text(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1019 "text/html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1020 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1021 const detail = documentCopy.querySelector("main, .container") ||
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1022 documentCopy.body;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1023 if (!detail) throw new Error("Tool content is unavailable");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1024 sanitizeToolDetail(detail);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1025 if (request !== this._toolRequest || this.dataset.selection !== "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1026 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1027 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1028 content.replaceChildren(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1029 this.createReadonlyToolNote(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1030 ...detail.childNodes,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1031 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1032 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1033 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1034 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1035 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1036 status.textContent = `Unable to load tool: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1037 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1038 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1039
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1040 async renderMarkdownTool(content, status, request) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1041 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1042 <section class="jrpg-functional-tool" data-functional-tool="markdown">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1043 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1044 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1045 <h2><zen-icon name="code"></zen-icon> Markdown source</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1046 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1047 <label for="jrpgMarkdownSource">Markdown</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1048 <textarea id="jrpgMarkdownSource" data-markdown-source></textarea>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1049 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1050 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1051 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1052 <h2><zen-icon name="check"></zen-icon> Converted HTML</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1053 <div class="jrpg-markdown-output" data-markdown-output></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1054 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1055 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1056 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1057 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1058 const source = content.querySelector("[data-markdown-source]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1059 const output = content.querySelector("[data-markdown-output]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1060 source.value = [
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1061 "# JRPG Markdown",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1062 "",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1063 "Edit this **Markdown** to update the converted panel.",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1064 "",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1065 "- Bazel-built WASM",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1066 "- Sanitized HTML output",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1067 ].join("\n");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1068 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1069 status.textContent = "Loading the Markdown converter...";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1070 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1071 this._markdownModule ||= import("/markdown_to_html_bin.js")
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1072 .then(module => module.default());
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1073 const module = await this._markdownModule;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1074 if (request !== this._toolRequest || !source.isConnected) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1075 const convertMarkdown = module.cwrap(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1076 "markdown_to_html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1077 "number",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1078 ["string"],
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1079 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1080 const freeMarkdown = module.cwrap("markdown_free", null, ["number"]);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1081 const convert = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1082 const pointer = convertMarkdown(source.value);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1083 const html = module.UTF8ToString(pointer);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1084 freeMarkdown(pointer);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1085 const documentCopy = new DOMParser().parseFromString(html, "text/html");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1086 sanitizeFetchedMain(documentCopy.body);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1087 output.replaceChildren(...documentCopy.body.childNodes);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1088 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1089 source.addEventListener("input", convert);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1090 convert();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1091 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1092 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1093 this._markdownModule = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1094 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1095 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1096 status.textContent = `Unable to start Markdown: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1097 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1098 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1099
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1100 renderFileConverterTool(content, status) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1101 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1102 <section class="jrpg-functional-tool" data-functional-tool="converter">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1103 <p class="jrpg-tool-status" data-converter-status>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1104 Choose a file in either panel.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1105 </p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1106 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1107 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1108 <h2><zen-icon name="file"></zen-icon> Image to WebP</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1109 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1110 <label for="jrpgImageInput">Image file</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1111 <input id="jrpgImageInput" data-converter-input="image" type="file" accept="image/*">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1112 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1113 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1114 <button type="button" data-convert-kind="image">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1115 Convert image <zen-icon name="arrow-right"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1116 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1117 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1118 <a data-converter-download="image" hidden>Download WebP</a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1119 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1120 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1121 <h2><zen-icon name="play"></zen-icon> Video to MP4</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1122 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1123 <label for="jrpgVideoInput">Video file</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1124 <input id="jrpgVideoInput" data-converter-input="video" type="file" accept="video/*">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1125 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1126 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1127 <button type="button" data-convert-kind="video">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1128 Convert video <zen-icon name="arrow-right"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1129 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1130 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1131 <a data-converter-download="video" hidden>Download MP4</a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1132 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1133 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1134 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1135 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1136 const toolStatus = content.querySelector("[data-converter-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1137 let disposed = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1138 const downloads = new Set();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1139 const discardDownload = url => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1140 if (!url) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1141 downloads.delete(url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1142 void fetch(url, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1143 method: "DELETE",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1144 keepalive: true,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1145 }).then(response => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1146 if (!response.ok && response.status !== 404) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1147 console.warn(`Unable to discard converted file (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1148 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1149 }).catch(error => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1150 console.warn(`Unable to discard converted file: ${error.message}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1151 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1152 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1153 const convert = async kind => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1154 const input = content.querySelector(`[data-converter-input="${kind}"]`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1155 const button = content.querySelector(`[data-convert-kind="${kind}"]`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1156 const download = content.querySelector(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1157 `[data-converter-download="${kind}"]`,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1158 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1159 const file = input.files?.[0];
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1160 if (!file) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1161 toolStatus.textContent = `Choose a ${kind} file first.`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1162 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1163 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1164 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1165 button.disabled = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1166 discardDownload(download.dataset.cleanupUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1167 delete download.dataset.cleanupUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1168 download.removeAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1169 download.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1170 toolStatus.textContent = `Converting ${file.name}...`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1171 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1172 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1173 const endpoint = kind === "image"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1174 ? "/api/convert/image-to-webp"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1175 : "/api/convert/video-to-mp4";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1176 const response = await fetch(endpoint, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1177 method: "POST",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1178 body: file,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1179 headers: { "Content-Type": file.type || "application/octet-stream" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1180 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1181 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1182 throw new Error(await response.text() || `Conversion failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1183 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1184 const result = await response.json();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1185 if (disposed || !download.isConnected) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1186 discardDownload(result.download_url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1187 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1188 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1189 download.href = result.download_url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1190 download.dataset.cleanupUrl = result.download_url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1191 downloads.add(result.download_url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1192 download.download = file.name.replace(/\.[^/.]+$/, "") +
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1193 (kind === "image" ? ".webp" : ".mp4");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1194 download.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1195 toolStatus.textContent = "Conversion complete.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1196 toolStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1197 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1198 if (!disposed) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1199 toolStatus.textContent = `Conversion failed: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1200 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1201 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1202 } finally {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1203 if (button.isConnected) button.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1204 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1205 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1206 for (const button of content.querySelectorAll("[data-convert-kind]")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1207 button.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1208 void convert(button.dataset.convertKind);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1209 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1210 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1211 for (const download of content.querySelectorAll("[data-converter-download]")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1212 download.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1213 downloads.delete(download.dataset.cleanupUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1214 delete download.dataset.cleanupUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1215 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1216 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1217 this._toolCleanup = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1218 disposed = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1219 for (const download of [...downloads]) discardDownload(download);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1220 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1221 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1222 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1223
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1224 async renderHlsTool(content, status, request) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1225 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1226 <section class="jrpg-functional-tool" data-functional-tool="hls">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1227 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1228 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1229 <h2><zen-icon name="file"></zen-icon> HLS source</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1230 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1231 <label for="jrpgHlsUrl">Playlist URL</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1232 <input
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1233 id="jrpgHlsUrl"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1234 data-hls-url
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1235 type="url"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1236 value="/public/hls-sample/h264-ts-stream.m3u8"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1237 >
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1238 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1239 <div class="jrpg-tool-actions">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1240 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1241 <button type="button" data-hls-load>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1242 Load URL <zen-icon name="arrow-right"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1243 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1244 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1245 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1246 <button type="button" data-hls-sample>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1247 Load sample <zen-icon name="play"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1248 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1249 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1250 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1251 <p class="jrpg-tool-status" data-hls-status>Loading player engine...</p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1252 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1253 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1254 <h2><zen-icon name="play"></zen-icon> Playback</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1255 <video data-hls-video controls playsinline preload="metadata"></video>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1256 <dl class="jrpg-tool-details" data-hls-details hidden>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1257 <div><dt>Playback</dt><dd data-detail="mode">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1258 <div><dt>Segments</dt><dd data-detail="segments">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1259 <div><dt>Duration</dt><dd data-detail="duration">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1260 <div><dt>Codec</dt><dd data-detail="codecs">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1261 </dl>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1262 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1263 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1264 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1265 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1266 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1267 await loadScript("/public/hls.min.js");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1268 await loadScript("/tools/hls_player/hls-player.js");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1269 if (request !== this._toolRequest || !content.isConnected) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1270 const video = content.querySelector("[data-hls-video]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1271 const playerStatus = content.querySelector("[data-hls-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1272 const details = content.querySelector("[data-hls-details]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1273 const url = content.querySelector("[data-hls-url]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1274 const player = new window.HlsPlayerModule.HlsPlayer(video, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1275 statusElement: playerStatus,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1276 detailsElement: details,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1277 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1278 const load = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1279 void player.load(url.value.trim()).catch(() => {});
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1280 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1281 content.querySelector("[data-hls-load]").addEventListener("click", load);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1282 content.querySelector("[data-hls-sample]").addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1283 url.value = "/public/hls-sample/h264-ts-stream.m3u8";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1284 load();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1285 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1286 playerStatus.textContent = "Ready to load a playlist.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1287 playerStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1288 this._toolCleanup = () => player.destroy();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1289 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1290 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1291 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1292 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1293 status.textContent = `Unable to start HLS: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1294 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1295 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1296
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1297 renderLatexTool(content, status) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1298 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1299 <section class="jrpg-functional-tool" data-functional-tool="latex">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1300 <p class="jrpg-tool-status" data-latex-status>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1301 Edit the source, then compile the PDF.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1302 </p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1303 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1304 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1305 <div class="jrpg-tool-pane-heading">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1306 <h2><zen-icon name="code"></zen-icon> LaTeX source</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1307 <span data-latex-size></span>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1308 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1309 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1310 <label for="jrpgLatexSource">document.tex</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1311 <textarea id="jrpgLatexSource" data-latex-source></textarea>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1312 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1313 <div class="jrpg-tool-actions">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1314 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1315 <button type="button" data-latex-compile>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1316 Compile PDF <zen-icon name="file"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1317 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1318 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1319 <zen-button appearance="plain" size="md" data-latex-download hidden>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1320 <a download="document.pdf">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1321 Download PDF <zen-icon name="download"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1322 </a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1323 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1324 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1325 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1326 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1327 <h2><zen-icon name="file"></zen-icon> PDF preview</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1328 <iframe data-latex-preview title="Compiled LaTeX PDF preview"></iframe>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1329 <pre data-latex-diagnostics hidden></pre>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1330 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1331 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1332 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1333 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1334 const source = content.querySelector("[data-latex-source]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1335 const compileButton = content.querySelector("[data-latex-compile]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1336 const toolStatus = content.querySelector("[data-latex-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1337 const size = content.querySelector("[data-latex-size]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1338 const preview = content.querySelector("[data-latex-preview]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1339 const diagnostics = content.querySelector("[data-latex-diagnostics]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1340 const downloadOwner = content.querySelector("[data-latex-download]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1341 const download = downloadOwner.querySelector("a");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1342 source.value = String.raw`\documentclass[11pt]{article}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1343 \usepackage[margin=1in]{geometry}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1344 \title{JRPG LaTeX}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1345 \author{MrJuneJune}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1346 \begin{document}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1347 \maketitle
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1348 This PDF was compiled from the cyberpunk tool modal.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1349 \end{document}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1350 let controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1351 let pdfUrl = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1352 let compileGeneration = 0;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1353 const clearPdf = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1354 if (pdfUrl) URL.revokeObjectURL(pdfUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1355 pdfUrl = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1356 preview.src = "about:blank";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1357 preview.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1358 diagnostics.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1359 download.removeAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1360 downloadOwner.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1361 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1362 const updateSize = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1363 const bytes = new TextEncoder().encode(source.value).byteLength;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1364 size.textContent = `${bytes.toLocaleString()} / 65,536 bytes`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1365 return bytes;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1366 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1367 const compile = async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1368 const generation = ++compileGeneration;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1369 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1370 const bytes = updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1371 if (!source.value.trim() || bytes > 64 * 1024) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1372 toolStatus.textContent = bytes > 64 * 1024
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1373 ? "Source exceeds 64 KiB."
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1374 : "Write LaTeX before compiling.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1375 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1376 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1377 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1378 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1379 const requestController = new AbortController();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1380 controller = requestController;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1381 compileButton.disabled = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1382 toolStatus.textContent = "Compiling on the server...";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1383 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1384 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1385 const response = await fetch("/api/latex/render", {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1386 method: "POST",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1387 headers: { "Content-Type": "text/plain; charset=utf-8" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1388 body: source.value,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1389 cache: "no-store",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1390 signal: requestController.signal,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1391 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1392 if (generation !== compileGeneration) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1393 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1394 throw new Error(await response.text() || `Compilation failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1395 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1396 const blob = await response.blob();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1397 if (generation !== compileGeneration) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1398 if (blob.type !== "application/pdf" || blob.size < 5) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1399 throw new Error("The server returned an invalid PDF.");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1400 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1401 if (pdfUrl) URL.revokeObjectURL(pdfUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1402 pdfUrl = URL.createObjectURL(blob);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1403 preview.src = pdfUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1404 preview.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1405 diagnostics.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1406 download.href = pdfUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1407 downloadOwner.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1408 toolStatus.textContent = `PDF ready (${Math.ceil(blob.size / 1024)} KiB).`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1409 toolStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1410 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1411 if (error.name !== "AbortError" && generation === compileGeneration) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1412 diagnostics.textContent = error.message;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1413 diagnostics.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1414 preview.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1415 toolStatus.textContent = "Compilation failed.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1416 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1417 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1418 } finally {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1419 if (controller === requestController) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1420 controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1421 compileButton.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1422 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1423 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1424 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1425 source.addEventListener("input", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1426 compileGeneration++;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1427 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1428 controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1429 compileButton.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1430 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1431 updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1432 toolStatus.textContent = "Changes ready to compile.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1433 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1434 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1435 compileButton.addEventListener("click", () => void compile());
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1436 updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1437 this._toolCleanup = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1438 compileGeneration++;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1439 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1440 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1441 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1442 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1443 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1444
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1445 cleanupTool() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1446 this._toolCleanup?.();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1447 this._toolCleanup = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1448 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1449
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1450 createReadonlyToolNote() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1451 const introduction = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1452 introduction.className = "jrpg-tool-readonly-note";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1453 introduction.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1454 "Read-only preview. Open the full page to use this tool.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1455 return introduction;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1456 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1457 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1458
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1459 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1460 MjjConversationArchive — archive panel within the scene
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1461 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1462
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1463 class MjjConversationArchive extends HTMLElement {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1464 connectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1465 this._list = this.querySelector("[data-archive-list]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1466 this._status = this.querySelector("[data-archive-status]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1467 this._loadMoreOwner = this.querySelector("[data-archive-load-more-owner]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1468 this._loadMoreButton = this.querySelector("[data-archive-load-more]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1469 this._newButton = this.querySelector("[data-archive-new]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1470 this._closeButton = this.querySelector("[data-archive-close]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1471 this._deleteDialog = this.querySelector("[data-archive-delete-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1472 this._deleteNameEl = this.querySelector("[data-archive-delete-name]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1473 this._deleteCancelButton = this.querySelector("[data-archive-delete-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1474 this._deleteConfirmButton = this.querySelector("[data-archive-delete-confirm]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1475 this._renameDialog = this.querySelector("[data-archive-rename-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1476 this._renameInput = this.querySelector("[data-archive-rename-input]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1477 this._renameForm = this.querySelector("[data-archive-rename-form]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1478 this._renameCancelButton = this.querySelector("[data-archive-rename-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1479 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1480 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1481 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1482 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1483
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1484 this._closeButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1485 this.dispatchEvent(new CustomEvent("mjj-archive-toggle", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1486 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1487
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1488 this._newButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1489 this.dispatchEvent(new CustomEvent("mjj-archive-new", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1490 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1491
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1492 this._loadMoreButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1493 this.dispatchEvent(new CustomEvent("mjj-archive-load-more", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1494 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1495
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1496 this._deleteCancelButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1497 this._deleteDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1498 const t = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1499 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1500 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1501 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1502 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1503
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1504 this._deleteConfirmButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1505 const id = this._pendingDeleteId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1506 const trigger = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1507 this._deleteDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1508 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1509 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1510 if (id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1511 this.dispatchEvent(new CustomEvent("mjj-archive-delete", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1512 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1513 detail: { id, trigger },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1514 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1515 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1516 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1517
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1518 this._deleteDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1519 const t = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1520 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1521 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1522 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1523 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1524
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1525 this._renameCancelButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1526 this._renameDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1527 const t = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1528 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1529 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1530 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1531 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1532
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1533 this._renameForm?.addEventListener("submit", event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1534 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1535 const id = this._pendingRenameId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1536 const trigger = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1537 const newTitle = this._renameInput?.value.trim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1538 if (!id || !newTitle) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1539 this._renameDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1540 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1541 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1542 this.dispatchEvent(new CustomEvent("mjj-archive-rename", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1543 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1544 detail: { id, newTitle, trigger },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1545 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1546 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1547
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1548 this._renameDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1549 const t = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1550 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1551 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1552 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1553 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1554
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1555 this._onListClick = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1556 const openButton = event.target.closest("[data-conv-open]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1557 if (openButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1558 const id = openButton.closest("[data-conv-item]")?._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1559 if (id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1560 this.dispatchEvent(new CustomEvent("mjj-archive-open", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1561 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1562 detail: { id },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1563 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1564 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1565 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1566 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1567 const renameButton = event.target.closest("[data-conv-rename]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1568 if (renameButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1569 const item = renameButton.closest("[data-conv-item]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1570 if (item?._convId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1571 this._pendingRenameId = item._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1572 this._pendingRenameTrigger = renameButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1573 if (this._renameInput) this._renameInput.value = item._convTitle || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1574 this._renameDialog?.showModal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1575 requestAnimationFrame(() => { this._renameInput?.select(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1576 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1577 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1578 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1579 const deleteButton = event.target.closest("[data-conv-delete]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1580 if (deleteButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1581 const item = deleteButton.closest("[data-conv-item]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1582 if (item?._convId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1583 this._pendingDeleteId = item._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1584 this._pendingDeleteTrigger = deleteButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1585 if (this._deleteNameEl) this._deleteNameEl.textContent = item._convTitle || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1586 this._deleteDialog?.showModal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1587 requestAnimationFrame(() => { this._deleteConfirmButton?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1588 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1589 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1590 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1591 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1592 this._list?.addEventListener("click", this._onListClick);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1593
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1594 this._onListKeydown = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1595 if (!["ArrowUp", "ArrowDown"].includes(event.key)) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1596 const items = [...(this._list?.querySelectorAll("[data-conv-open]") || [])];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1597 const index = items.indexOf(document.activeElement);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1598 if (index < 0) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1599 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1600 const next = event.key === "ArrowDown"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1601 ? items[Math.min(index + 1, items.length - 1)]
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1602 : items[Math.max(index - 1, 0)];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1603 next?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1604 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1605 this._list?.addEventListener("keydown", this._onListKeydown);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1606 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1607
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1608 disconnectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1609 this._list?.removeEventListener("click", this._onListClick);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1610 this._list?.removeEventListener("keydown", this._onListKeydown);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1611 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1612
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1613 setConversations(conversations, cursor, append = false) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1614 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1615 if (!append) this._list.replaceChildren();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1616 for (const conv of conversations) this._list.append(this._createItem(conv));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1617 if (this._loadMoreOwner) this._loadMoreOwner.hidden = !cursor;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1618 if (!conversations.length && !append) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1619 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1620 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1621 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1622 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1623 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1624
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1625 setCurrentId(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1626 for (const button of (this._list?.querySelectorAll("[data-conv-open]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1627 const isCurrent = id && button.closest("[data-conv-item]")?._convId === id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1628 button.setAttribute("aria-current", String(Boolean(isCurrent)));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1629 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1630 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1631
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1632 addConversation(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1633 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1634 this._list.prepend(this._createItem(conv));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1635 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1636 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1637
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1638 updateConversation(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1639 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1640 for (const item of this._list.querySelectorAll("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1641 if (item._convId === conv.id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1642 const titleEl = item.querySelector("[data-conv-title]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1643 const metaEl = item.querySelector("[data-conv-meta]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1644 if (titleEl) titleEl.textContent = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1645 if (metaEl) metaEl.textContent = this._formatMeta(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1646 item._convTitle = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1647 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1648 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1649 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1650 this.addConversation(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1651 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1652
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1653 removeConversation(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1654 for (const item of (this._list?.querySelectorAll("[data-conv-item]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1655 if (item._convId === id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1656 item.remove();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1657 break;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1658 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1659 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1660 if (!this._list?.querySelector("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1661 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1662 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1663 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1664
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1665 setLoading(loading) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1666 if (loading) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1667 this._showStatus("Loading conversations...");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1668 } else if (!this._list?.querySelector("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1669 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1670 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1671 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1672 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1673 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1674
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1675 setError(message) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1676 this._showStatus(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1677 if (this._status) this._status.dataset.state = "error";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1678 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1679
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1680 setLoadMoreDisabled(disabled) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1681 if (this._loadMoreButton) this._loadMoreButton.disabled = disabled;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1682 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1683
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1684 /* Disable/enable destructive archive controls while a stream is active. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1685 setStreamActive(active) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1686 const controls = this._list?.querySelectorAll(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1687 "[data-conv-delete], [data-conv-rename]",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1688 ) || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1689 for (const el of controls) el.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1690 if (this._newButton) this._newButton.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1691 if (this._deleteConfirmButton) this._deleteConfirmButton.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1692 this.dataset.streamActive = active ? "true" : "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1693 if (!active) delete this.dataset.streamActive;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1694 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1695
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1696 /* Return the open-button of the adjacent surviving item, or New button. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1697 adjacentItemOrNewButton(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1698 const items = [...(this._list?.querySelectorAll("[data-conv-item]") || [])];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1699 const idx = items.findIndex(el => el._convId === id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1700 if (idx < 0) return this._newButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1701 const sibling = items[idx + 1] || items[idx - 1];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1702 return sibling?.querySelector("[data-conv-open]") || this._newButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1703 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1704
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1705 showLegacyClaim(statusMessage = null, showButton = true) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1706 if (!this._status) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1707 this._status.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1708 delete this._status.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1709 this._status.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1710 const msg = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1711 msg.textContent = statusMessage ?? "You have a prior conversation. ";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1712 this._status.append(msg);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1713 if (showButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1714 const btn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1715 btn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1716 btn.textContent = "Claim it";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1717 btn.dataset.archiveClaim = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1718 btn.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1719 this.dispatchEvent(new CustomEvent("mjj-archive-claim", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1720 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1721 this._status.append(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1722 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1723 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1724
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1725 _showStatus(text) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1726 if (this._status) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1727 this._status.textContent = text;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1728 this._status.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1729 delete this._status.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1730 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1731 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1732
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1733 _hideStatus() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1734 if (this._status) this._status.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1735 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1736
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1737 _createItem(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1738 const item = document.createElement("li");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1739 item.dataset.convItem = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1740 item._convId = conv.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1741 item._convTitle = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1742
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1743 const openButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1744 openButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1745 openButton.dataset.convOpen = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1746 openButton.setAttribute("aria-current", "false");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1747
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1748 const titleEl = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1749 titleEl.dataset.convTitle = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1750 titleEl.textContent = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1751
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1752 const metaEl = document.createElement("small");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1753 metaEl.dataset.convMeta = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1754 metaEl.textContent = this._formatMeta(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1755
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1756 openButton.append(titleEl, metaEl);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1757
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1758 const actions = document.createElement("div");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1759 actions.className = "jrpg-archive-item-actions";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1760
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1761 const renameButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1762 renameButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1763 renameButton.dataset.convRename = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1764 renameButton.setAttribute("aria-label", `Rename "${conv.title}"`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1765 renameButton.textContent = "Rename";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1766
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1767 const deleteButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1768 deleteButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1769 deleteButton.dataset.convDelete = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1770 deleteButton.setAttribute("aria-label", `Delete "${conv.title}"`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1771 deleteButton.textContent = "Delete";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1772
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1773 actions.append(renameButton, deleteButton);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1774 item.append(openButton, actions);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1775 return item;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1776 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1777
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1778 _formatMeta(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1779 const count = conv.turn_count ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1780 const turns = count === 1 ? "1 turn" : `${count} turns`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1781 if (conv.last_message_preview) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1782 return `${turns} · ${conv.last_message_preview.slice(0, 28)}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1783 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1784 return turns;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1785 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1786 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1787
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1788 class MjjJrpgShell extends HTMLElement {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1789 connectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1790 this._mobileMenuQuery = window.matchMedia(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1791 "(max-width: 52rem) and (orientation: portrait)",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1792 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1793 this._syncMobileMenu = expanded => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1794 const button = this.querySelector("[data-mobile-menu-toggle]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1795 const menu = this.querySelector("mjj-jrpg-menu");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1796 if (!button || !menu) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1797 if (!this._mobileMenuQuery.matches) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1798 button.setAttribute("aria-expanded", "true");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1799 menu.removeAttribute("data-mobile-menu-collapsed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1800 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1801 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1802 button.setAttribute("aria-expanded", String(expanded));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1803 menu.toggleAttribute("data-mobile-menu-collapsed", !expanded);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1804 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1805 this._onMenuToggle = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1806 const button = event.target.closest("[data-mobile-menu-toggle]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1807 if (!button || !this.contains(button)) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1808 const expanded = button.getAttribute("aria-expanded") === "true";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1809 this._syncMobileMenu(!expanded);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1810 if (!expanded) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1811 const firstItem = this.querySelector(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1812 "mjj-jrpg-menu button[data-preview]",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1813 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1814 if (firstItem) firstItem.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1815 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1816 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1817 this._onMobileMenuMediaChange = () => this._syncMobileMenu(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1818 this.addEventListener("click", this._onMenuToggle);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1819 this._mobileMenuQuery.addEventListener(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1820 "change",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1821 this._onMobileMenuMediaChange,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1822 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1823 this._syncMobileMenu(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1824 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1825
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1826 disconnectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1827 this.removeEventListener("click", this._onMenuToggle);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1828 this._mobileMenuQuery?.removeEventListener(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1829 "change",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1830 this._onMobileMenuMediaChange,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1831 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1832 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1833 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1834
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1835 for (const [name, constructor] of Object.entries({
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1836 "mjj-conversation-archive": MjjConversationArchive,
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1837 "mjj-jrpg-character": MjjJrpgCharacter,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1838 "mjj-jrpg-chat": MjjJrpgChat,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1839 "mjj-jrpg-composer": MjjJrpgComposer,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1840 "mjj-jrpg-menu": MjjJrpgMenu,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1841 "mjj-jrpg-preview": MjjJrpgPreview,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1842 "mjj-jrpg-shell": MjjJrpgShell,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1843 })) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1844 if (!customElements.get(name)) customElements.define(name, constructor);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1845 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1846
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1847 async function requestJson(url, options = {}) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1848 const response = await fetch(url, {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1849 ...options,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1850 credentials: "same-origin",
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1851 headers: {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1852 "Content-Type": "application/json",
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1853 ...options.headers,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1854 },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1855 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1856 if (!response.ok) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1857 let message = `Request failed (${response.status})`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1858 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1859 const body = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1860 message = body.error?.message || message;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1861 } catch {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1862 // Preserve the status-derived message.
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1863 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1864 throw new Error(message);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1865 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1866 return response.status === 204 ? null : response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1867 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1868
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1869 /* Session state for CSRF: bootstrapped once, refreshed at most once on 401/403 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1870 let _csrfToken = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1871 let _sessionData = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1872 let _sessionRefreshInFlight = null; /* single-flight guard */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1873
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1874 function _principalFingerprint(data) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1875 if (!data) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1876 if (data.kind === "user") return `user:${data.username || ""}:${data.role || ""}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1877 if (data.kind === "guest") return `guest:${data.csrfToken || ""}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1878 return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1879 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1880
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1881 async function _fetchSession() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1882 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1883 const response = await fetch("/api/auth/session", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1884 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1885 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1886 if (!response.ok) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1887 const data = await response.json().catch(() => null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1888 if (!data) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1889 _sessionData = data;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1890 return data.csrfToken || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1891 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1892 return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1893 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1894 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1895
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1896 /* Single-flight session refresh: dedupe concurrent callers. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1897 async function _refreshSessionOnce() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1898 if (_sessionRefreshInFlight) return _sessionRefreshInFlight;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1899 _sessionRefreshInFlight = (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1900 const prev = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1901 const token = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1902 const next = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1903 return { token, prev, next };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1904 })();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1905 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1906 return await _sessionRefreshInFlight;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1907 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1908 _sessionRefreshInFlight = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1909 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1910 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1911
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1912 /*
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1913 * Perform a state-changing (mutating) fetch with CSRF token and same-origin
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1914 * credentials. On 401 or CSRF-specific 403 (csrf_invalid), refreshes the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1915 * session once and retries — but only if the refreshed principal matches the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1916 * original. If the user becomes a guest or a different account, rejects and
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1917 * triggers the session-changed callback.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1918 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1919 let _sessionRefreshCallback = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1920
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1921 async function mutatingFetch(url, options = {}) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1922 const buildHeaders = () => ({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1923 ...(options.headers || {}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1924 Origin: window.location.origin,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1925 ...(_csrfToken ? { "X-CSRF-Token": _csrfToken } : {}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1926 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1927
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1928 const response = await fetch(url, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1929 ...options,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1930 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1931 headers: buildHeaders(),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1932 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1933
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1934 const shouldRefresh = response.status === 401 ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1935 (response.status === 403 && await (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1936 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1937 const clone = response.clone();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1938 const body = await clone.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1939 return body?.error?.code === "csrf_invalid";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1940 } catch { return false; }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1941 })());
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1942
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1943 if (shouldRefresh) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1944 const prevFingerprint = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1945 const { token, next } = await _refreshSessionOnce();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1946 _csrfToken = token;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1947 if (next !== prevFingerprint) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1948 if (_sessionRefreshCallback) _sessionRefreshCallback();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1949 const err = Object.assign(new Error("Session changed"), { status: 401 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1950 throw err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1951 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1952 const retried = await fetch(url, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1953 ...options,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1954 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1955 headers: buildHeaders(),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1956 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1957 if (retried.status === 401 && _sessionRefreshCallback) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1958 _sessionRefreshCallback();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1959 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1960 return retried;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1961 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1962 return response;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1963 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1964
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1965 async function consumeSse(response, onEvent) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1966 if (!response.body) throw new Error("Streaming response body is unavailable");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1967 const reader = response.body.getReader();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1968 const decoder = new TextDecoder();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1969 let buffer = "";
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1970
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1971 const dispatch = block => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1972 let eventName = "message";
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1973 const data = [];
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1974 for (const line of block.split("\n")) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1975 if (line.startsWith("event:")) eventName = line.slice(6).trim();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1976 else if (line.startsWith("data:")) data.push(line.slice(5).trimStart());
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1977 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1978 if (!data.length) return;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1979 onEvent(eventName, JSON.parse(data.join("\n")));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1980 };
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1981
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1982 while (true) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1983 const { done, value } = await reader.read();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1984 buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1985 buffer = buffer.replaceAll("\r\n", "\n");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1986 let boundary;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1987 while ((boundary = buffer.indexOf("\n\n")) >= 0) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1988 const block = buffer.slice(0, boundary);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1989 buffer = buffer.slice(boundary + 2);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1990 if (block.trim()) dispatch(block);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1991 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1992 if (done) break;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1993 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1994 if (buffer.trim()) dispatch(buffer);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1995 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1996
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1997 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1998 Account slot rendering
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1999 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2000
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2001 function renderAccountSlot(slotEl, sessionData) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2002 if (!slotEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2003 slotEl.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2004 if (!sessionData || sessionData.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2005 const owner = document.createElement("zen-button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2006 const btn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2007 owner.setAttribute("appearance", "plain");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2008 owner.setAttribute("size", "xs");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2009 btn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2010 btn.dataset.loginOpen = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2011 btn.textContent = "LOGIN";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2012 owner.append(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2013 slotEl.setAttribute("aria-label", "Not signed in");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2014 slotEl.append(owner);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2015 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2016 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2017 const username = (sessionData.username || "").slice(0, 8).toUpperCase();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2018 if (sessionData.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2019 const link = document.createElement("a");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2020 link.href = "/account/password";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2021 link.textContent = username ? `${username}\u26A0` : "PASSWD";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2022 link.title = "Password change required";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2023 slotEl.setAttribute("aria-label", `${sessionData.username}: password change required`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2024 slotEl.append(link);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2025 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2026 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2027 const nameSpan = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2028 nameSpan.dataset.accountUser = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2029 nameSpan.textContent = username;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2030 slotEl.append(nameSpan);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2031 if (sessionData.role === "admin") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2032 const adminLink = document.createElement("a");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2033 adminLink.href = "/admin/users";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2034 adminLink.textContent = "\u00A0ADM";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2035 slotEl.append(adminLink);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2036 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2037 const logoutBtn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2038 logoutBtn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2039 logoutBtn.textContent = "\u00A0LGOUT";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2040 logoutBtn.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2041 logoutBtn.disabled = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2042 mutatingFetch("/api/auth/logout", { method: "POST" }).then(response => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2043 if (!response.ok) throw new Error(`Logout failed (${response.status})`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2044 window.dispatchEvent(new CustomEvent("mjj-logout"));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2045 }).catch(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2046 logoutBtn.disabled = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2047 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2048 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2049 slotEl.append(logoutBtn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2050 slotEl.setAttribute("aria-label", `Signed in as ${sessionData.username}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2051 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2052
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2053 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2054 Quota display helpers
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2055 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2056
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2057 function formatQuotaText(quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2058 if (!quota) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2059 const turns = quota.turnsRemaining ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2060 const tokens = quota.outputTokensRemaining ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2061 if (turns <= 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2062 const resetAt = quota.resetsAt ? new Date(quota.resetsAt * 1000) : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2063 const resetStr = resetAt
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2064 ? ` Resets ${resetAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2065 : "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2066 return { text: `Quota exhausted.${resetStr}`, state: "exhausted" };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2067 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2068 if (turns <= 2) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2069 return {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2070 text: `${turns} turn${turns === 1 ? "" : "s"} remaining (${tokens.toLocaleString()} tokens)`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2071 state: "warning",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2072 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2073 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2074 return {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2075 text: `${turns} turns remaining (${tokens.toLocaleString()} tokens)`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2076 state: "ok",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2077 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2078 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2079
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2080 function updateQuotaDisplay(quotaEl, quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2081 if (!quotaEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2082 if (!quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2083 quotaEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2084 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2085 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2086 const result = formatQuotaText(quota);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2087 /* Only show the bar for low or exhausted quota; hide when turns are plentiful */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2088 if (!result || result.state === "ok") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2089 quotaEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2090 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2091 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2092 quotaEl.textContent = result.text;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2093 if (result.state === "exhausted") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2094 quotaEl.dataset.state = "exhausted";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2095 } else if (result.state === "warning") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2096 quotaEl.dataset.state = "warning";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2097 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2098 delete quotaEl.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2099 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2100 quotaEl.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2101 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2102
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2103 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2104 Conversation archive helpers
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2105 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2106
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2107 async function fetchConversationList(cursor) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2108 const url = cursor
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2109 ? `/api/conversations?limit=${ARCHIVE_PAGE_SIZE}&cursor=${encodeURIComponent(cursor)}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2110 : `/api/conversations?limit=${ARCHIVE_PAGE_SIZE}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2111 const response = await fetch(url, { credentials: "same-origin" });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2112 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2113 let message = `Failed to load conversations (${response.status})`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2114 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2115 const body = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2116 message = body.error?.message || message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2117 } catch { /* keep status message */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2118 throw new Error(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2119 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2120 return response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2121 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2122
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2123 async function initializeJrpgPage() {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2124 const colorProbe = document.createElement("span");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2125 colorProbe.style.background = "var(--zenbu-sys-color-surface-page)";
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2126 document.body.append(colorProbe);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2127 const themeColor = getComputedStyle(colorProbe).backgroundColor;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2128 colorProbe.remove();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2129 for (const meta of document.querySelectorAll('meta[name="theme-color"]')) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2130 meta.setAttribute("content", themeColor);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2131 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2132
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2133 /* Bootstrap session to obtain CSRF token and session data */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2134 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2135
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2136 const shell = document.querySelector("mjj-jrpg-shell");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2137 const character = shell?.querySelector("mjj-jrpg-character");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2138 const chat = shell?.querySelector("mjj-jrpg-chat");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2139 const composer = shell?.querySelector("mjj-jrpg-composer");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2140 const preview = shell?.querySelector("mjj-jrpg-preview");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2141 const archive = shell?.querySelector("mjj-conversation-archive");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2142 const accountSlot = shell?.querySelector("[data-frame-account]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2143 const quotaEl = shell?.querySelector("[data-quota]");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2144 const frameNetwork = shell?.querySelector("[data-frame-network]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2145 const frameUptime = shell?.querySelector("[data-frame-uptime]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2146 const minimizeButton = shell?.querySelector("[data-frame-minimize]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2147 const fullscreenButton = shell?.querySelector("[data-frame-fullscreen]");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2148
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2149 let characterTimer = 0;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2150 let currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2151 let currentPanel = "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2152 let archiveCursor = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2153 let activeController = null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2154 let archiveLoadInFlight = false; /* serialize pagination requests */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2155 let openSeq = 0; /* sequence guard for conversation open */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2156 let popSeq = 0; /* sequence guard for popstate */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2157 let _pendingPopState = null; /* queued popstate received during active stream */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2158 let _claimInFlight = false; /* guard against duplicate concurrent claims */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2159 let legacyClaimId = sessionStorage.getItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2160 let principalEpoch = 0; /* rejects responses from a prior owner */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2161 /* Dedupe conversation IDs: set of IDs currently rendered in archive */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2162 const _renderedIds = new Set();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2163 const invalidatePrincipalRequests = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2164 principalEpoch += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2165 openSeq += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2166 popSeq += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2167 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2168 };
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2169 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2170
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2171 /* Disable composer until fully initialized */
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2172 composer?.setDisabled(true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2173
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2174 const setFrameStatus = (label, state, title = "") => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2175 if (frameNetwork) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2176 frameNetwork.textContent = label;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2177 frameNetwork.setAttribute("aria-label", `Status ${label.toLowerCase()}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2178 frameNetwork.dataset.state = state;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2179 frameNetwork.title = title;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2180 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2181 };
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2182
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2183 /* Bootstrap failure: keep controls disabled and expose retry */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2184 if (!_csrfToken) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2185 setFrameStatus("OFFLINE", "offline", "Bootstrap failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2186 archive?.setError("Connection failed. Reload to retry.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2187 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2188 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2189
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2190 setFrameStatus("ONLINE", "online");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2191
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2192 const startedAt = Date.now();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2193 const updateUptime = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2194 const elapsed = Math.floor((Date.now() - startedAt) / 1000);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2195 const hours = String(Math.floor(elapsed / 3600)).padStart(2, "0");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2196 const minutes = String(Math.floor((elapsed % 3600) / 60)).padStart(2, "0");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2197 const seconds = String(elapsed % 60).padStart(2, "0");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2198 if (frameUptime) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2199 frameUptime.textContent = `${hours}:${minutes}:${seconds}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2200 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2201 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2202 updateUptime();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2203 window.setInterval(updateUptime, 1000);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2204
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2205 minimizeButton?.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2206 const minimized = shell.dataset.minimized !== "true";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2207 shell.dataset.minimized = String(minimized);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2208 minimizeButton.setAttribute("aria-pressed", String(minimized));
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2209 minimizeButton.setAttribute(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2210 "aria-label",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2211 minimized ? "Restore interface" : "Minimize interface",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2212 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2213 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2214 fullscreenButton?.addEventListener("click", async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2215 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2216 if (document.fullscreenElement) await document.exitFullscreen();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2217 else await shell.requestFullscreen();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2218 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2219 setFrameStatus("DENIED", "offline", error.message);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2220 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2221 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2222 document.addEventListener("fullscreenchange", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2223 fullscreenButton?.setAttribute(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2224 "aria-pressed",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2225 String(Boolean(document.fullscreenElement)),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2226 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2227 });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2228
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2229 /* Render account slot */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2230 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2231
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2232 /* ---- Login modal ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2233 const loginDialogOwner = shell?.querySelector("[data-login-dialog-owner]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2234 const loginDialog = loginDialogOwner?.querySelector("[data-login-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2235 const loginForm = loginDialog?.querySelector("[data-login-form]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2236 const loginSubmitBtn = loginDialog?.querySelector("[data-login-submit]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2237 const loginCancelBtn = loginDialog?.querySelector("[data-login-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2238 const loginErrorEl = loginDialog?.querySelector("[data-login-error]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2239 const loginUsernameInput = loginDialog?.querySelector("#jrpg-login-username");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2240 const loginPasswordInput = loginDialog?.querySelector("#jrpg-login-password");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2241 let _loginTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2242 const setAccountActionsDisabled = disabled => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2243 for (const control of accountSlot?.querySelectorAll("button") || []) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2244 control.disabled = disabled;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2245 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2246 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2247
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2248 const _clearLoginError = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2249 if (loginErrorEl) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2250 loginErrorEl.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2251 loginErrorEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2252 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2253 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2254
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2255 const _showLoginError = (message) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2256 if (!loginErrorEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2257 loginErrorEl.textContent = message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2258 loginErrorEl.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2259 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2260
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2261 const openLoginModal = (triggerEl) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2262 if (!loginDialogOwner || !loginDialog) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2263 _loginTrigger = triggerEl || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2264 _clearLoginError();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2265 loginForm?.reset();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2266 loginDialogOwner.open();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2267 requestAnimationFrame(() => { loginUsernameInput?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2268 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2269
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2270 /* Delegate clicks on data-login-open in the account slot */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2271 accountSlot?.addEventListener("click", event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2272 const btn = event.target.closest("[data-login-open]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2273 if (!btn || activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2274 openLoginModal(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2275 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2276
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2277 /* Cancel button */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2278 loginCancelBtn?.addEventListener("click", () => { loginDialog?.close(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2279
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2280 /* Cleanup on any close (Escape, X button, cancel) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2281 loginDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2282 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2283 _clearLoginError();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2284 const trigger = _loginTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2285 _loginTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2286 requestAnimationFrame(() => { trigger?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2287 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2288
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2289 /* Login form submission */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2290 loginForm?.addEventListener("submit", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2291 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2292 if (loginSubmitBtn?.disabled) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2293 const username = loginUsernameInput?.value.trim() || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2294 const password = loginPasswordInput?.value || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2295 if (!username || !password) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2296 _showLoginError("Please enter your username and password.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2297 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2298 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2299 if (loginSubmitBtn) loginSubmitBtn.disabled = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2300 _clearLoginError();
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2301 try {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2302 const response = await fetch("/api/auth/login", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2303 method: "POST",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2304 headers: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2305 "Content-Type": "application/json",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2306 "Origin": window.location.origin,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2307 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2308 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2309 body: JSON.stringify({ username, password, csrfToken: _csrfToken }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2310 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2311 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2312 if (response.status === 429) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2313 _showLoginError("Too many attempts. Please try again later.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2314 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2315 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2316 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2317 _showLoginError("Invalid username or password.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2318 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2319 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2320 let data = await response.json().catch(() => null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2321 if (!data) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2322 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2323 if (_sessionData?.kind !== "user") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2324 loginDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2325 window.location.reload();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2326 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2327 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2328 data = _sessionData;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2329 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2330 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2331 loginDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2332 _sessionData = {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2333 kind: "user",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2334 username: data.username || username,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2335 role: data.role || "member",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2336 mustChangePassword: Boolean(data.mustChangePassword),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2337 csrfToken: data.csrfToken,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2338 quota: null,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2339 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2340 _csrfToken = data.csrfToken || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2341 if (data.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2342 window.location.href = "/account/password";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2343 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2344 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2345 /* Commit the returned principal before optional UI refresh work. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2346 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2347 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2348 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2349 /* Clear conversation state — guest convs may transfer to user */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2350 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2351 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2352 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2353 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2354 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2355 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2356 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2357 await refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2358 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2359 archive?.setError(`Signed in, but archive refresh failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2360 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2361 requestAnimationFrame(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2362 accountSlot?.querySelector("button, a")?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2363 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2364 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2365 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2366 _showLoginError("Sign-in failed. Please try again.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2367 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2368 if (loginSubmitBtn) loginSubmitBtn.disabled = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2369 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2370 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2371
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2372 /* Handle logout */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2373 _sessionRefreshCallback = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2374 /* Account changed or session lost — clear active state */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2375 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2376 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2377 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2378 activeController = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2379 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2380 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2381 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2382 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2383 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2384 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2385 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2386 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2387 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2388 archive?.setConversations([], null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2389 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2390 archive?.setError("Session changed. Loading conversations...");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2391 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2392 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2393 window.addEventListener("mjj-logout", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2394 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2395 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2396 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2397 activeController = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2398 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2399 archive?.setStreamActive(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2400 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2401 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2402 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2403 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2404 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2405 chat?.setGreeting(ROLE_GREETINGS.guest, true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2406 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2407 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2408 archive?.setConversations([], null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2409 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2410 void (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2411 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2412 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2413 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2414 if (_sessionData?.kind === "user" && _sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2415 composer?.setDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2416 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2417 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2418 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2419 })();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2420 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2421
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2422 /* Session quota display */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2423 if (_sessionData?.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2424 updateQuotaDisplay(quotaEl, _sessionData.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2425 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2426
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2427 /* Disable composer for forced-password-change */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2428 const isForced = _sessionData?.kind === "user" && _sessionData?.mustChangePassword;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2429
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2430 /* ---- URL state helpers ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2431 const _buildStateUrl = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2432 const url = new URL(window.location.href);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2433 if (panel && VALID_PANELS.includes(panel)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2434 url.searchParams.set("panel", panel);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2435 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2436 url.searchParams.delete("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2437 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2438 if (convId && UUID_REGEX.test(convId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2439 url.searchParams.set("conversation", convId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2440 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2441 url.searchParams.delete("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2442 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2443 return url;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2444 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2445
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2446 const _pushUrlState = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2447 history.pushState({ panel, conversation: convId || null }, "", _buildStateUrl(panel, convId));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2448 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2449
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2450 const _replaceUrlState = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2451 history.replaceState({ panel, conversation: convId || null }, "", _buildStateUrl(panel, convId));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2452 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2453
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2454 /* ---- Panel selection: shows archive or preview in utility ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2455 const utilityEl = shell?.querySelector(".jrpg-utility");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2456
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2457 const selectPanel = (panel, pushHistory = true) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2458 const validPanel = VALID_PANELS.includes(panel) ? panel : "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2459 currentPanel = validPanel;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2460 for (const btn of (shell?.querySelectorAll("button[data-preview]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2461 btn.setAttribute("aria-pressed", String(btn.dataset.preview === validPanel));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2462 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2463 if (validPanel === "conversations") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2464 if (archive) archive.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2465 if (preview) preview.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2466 if (utilityEl) utilityEl.setAttribute("aria-label", "Conversations");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2467 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2468 if (archive) archive.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2469 if (preview) preview.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2470 preview?.show(validPanel);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2471 if (utilityEl) utilityEl.setAttribute("aria-label", "Selected destination");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2472 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2473 if (pushHistory) _pushUrlState(validPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2474 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2475
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2476 /* ---- Archive close button: navigate back to resume panel ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2477 shell?.addEventListener("mjj-archive-toggle", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2478 selectPanel("resume");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2479 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2480
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2481 shell?.addEventListener("mjj-archive-new", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2482 if (activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2483 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2484 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2485 _pushUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2486 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2487 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2488 composer?.querySelector("textarea")?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2489 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2490
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2491 /* ---- Popstate: re-apply panel and conversation on back/forward ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2492 /* _applyPopState always increments openSeq so any pending archive-open
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2493 fetch (including a transition to no conversation) cannot overwrite the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2494 state the user navigated to (Fix 2). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2495 const _applyPopState = async (seq, popPanel, popConvId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2496 const localSeq = ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2497 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2498
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2499 selectPanel(popPanel, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2500
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2501 if (popConvId && popConvId !== currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2502 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2503 const conversation = await requestJson(`/api/conversations/${encodeURIComponent(popConvId)}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2504 if (seq !== popSeq || localSeq !== openSeq ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2505 epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2506 currentConversationId = popConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2507 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2508 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2509 archive?.setCurrentId(popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2510 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2511 if (seq !== popSeq || localSeq !== openSeq ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2512 epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2513 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2514 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2515 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2516 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2517 history.replaceState(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2518 { panel: popPanel, conversation: null },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2519 "",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2520 _buildStateUrl(popPanel, null),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2521 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2522 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2523 } else if (!popConvId && currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2524 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2525 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2526 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2527 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2528 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2529 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2530
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2531 /* When popstate fires while a stream is active the URL has already changed.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2532 Queue the parsed destination, abort the stream, and apply exactly once in
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2533 the stream's finally block so URL and UI always converge (Fix 1). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2534 window.addEventListener("popstate", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2535 const seq = ++popSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2536 ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2537 const state = event.state || {};
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2538 const popPanel = VALID_PANELS.includes(state.panel) ? state.panel : "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2539 const popConvId = (state.conversation && UUID_REGEX.test(state.conversation))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2540 ? state.conversation : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2541
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2542 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2543 _pendingPopState = { seq, popPanel, popConvId };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2544 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2545 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2546 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2547
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2548 await _applyPopState(seq, popPanel, popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2549 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2550
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2551 shell?.addEventListener("mjj-archive-rename", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2552 const { id, newTitle, trigger } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2553 if (activeController) { trigger?.focus(); return; }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2554 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2555 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2556 `/api/conversations/${encodeURIComponent(id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2557 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2558 method: "PATCH",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2559 headers: { "Content-Type": "application/json" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2560 body: JSON.stringify({ title: newTitle }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2561 },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2562 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2563 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2564 const body = await response.json().catch(() => ({}));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2565 throw new Error(body.error?.message || `Rename failed (${response.status})`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2566 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2567 archive?.updateConversation({ id, title: newTitle, turn_count: null });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2568 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2569 archive?.setError(`Rename failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2570 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2571 trigger?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2572 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2573 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2574
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2575 shell?.addEventListener("mjj-archive-open", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2576 const { id } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2577 if (id === currentConversationId) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2578 if (activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2579 const seq = ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2580 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2581 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2582 const conversation = await requestJson(`/api/conversations/${encodeURIComponent(id)}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2583 if (seq !== openSeq || epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2584 currentConversationId = id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2585 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2586 _pushUrlState(currentPanel, id);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2587 chat?.replaceMessages(conversation.turns);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2588 archive?.setCurrentId(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2589 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2590 if (seq !== openSeq || epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2591 archive?.setError(`Unable to open: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2592 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2593 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2594
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2595 shell?.addEventListener("mjj-archive-delete", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2596 const { id, trigger } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2597 if (activeController) { trigger?.focus(); return; } /* block while streaming */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2598 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2599 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2600 `/api/conversations/${encodeURIComponent(id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2601 { method: "DELETE" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2602 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2603 if (response.ok || response.status === 404) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2604 /* Focus adjacent archive item or New button before removing the node */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2605 const focusTarget = archive?.adjacentItemOrNewButton(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2606 _renderedIds.delete(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2607 archive?.removeConversation(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2608 if (id === currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2609 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2610 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2611 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2612 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2613 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2614 /* Focus surviving element — never the detached trigger */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2615 requestAnimationFrame(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2616 (focusTarget || archive?.querySelector("[data-archive-new]"))?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2617 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2618 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2619 const body = await response.json().catch(() => ({}));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2620 archive?.setError(`Delete failed: ${body.error?.message || response.status}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2621 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2622 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2623 archive?.setError(`Delete failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2624 /* On error, the trigger is still attached — focus it */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2625 trigger?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2626 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2627 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2628
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2629 shell?.addEventListener("mjj-archive-load-more", async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2630 if (!archiveCursor || archiveLoadInFlight) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2631 archiveLoadInFlight = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2632 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2633 archive?.setLoadMoreDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2634 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2635 const data = await fetchConversationList(archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2636 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2637 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2638 const newConvs = (data.conversations || []).filter(c => !_renderedIds.has(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2639 newConvs.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2640 archive?.setConversations(newConvs, archiveCursor, true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2641 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2642 archive?.setError(`Load failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2643 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2644 archiveLoadInFlight = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2645 archive?.setLoadMoreDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2646 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2647 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2648
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2649 shell?.addEventListener("mjj-archive-claim", async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2650 const legacyId =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2651 legacyClaimId || sessionStorage.getItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2652 if (!legacyId || _sessionData?.kind !== "user" || _claimInFlight) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2653 _claimInFlight = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2654 archive?.showLegacyClaim("Claiming prior conversation...", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2655 let _claimOutcome = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2656 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2657 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2658 "/api/conversations/claim",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2659 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2660 method: "POST",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2661 headers: { "Content-Type": "application/json" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2662 body: JSON.stringify({ conversationId: legacyId }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2663 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2664 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2665 if (response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2666 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2667 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2668 _claimOutcome = "success";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2669 } else if (response.status === 409) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2670 /* Definitive non-legacy conflict: clear key, no retry needed */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2671 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2672 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2673 _claimOutcome = "conflict";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2674 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2675 /* Retryable (401, 403, 429, 5xx): retain key */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2676 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, legacyId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2677 legacyClaimId = legacyId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2678 _claimOutcome = "retry";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2679 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2680 } catch {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2681 /* Network error: retain key */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2682 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, legacyId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2683 legacyClaimId = legacyId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2684 _claimOutcome = "network-error";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2685 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2686 /* Reset guard BEFORE re-rendering so the retry button is immediately usable */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2687 _claimInFlight = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2688 if (_claimOutcome === "success") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2689 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2690 } else if (_claimOutcome === "conflict") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2691 archive?.showLegacyClaim("Prior conversation already claimed.", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2692 } else if (_claimOutcome === "retry") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2693 selectPanel("conversations", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2694 archive?.showLegacyClaim("Claim failed. Try again. ");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2695 } else if (_claimOutcome === "network-error") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2696 selectPanel("conversations", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2697 archive?.showLegacyClaim("Claim failed. Check connection. ");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2698 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2699 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2700 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2701
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2702 async function refreshArchiveFromScratch() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2703 if (!archive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2704 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2705 archive.setLoading(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2706 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2707 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2708 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2709 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2710 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2711 const convs = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2712 convs.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2713 archive.setConversations(convs, archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2714 archive.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2715 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2716 archive.setError(`Archive error: ${error.message}`);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2717 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2718 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2719
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2720 async function refreshArchiveHead() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2721 if (!archive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2722 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2723 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2724 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2725 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2726 const fresh = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2727 for (const conv of fresh) archive.updateConversation(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2728 if (fresh.length > 0) archive.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2729 } catch { /* silent refresh failure */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2730 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2731
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2732 /* ---- Parse and validate URL params at startup ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2733 /* Track whether ?conversation was explicitly present (even if malformed)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2734 so we never migrate sessionStorage when the param was given but invalid. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2735 let _convParamWasExplicit = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2736 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2737 const initParams = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2738 const rawPanel = initParams.get("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2739 const rawConvId = initParams.get("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2740 _convParamWasExplicit = rawConvId !== null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2741 const validPanel = VALID_PANELS.includes(rawPanel) ? rawPanel : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2742 const validConvId = (rawConvId && UUID_REGEX.test(rawConvId)) ? rawConvId : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2743 /* Remove invalid params with replaceState */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2744 if ((rawPanel && !validPanel) || (rawConvId && !validConvId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2745 const normalUrl = new URL(window.location.href);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2746 if (!validPanel) normalUrl.searchParams.delete("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2747 if (!validConvId) normalUrl.searchParams.delete("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2748 history.replaceState(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2749 { panel: validPanel, conversation: validConvId },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2750 "",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2751 normalUrl,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2752 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2753 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2754 currentPanel = validPanel || "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2755 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2756
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2757 /* ---- Load archive and restore conversation from URL or sessionStorage ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2758 const startupEpoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2759 const ensureStartupPrincipal = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2760 if (startupEpoch !== principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2761 const error = new Error("Principal changed during startup");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2762 error.name = "StalePrincipalError";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2763 throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2764 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2765 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2766 archive?.setLoading(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2767 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2768 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2769 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2770 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2771 const conversations = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2772 conversations.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2773 archive?.setConversations(conversations, archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2774
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2775 /* Determine target conversation: URL param is canonical; migrate sessionStorage once */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2776 const initParams2 = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2777 const urlConvId = initParams2.get("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2778 const urlConvValid = urlConvId && UUID_REGEX.test(urlConvId) ? urlConvId : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2779
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2780 let resolvedConvId = urlConvValid;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2781 let migratedFromStorage = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2782 if (!resolvedConvId && !_convParamWasExplicit) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2783 const stored = sessionStorage.getItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2784 if (stored && UUID_REGEX.test(stored)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2785 resolvedConvId = stored;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2786 migratedFromStorage = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2787 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2788 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2789
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2790 if (resolvedConvId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2791 const inArchive = conversations.find(c => c.id === resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2792 if (inArchive) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2793 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2794 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2795 `/api/conversations/${encodeURIComponent(resolvedConvId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2796 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2797 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2798 currentConversationId = resolvedConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2799 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2800 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2801 archive?.setCurrentId(resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2802 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2803 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2804 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2805 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2806 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2807 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2808 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2809 /* Not on first page — fetch directly to verify ownership */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2810 let ownedConversation = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2811 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2812 ownedConversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2813 `/api/conversations/${encodeURIComponent(resolvedConvId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2814 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2815 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2816 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2817 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2818 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2819 ownedConversation = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2820 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2821 if (ownedConversation) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2822 currentConversationId = resolvedConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2823 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2824 chat?.replaceMessages(ownedConversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2825 if (!_renderedIds.has(resolvedConvId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2826 _renderedIds.add(resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2827 archive?.addConversation({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2828 id: resolvedConvId,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2829 title: ownedConversation.title || "Conversation",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2830 turn_count: ownedConversation.turns?.length || 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2831 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2832 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2833 archive?.setCurrentId(resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2834 } else if (_sessionData?.kind === "user") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2835 /* Authenticated and 404 — offer legacy claim for sessionStorage migrations */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2836 if (migratedFromStorage) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2837 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2838 legacyClaimId = resolvedConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2839 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2840 archive?.showLegacyClaim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2841 if (conversations.length > 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2842 currentConversationId = conversations[0].id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2843 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2844 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2845 const conv = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2846 `/api/conversations/${encodeURIComponent(currentConversationId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2847 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2848 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2849 chat?.replaceMessages(conv.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2850 archive?.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2851 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2852 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2853 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2854 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2855 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2856 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2857 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2858 /* URL-specified conv returned 404 — clear it */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2859 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2860 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2861 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2862 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2863 /* Guest — not owned, clear stale ID */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2864 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2865 if (migratedFromStorage) sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2866 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2867 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2868 } else if (conversations.length > 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2869 /* No target conversation — open the most recent */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2870 const most_recent = conversations[0];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2871 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2872 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2873 `/api/conversations/${encodeURIComponent(most_recent.id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2874 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2875 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2876 currentConversationId = most_recent.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2877 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2878 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2879 archive?.setCurrentId(most_recent.id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2880 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2881 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2882 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2883 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2884 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2885 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2886 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2887 if (error.name !== "StalePrincipalError" &&
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2888 startupEpoch === principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2889 archive?.setError(`Archive error: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2890 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2891 /* Fall back to direct fetch of URL/stored ID */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2892 const initParams3 = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2893 const fallbackId = (
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2894 initParams3.get("conversation") ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2895 (!_convParamWasExplicit
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2896 ? sessionStorage.getItem(CONVERSATION_STORAGE_KEY)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2897 : null)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2898 ) || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2899 if (startupEpoch === principalEpoch &&
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2900 fallbackId && UUID_REGEX.test(fallbackId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2901 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2902 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2903 `/api/conversations/${encodeURIComponent(fallbackId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2904 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2905 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2906 currentConversationId = fallbackId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2907 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2908 } catch (fallbackError) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2909 if (fallbackError.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2910 startupEpoch !== principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2911 // The new principal owns subsequent rendering.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2912 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2913 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2914 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2915 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2916 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2917 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2918 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2919
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2920 /* Apply startup panel and normalize URL (replaceState — not a user navigation) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2921 if (startupEpoch === principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2922 selectPanel(currentPanel, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2923 _replaceUrlState(currentPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2924 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2925
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2926 shell?.addEventListener("mjj-jrpg-preview-change", event => {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2927 selectPanel(event.detail.selection);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2928 character?.setAttribute("state", "thinking");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2929 window.clearTimeout(characterTimer);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2930 characterTimer = window.setTimeout(() => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2931 character?.setAttribute("state", "default");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2932 }, 650);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2933 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2934
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2935 shell?.addEventListener("mjj-jrpg-cancel", () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2936 activeController?.abort();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2937 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2938
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2939 shell?.addEventListener("mjj-jrpg-submit", async event => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2940 if (activeController) return;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2941 /* Block sends when password change is required */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2942 if (_sessionData?.kind === "user" && _sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2943 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2944 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2945 chat?.appendMessage("June", event.detail.message);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2946 composer?.setBusy(true);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2947 character?.setAttribute("state", "thinking");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2948 window.clearTimeout(characterTimer);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2949 const assistantItem = chat?.startAssistantMessage();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2950 chat?.setMessageStreaming(assistantItem, true);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2951 const assistantText = new StreamingTextAnimator(text => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2952 chat?.setMessageText(assistantItem, text);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2953 });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2954 let turnFinished = false;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2955 activeController = new AbortController();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2956 activeController.signal.addEventListener("abort", () => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2957 assistantText.cancel();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2958 }, { once: true });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2959
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2960 /* Disable archive/account mutations while stream is active (finding #7) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2961 archive?.setStreamActive(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2962 setAccountActionsDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2963
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2964 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2965 const createConversation = async () => {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2966 const resp = await mutatingFetch("/api/conversations", {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2967 method: "POST",
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2968 headers: { "Content-Type": "application/json" },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2969 body: JSON.stringify({ title: "Shiba Quest" }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2970 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2971 if (!resp.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2972 let message = `Create failed (${resp.status})`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2973 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2974 const body = await resp.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2975 message = body.error?.message || message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2976 } catch { /* keep */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2977 throw new Error(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2978 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2979 const conversation = await resp.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2980 currentConversationId = conversation.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2981 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2982 _pushUrlState(currentPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2983 if (!_renderedIds.has(currentConversationId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2984 _renderedIds.add(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2985 archive?.addConversation({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2986 id: currentConversationId,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2987 title: "Shiba Quest",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2988 turn_count: 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2989 last_message_preview: event.detail.message.slice(0, 32),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2990 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2991 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2992 archive?.setCurrentId(currentConversationId);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2993 };
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2994
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2995 if (!currentConversationId) {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2996 await createConversation();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2997 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2998
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2999 const postTurn = () => mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3000 `/api/conversations/${encodeURIComponent(currentConversationId)}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3001 {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3002 method: "POST",
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3003 headers: { "Content-Type": "application/json" },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3004 body: JSON.stringify({ prompt: event.detail.message }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3005 signal: activeController.signal,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3006 },
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3007 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3008
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3009 let response = await postTurn();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3010
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3011 /* Handle 404: conversation may have expired — recreate it */
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3012 if (response.status === 404) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3013 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3014 currentConversationId = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3015 await createConversation();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3016 response = await postTurn();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3017 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3018
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3019 /* Handle 429: distinguish permanent quota from temporary capacity */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3020 if (response.status === 429) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3021 let errorBody = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3022 try { errorBody = await response.json(); } catch { /* keep */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3023 const code = errorBody?.error?.code || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3024 const isQuotaExhausted =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3025 code === "guest_quota_turns_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3026 code === "guest_quota_tokens_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3027 code === "guest_turn_quota_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3028 code === "guest_token_quota_exhausted";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3029
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3030 if (isQuotaExhausted) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3031 const quotaState = errorBody?.error?.quota || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3032 updateQuotaDisplay(quotaEl, quotaState);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3033 composer?.setDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3034 chat?.setMessageStreaming(assistantItem, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3035 chat?.setMessageText(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3036 assistantItem,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3037 quotaState
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3038 ? `Quota exhausted. ${formatQuotaText(quotaState)?.text || ""}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3039 : "Quota exhausted. Try again later.",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3040 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3041 character?.setAttribute("state", "sad");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3042 setFrameStatus("QUOTA", "offline");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3043 /* Re-enable when quota resets */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3044 const resetsAt = quotaState?.resetsAt;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3045 if (resetsAt) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3046 const delay = Math.max(0, resetsAt * 1000 - Date.now());
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3047 window.setTimeout(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3048 if (_sessionData?.kind !== "user" || !_sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3049 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3050 setFrameStatus("ONLINE", "online");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3051 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3052 }, Math.min(delay + 1000, 24 * 3600 * 1000));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3053 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3054 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3055 /* Temporary capacity 429 — use Retry-After or default 5 s */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3056 const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3057 chat?.setMessageStreaming(assistantItem, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3058 chat?.setMessageText(assistantItem, "Server busy. Retrying shortly…");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3059 character?.setAttribute("state", "sad");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3060 setFrameStatus("BUSY", "offline");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3061 window.setTimeout(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3062 if (!activeController?.signal.aborted) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3063 setFrameStatus("ONLINE", "online");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3064 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3065 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3066 }, retryAfter * 1000);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3067 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3068 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3069 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3070
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3071 if (!response.ok) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3072 let message = `Turn failed (${response.status})`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3073 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3074 const body = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3075 message = body.error?.message || message;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3076 } catch { /* keep */ }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3077 throw new Error(message);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3078 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3079
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3080 await consumeSse(response, (eventName, data) => {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3081 shell?.dispatchEvent(new CustomEvent("mjj-jrpg-stream-event", {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3082 bubbles: true,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3083 detail: { data, type: eventName },
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3084 }));
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3085 if (eventName === "assistant.delta") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3086 assistantText.append(data.delta || "");
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3087 } else if (eventName === "assistant.completed") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3088 assistantText.complete(data.content);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3089 } else if (eventName === "turn.error") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3090 throw new Error(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3091 data.error?.message || data.message || "Inference turn failed",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3092 );
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3093 } else if (eventName === "turn.done") {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3094 turnFinished = true;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3095 composer?.setCancellable(false);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3096 if (data.failed || data.aborted) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3097 throw new Error(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3098 data.aborted ? "Inference turn aborted" : "Inference turn failed",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3099 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3100 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3101 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3102 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3103
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3104 if (!turnFinished) throw new Error("Inference stream ended unexpectedly");
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3105 await assistantText.waitForIdle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3106 if (activeController.signal.aborted) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3107 throw new DOMException("Quest cancelled", "AbortError");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3108 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3109 chat?.setMessageStreaming(assistantItem, false);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3110 character?.setAttribute("state", "default");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
3111 setFrameStatus("ONLINE", "online");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3112
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3113 /* Refresh archive and quota after successful turn */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3114 void refreshArchiveHead();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3115 if (_sessionData?.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3116 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3117 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3118 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3119 } catch (error) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3120 const aborted = error instanceof DOMException && error.name === "AbortError";
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3121 assistantText.cancel();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3122 chat?.setMessageStreaming(assistantItem, false);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3123 chat?.setMessageText(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3124 assistantItem,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3125 aborted ? "Quest cancelled." : `System error: ${error.message}`,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3126 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3127 character?.setAttribute("state", "sad");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
3128 if (!aborted) setFrameStatus("OFFLINE", "offline");
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3129 } finally {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3130 composer?.setBusy(false);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3131 composer?.querySelector("textarea")?.focus();
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3132 activeController = null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3133 /* Re-enable archive/account mutations exactly once */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3134 archive?.setStreamActive(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3135 setAccountActionsDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3136 /* Apply popstate that was queued during the stream (Fix 1) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3137 if (_pendingPopState) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3138 const pending = _pendingPopState;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3139 _pendingPopState = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3140 if (pending.seq === popSeq) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3141 await _applyPopState(pending.seq, pending.popPanel, pending.popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3142 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3143 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3144 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3145 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3146
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3147 /* Enable composer unless forced password change */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3148 if (!isForced) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3149 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3150 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3151 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3152
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3153 if (document.readyState === "loading") {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3154 document.addEventListener("DOMContentLoaded", () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3155 void initializeJrpgPage();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3156 }, {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3157 once: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3158 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3159 } else {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3160 void initializeJrpgPage();
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3161 }