annotate mrjunejune/src/jrpg/jrpg.js @ 264:04fee26ecce0

add authenticated JRPG conversation platform Add reusable auth/session storage, owned conversation recovery, guest quotas, admin workflows, URL-routed conversation UI, mobile frame support, and parallel browser acceptance. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 07:34:12 -0700
parents ee04e4e69fed
children 056790c4fb0d
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"]);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
92 const TOOL_ICON_NAMES = Object.freeze({
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
93 "/notes": "repository",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
94 "/tools/file_converter": "retry",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
95 "/tools/hls_player": "play",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
96 "/tools/latex_editor": "file",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
97 "/tools/markdown_to_html": "code",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
98 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
99
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
100 function loadScript(source) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
101 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
102 const loading = new Promise((resolve, reject) => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
103 const script = document.createElement("script");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
104 script.src = source;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
105 script.addEventListener("load", resolve, { once: true });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
106 script.addEventListener("error", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
107 SCRIPT_LOADS.delete(source);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
108 script.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
109 reject(new Error(`Unable to load ${source}`));
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
110 }, { once: true });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
111 document.head.append(script);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
112 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
113 SCRIPT_LOADS.set(source, loading);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
114 return loading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
115 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
116
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
117 class StreamingTextAnimator {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
118 constructor(render) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
119 this._render = render;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
120 this._displayed = "";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
121 this._target = "";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
122 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
123 this._waiters = [];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
124 this._reduceMotion = window.matchMedia(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
125 "(prefers-reduced-motion: reduce)",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
126 ).matches;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
127 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
128
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
129 append(text) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
130 if (typeof text !== "string" || !text) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
131 this._target += text;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
132 this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
133 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
134
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
135 complete(text) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
136 if (typeof text === "string") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
137 this._target = text;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
138 if (!this._target.startsWith(this._displayed)) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
139 let shared = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
140 while (
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
141 shared < this._target.length &&
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
142 shared < this._displayed.length &&
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
143 this._target[shared] === this._displayed[shared]
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
144 ) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
145 shared++;
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 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
148 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
149 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
150 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
151 this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
152 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
153
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
154 waitForIdle() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
155 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
156 return Promise.resolve();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
157 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
158 return new Promise(resolve => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
159 this._waiters.push(resolve);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
160 });
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 cancel() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
164 window.clearTimeout(this._timer);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
165 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
166 this._target = this._displayed;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
167 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
168 }
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 _schedule() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
171 if (this._reduceMotion) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
172 this._displayed = this._target;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
173 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
174 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
175 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
176 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
177 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
178 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
179 }
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 _tick() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
182 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
183 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
184 if (remaining <= 0) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
185 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
186 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
187 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
188 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
189 this._displayed = this._target.slice(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
190 0,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
191 this._displayed.length + amount,
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 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
194 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
195 else this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
196 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
197
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
198 _settle() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
199 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
200 const waiters = this._waiters.splice(0);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
201 for (const resolve of waiters) resolve();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
202 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
203 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
204
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
205 class MjjJrpgCharacter extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
206 static get observedAttributes() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
207 return ["state"];
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
208 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
209
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
210 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
211 this.render();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
212 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
213
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
214 attributeChangedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
215 if (this.isConnected) this.render();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
216 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
217
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
218 render() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
219 const image = this.querySelector("[data-character]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
220 if (!image) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
221 const state = CHARACTER_STATES[this.getAttribute("state")] ||
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
222 CHARACTER_STATES.default;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
223 image.src = state.src;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
224 image.alt = state.alt;
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 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
227
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
228 class MjjJrpgChat extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
229 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
230 this._messages = this.querySelector("[data-messages]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
231 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
232 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
233 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
234 this._position = this.querySelector("[data-turn-position]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
235 this._initialMessages = [...(this._messages?.children || [])].map(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
236 item => item.cloneNode(true),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
237 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
238 this._turnSequence = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
239 this._activeGroup = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
240 this._onNavigationClick = event => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
241 const button = event.target.closest("button");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
242 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
243 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
244 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
245 };
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
246 this.addEventListener("click", this._onNavigationClick);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
247 this._refreshTurnGroups("start");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
248 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
249
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
250 disconnectedCallback() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
251 this.removeEventListener("click", this._onNavigationClick);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
252 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
253
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
254 appendMessage(speaker, text) {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
255 let group = this._activeGroup || "start";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
256 if (speaker === "June") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
257 this._turnSequence++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
258 group = `turn-${this._turnSequence}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
259 this._activeGroup = group;
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 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
262 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
263
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
264 _createMessage(speaker, text, group, select) {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
265 if (!this._messages) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
266 const item = document.createElement("li");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
267 const name = document.createElement("strong");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
268 const copy = document.createElement("p");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
269 item.className = "jrpg-message";
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
270 item.dataset.speaker = speaker;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
271 item.dataset.turnGroup = group;
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
272 name.textContent = speaker;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
273 copy.textContent = text;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
274 item.append(name, copy);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
275 this._messages.append(item);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
276 this._refreshTurnGroups(select ? group : this._currentGroup);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
277 return item;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
278 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
279
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
280 replaceMessages(turns) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
281 if (!this._messages) return;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
282 this._messages.replaceChildren();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
283 this._turnSequence = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
284 this._activeGroup = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
285 const visibleTurns = turns.filter(turn =>
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
286 ["user", "assistant"].includes(turn.role)
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
287 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
288 if (!visibleTurns.length) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
289 this._messages.append(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
290 ...this._initialMessages.map(item => item.cloneNode(true)),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
291 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
292 this._refreshTurnGroups("start");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
293 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
294 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
295 let group = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
296 for (const turn of visibleTurns) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
297 if (turn.role === "user" || !group) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
298 this._turnSequence++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
299 group = `turn-${this._turnSequence}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
300 this._activeGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
301 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
302 this._createMessage(
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
303 turn.role === "user" ? "June" : "Epi",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
304 turn.content ||
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
305 (turn.status === "aborted"
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
306 ? "Quest cancelled."
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
307 : turn.status === "failed"
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
308 ? "The quest failed."
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
309 : ""),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
310 group,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
311 false,
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
312 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
313 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
314 this._refreshTurnGroups(group);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
315 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
316
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
317 startAssistantMessage() {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
318 return this.appendMessage("Epi", "");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
319 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
320
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
321 setMessageText(item, text) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
322 const copy = item?.querySelector("p");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
323 if (copy) copy.textContent = text;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
324 this.scrollEnd();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
325 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
326
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
327 setMessageStreaming(item, streaming) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
328 if (!item) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
329 if (streaming) item.dataset.streaming = "true";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
330 else delete item.dataset.streaming;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
331 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
332 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
333
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
334 _refreshTurnGroups(preferredGroup) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
335 const groups = [];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
336 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
337 const group = item.dataset.turnGroup || "start";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
338 item.dataset.turnGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
339 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
340 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
341 this._turnGroups = groups;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
342 const preferredIndex = groups.indexOf(preferredGroup);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
343 const currentIndex = groups.indexOf(this._currentGroup);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
344 this._turnIndex = preferredIndex >= 0
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
345 ? preferredIndex
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
346 : currentIndex >= 0
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
347 ? currentIndex
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
348 : Math.max(0, groups.length - 1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
349 this._showTurn();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
350 }
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 _moveTurn(offset) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
353 const nextIndex = Math.max(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
354 0,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
355 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
356 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
357 if (nextIndex === this._turnIndex) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
358 this._turnIndex = nextIndex;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
359 this._showTurn();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
360 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
361
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
362 _showTurn() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
363 const group = this._turnGroups?.[this._turnIndex];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
364 this._currentGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
365 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
366 item.hidden = item.dataset.turnGroup !== group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
367 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
368 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
369 if (this._nextButton) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
370 this._nextButton.disabled =
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
371 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
372 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
373 if (this._position) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
374 if (group === "start") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
375 this._position.textContent = "START";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
376 } else {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
377 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
378 this._position.textContent =
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
379 `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
380 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
381 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
382 this.scrollEnd();
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
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
385 scrollEnd() {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
386 requestAnimationFrame(() => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
387 if (this._viewport) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
388 this._viewport.scrollTop = this._viewport.scrollHeight;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
389 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
390 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
391 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
392 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
393
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
394 class MjjJrpgComposer extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
395 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
396 this._form = this.querySelector("[data-composer]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
397 this._textarea = this.querySelector("textarea");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
398 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
399 this._cancel = this.querySelector("[data-cancel]");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
400 this._cancelControl = this.querySelector("[data-cancel-control]");
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
401 this._onSubmit = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
402 event.preventDefault();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
403 const message = this._textarea?.value.trim();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
404 if (!message || this._button?.disabled) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
405 this.dispatchEvent(new CustomEvent("mjj-jrpg-submit", {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
406 bubbles: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
407 detail: { message },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
408 }));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
409 this._form.reset();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
410 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
411 this._onKeydown = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
412 if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
413 event.preventDefault();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
414 this._form?.requestSubmit();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
415 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
416 this._form?.addEventListener("submit", this._onSubmit);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
417 this._textarea?.addEventListener("keydown", this._onKeydown);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
418 this._onCancel = () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
419 this.dispatchEvent(new CustomEvent("mjj-jrpg-cancel", {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
420 bubbles: true,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
421 }));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
422 };
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
423 this._cancel?.addEventListener("click", this._onCancel);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
424 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
425
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
426 disconnectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
427 this._form?.removeEventListener("submit", this._onSubmit);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
428 this._textarea?.removeEventListener("keydown", this._onKeydown);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
429 this._cancel?.removeEventListener("click", this._onCancel);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
430 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
431
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
432 setBusy(busy) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
433 if (this._textarea) this._textarea.disabled = busy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
434 if (this._button) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
435 this._button.disabled = busy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
436 this._button.setAttribute("aria-busy", String(busy));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
437 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
438 if (this._cancelControl) this._cancelControl.hidden = !busy;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
439 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
440
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
441 setCancellable(cancellable) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
442 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
443 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
444
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
445 setDisabled(disabled) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
446 if (this._textarea) this._textarea.disabled = disabled;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
447 if (this._button) this._button.disabled = disabled;
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
448 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
449 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
450
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
451 class MjjJrpgMenu extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
452 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
453 this._onClick = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
454 const button = event.target.closest("button[data-preview]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
455 if (!button || !this.contains(button)) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
456 for (const item of this.querySelectorAll("button[data-preview]")) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
457 item.setAttribute("aria-pressed", String(item === button));
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.dispatchEvent(new CustomEvent("mjj-jrpg-preview-change", {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
460 bubbles: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
461 detail: { selection: button.dataset.preview },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
462 }));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
463 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
464 this.addEventListener("click", this._onClick);
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
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
467 disconnectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
468 this.removeEventListener("click", this._onClick);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
469 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
470 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
471
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
472 function sanitizeFetchedMain(main) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
473 for (const unsafe of main.querySelectorAll(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
474 "embed, form, iframe, object, script, style, svg",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
475 )) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
476 unsafe.remove();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
477 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
478 for (const element of main.querySelectorAll("*")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
479 for (const attribute of [...element.attributes]) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
480 const name = attribute.name.toLowerCase();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
481 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
482 element.removeAttribute(attribute.name);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
483 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
484 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
485 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
486 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
487 const href = anchor.getAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
488 if (!href || anchor.getAttribute("aria-disabled") === "true") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
489 anchor.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
490 continue;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
491 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
492 const target = new URL(
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
493 href,
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
494 window.location.href,
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
495 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
496 anchor.href = target.href;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
497 if (target.origin !== window.location.origin) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
498 anchor.target = "_blank";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
499 anchor.rel = "noreferrer";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
500 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
501 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
502 for (const image of main.querySelectorAll("img")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
503 const source = image.getAttribute("src") || "";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
504 if (source.startsWith("/public/") && source.endsWith(".png")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
505 image.src = source.replace(/\.png$/i, ".webp");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
506 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
507 image.loading = "lazy";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
508 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
509 return main;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
510 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
511
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
512 function sanitizeToolDetail(main) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
513 sanitizeFetchedMain(main);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
514 for (const textarea of main.querySelectorAll("textarea")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
515 const sample = document.createElement("pre");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
516 sample.className = "jrpg-tool-source-sample";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
517 sample.textContent = textarea.value || textarea.textContent;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
518 textarea.replaceWith(sample);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
519 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
520 for (const inactive of main.querySelectorAll(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
521 "button, canvas, dialog, input, select, video, [hidden]",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
522 )) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
523 inactive.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
524 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
525 for (const owner of main.querySelectorAll(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
526 "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
527 )) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
528 owner.replaceWith(...owner.childNodes);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
529 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
530 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
531 label.removeAttribute("for");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
532 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
533 return main;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
534 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
535
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
536 class MjjJrpgPreview extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
537 connectedCallback() {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
538 this._dialog = this.querySelector("dialog");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
539 this._onDialogClose = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
540 this._toolRequest = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
541 this.cleanupTool();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
542 };
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
543 this._onClick = event => {
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
544 const trigger = event.target.closest("[data-zen-trigger]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
545 if (trigger) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
546 if (this.dataset.selection === "resume") void this.loadResume();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
547 if (this.dataset.selection === "blog") {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
548 this.show("blog");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
549 void this.loadBlogs();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
550 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
551 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
552 this.show("tools");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
553 void this.openToolWorkspace();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
554 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
555 return;
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 const blogEntry = event.target.closest("[data-blog-entry]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
558 if (blogEntry) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
559 void this.loadBlogDetail(blogEntry.dataset.blogUrl);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
560 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
561 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
562 const latestBlog = event.target.closest("[data-latest-blog]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
563 if (latestBlog && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
564 event.preventDefault();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
565 this.querySelector("zen-dialog").open();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
566 void this.loadBlogDetail(latestBlog.dataset.blogUrl);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
567 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
568 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
569 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
570 if (blogArchive) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
571 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
572 this.querySelector("zen-dialog").open();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
573 this.show("blog");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
574 void this.loadBlogs();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
575 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
576 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
577 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
578 if (resumeModal) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
579 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
580 this.querySelector("zen-dialog").open();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
581 this.show("resume");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
582 void this.loadResume();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
583 return;
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 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
586 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
587 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
588 this.querySelector("zen-dialog").open();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
589 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
590 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
591 };
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
592 this.addEventListener("click", this._onClick);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
593 this._dialog.addEventListener("close", this._onDialogClose);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
594 this.show(this.dataset.selection || "resume");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
595 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
596
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
597 disconnectedCallback() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
598 this.removeEventListener("click", this._onClick);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
599 this._dialog?.removeEventListener("close", this._onDialogClose);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
600 this.cleanupTool();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
601 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
602
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
603 show(selection) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
604 this.cleanupTool();
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
605 const preview = PREVIEWS[selection] || PREVIEWS.resume;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
606 this.dataset.selection = selection;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
607 this.querySelector("[data-preview-title]").textContent = preview.title;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
608 this.querySelector("[data-preview-copy]").textContent = preview.copy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
609 this.querySelector("[data-dialog-title]").textContent = preview.title;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
610 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
611 this.renderShowcase(preview.works);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
612 const link = this.querySelector("[data-preview-link]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
613 link.href = preview.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
614 link.setAttribute("aria-label", `Open ${preview.title}`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
615 const resumeDossier = this.querySelector("[data-resume-dossier]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
616 const resumeDownload = this.querySelector("[data-resume-download]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
617 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
618 const toolBrowser = this.querySelector("[data-tool-browser]");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
619 const isResume = selection === "resume";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
620 const isBlog = selection === "blog";
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
621 const isTools = selection === "tools";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
622 this._dialog.dataset.detailMode = isTools ? "tools" : "content";
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
623 resumeDossier.hidden = !isResume;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
624 resumeDownload.hidden = !isResume;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
625 this.querySelector("[data-dialog-actions]").hidden = !isResume;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
626 blogBrowser.hidden = !isBlog;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
627 toolBrowser.hidden = !isTools;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
628 this.querySelector("[data-dialog-copy]").hidden =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
629 isResume || isBlog || isTools;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
630 if (!isBlog) this._blogRequest = (this._blogRequest || 0) + 1;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
631 if (isBlog) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
632 const status = this.querySelector("[data-blog-status]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
633 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
634 status.textContent = "Select a blog to inspect.";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
635 this.querySelector("[data-blog-content]").replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
636 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
637 button.setAttribute("aria-pressed", "false");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
638 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
639 void this.loadBlogs();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
640 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
641 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
642 if (isTools) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
643 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
644 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
645 status.textContent = "Select a tool to inspect.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
646 this.querySelector("[data-tool-content]").replaceChildren();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
647 void this.loadTools();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
648 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
649 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
650
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
651 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
652 const showcase = this.querySelector("[data-work-showcase]");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
653 showcase.replaceChildren(...works.map(work => {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
654 const item = document.createElement("li");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
655 const link = document.createElement("a");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
656 const label = document.createElement("span");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
657 const detail = document.createElement("small");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
658 link.href = work.url;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
659 if (latestBlogs) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
660 if (work.modal === "blog") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
661 link.dataset.blogArchive = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
662 } else {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
663 link.dataset.latestBlog = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
664 link.dataset.blogUrl = work.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
665 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
666 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
667 if (work.modal === "resume") link.dataset.resumeModal = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
668 if (latestKind === "tool") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
669 link.dataset.latestTool = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
670 link.dataset.toolUrl = work.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
671 const icon = document.createElement("zen-icon");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
672 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
673 link.prepend(icon);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
674 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
675 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
676 link.target = "_blank";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
677 link.rel = "noreferrer";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
678 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
679 label.textContent = work.label;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
680 detail.textContent = work.detail;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
681 link.append(label, detail);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
682 item.append(link);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
683 return item;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
684 }));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
685 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
686
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
687 async loadResume() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
688 if (this._resumeLoaded || this._resumeLoading) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
689 this._resumeLoading = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
690 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
691 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
692 try {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
693 const response = await fetch("/resume", {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
694 headers: { Accept: "text/html" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
695 });
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
696 if (!response.ok) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
697 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
698 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
699 const documentCopy = new DOMParser().parseFromString(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
700 await response.text(),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
701 "text/html",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
702 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
703 const resume = documentCopy.querySelector("main");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
704 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
705 sanitizeFetchedMain(resume);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
706 content.replaceChildren(...resume.childNodes);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
707 status.hidden = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
708 this._resumeLoaded = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
709 } catch (error) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
710 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
711 } finally {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
712 this._resumeLoading = false;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
713 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
714 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
715
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
716 async loadBlogs() {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
717 if (this._blogs) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
718 if (this.dataset.selection === "blog") {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
719 this.renderShowcase([
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
720 ...this._blogs.slice(0, 4),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
721 {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
722 detail: "Full archive",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
723 label: "All blogs",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
724 modal: "blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
725 url: "/blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
726 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
727 ], true);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
728 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
729 return true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
730 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
731 if (this._blogsLoading) return this._blogsLoading;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
732 const list = this.querySelector("[data-blog-list]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
733 this._blogsLoading = (async () => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
734 const response = await fetch("/blog", {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
735 headers: { Accept: "text/html" },
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
736 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
737 if (!response.ok) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
738 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
739 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
740 const documentCopy = new DOMParser().parseFromString(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
741 await response.text(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
742 "text/html",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
743 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
744 this._blogs = [...documentCopy.querySelectorAll(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
745 'main li a[href^="/blog/"]',
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
746 )].map(anchor => ({
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
747 date: anchor.closest("li")?.querySelector("span")?.textContent.trim() ||
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
748 "Archive",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
749 detail: anchor.closest("li")?.querySelector("span")?.textContent.trim() ||
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
750 "Archive",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
751 label: anchor.textContent.trim(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
752 url: anchor.getAttribute("href"),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
753 }));
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
754 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
755 this.renderBlogList();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
756 if (this.dataset.selection === "blog") {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
757 this.renderShowcase([
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
758 ...this._blogs.slice(0, 4),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
759 {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
760 detail: "Full archive",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
761 label: "All blogs",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
762 modal: "blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
763 url: "/blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
764 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
765 ], true);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
766 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
767 return true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
768 })().catch(error => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
769 list.replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
770 const item = document.createElement("li");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
771 item.textContent = `Unable to load blogs: ${error.message}`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
772 list.append(item);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
773 return false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
774 }).finally(() => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
775 this._blogsLoading = null;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
776 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
777 return this._blogsLoading;
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
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
780 renderBlogList() {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
781 const list = this.querySelector("[data-blog-list]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
782 list.replaceChildren(...this._blogs.map(blog => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
783 const item = document.createElement("li");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
784 const owner = document.createElement("zen-button");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
785 const button = document.createElement("button");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
786 const title = document.createElement("span");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
787 const date = document.createElement("small");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
788 owner.setAttribute("appearance", "plain");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
789 owner.setAttribute("size", "sm");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
790 button.type = "button";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
791 button.dataset.blogEntry = "";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
792 button.dataset.blogUrl = blog.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
793 title.textContent = blog.label;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
794 date.textContent = blog.date;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
795 button.append(title, date);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
796 owner.append(button);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
797 item.append(owner);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
798 return item;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
799 }));
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
800 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
801
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
802 async loadBlogDetail(url) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
803 if (!await this.loadBlogs()) return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
804 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
805 const status = this.querySelector("[data-blog-status]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
806 const content = this.querySelector("[data-blog-content]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
807 if (!blog) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
808 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
809 status.textContent = "Unable to load blog: unknown entry.";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
810 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
811 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
812 const request = (this._blogRequest || 0) + 1;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
813 this._blogRequest = request;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
814 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
815 status.textContent = `Loading ${blog.label}...`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
816 content.replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
817 this.querySelector("[data-dialog-title]").textContent = blog.label;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
818 const fullPage = this.querySelector("[data-preview-link]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
819 fullPage.href = blog.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
820 fullPage.setAttribute("aria-label", `Open ${blog.label}`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
821 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
822 button.setAttribute(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
823 "aria-pressed",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
824 String(button.dataset.blogUrl === blog.url),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
825 );
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 try {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
828 const response = await fetch(blog.url, {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
829 headers: { Accept: "text/html" },
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
830 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
831 if (!response.ok) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
832 throw new Error(`Blog request failed (${response.status})`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
833 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
834 const documentCopy = new DOMParser().parseFromString(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
835 await response.text(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
836 "text/html",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
837 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
838 const article = documentCopy.querySelector("main");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
839 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
840 sanitizeFetchedMain(article);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
841 if (request !== this._blogRequest || this.dataset.selection !== "blog") {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
842 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
843 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
844 content.replaceChildren(...article.childNodes);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
845 status.hidden = true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
846 } catch (error) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
847 if (request !== this._blogRequest) return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
848 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
849 status.textContent = `Unable to load blog: ${error.message}`;
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 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
852
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
853 async loadTools() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
854 if (this._tools) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
855 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
856 this.renderShowcase(this._tools, false, "tool");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
857 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
858 return true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
859 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
860 if (this._toolsLoading) return this._toolsLoading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
861 this._toolsLoading = (async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
862 const response = await fetch("/tools", {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
863 headers: { Accept: "text/html" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
864 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
865 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
866 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
867 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
868 const documentCopy = new DOMParser().parseFromString(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
869 await response.text(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
870 "text/html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
871 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
872 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
873 anchor => ({
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
874 detail: anchor.getAttribute("href").startsWith("/notes")
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
875 ? "Writing"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
876 : "Utility",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
877 label: anchor.textContent.trim(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
878 url: anchor.getAttribute("href"),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
879 }),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
880 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
881 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
882 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
883 this.renderShowcase(this._tools, false, "tool");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
884 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
885 return true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
886 })().catch(error => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
887 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
888 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
889 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
890 return false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
891 }).finally(() => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
892 this._toolsLoading = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
893 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
894 return this._toolsLoading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
895 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
896
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
897 async openToolWorkspace() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
898 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
899 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
900 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
901
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
902 async loadToolDetail(url) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
903 const request = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
904 this._toolRequest = request;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
905 if (!await this.loadTools()) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
906 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
907 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
908 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
909 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
910 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
911 const content = this.querySelector("[data-tool-content]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
912 if (!tool) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
913 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
914 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
915 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
916 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
917 this._activeToolUrl = tool.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
918 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
919 status.textContent = `Loading ${tool.label}...`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
920 this.cleanupTool();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
921 content.replaceChildren();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
922 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
923 const fullPage = this.querySelector("[data-preview-link]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
924 fullPage.href = tool.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
925 fullPage.setAttribute("aria-label", `Open ${tool.label}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
926 if (tool.url === "/tools/markdown_to_html") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
927 await this.renderMarkdownTool(content, status, request);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
928 return;
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 if (tool.url === "/tools/file_converter") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
931 this.renderFileConverterTool(content, status);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
932 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
933 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
934 if (tool.url === "/tools/hls_player") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
935 await this.renderHlsTool(content, status, request);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
936 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
937 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
938 if (tool.url === "/tools/latex_editor") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
939 this.renderLatexTool(content, status);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
940 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
941 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
942 if (tool.url === "/notes") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
943 const heading = document.createElement("h1");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
944 const description = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
945 const privacy = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
946 heading.textContent = "Personal Notes";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
947 description.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
948 "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
949 privacy.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
950 "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
951 content.replaceChildren(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
952 this.createReadonlyToolNote(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
953 heading,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
954 description,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
955 privacy,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
956 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
957 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
958 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
959 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
960 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
961 const response = await fetch(tool.url, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
962 headers: { Accept: "text/html" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
963 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
964 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
965 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
966 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
967 const documentCopy = new DOMParser().parseFromString(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
968 await response.text(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
969 "text/html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
970 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
971 const detail = documentCopy.querySelector("main, .container") ||
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
972 documentCopy.body;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
973 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
974 sanitizeToolDetail(detail);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
975 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
976 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
977 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
978 content.replaceChildren(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
979 this.createReadonlyToolNote(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
980 ...detail.childNodes,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
981 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
982 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
983 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
984 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
985 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
986 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
987 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
988 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
989
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
990 async renderMarkdownTool(content, status, request) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
991 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
992 <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
993 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
994 <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
995 <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
996 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
997 <label for="jrpgMarkdownSource">Markdown</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
998 <textarea id="jrpgMarkdownSource" data-markdown-source></textarea>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
999 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1000 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1001 <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
1002 <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
1003 <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
1004 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1005 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1006 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1007 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1008 const source = content.querySelector("[data-markdown-source]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1009 const output = content.querySelector("[data-markdown-output]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1010 source.value = [
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1011 "# JRPG Markdown",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1012 "",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1013 "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
1014 "",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1015 "- Bazel-built WASM",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1016 "- Sanitized HTML output",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1017 ].join("\n");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1018 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1019 status.textContent = "Loading the Markdown converter...";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1020 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1021 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
1022 .then(module => module.default());
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1023 const module = await this._markdownModule;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1024 if (request !== this._toolRequest || !source.isConnected) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1025 const convertMarkdown = module.cwrap(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1026 "markdown_to_html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1027 "number",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1028 ["string"],
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1029 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1030 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
1031 const convert = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1032 const pointer = convertMarkdown(source.value);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1033 const html = module.UTF8ToString(pointer);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1034 freeMarkdown(pointer);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1035 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
1036 sanitizeFetchedMain(documentCopy.body);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1037 output.replaceChildren(...documentCopy.body.childNodes);
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 source.addEventListener("input", convert);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1040 convert();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1041 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1042 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1043 this._markdownModule = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1044 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1045 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1046 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
1047 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1048 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1049
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1050 renderFileConverterTool(content, status) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1051 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1052 <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
1053 <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
1054 Choose a file in either panel.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1055 </p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1056 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1057 <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
1058 <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
1059 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1060 <label for="jrpgImageInput">Image file</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1061 <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
1062 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1063 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1064 <button type="button" data-convert-kind="image">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1065 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
1066 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1067 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1068 <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
1069 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1070 <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
1071 <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
1072 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1073 <label for="jrpgVideoInput">Video file</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1074 <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
1075 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1076 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1077 <button type="button" data-convert-kind="video">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1078 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
1079 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1080 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1081 <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
1082 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1083 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1084 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1085 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1086 const toolStatus = content.querySelector("[data-converter-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1087 let disposed = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1088 const downloads = new Set();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1089 const discardDownload = url => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1090 if (!url) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1091 downloads.delete(url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1092 void fetch(url, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1093 method: "DELETE",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1094 keepalive: true,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1095 }).then(response => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1096 if (!response.ok && response.status !== 404) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1097 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
1098 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1099 }).catch(error => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1100 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
1101 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1102 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1103 const convert = async kind => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1104 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
1105 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
1106 const download = content.querySelector(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1107 `[data-converter-download="${kind}"]`,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1108 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1109 const file = input.files?.[0];
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1110 if (!file) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1111 toolStatus.textContent = `Choose a ${kind} file first.`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1112 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1113 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1114 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1115 button.disabled = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1116 discardDownload(download.dataset.cleanupUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1117 delete download.dataset.cleanupUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1118 download.removeAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1119 download.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1120 toolStatus.textContent = `Converting ${file.name}...`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1121 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1122 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1123 const endpoint = kind === "image"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1124 ? "/api/convert/image-to-webp"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1125 : "/api/convert/video-to-mp4";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1126 const response = await fetch(endpoint, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1127 method: "POST",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1128 body: file,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1129 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
1130 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1131 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1132 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
1133 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1134 const result = await response.json();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1135 if (disposed || !download.isConnected) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1136 discardDownload(result.download_url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1137 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1138 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1139 download.href = result.download_url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1140 download.dataset.cleanupUrl = result.download_url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1141 downloads.add(result.download_url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1142 download.download = file.name.replace(/\.[^/.]+$/, "") +
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1143 (kind === "image" ? ".webp" : ".mp4");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1144 download.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1145 toolStatus.textContent = "Conversion complete.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1146 toolStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1147 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1148 if (!disposed) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1149 toolStatus.textContent = `Conversion failed: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1150 toolStatus.dataset.state = "error";
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 } finally {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1153 if (button.isConnected) button.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1154 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1155 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1156 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
1157 button.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1158 void convert(button.dataset.convertKind);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1159 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1160 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1161 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
1162 download.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1163 downloads.delete(download.dataset.cleanupUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1164 delete download.dataset.cleanupUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1165 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1166 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1167 this._toolCleanup = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1168 disposed = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1169 for (const download of [...downloads]) discardDownload(download);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1170 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1171 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1172 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1173
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1174 async renderHlsTool(content, status, request) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1175 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1176 <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
1177 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1178 <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
1179 <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
1180 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1181 <label for="jrpgHlsUrl">Playlist URL</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1182 <input
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1183 id="jrpgHlsUrl"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1184 data-hls-url
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1185 type="url"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1186 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
1187 >
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1188 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1189 <div class="jrpg-tool-actions">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1190 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1191 <button type="button" data-hls-load>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1192 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
1193 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1194 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1195 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1196 <button type="button" data-hls-sample>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1197 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
1198 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1199 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1200 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1201 <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
1202 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1203 <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
1204 <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
1205 <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
1206 <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
1207 <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
1208 <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
1209 <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
1210 <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
1211 </dl>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1212 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1213 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1214 </section>
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 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1217 await loadScript("/public/hls.min.js");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1218 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
1219 if (request !== this._toolRequest || !content.isConnected) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1220 const video = content.querySelector("[data-hls-video]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1221 const playerStatus = content.querySelector("[data-hls-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1222 const details = content.querySelector("[data-hls-details]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1223 const url = content.querySelector("[data-hls-url]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1224 const player = new window.HlsPlayerModule.HlsPlayer(video, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1225 statusElement: playerStatus,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1226 detailsElement: details,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1227 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1228 const load = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1229 void player.load(url.value.trim()).catch(() => {});
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1230 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1231 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
1232 content.querySelector("[data-hls-sample]").addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1233 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
1234 load();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1235 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1236 playerStatus.textContent = "Ready to load a playlist.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1237 playerStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1238 this._toolCleanup = () => player.destroy();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1239 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1240 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1241 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1242 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1243 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
1244 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1245 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1246
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1247 renderLatexTool(content, status) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1248 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1249 <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
1250 <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
1251 Edit the source, then compile the PDF.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1252 </p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1253 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1254 <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
1255 <div class="jrpg-tool-pane-heading">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1256 <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
1257 <span data-latex-size></span>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1258 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1259 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1260 <label for="jrpgLatexSource">document.tex</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1261 <textarea id="jrpgLatexSource" data-latex-source></textarea>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1262 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1263 <div class="jrpg-tool-actions">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1264 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1265 <button type="button" data-latex-compile>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1266 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
1267 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1268 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1269 <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
1270 <a download="document.pdf">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1271 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
1272 </a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1273 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1274 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1275 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1276 <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
1277 <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
1278 <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
1279 <pre data-latex-diagnostics hidden></pre>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1280 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1281 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1282 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1283 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1284 const source = content.querySelector("[data-latex-source]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1285 const compileButton = content.querySelector("[data-latex-compile]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1286 const toolStatus = content.querySelector("[data-latex-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1287 const size = content.querySelector("[data-latex-size]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1288 const preview = content.querySelector("[data-latex-preview]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1289 const diagnostics = content.querySelector("[data-latex-diagnostics]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1290 const downloadOwner = content.querySelector("[data-latex-download]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1291 const download = downloadOwner.querySelector("a");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1292 source.value = String.raw`\documentclass[11pt]{article}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1293 \usepackage[margin=1in]{geometry}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1294 \title{JRPG LaTeX}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1295 \author{MrJuneJune}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1296 \begin{document}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1297 \maketitle
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1298 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
1299 \end{document}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1300 let controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1301 let pdfUrl = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1302 let compileGeneration = 0;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1303 const clearPdf = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1304 if (pdfUrl) URL.revokeObjectURL(pdfUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1305 pdfUrl = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1306 preview.src = "about:blank";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1307 preview.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1308 diagnostics.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1309 download.removeAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1310 downloadOwner.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1311 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1312 const updateSize = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1313 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
1314 size.textContent = `${bytes.toLocaleString()} / 65,536 bytes`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1315 return bytes;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1316 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1317 const compile = async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1318 const generation = ++compileGeneration;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1319 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1320 const bytes = updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1321 if (!source.value.trim() || bytes > 64 * 1024) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1322 toolStatus.textContent = bytes > 64 * 1024
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1323 ? "Source exceeds 64 KiB."
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1324 : "Write LaTeX before compiling.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1325 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1326 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1327 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1328 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1329 const requestController = new AbortController();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1330 controller = requestController;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1331 compileButton.disabled = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1332 toolStatus.textContent = "Compiling on the server...";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1333 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1334 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1335 const response = await fetch("/api/latex/render", {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1336 method: "POST",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1337 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
1338 body: source.value,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1339 cache: "no-store",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1340 signal: requestController.signal,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1341 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1342 if (generation !== compileGeneration) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1343 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1344 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
1345 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1346 const blob = await response.blob();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1347 if (generation !== compileGeneration) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1348 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
1349 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
1350 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1351 if (pdfUrl) URL.revokeObjectURL(pdfUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1352 pdfUrl = URL.createObjectURL(blob);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1353 preview.src = pdfUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1354 preview.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1355 diagnostics.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1356 download.href = pdfUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1357 downloadOwner.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1358 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
1359 toolStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1360 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1361 if (error.name !== "AbortError" && generation === compileGeneration) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1362 diagnostics.textContent = error.message;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1363 diagnostics.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1364 preview.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1365 toolStatus.textContent = "Compilation failed.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1366 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1367 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1368 } finally {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1369 if (controller === requestController) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1370 controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1371 compileButton.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1372 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1373 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1374 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1375 source.addEventListener("input", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1376 compileGeneration++;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1377 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1378 controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1379 compileButton.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1380 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1381 updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1382 toolStatus.textContent = "Changes ready to compile.";
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 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1385 compileButton.addEventListener("click", () => void compile());
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1386 updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1387 this._toolCleanup = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1388 compileGeneration++;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1389 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1390 clearPdf();
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 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1393 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1394
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1395 cleanupTool() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1396 this._toolCleanup?.();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1397 this._toolCleanup = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1398 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1399
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1400 createReadonlyToolNote() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1401 const introduction = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1402 introduction.className = "jrpg-tool-readonly-note";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1403 introduction.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1404 "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
1405 return introduction;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1406 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1407 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1408
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1409 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1410 MjjConversationArchive — archive panel within the scene
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1411 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1412
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1413 class MjjConversationArchive extends HTMLElement {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1414 connectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1415 this._list = this.querySelector("[data-archive-list]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1416 this._status = this.querySelector("[data-archive-status]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1417 this._loadMoreOwner = this.querySelector("[data-archive-load-more-owner]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1418 this._loadMoreButton = this.querySelector("[data-archive-load-more]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1419 this._newButton = this.querySelector("[data-archive-new]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1420 this._closeButton = this.querySelector("[data-archive-close]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1421 this._deleteDialog = this.querySelector("[data-archive-delete-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1422 this._deleteNameEl = this.querySelector("[data-archive-delete-name]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1423 this._deleteCancelButton = this.querySelector("[data-archive-delete-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1424 this._deleteConfirmButton = this.querySelector("[data-archive-delete-confirm]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1425 this._renameDialog = this.querySelector("[data-archive-rename-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1426 this._renameInput = this.querySelector("[data-archive-rename-input]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1427 this._renameForm = this.querySelector("[data-archive-rename-form]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1428 this._renameCancelButton = this.querySelector("[data-archive-rename-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1429 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1430 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1431 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1432 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1433
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1434 this._closeButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1435 this.dispatchEvent(new CustomEvent("mjj-archive-toggle", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1436 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1437
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1438 this._newButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1439 this.dispatchEvent(new CustomEvent("mjj-archive-new", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1440 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1441
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1442 this._loadMoreButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1443 this.dispatchEvent(new CustomEvent("mjj-archive-load-more", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1444 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1445
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1446 this._deleteCancelButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1447 this._deleteDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1448 const t = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1449 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1450 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1451 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1452 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1453
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1454 this._deleteConfirmButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1455 const id = this._pendingDeleteId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1456 const trigger = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1457 this._deleteDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1458 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1459 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1460 if (id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1461 this.dispatchEvent(new CustomEvent("mjj-archive-delete", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1462 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1463 detail: { id, trigger },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1464 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1465 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1466 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1467
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1468 this._deleteDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1469 const t = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1470 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1471 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1472 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1473 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1474
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1475 this._renameCancelButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1476 this._renameDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1477 const t = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1478 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1479 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1480 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1481 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1482
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1483 this._renameForm?.addEventListener("submit", event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1484 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1485 const id = this._pendingRenameId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1486 const trigger = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1487 const newTitle = this._renameInput?.value.trim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1488 if (!id || !newTitle) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1489 this._renameDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1490 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1491 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1492 this.dispatchEvent(new CustomEvent("mjj-archive-rename", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1493 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1494 detail: { id, newTitle, trigger },
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 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1497
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1498 this._renameDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1499 const t = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1500 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1501 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1502 t?.focus();
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
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1505 this._onListClick = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1506 const openButton = event.target.closest("[data-conv-open]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1507 if (openButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1508 const id = openButton.closest("[data-conv-item]")?._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1509 if (id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1510 this.dispatchEvent(new CustomEvent("mjj-archive-open", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1511 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1512 detail: { id },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1513 }));
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 return;
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 const renameButton = event.target.closest("[data-conv-rename]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1518 if (renameButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1519 const item = renameButton.closest("[data-conv-item]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1520 if (item?._convId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1521 this._pendingRenameId = item._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1522 this._pendingRenameTrigger = renameButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1523 if (this._renameInput) this._renameInput.value = item._convTitle || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1524 this._renameDialog?.showModal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1525 requestAnimationFrame(() => { this._renameInput?.select(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1526 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1527 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1528 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1529 const deleteButton = event.target.closest("[data-conv-delete]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1530 if (deleteButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1531 const item = deleteButton.closest("[data-conv-item]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1532 if (item?._convId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1533 this._pendingDeleteId = item._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1534 this._pendingDeleteTrigger = deleteButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1535 if (this._deleteNameEl) this._deleteNameEl.textContent = item._convTitle || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1536 this._deleteDialog?.showModal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1537 requestAnimationFrame(() => { this._deleteConfirmButton?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1538 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1539 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1540 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1541 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1542 this._list?.addEventListener("click", this._onListClick);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1543
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1544 this._onListKeydown = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1545 if (!["ArrowUp", "ArrowDown"].includes(event.key)) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1546 const items = [...(this._list?.querySelectorAll("[data-conv-open]") || [])];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1547 const index = items.indexOf(document.activeElement);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1548 if (index < 0) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1549 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1550 const next = event.key === "ArrowDown"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1551 ? items[Math.min(index + 1, items.length - 1)]
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1552 : items[Math.max(index - 1, 0)];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1553 next?.focus();
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._list?.addEventListener("keydown", this._onListKeydown);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1556 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1557
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1558 disconnectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1559 this._list?.removeEventListener("click", this._onListClick);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1560 this._list?.removeEventListener("keydown", this._onListKeydown);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1561 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1562
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1563 setConversations(conversations, cursor, append = false) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1564 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1565 if (!append) this._list.replaceChildren();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1566 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
1567 if (this._loadMoreOwner) this._loadMoreOwner.hidden = !cursor;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1568 if (!conversations.length && !append) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1569 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1570 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1571 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1572 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1573 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1574
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1575 setCurrentId(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1576 for (const button of (this._list?.querySelectorAll("[data-conv-open]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1577 const isCurrent = id && button.closest("[data-conv-item]")?._convId === id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1578 button.setAttribute("aria-current", String(Boolean(isCurrent)));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1579 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1580 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1581
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1582 addConversation(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1583 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1584 this._list.prepend(this._createItem(conv));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1585 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1586 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1587
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1588 updateConversation(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1589 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1590 for (const item of this._list.querySelectorAll("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1591 if (item._convId === conv.id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1592 const titleEl = item.querySelector("[data-conv-title]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1593 const metaEl = item.querySelector("[data-conv-meta]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1594 if (titleEl) titleEl.textContent = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1595 if (metaEl) metaEl.textContent = this._formatMeta(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1596 item._convTitle = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1597 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1598 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1599 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1600 this.addConversation(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1601 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1602
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1603 removeConversation(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1604 for (const item of (this._list?.querySelectorAll("[data-conv-item]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1605 if (item._convId === id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1606 item.remove();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1607 break;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1608 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1609 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1610 if (!this._list?.querySelector("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1611 this._showStatus("No conversations yet. Start a new one!");
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 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1614
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1615 setLoading(loading) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1616 if (loading) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1617 this._showStatus("Loading conversations...");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1618 } else if (!this._list?.querySelector("[data-conv-item]")) {
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 setError(message) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1626 this._showStatus(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1627 if (this._status) this._status.dataset.state = "error";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1628 }
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 setLoadMoreDisabled(disabled) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1631 if (this._loadMoreButton) this._loadMoreButton.disabled = disabled;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1632 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1633
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1634 /* Disable/enable destructive archive controls while a stream is active. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1635 setStreamActive(active) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1636 const controls = this._list?.querySelectorAll(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1637 "[data-conv-delete], [data-conv-rename]",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1638 ) || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1639 for (const el of controls) el.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1640 if (this._newButton) this._newButton.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1641 if (this._deleteConfirmButton) this._deleteConfirmButton.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1642 this.dataset.streamActive = active ? "true" : "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1643 if (!active) delete this.dataset.streamActive;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1644 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1645
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1646 /* 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
1647 adjacentItemOrNewButton(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1648 const items = [...(this._list?.querySelectorAll("[data-conv-item]") || [])];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1649 const idx = items.findIndex(el => el._convId === id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1650 if (idx < 0) return this._newButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1651 const sibling = items[idx + 1] || items[idx - 1];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1652 return sibling?.querySelector("[data-conv-open]") || this._newButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1653 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1654
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1655 showLegacyClaim(statusMessage = null, showButton = true) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1656 if (!this._status) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1657 this._status.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1658 delete this._status.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1659 this._status.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1660 const msg = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1661 msg.textContent = statusMessage ?? "You have a prior conversation. ";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1662 this._status.append(msg);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1663 if (showButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1664 const btn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1665 btn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1666 btn.textContent = "Claim it";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1667 btn.dataset.archiveClaim = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1668 btn.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1669 this.dispatchEvent(new CustomEvent("mjj-archive-claim", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1670 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1671 this._status.append(btn);
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 _showStatus(text) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1676 if (this._status) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1677 this._status.textContent = text;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1678 this._status.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1679 delete this._status.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1680 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1681 }
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 _hideStatus() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1684 if (this._status) this._status.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1685 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1686
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1687 _createItem(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1688 const item = document.createElement("li");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1689 item.dataset.convItem = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1690 item._convId = conv.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1691 item._convTitle = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1692
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1693 const openButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1694 openButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1695 openButton.dataset.convOpen = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1696 openButton.setAttribute("aria-current", "false");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1697
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1698 const titleEl = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1699 titleEl.dataset.convTitle = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1700 titleEl.textContent = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1701
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1702 const metaEl = document.createElement("small");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1703 metaEl.dataset.convMeta = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1704 metaEl.textContent = this._formatMeta(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1705
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1706 openButton.append(titleEl, metaEl);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1707
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1708 const actions = document.createElement("div");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1709 actions.className = "jrpg-archive-item-actions";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1710
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1711 const renameButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1712 renameButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1713 renameButton.dataset.convRename = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1714 renameButton.setAttribute("aria-label", `Rename "${conv.title}"`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1715 renameButton.textContent = "Rename";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1716
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1717 const deleteButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1718 deleteButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1719 deleteButton.dataset.convDelete = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1720 deleteButton.setAttribute("aria-label", `Delete "${conv.title}"`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1721 deleteButton.textContent = "Delete";
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 actions.append(renameButton, deleteButton);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1724 item.append(openButton, actions);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1725 return item;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1726 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1727
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1728 _formatMeta(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1729 const count = conv.turn_count ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1730 const turns = count === 1 ? "1 turn" : `${count} turns`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1731 if (conv.last_message_preview) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1732 return `${turns} · ${conv.last_message_preview.slice(0, 28)}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1733 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1734 return turns;
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
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1738 class MjjJrpgShell extends HTMLElement {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1739 connectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1740 this._mobileMenuQuery = window.matchMedia(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1741 "(max-width: 52rem) and (orientation: portrait)",
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 this._syncMobileMenu = expanded => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1744 const button = this.querySelector("[data-mobile-menu-toggle]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1745 const menu = this.querySelector("mjj-jrpg-menu");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1746 if (!button || !menu) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1747 if (!this._mobileMenuQuery.matches) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1748 button.setAttribute("aria-expanded", "true");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1749 menu.removeAttribute("data-mobile-menu-collapsed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1750 return;
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 button.setAttribute("aria-expanded", String(expanded));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1753 menu.toggleAttribute("data-mobile-menu-collapsed", !expanded);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1754 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1755 this._onMenuToggle = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1756 const button = event.target.closest("[data-mobile-menu-toggle]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1757 if (!button || !this.contains(button)) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1758 const expanded = button.getAttribute("aria-expanded") === "true";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1759 this._syncMobileMenu(!expanded);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1760 if (!expanded) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1761 const firstItem = this.querySelector(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1762 "mjj-jrpg-menu button[data-preview]",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1763 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1764 if (firstItem) firstItem.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1765 }
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 this._onMobileMenuMediaChange = () => this._syncMobileMenu(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1768 this.addEventListener("click", this._onMenuToggle);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1769 this._mobileMenuQuery.addEventListener(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1770 "change",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1771 this._onMobileMenuMediaChange,
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 this._syncMobileMenu(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1774 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1775
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1776 disconnectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1777 this.removeEventListener("click", this._onMenuToggle);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1778 this._mobileMenuQuery?.removeEventListener(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1779 "change",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1780 this._onMobileMenuMediaChange,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1781 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1782 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1783 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1784
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1785 for (const [name, constructor] of Object.entries({
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1786 "mjj-conversation-archive": MjjConversationArchive,
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1787 "mjj-jrpg-character": MjjJrpgCharacter,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1788 "mjj-jrpg-chat": MjjJrpgChat,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1789 "mjj-jrpg-composer": MjjJrpgComposer,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1790 "mjj-jrpg-menu": MjjJrpgMenu,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1791 "mjj-jrpg-preview": MjjJrpgPreview,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1792 "mjj-jrpg-shell": MjjJrpgShell,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1793 })) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1794 if (!customElements.get(name)) customElements.define(name, constructor);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1795 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1796
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1797 async function requestJson(url, options = {}) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1798 const response = await fetch(url, {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1799 ...options,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1800 credentials: "same-origin",
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1801 headers: {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1802 "Content-Type": "application/json",
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1803 ...options.headers,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1804 },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1805 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1806 if (!response.ok) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1807 let message = `Request failed (${response.status})`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1808 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1809 const body = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1810 message = body.error?.message || message;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1811 } catch {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1812 // Preserve the status-derived message.
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1813 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1814 throw new Error(message);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1815 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1816 return response.status === 204 ? null : response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1817 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1818
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1819 /* 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
1820 let _csrfToken = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1821 let _sessionData = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1822 let _sessionRefreshInFlight = null; /* single-flight guard */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1823
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1824 function _principalFingerprint(data) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1825 if (!data) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1826 if (data.kind === "user") return `user:${data.username || ""}:${data.role || ""}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1827 if (data.kind === "guest") return `guest:${data.csrfToken || ""}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1828 return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1829 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1830
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1831 async function _fetchSession() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1832 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1833 const response = await fetch("/api/auth/session", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1834 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1835 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1836 if (!response.ok) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1837 const data = await response.json().catch(() => null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1838 if (!data) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1839 _sessionData = data;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1840 return data.csrfToken || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1841 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1842 return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1843 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1844 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1845
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1846 /* Single-flight session refresh: dedupe concurrent callers. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1847 async function _refreshSessionOnce() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1848 if (_sessionRefreshInFlight) return _sessionRefreshInFlight;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1849 _sessionRefreshInFlight = (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1850 const prev = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1851 const token = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1852 const next = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1853 return { token, prev, next };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1854 })();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1855 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1856 return await _sessionRefreshInFlight;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1857 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1858 _sessionRefreshInFlight = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1859 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1860 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1861
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1862 /*
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1863 * 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
1864 * 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
1865 * 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
1866 * 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
1867 * triggers the session-changed callback.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1868 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1869 let _sessionRefreshCallback = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1870
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1871 async function mutatingFetch(url, options = {}) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1872 const buildHeaders = () => ({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1873 ...(options.headers || {}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1874 Origin: window.location.origin,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1875 ...(_csrfToken ? { "X-CSRF-Token": _csrfToken } : {}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1876 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1877
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1878 const response = await fetch(url, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1879 ...options,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1880 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1881 headers: buildHeaders(),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1882 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1883
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1884 const shouldRefresh = response.status === 401 ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1885 (response.status === 403 && await (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1886 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1887 const clone = response.clone();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1888 const body = await clone.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1889 return body?.error?.code === "csrf_invalid";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1890 } catch { return false; }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1891 })());
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1892
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1893 if (shouldRefresh) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1894 const prevFingerprint = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1895 const { token, next } = await _refreshSessionOnce();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1896 _csrfToken = token;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1897 if (next !== prevFingerprint) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1898 if (_sessionRefreshCallback) _sessionRefreshCallback();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1899 const err = Object.assign(new Error("Session changed"), { status: 401 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1900 throw err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1901 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1902 const retried = await fetch(url, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1903 ...options,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1904 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1905 headers: buildHeaders(),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1906 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1907 if (retried.status === 401 && _sessionRefreshCallback) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1908 _sessionRefreshCallback();
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 return retried;
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 return response;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1913 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1914
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1915 async function consumeSse(response, onEvent) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1916 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
1917 const reader = response.body.getReader();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1918 const decoder = new TextDecoder();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1919 let buffer = "";
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1920
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1921 const dispatch = block => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1922 let eventName = "message";
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1923 const data = [];
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1924 for (const line of block.split("\n")) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1925 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
1926 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
1927 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1928 if (!data.length) return;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1929 onEvent(eventName, JSON.parse(data.join("\n")));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1930 };
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1931
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1932 while (true) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1933 const { done, value } = await reader.read();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1934 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
1935 buffer = buffer.replaceAll("\r\n", "\n");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1936 let boundary;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1937 while ((boundary = buffer.indexOf("\n\n")) >= 0) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1938 const block = buffer.slice(0, boundary);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1939 buffer = buffer.slice(boundary + 2);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1940 if (block.trim()) dispatch(block);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1941 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1942 if (done) break;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1943 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1944 if (buffer.trim()) dispatch(buffer);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1945 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1946
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1947 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1948 Account slot rendering
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1949 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1950
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1951 function renderAccountSlot(slotEl, sessionData) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1952 if (!slotEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1953 slotEl.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1954 if (!sessionData || sessionData.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1955 const owner = document.createElement("zen-button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1956 const btn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1957 owner.setAttribute("appearance", "plain");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1958 owner.setAttribute("size", "xs");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1959 btn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1960 btn.dataset.loginOpen = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1961 btn.textContent = "LOGIN";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1962 owner.append(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1963 slotEl.setAttribute("aria-label", "Not signed in");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1964 slotEl.append(owner);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1965 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1966 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1967 const username = (sessionData.username || "").slice(0, 8).toUpperCase();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1968 if (sessionData.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1969 const link = document.createElement("a");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1970 link.href = "/account/password";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1971 link.textContent = username ? `${username}\u26A0` : "PASSWD";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1972 link.title = "Password change required";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1973 slotEl.setAttribute("aria-label", `${sessionData.username}: password change required`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1974 slotEl.append(link);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1975 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1976 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1977 const nameSpan = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1978 nameSpan.dataset.accountUser = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1979 nameSpan.textContent = username;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1980 slotEl.append(nameSpan);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1981 if (sessionData.role === "admin") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1982 const adminLink = document.createElement("a");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1983 adminLink.href = "/admin/users";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1984 adminLink.textContent = "\u00A0ADM";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1985 slotEl.append(adminLink);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1986 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1987 const logoutBtn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1988 logoutBtn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1989 logoutBtn.textContent = "\u00A0LGOUT";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1990 logoutBtn.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1991 logoutBtn.disabled = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1992 mutatingFetch("/api/auth/logout", { method: "POST" }).then(response => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1993 if (!response.ok) throw new Error(`Logout failed (${response.status})`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1994 window.dispatchEvent(new CustomEvent("mjj-logout"));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1995 }).catch(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1996 logoutBtn.disabled = false;
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 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1999 slotEl.append(logoutBtn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2000 slotEl.setAttribute("aria-label", `Signed in as ${sessionData.username}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2001 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2002
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2003 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2004 Quota display helpers
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2005 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2006
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2007 function formatQuotaText(quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2008 if (!quota) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2009 const turns = quota.turnsRemaining ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2010 const tokens = quota.outputTokensRemaining ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2011 if (turns <= 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2012 const resetAt = quota.resetsAt ? new Date(quota.resetsAt * 1000) : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2013 const resetStr = resetAt
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2014 ? ` Resets ${resetAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2015 : "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2016 return { text: `Quota exhausted.${resetStr}`, state: "exhausted" };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2017 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2018 if (turns <= 2) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2019 return {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2020 text: `${turns} turn${turns === 1 ? "" : "s"} remaining (${tokens.toLocaleString()} tokens)`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2021 state: "warning",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2022 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2023 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2024 return {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2025 text: `${turns} turns remaining (${tokens.toLocaleString()} tokens)`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2026 state: "ok",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2027 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2028 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2029
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2030 function updateQuotaDisplay(quotaEl, quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2031 if (!quotaEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2032 if (!quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2033 quotaEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2034 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2035 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2036 const result = formatQuotaText(quota);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2037 /* 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
2038 if (!result || result.state === "ok") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2039 quotaEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2040 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2041 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2042 quotaEl.textContent = result.text;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2043 if (result.state === "exhausted") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2044 quotaEl.dataset.state = "exhausted";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2045 } else if (result.state === "warning") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2046 quotaEl.dataset.state = "warning";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2047 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2048 delete quotaEl.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2049 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2050 quotaEl.hidden = false;
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 Conversation archive 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 async function fetchConversationList(cursor) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2058 const url = cursor
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2059 ? `/api/conversations?limit=${ARCHIVE_PAGE_SIZE}&cursor=${encodeURIComponent(cursor)}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2060 : `/api/conversations?limit=${ARCHIVE_PAGE_SIZE}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2061 const response = await fetch(url, { credentials: "same-origin" });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2062 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2063 let message = `Failed to load conversations (${response.status})`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2064 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2065 const body = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2066 message = body.error?.message || message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2067 } catch { /* keep status message */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2068 throw new Error(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2069 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2070 return response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2071 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2072
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2073 async function initializeJrpgPage() {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2074 const colorProbe = document.createElement("span");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2075 colorProbe.style.background = "var(--zenbu-sys-color-surface-page)";
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2076 document.body.append(colorProbe);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2077 const themeColor = getComputedStyle(colorProbe).backgroundColor;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2078 colorProbe.remove();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2079 for (const meta of document.querySelectorAll('meta[name="theme-color"]')) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2080 meta.setAttribute("content", themeColor);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2081 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2082
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2083 /* Bootstrap session to obtain CSRF token and session data */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2084 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2085
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2086 const shell = document.querySelector("mjj-jrpg-shell");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2087 const character = shell?.querySelector("mjj-jrpg-character");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2088 const chat = shell?.querySelector("mjj-jrpg-chat");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2089 const composer = shell?.querySelector("mjj-jrpg-composer");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2090 const preview = shell?.querySelector("mjj-jrpg-preview");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2091 const archive = shell?.querySelector("mjj-conversation-archive");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2092 const accountSlot = shell?.querySelector("[data-frame-account]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2093 const quotaEl = shell?.querySelector("[data-quota]");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2094 const frameNetwork = shell?.querySelector("[data-frame-network]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2095 const frameUptime = shell?.querySelector("[data-frame-uptime]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2096 const minimizeButton = shell?.querySelector("[data-frame-minimize]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2097 const fullscreenButton = shell?.querySelector("[data-frame-fullscreen]");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2098
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2099 let characterTimer = 0;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2100 let currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2101 let currentPanel = "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2102 let archiveCursor = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2103 let activeController = null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2104 let archiveLoadInFlight = false; /* serialize pagination requests */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2105 let openSeq = 0; /* sequence guard for conversation open */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2106 let popSeq = 0; /* sequence guard for popstate */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2107 let _pendingPopState = null; /* queued popstate received during active stream */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2108 let _claimInFlight = false; /* guard against duplicate concurrent claims */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2109 let legacyClaimId = sessionStorage.getItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2110 let principalEpoch = 0; /* rejects responses from a prior owner */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2111 /* Dedupe conversation IDs: set of IDs currently rendered in archive */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2112 const _renderedIds = new Set();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2113 const invalidatePrincipalRequests = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2114 principalEpoch += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2115 openSeq += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2116 popSeq += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2117 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2118 };
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 /* Disable composer until fully initialized */
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2121 composer?.setDisabled(true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2122
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2123 const setFrameStatus = (label, state, title = "") => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2124 if (frameNetwork) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2125 frameNetwork.textContent = label;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2126 frameNetwork.setAttribute("aria-label", `Status ${label.toLowerCase()}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2127 frameNetwork.dataset.state = state;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2128 frameNetwork.title = title;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2129 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2130 };
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2131
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2132 /* Bootstrap failure: keep controls disabled and expose retry */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2133 if (!_csrfToken) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2134 setFrameStatus("OFFLINE", "offline", "Bootstrap failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2135 archive?.setError("Connection failed. Reload to retry.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2136 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2137 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2138
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2139 setFrameStatus("ONLINE", "online");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2140
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2141 const startedAt = Date.now();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2142 const updateUptime = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2143 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
2144 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
2145 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
2146 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
2147 if (frameUptime) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2148 frameUptime.textContent = `${hours}:${minutes}:${seconds}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2149 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2150 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2151 updateUptime();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2152 window.setInterval(updateUptime, 1000);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2153
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2154 minimizeButton?.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2155 const minimized = shell.dataset.minimized !== "true";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2156 shell.dataset.minimized = String(minimized);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2157 minimizeButton.setAttribute("aria-pressed", String(minimized));
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2158 minimizeButton.setAttribute(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2159 "aria-label",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2160 minimized ? "Restore interface" : "Minimize interface",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2161 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2162 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2163 fullscreenButton?.addEventListener("click", async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2164 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2165 if (document.fullscreenElement) await document.exitFullscreen();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2166 else await shell.requestFullscreen();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2167 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2168 setFrameStatus("DENIED", "offline", error.message);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2169 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2170 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2171 document.addEventListener("fullscreenchange", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2172 fullscreenButton?.setAttribute(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2173 "aria-pressed",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2174 String(Boolean(document.fullscreenElement)),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2175 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2176 });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2177
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2178 /* Render account slot */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2179 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2180
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2181 /* ---- Login modal ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2182 const loginDialogOwner = shell?.querySelector("[data-login-dialog-owner]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2183 const loginDialog = loginDialogOwner?.querySelector("[data-login-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2184 const loginForm = loginDialog?.querySelector("[data-login-form]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2185 const loginSubmitBtn = loginDialog?.querySelector("[data-login-submit]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2186 const loginCancelBtn = loginDialog?.querySelector("[data-login-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2187 const loginErrorEl = loginDialog?.querySelector("[data-login-error]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2188 const loginUsernameInput = loginDialog?.querySelector("#jrpg-login-username");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2189 const loginPasswordInput = loginDialog?.querySelector("#jrpg-login-password");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2190 let _loginTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2191 const setAccountActionsDisabled = disabled => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2192 for (const control of accountSlot?.querySelectorAll("button") || []) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2193 control.disabled = disabled;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2194 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2195 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2196
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2197 const _clearLoginError = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2198 if (loginErrorEl) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2199 loginErrorEl.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2200 loginErrorEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2201 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2202 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2203
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2204 const _showLoginError = (message) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2205 if (!loginErrorEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2206 loginErrorEl.textContent = message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2207 loginErrorEl.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2208 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2209
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2210 const openLoginModal = (triggerEl) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2211 if (!loginDialogOwner || !loginDialog) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2212 _loginTrigger = triggerEl || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2213 _clearLoginError();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2214 loginForm?.reset();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2215 loginDialogOwner.open();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2216 requestAnimationFrame(() => { loginUsernameInput?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2217 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2218
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2219 /* Delegate clicks on data-login-open in the account slot */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2220 accountSlot?.addEventListener("click", event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2221 const btn = event.target.closest("[data-login-open]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2222 if (!btn || activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2223 openLoginModal(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2224 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2225
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2226 /* Cancel button */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2227 loginCancelBtn?.addEventListener("click", () => { loginDialog?.close(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2228
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2229 /* Cleanup on any close (Escape, X button, cancel) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2230 loginDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2231 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2232 _clearLoginError();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2233 const trigger = _loginTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2234 _loginTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2235 requestAnimationFrame(() => { trigger?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2236 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2237
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2238 /* Login form submission */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2239 loginForm?.addEventListener("submit", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2240 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2241 if (loginSubmitBtn?.disabled) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2242 const username = loginUsernameInput?.value.trim() || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2243 const password = loginPasswordInput?.value || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2244 if (!username || !password) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2245 _showLoginError("Please enter your username and password.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2246 return;
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 if (loginSubmitBtn) loginSubmitBtn.disabled = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2249 _clearLoginError();
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2250 try {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2251 const response = await fetch("/api/auth/login", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2252 method: "POST",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2253 headers: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2254 "Content-Type": "application/json",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2255 "Origin": window.location.origin,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2256 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2257 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2258 body: JSON.stringify({ username, password, csrfToken: _csrfToken }),
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 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2261 if (response.status === 429) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2262 _showLoginError("Too many attempts. Please try again later.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2263 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2264 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2265 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2266 _showLoginError("Invalid username or password.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2267 return;
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 let data = await response.json().catch(() => null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2270 if (!data) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2271 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2272 if (_sessionData?.kind !== "user") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2273 loginDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2274 window.location.reload();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2275 return;
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 data = _sessionData;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2278 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2279 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2280 loginDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2281 _sessionData = {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2282 kind: "user",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2283 username: data.username || username,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2284 role: data.role || "member",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2285 mustChangePassword: Boolean(data.mustChangePassword),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2286 csrfToken: data.csrfToken,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2287 quota: null,
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 _csrfToken = data.csrfToken || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2290 if (data.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2291 window.location.href = "/account/password";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2292 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2293 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2294 /* Commit the returned principal before optional UI refresh work. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2295 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2296 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2297 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2298 /* Clear conversation state — guest convs may transfer to user */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2299 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2300 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2301 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2302 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2303 _replaceUrlState(currentPanel, null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2304 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2305 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2306 await refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2307 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2308 archive?.setError(`Signed in, but archive refresh failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2309 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2310 requestAnimationFrame(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2311 accountSlot?.querySelector("button, a")?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2312 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2313 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2314 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2315 _showLoginError("Sign-in failed. Please try again.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2316 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2317 if (loginSubmitBtn) loginSubmitBtn.disabled = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2318 }
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
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2321 /* Handle logout */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2322 _sessionRefreshCallback = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2323 /* Account changed or session lost — clear active state */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2324 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2325 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2326 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2327 activeController = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2328 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2329 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2330 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2331 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2332 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2333 _replaceUrlState(currentPanel, null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2334 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2335 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2336 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2337 archive?.setConversations([], null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2338 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2339 archive?.setError("Session changed. Loading conversations...");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2340 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2341 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2342 window.addEventListener("mjj-logout", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2343 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2344 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2345 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2346 activeController = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2347 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2348 archive?.setStreamActive(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2349 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2350 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2351 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2352 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2353 _replaceUrlState(currentPanel, null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2354 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2355 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2356 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2357 archive?.setConversations([], null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2358 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2359 void (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2360 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2361 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2362 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2363 if (_sessionData?.kind === "user" && _sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2364 composer?.setDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2365 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2366 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2367 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2368 })();
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 /* Session quota display */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2372 if (_sessionData?.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2373 updateQuotaDisplay(quotaEl, _sessionData.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2374 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2375
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2376 /* Disable composer for forced-password-change */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2377 const isForced = _sessionData?.kind === "user" && _sessionData?.mustChangePassword;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2378
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2379 /* ---- URL state helpers ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2380 const _buildStateUrl = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2381 const url = new URL(window.location.href);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2382 if (panel && VALID_PANELS.includes(panel)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2383 url.searchParams.set("panel", panel);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2384 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2385 url.searchParams.delete("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2386 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2387 if (convId && UUID_REGEX.test(convId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2388 url.searchParams.set("conversation", convId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2389 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2390 url.searchParams.delete("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2391 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2392 return url;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2393 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2394
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2395 const _pushUrlState = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2396 history.pushState({ panel, conversation: convId || null }, "", _buildStateUrl(panel, convId));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2397 };
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 const _replaceUrlState = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2400 history.replaceState({ panel, conversation: convId || null }, "", _buildStateUrl(panel, convId));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2401 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2402
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2403 /* ---- Panel selection: shows archive or preview in utility ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2404 const utilityEl = shell?.querySelector(".jrpg-utility");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2405
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2406 const selectPanel = (panel, pushHistory = true) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2407 const validPanel = VALID_PANELS.includes(panel) ? panel : "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2408 currentPanel = validPanel;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2409 for (const btn of (shell?.querySelectorAll("button[data-preview]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2410 btn.setAttribute("aria-pressed", String(btn.dataset.preview === validPanel));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2411 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2412 if (validPanel === "conversations") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2413 if (archive) archive.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2414 if (preview) preview.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2415 if (utilityEl) utilityEl.setAttribute("aria-label", "Conversations");
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 if (archive) archive.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2418 if (preview) preview.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2419 preview?.show(validPanel);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2420 if (utilityEl) utilityEl.setAttribute("aria-label", "Selected destination");
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 if (pushHistory) _pushUrlState(validPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2423 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2424
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2425 /* ---- Archive close button: navigate back to resume panel ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2426 shell?.addEventListener("mjj-archive-toggle", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2427 selectPanel("resume");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2428 });
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 shell?.addEventListener("mjj-archive-new", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2431 if (activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2432 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2433 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2434 _pushUrlState(currentPanel, null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2435 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2436 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2437 composer?.querySelector("textarea")?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2438 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2439
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2440 /* ---- Popstate: re-apply panel and conversation on back/forward ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2441 /* _applyPopState always increments openSeq so any pending archive-open
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2442 fetch (including a transition to no conversation) cannot overwrite the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2443 state the user navigated to (Fix 2). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2444 const _applyPopState = async (seq, popPanel, popConvId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2445 const localSeq = ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2446 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2447
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2448 selectPanel(popPanel, false);
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 if (popConvId && popConvId !== currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2451 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2452 const conversation = await requestJson(`/api/conversations/${encodeURIComponent(popConvId)}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2453 if (seq !== popSeq || localSeq !== openSeq ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2454 epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2455 currentConversationId = popConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2456 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2457 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2458 archive?.setCurrentId(popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2459 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2460 if (seq !== popSeq || localSeq !== openSeq ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2461 epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2462 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2463 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2464 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2465 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2466 history.replaceState(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2467 { panel: popPanel, conversation: null },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2468 "",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2469 _buildStateUrl(popPanel, null),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2470 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2471 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2472 } else if (!popConvId && currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2473 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2474 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2475 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2476 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2477 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2478 };
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 /* 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
2481 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
2482 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
2483 window.addEventListener("popstate", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2484 const seq = ++popSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2485 ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2486 const state = event.state || {};
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2487 const popPanel = VALID_PANELS.includes(state.panel) ? state.panel : "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2488 const popConvId = (state.conversation && UUID_REGEX.test(state.conversation))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2489 ? state.conversation : null;
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 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2492 _pendingPopState = { seq, popPanel, popConvId };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2493 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2494 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2495 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2496
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2497 await _applyPopState(seq, popPanel, popConvId);
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
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2500 shell?.addEventListener("mjj-archive-rename", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2501 const { id, newTitle, trigger } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2502 if (activeController) { trigger?.focus(); return; }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2503 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2504 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2505 `/api/conversations/${encodeURIComponent(id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2506 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2507 method: "PATCH",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2508 headers: { "Content-Type": "application/json" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2509 body: JSON.stringify({ title: newTitle }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2510 },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2511 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2512 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2513 const body = await response.json().catch(() => ({}));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2514 throw new Error(body.error?.message || `Rename failed (${response.status})`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2515 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2516 archive?.updateConversation({ id, title: newTitle, turn_count: null });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2517 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2518 archive?.setError(`Rename failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2519 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2520 trigger?.focus();
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
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2524 shell?.addEventListener("mjj-archive-open", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2525 const { id } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2526 if (id === currentConversationId) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2527 if (activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2528 const seq = ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2529 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2530 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2531 const conversation = await requestJson(`/api/conversations/${encodeURIComponent(id)}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2532 if (seq !== openSeq || epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2533 currentConversationId = id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2534 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2535 _pushUrlState(currentPanel, id);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2536 chat?.replaceMessages(conversation.turns);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2537 archive?.setCurrentId(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2538 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2539 if (seq !== openSeq || epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2540 archive?.setError(`Unable to open: ${error.message}`);
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 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2543
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2544 shell?.addEventListener("mjj-archive-delete", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2545 const { id, trigger } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2546 if (activeController) { trigger?.focus(); return; } /* block while streaming */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2547 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2548 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2549 `/api/conversations/${encodeURIComponent(id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2550 { method: "DELETE" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2551 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2552 if (response.ok || response.status === 404) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2553 /* 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
2554 const focusTarget = archive?.adjacentItemOrNewButton(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2555 _renderedIds.delete(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2556 archive?.removeConversation(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2557 if (id === currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2558 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2559 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2560 _replaceUrlState(currentPanel, null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2561 chat?.replaceMessages([]);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2562 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2563 /* Focus surviving element — never the detached trigger */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2564 requestAnimationFrame(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2565 (focusTarget || archive?.querySelector("[data-archive-new]"))?.focus();
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 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2568 const body = await response.json().catch(() => ({}));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2569 archive?.setError(`Delete failed: ${body.error?.message || response.status}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2570 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2571 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2572 archive?.setError(`Delete failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2573 /* On error, the trigger is still attached — focus it */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2574 trigger?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2575 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2576 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2577
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2578 shell?.addEventListener("mjj-archive-load-more", async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2579 if (!archiveCursor || archiveLoadInFlight) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2580 archiveLoadInFlight = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2581 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2582 archive?.setLoadMoreDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2583 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2584 const data = await fetchConversationList(archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2585 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2586 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2587 const newConvs = (data.conversations || []).filter(c => !_renderedIds.has(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2588 newConvs.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2589 archive?.setConversations(newConvs, archiveCursor, true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2590 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2591 archive?.setError(`Load failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2592 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2593 archiveLoadInFlight = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2594 archive?.setLoadMoreDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2595 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2596 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2597
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2598 shell?.addEventListener("mjj-archive-claim", async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2599 const legacyId =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2600 legacyClaimId || sessionStorage.getItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2601 if (!legacyId || _sessionData?.kind !== "user" || _claimInFlight) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2602 _claimInFlight = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2603 archive?.showLegacyClaim("Claiming prior conversation...", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2604 let _claimOutcome = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2605 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2606 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2607 "/api/conversations/claim",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2608 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2609 method: "POST",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2610 headers: { "Content-Type": "application/json" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2611 body: JSON.stringify({ conversationId: legacyId }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2612 },
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 if (response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2615 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2616 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2617 _claimOutcome = "success";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2618 } else if (response.status === 409) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2619 /* Definitive non-legacy conflict: clear key, no retry needed */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2620 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2621 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2622 _claimOutcome = "conflict";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2623 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2624 /* Retryable (401, 403, 429, 5xx): retain key */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2625 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, legacyId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2626 legacyClaimId = legacyId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2627 _claimOutcome = "retry";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2628 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2629 } catch {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2630 /* Network error: retain key */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2631 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, legacyId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2632 legacyClaimId = legacyId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2633 _claimOutcome = "network-error";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2634 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2635 /* 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
2636 _claimInFlight = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2637 if (_claimOutcome === "success") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2638 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2639 } else if (_claimOutcome === "conflict") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2640 archive?.showLegacyClaim("Prior conversation already claimed.", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2641 } else if (_claimOutcome === "retry") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2642 selectPanel("conversations", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2643 archive?.showLegacyClaim("Claim failed. Try again. ");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2644 } else if (_claimOutcome === "network-error") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2645 selectPanel("conversations", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2646 archive?.showLegacyClaim("Claim failed. Check connection. ");
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 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2650
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2651 async function refreshArchiveFromScratch() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2652 if (!archive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2653 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2654 archive.setLoading(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2655 _renderedIds.clear();
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 data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2658 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2659 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2660 const convs = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2661 convs.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2662 archive.setConversations(convs, archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2663 archive.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2664 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2665 archive.setError(`Archive error: ${error.message}`);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2666 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2667 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2668
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2669 async function refreshArchiveHead() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2670 if (!archive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2671 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2672 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2673 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2674 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2675 const fresh = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2676 for (const conv of fresh) archive.updateConversation(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2677 if (fresh.length > 0) archive.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2678 } catch { /* silent refresh failure */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2679 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2680
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2681 /* ---- Parse and validate URL params at startup ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2682 /* Track whether ?conversation was explicitly present (even if malformed)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2683 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
2684 let _convParamWasExplicit = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2685 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2686 const initParams = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2687 const rawPanel = initParams.get("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2688 const rawConvId = initParams.get("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2689 _convParamWasExplicit = rawConvId !== null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2690 const validPanel = VALID_PANELS.includes(rawPanel) ? rawPanel : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2691 const validConvId = (rawConvId && UUID_REGEX.test(rawConvId)) ? rawConvId : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2692 /* Remove invalid params with replaceState */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2693 if ((rawPanel && !validPanel) || (rawConvId && !validConvId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2694 const normalUrl = new URL(window.location.href);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2695 if (!validPanel) normalUrl.searchParams.delete("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2696 if (!validConvId) normalUrl.searchParams.delete("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2697 history.replaceState(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2698 { panel: validPanel, conversation: validConvId },
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 normalUrl,
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 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2703 currentPanel = validPanel || "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2704 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2705
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2706 /* ---- Load archive and restore conversation from URL or sessionStorage ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2707 const startupEpoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2708 const ensureStartupPrincipal = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2709 if (startupEpoch !== principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2710 const error = new Error("Principal changed during startup");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2711 error.name = "StalePrincipalError";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2712 throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2713 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2714 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2715 archive?.setLoading(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2716 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2717 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2718 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2719 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2720 const conversations = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2721 conversations.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2722 archive?.setConversations(conversations, archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2723
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2724 /* Determine target conversation: URL param is canonical; migrate sessionStorage once */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2725 const initParams2 = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2726 const urlConvId = initParams2.get("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2727 const urlConvValid = urlConvId && UUID_REGEX.test(urlConvId) ? urlConvId : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2728
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2729 let resolvedConvId = urlConvValid;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2730 let migratedFromStorage = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2731 if (!resolvedConvId && !_convParamWasExplicit) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2732 const stored = sessionStorage.getItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2733 if (stored && UUID_REGEX.test(stored)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2734 resolvedConvId = stored;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2735 migratedFromStorage = true;
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 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2738
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2739 if (resolvedConvId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2740 const inArchive = conversations.find(c => c.id === resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2741 if (inArchive) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2742 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2743 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2744 `/api/conversations/${encodeURIComponent(resolvedConvId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2745 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2746 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2747 currentConversationId = resolvedConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2748 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2749 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2750 archive?.setCurrentId(resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2751 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2752 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2753 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2754 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2755 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
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 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2758 /* Not on first page — fetch directly to verify ownership */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2759 let ownedConversation = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2760 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2761 ownedConversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2762 `/api/conversations/${encodeURIComponent(resolvedConvId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2763 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2764 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2765 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2766 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2767 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2768 ownedConversation = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2769 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2770 if (ownedConversation) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2771 currentConversationId = resolvedConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2772 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2773 chat?.replaceMessages(ownedConversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2774 if (!_renderedIds.has(resolvedConvId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2775 _renderedIds.add(resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2776 archive?.addConversation({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2777 id: resolvedConvId,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2778 title: ownedConversation.title || "Conversation",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2779 turn_count: ownedConversation.turns?.length || 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2780 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2781 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2782 archive?.setCurrentId(resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2783 } else if (_sessionData?.kind === "user") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2784 /* Authenticated and 404 — offer legacy claim for sessionStorage migrations */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2785 if (migratedFromStorage) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2786 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, resolvedConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2787 legacyClaimId = resolvedConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2788 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2789 archive?.showLegacyClaim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2790 if (conversations.length > 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2791 currentConversationId = conversations[0].id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2792 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
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 conv = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2795 `/api/conversations/${encodeURIComponent(currentConversationId)}`,
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 chat?.replaceMessages(conv.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2799 archive?.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2800 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2801 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2802 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2803 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2804 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2805 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2806 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2807 /* URL-specified conv returned 404 — clear it */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2808 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2809 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2810 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2811 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2812 /* Guest — not owned, clear stale ID */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2813 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2814 if (migratedFromStorage) sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2815 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2816 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2817 } else if (conversations.length > 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2818 /* No target conversation — open the most recent */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2819 const most_recent = conversations[0];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2820 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2821 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2822 `/api/conversations/${encodeURIComponent(most_recent.id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2823 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2824 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2825 currentConversationId = most_recent.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2826 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2827 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2828 archive?.setCurrentId(most_recent.id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2829 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2830 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2831 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2832 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2833 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2834 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2835 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2836 if (error.name !== "StalePrincipalError" &&
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2837 startupEpoch === principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2838 archive?.setError(`Archive error: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2839 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2840 /* Fall back to direct fetch of URL/stored ID */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2841 const initParams3 = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2842 const fallbackId = (
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2843 initParams3.get("conversation") ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2844 (!_convParamWasExplicit
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2845 ? sessionStorage.getItem(CONVERSATION_STORAGE_KEY)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2846 : null)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2847 ) || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2848 if (startupEpoch === principalEpoch &&
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2849 fallbackId && UUID_REGEX.test(fallbackId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2850 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2851 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2852 `/api/conversations/${encodeURIComponent(fallbackId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2853 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2854 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2855 currentConversationId = fallbackId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2856 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2857 } catch (fallbackError) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2858 if (fallbackError.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2859 startupEpoch !== principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2860 // The new principal owns subsequent rendering.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2861 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2862 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2863 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2864 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2865 }
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
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2869 /* 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
2870 if (startupEpoch === principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2871 selectPanel(currentPanel, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2872 _replaceUrlState(currentPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2873 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2874
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2875 shell?.addEventListener("mjj-jrpg-preview-change", event => {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2876 selectPanel(event.detail.selection);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2877 character?.setAttribute("state", "thinking");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2878 window.clearTimeout(characterTimer);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2879 characterTimer = window.setTimeout(() => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2880 character?.setAttribute("state", "default");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2881 }, 650);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2882 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2883
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2884 shell?.addEventListener("mjj-jrpg-cancel", () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2885 activeController?.abort();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2886 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2887
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2888 shell?.addEventListener("mjj-jrpg-submit", async event => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2889 if (activeController) return;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2890 /* Block sends when password change is required */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2891 if (_sessionData?.kind === "user" && _sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2892 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2893 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2894 chat?.appendMessage("June", event.detail.message);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2895 composer?.setBusy(true);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2896 character?.setAttribute("state", "thinking");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2897 window.clearTimeout(characterTimer);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2898 const assistantItem = chat?.startAssistantMessage();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2899 chat?.setMessageStreaming(assistantItem, true);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2900 const assistantText = new StreamingTextAnimator(text => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2901 chat?.setMessageText(assistantItem, text);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2902 });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2903 let turnFinished = false;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2904 activeController = new AbortController();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2905 activeController.signal.addEventListener("abort", () => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2906 assistantText.cancel();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2907 }, { once: true });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2908
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2909 /* Disable archive/account mutations while stream is active (finding #7) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2910 archive?.setStreamActive(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2911 setAccountActionsDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2912
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2913 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2914 const createConversation = async () => {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2915 const resp = await mutatingFetch("/api/conversations", {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2916 method: "POST",
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2917 headers: { "Content-Type": "application/json" },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2918 body: JSON.stringify({ title: "Shiba Quest" }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2919 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2920 if (!resp.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2921 let message = `Create failed (${resp.status})`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2922 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2923 const body = await resp.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2924 message = body.error?.message || message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2925 } catch { /* keep */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2926 throw new Error(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2927 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2928 const conversation = await resp.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2929 currentConversationId = conversation.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2930 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2931 _pushUrlState(currentPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2932 if (!_renderedIds.has(currentConversationId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2933 _renderedIds.add(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2934 archive?.addConversation({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2935 id: currentConversationId,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2936 title: "Shiba Quest",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2937 turn_count: 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2938 last_message_preview: event.detail.message.slice(0, 32),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2939 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2940 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2941 archive?.setCurrentId(currentConversationId);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2942 };
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2943
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2944 if (!currentConversationId) {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2945 await createConversation();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2946 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2947
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2948 const postTurn = () => mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2949 `/api/conversations/${encodeURIComponent(currentConversationId)}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2950 {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2951 method: "POST",
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2952 headers: { "Content-Type": "application/json" },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2953 body: JSON.stringify({ prompt: event.detail.message }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2954 signal: activeController.signal,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2955 },
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2956 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2957
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2958 let response = await postTurn();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2959
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2960 /* 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
2961 if (response.status === 404) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2962 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2963 currentConversationId = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2964 await createConversation();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2965 response = await postTurn();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2966 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2967
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2968 /* Handle 429: distinguish permanent quota from temporary capacity */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2969 if (response.status === 429) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2970 let errorBody = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2971 try { errorBody = await response.json(); } catch { /* keep */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2972 const code = errorBody?.error?.code || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2973 const isQuotaExhausted =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2974 code === "guest_quota_turns_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2975 code === "guest_quota_tokens_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2976 code === "guest_turn_quota_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2977 code === "guest_token_quota_exhausted";
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 if (isQuotaExhausted) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2980 const quotaState = errorBody?.error?.quota || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2981 updateQuotaDisplay(quotaEl, quotaState);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2982 composer?.setDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2983 chat?.setMessageStreaming(assistantItem, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2984 chat?.setMessageText(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2985 assistantItem,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2986 quotaState
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2987 ? `Quota exhausted. ${formatQuotaText(quotaState)?.text || ""}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2988 : "Quota exhausted. Try again later.",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2989 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2990 character?.setAttribute("state", "sad");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2991 setFrameStatus("QUOTA", "offline");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2992 /* Re-enable when quota resets */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2993 const resetsAt = quotaState?.resetsAt;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2994 if (resetsAt) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2995 const delay = Math.max(0, resetsAt * 1000 - Date.now());
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2996 window.setTimeout(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2997 if (_sessionData?.kind !== "user" || !_sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2998 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2999 setFrameStatus("ONLINE", "online");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3000 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3001 }, Math.min(delay + 1000, 24 * 3600 * 1000));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3002 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3003 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3004 /* Temporary capacity 429 — use Retry-After or default 5 s */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3005 const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3006 chat?.setMessageStreaming(assistantItem, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3007 chat?.setMessageText(assistantItem, "Server busy. Retrying shortly…");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3008 character?.setAttribute("state", "sad");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3009 setFrameStatus("BUSY", "offline");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3010 window.setTimeout(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3011 if (!activeController?.signal.aborted) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3012 setFrameStatus("ONLINE", "online");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3013 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3014 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3015 }, retryAfter * 1000);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3016 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3017 return;
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
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3020 if (!response.ok) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3021 let message = `Turn failed (${response.status})`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3022 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3023 const body = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3024 message = body.error?.message || message;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3025 } catch { /* keep */ }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3026 throw new Error(message);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3027 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3028
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3029 await consumeSse(response, (eventName, data) => {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3030 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
3031 bubbles: true,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3032 detail: { data, type: eventName },
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3033 }));
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3034 if (eventName === "assistant.delta") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3035 assistantText.append(data.delta || "");
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3036 } else if (eventName === "assistant.completed") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3037 assistantText.complete(data.content);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3038 } else if (eventName === "turn.error") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3039 throw new Error(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3040 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
3041 );
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3042 } else if (eventName === "turn.done") {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3043 turnFinished = true;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3044 composer?.setCancellable(false);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3045 if (data.failed || data.aborted) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3046 throw new Error(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3047 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
3048 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3049 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3050 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3051 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3052
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3053 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
3054 await assistantText.waitForIdle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3055 if (activeController.signal.aborted) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3056 throw new DOMException("Quest cancelled", "AbortError");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3057 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3058 chat?.setMessageStreaming(assistantItem, false);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3059 character?.setAttribute("state", "default");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
3060 setFrameStatus("ONLINE", "online");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3061
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3062 /* Refresh archive and quota after successful turn */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3063 void refreshArchiveHead();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3064 if (_sessionData?.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3065 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3066 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3067 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3068 } catch (error) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3069 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
3070 assistantText.cancel();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3071 chat?.setMessageStreaming(assistantItem, false);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3072 chat?.setMessageText(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3073 assistantItem,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3074 aborted ? "Quest cancelled." : `System error: ${error.message}`,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3075 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3076 character?.setAttribute("state", "sad");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
3077 if (!aborted) setFrameStatus("OFFLINE", "offline");
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3078 } finally {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3079 composer?.setBusy(false);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3080 composer?.querySelector("textarea")?.focus();
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3081 activeController = null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3082 /* Re-enable archive/account mutations exactly once */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3083 archive?.setStreamActive(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3084 setAccountActionsDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3085 /* Apply popstate that was queued during the stream (Fix 1) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3086 if (_pendingPopState) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3087 const pending = _pendingPopState;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3088 _pendingPopState = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3089 if (pending.seq === popSeq) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3090 await _applyPopState(pending.seq, pending.popPanel, pending.popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3091 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3092 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3093 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3094 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3095
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3096 /* Enable composer unless forced password change */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3097 if (!isForced) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3098 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3099 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3100 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3101
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3102 if (document.readyState === "loading") {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3103 document.addEventListener("DOMContentLoaded", () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3104 void initializeJrpgPage();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3105 }, {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3106 once: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3107 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3108 } else {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3109 void initializeJrpgPage();
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3110 }