(() => { const root = document.querySelector("[data-writing-context]"); if (!root) { return; } const contentHost = root.querySelector("[data-writing-context-content]"); const statusText = root.querySelector("[data-writing-context-status-text]"); const endpoint = root.dataset.contextUrl || "/Writing/Context/Scene"; const tabStorageKey = "plotdirector.writingContext.activeTab"; const scopedProjectId = Number.parseInt(root.dataset.scopeProjectId || "", 10); const scopedBookId = Number.parseInt(root.dataset.scopeBookId || "", 10); let currentSceneId = Number.parseInt(contentHost?.querySelector("[data-writing-context-fragment]")?.dataset.sceneId || "", 10); let loadingSceneId = null; const setStatus = (value) => { if (statusText && value) { statusText.textContent = value; } }; const activeTab = () => { const active = contentHost?.querySelector("[data-writing-context-tab].active")?.dataset.writingContextTab; if (active) { return active; } try { return localStorage.getItem(tabStorageKey) || "overview"; } catch { return "overview"; } }; const activateTab = (tabName) => { const nextTab = tabName || "overview"; contentHost?.querySelectorAll("[data-writing-context-tab]").forEach((button) => { const active = button.dataset.writingContextTab === nextTab; button.classList.toggle("active", active); button.setAttribute("aria-selected", String(active)); }); contentHost?.querySelectorAll("[data-writing-context-panel]").forEach((panel) => { const active = panel.dataset.writingContextPanel === nextTab; panel.classList.toggle("active", active); panel.hidden = !active; }); try { localStorage.setItem(tabStorageKey, nextTab); } catch { // Remembering the tab is a convenience only. } }; const wireFragment = () => { activateTab(activeTab()); const fragment = contentHost?.querySelector("[data-writing-context-fragment]"); const fragmentStatus = fragment?.dataset.followStatus; if (fragmentStatus) { setStatus(fragmentStatus); } const fragmentSceneId = Number.parseInt(fragment?.dataset.sceneId || "", 10); if (Number.isInteger(fragmentSceneId) && fragmentSceneId > 0) { currentSceneId = fragmentSceneId; } contentHost?.querySelectorAll("[data-writing-context-tab]").forEach((button) => { button.addEventListener("click", () => activateTab(button.dataset.writingContextTab)); }); contentHost?.querySelector("[data-writing-context-scene-select]")?.addEventListener("change", (event) => { const sceneId = Number.parseInt(event.target.value || "", 10); if (!Number.isInteger(sceneId) || sceneId <= 0) { return; } const fragment = contentHost?.querySelector("[data-writing-context-fragment]"); window.PlotDirectorStoryPosition?.publishChanged({ source: "WritingContext", projectId: fragment?.dataset.projectId, bookId: fragment?.dataset.bookId, chapterId: fragment?.dataset.chapterId, sceneId }, "WritingContext"); }); }; const inScope = (position) => { if (Number.isInteger(scopedProjectId) && scopedProjectId > 0 && position.projectId !== scopedProjectId) { return false; } if (Number.isInteger(scopedBookId) && scopedBookId > 0 && position.bookId && position.bookId !== scopedBookId) { return false; } return true; }; const loadPosition = async (position) => { if (!position || !Number.isInteger(position.sceneId) || position.sceneId <= 0 || !inScope(position)) { return; } if (position.sceneId === currentSceneId || position.sceneId === loadingSceneId) { return; } const tab = activeTab(); loadingSceneId = position.sceneId; setStatus("Updating"); const url = new URL(endpoint, window.location.origin); if (position.projectId) url.searchParams.set("projectId", position.projectId); if (position.bookId) url.searchParams.set("bookId", position.bookId); if (position.chapterId) url.searchParams.set("chapterId", position.chapterId); url.searchParams.set("sceneId", position.sceneId); try { const response = await fetch(url, { headers: { "X-Requested-With": "XMLHttpRequest" } }); if (!response.ok) { throw new Error(`Context request failed: ${response.status}`); } contentHost.innerHTML = await response.text(); currentSceneId = position.sceneId; wireFragment(); activateTab(tab); } catch (error) { console.debug("Writing Context could not update.", error); setStatus("Connected, waiting for scene"); } finally { loadingSceneId = null; } }; document.addEventListener(window.PlotDirectorStoryPosition?.eventName || "plotdirector:story-position-changed", (event) => { void loadPosition(event.detail); }); if (window.signalR) { const connection = new signalR.HubConnectionBuilder() .withUrl("/hubs/word-companion-follow") .withAutomaticReconnect() .build(); connection.on("storyPositionChanged", (payload) => { window.PlotDirectorStoryPosition?.publishChanged(payload, payload?.source || payload?.Source || "WordCompanion"); }); connection.start() .then(() => { if (!currentSceneId) { setStatus("Connected, waiting for scene"); } }) .catch((error) => { console.debug("Writing Context follow connection unavailable.", error); setStatus("Word not connected"); }); } else { setStatus("Word not connected"); } wireFragment(); })();