Phase 10N: Word Companion Auto Context Refresh
This commit is contained in:
parent
2136c6d686
commit
0ffe678d87
@ -47,9 +47,9 @@ public sealed class AccountController(IAuthService authService) : Controller
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public IActionResult Login(string? returnUrl = null)
|
public IActionResult Login(string? returnUrl = null, bool companion = false)
|
||||||
{
|
{
|
||||||
return View(new LoginViewModel { ReturnUrl = returnUrl });
|
return View(new LoginViewModel { ReturnUrl = returnUrl, IsCompanionLogin = IsCompanionLogin(returnUrl, companion) });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -59,6 +59,7 @@ public sealed class AccountController(IAuthService authService) : Controller
|
|||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
|
model.IsCompanionLogin = IsCompanionLogin(model.ReturnUrl, model.IsCompanionLogin);
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +72,7 @@ public sealed class AccountController(IAuthService authService) : Controller
|
|||||||
if (!result.Success)
|
if (!result.Success)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Login could not be completed.");
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Login could not be completed.");
|
||||||
|
model.IsCompanionLogin = IsCompanionLogin(model.ReturnUrl, model.IsCompanionLogin);
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +84,17 @@ public sealed class AccountController(IAuthService authService) : Controller
|
|||||||
return RedirectToAction("Index", "Projects");
|
return RedirectToAction("Index", "Projects");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsCompanionLogin(string? returnUrl, bool explicitCompanion)
|
||||||
|
{
|
||||||
|
if (explicitCompanion)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !string.IsNullOrWhiteSpace(returnUrl)
|
||||||
|
&& returnUrl.StartsWith("/word-companion", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Logout()
|
public async Task<IActionResult> Logout()
|
||||||
|
|||||||
@ -67,6 +67,8 @@ public sealed class LoginViewModel
|
|||||||
public bool RememberMe { get; set; }
|
public bool RememberMe { get; set; }
|
||||||
|
|
||||||
public string? ReturnUrl { get; set; }
|
public string? ReturnUrl { get; set; }
|
||||||
|
|
||||||
|
public bool IsCompanionLogin { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ForgotPasswordViewModel
|
public sealed class ForgotPasswordViewModel
|
||||||
|
|||||||
@ -1,24 +1,86 @@
|
|||||||
@model LoginViewModel
|
@model LoginViewModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Login";
|
ViewData["Title"] = "Login";
|
||||||
|
if (Model.IsCompanionLogin)
|
||||||
|
{
|
||||||
|
Layout = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="page-heading compact">
|
@if (Model.IsCompanionLogin)
|
||||||
|
{
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Word Companion Login - PlotDirector</title>
|
||||||
|
<link rel="stylesheet" href="~/css/word-companion.css" asp-append-version="true" />
|
||||||
|
</head>
|
||||||
|
<body class="word-companion-login-body">
|
||||||
|
<main class="word-companion-login-shell">
|
||||||
|
<header class="word-companion-login-header">
|
||||||
|
<h1>PlotDirector</h1>
|
||||||
|
<p>Word Companion</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
@if (TempData["AuthMessage"] is string authMessage)
|
||||||
|
{
|
||||||
|
<div class="word-companion-login-alert">@authMessage</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="word-companion-login-card">
|
||||||
|
<form asp-action="Login" method="post" class="word-companion-login-form">
|
||||||
|
<input asp-for="ReturnUrl" type="hidden" />
|
||||||
|
<input asp-for="IsCompanionLogin" type="hidden" />
|
||||||
|
<div asp-validation-summary="ModelOnly" class="word-companion-login-validation"></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label asp-for="Email">Email address</label>
|
||||||
|
<input asp-for="Email" autocomplete="email" />
|
||||||
|
<span asp-validation-for="Email"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label asp-for="Password">Password</label>
|
||||||
|
<input asp-for="Password" autocomplete="current-password" />
|
||||||
|
<span asp-validation-for="Password"></span>
|
||||||
|
</div>
|
||||||
|
<label class="word-companion-login-check">
|
||||||
|
<input asp-for="RememberMe" />
|
||||||
|
<span>Remember me</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
<div class="word-companion-login-links">
|
||||||
|
<a asp-action="Register">Create account</a>
|
||||||
|
<a asp-action="ForgotPassword">Forgot password?</a>
|
||||||
|
<a asp-action="ResendVerificationEmail">Need a new verification email?</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="page-heading compact">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Account</p>
|
<p class="eyebrow">Account</p>
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<p class="lead-text">Sign in to PlotDirector.</p>
|
<p class="lead-text">Sign in to PlotDirector.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (TempData["AuthMessage"] is string authMessage)
|
@if (TempData["AuthMessage"] is string authMessage)
|
||||||
{
|
{
|
||||||
<div class="alert alert-info">@authMessage</div>
|
<div class="alert alert-info">@authMessage</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<section class="edit-panel">
|
<section class="edit-panel">
|
||||||
<form asp-action="Login" method="post" class="asset-create-form">
|
<form asp-action="Login" method="post" class="asset-create-form">
|
||||||
<input asp-for="ReturnUrl" type="hidden" />
|
<input asp-for="ReturnUrl" type="hidden" />
|
||||||
|
<input asp-for="IsCompanionLogin" type="hidden" />
|
||||||
<div asp-validation-summary="ModelOnly" class="alert alert-warning"></div>
|
<div asp-validation-summary="ModelOnly" class="alert alert-warning"></div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
@ -49,8 +111,12 @@
|
|||||||
<a asp-action="ResendVerificationEmail">Need a new verification email?</a>
|
<a asp-action="ResendVerificationEmail">Need a new verification email?</a>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
}
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
@if (!Model.IsCompanionLogin)
|
||||||
|
{
|
||||||
|
@section Scripts {
|
||||||
|
<partial name="_ValidationScriptsPartial" />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,7 @@
|
|||||||
<div class="word-companion-badges">
|
<div class="word-companion-badges">
|
||||||
<span class="word-companion-badge" data-plotdirector-scene-status>Not detected</span>
|
<span class="word-companion-badge" data-plotdirector-scene-status>Not detected</span>
|
||||||
<span class="word-companion-badge" data-anchor-status>Not attached</span>
|
<span class="word-companion-badge" data-anchor-status>Not attached</span>
|
||||||
|
<span class="word-companion-badge">Scene Tracking: <strong data-scene-tracking-status>Manual</strong></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -172,6 +173,7 @@
|
|||||||
<button type="button" data-save-scene-links disabled>Save Scene Links</button>
|
<button type="button" data-save-scene-links disabled>Save Scene Links</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="word-companion-message" data-scene-links-status>Link a PlotDirector scene first.</p>
|
<p class="word-companion-message" data-scene-links-status>Link a PlotDirector scene first.</p>
|
||||||
|
<p class="word-companion-link-context" data-links-editing-scene>Link a PlotDirector scene first.</p>
|
||||||
<div>
|
<div>
|
||||||
<span>Characters</span>
|
<span>Characters</span>
|
||||||
<div class="word-companion-check-list" data-character-chips></div>
|
<div class="word-companion-check-list" data-character-chips></div>
|
||||||
|
|||||||
@ -27,6 +27,140 @@ body {
|
|||||||
font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-companion-login-body {
|
||||||
|
min-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-shell {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 14px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-header {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
border-bottom: 1px solid var(--companion-line);
|
||||||
|
padding-bottom: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
font-size: 1.22rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-header p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--companion-muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-card,
|
||||||
|
.word-companion-login-alert {
|
||||||
|
border: 1px solid var(--companion-line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--companion-panel);
|
||||||
|
box-shadow: 0 2px 8px rgba(74, 50, 31, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-card {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-alert {
|
||||||
|
padding: 10px;
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-form div {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-form label {
|
||||||
|
color: var(--companion-muted);
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-form input[type="email"],
|
||||||
|
.word-companion-login-form input[type="password"],
|
||||||
|
.word-companion-login-form input:not([type]) {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 34px;
|
||||||
|
border: 1px solid var(--companion-line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
background: #fffdf8;
|
||||||
|
color: var(--companion-ink);
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-form span {
|
||||||
|
color: #8f3528;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-check {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
color: var(--companion-ink) !important;
|
||||||
|
font-size: 0.84rem !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-check input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
|
accent-color: var(--companion-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-form button {
|
||||||
|
min-height: 36px;
|
||||||
|
border: 1px solid var(--companion-accent);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(180deg, #7b5432 0%, var(--companion-accent-dark) 100%);
|
||||||
|
color: #fff9ef;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-links {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
border-top: 1px solid var(--companion-line);
|
||||||
|
padding-top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-links a {
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-login-validation:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.word-companion-shell {
|
.word-companion-shell {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
@ -341,6 +475,7 @@ body {
|
|||||||
.word-companion-badge {
|
.word-companion-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
border: 1px solid rgba(111, 75, 45, 0.24);
|
border: 1px solid rgba(111, 75, 45, 0.24);
|
||||||
@ -353,6 +488,22 @@ body {
|
|||||||
text-transform: none !important;
|
text-transform: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-companion-badge strong {
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-link-context {
|
||||||
|
margin: -2px 0 2px;
|
||||||
|
border: 1px solid rgba(111, 75, 45, 0.16);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
background: var(--companion-soft);
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.word-companion-scene-card div,
|
.word-companion-scene-card div,
|
||||||
.word-companion-sync-meta div,
|
.word-companion-sync-meta div,
|
||||||
.word-companion-structure-grid div,
|
.word-companion-structure-grid div,
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const storageKeys = {
|
const storageKeys = {
|
||||||
projectId: "plotdirector.word.projectId",
|
projectId: "plotdirector.word.projectId",
|
||||||
bookId: "plotdirector.word.bookId"
|
bookId: "plotdirector.word.bookId",
|
||||||
|
activeTab: "plotdirector.word.activeTab"
|
||||||
};
|
};
|
||||||
|
const validTabNames = new Set(["scene", "links", "structure", "diagnostics"]);
|
||||||
|
|
||||||
const shell = document.querySelector(".word-companion-shell");
|
const shell = document.querySelector(".word-companion-shell");
|
||||||
const tabButtons = [...document.querySelectorAll("[data-tab-button]")];
|
const tabButtons = [...document.querySelectorAll("[data-tab-button]")];
|
||||||
@ -21,6 +23,7 @@
|
|||||||
const currentChapter = document.querySelector("[data-current-chapter]");
|
const currentChapter = document.querySelector("[data-current-chapter]");
|
||||||
const currentScene = document.querySelector("[data-current-scene]");
|
const currentScene = document.querySelector("[data-current-scene]");
|
||||||
const currentWordCount = document.querySelector("[data-current-word-count]");
|
const currentWordCount = document.querySelector("[data-current-word-count]");
|
||||||
|
const sceneTrackingStatus = document.querySelector("[data-scene-tracking-status]");
|
||||||
const documentMessage = document.querySelector("[data-document-message]");
|
const documentMessage = document.querySelector("[data-document-message]");
|
||||||
const syncNote = document.querySelector("[data-sync-note]");
|
const syncNote = document.querySelector("[data-sync-note]");
|
||||||
const documentOutline = document.querySelector("[data-document-outline]");
|
const documentOutline = document.querySelector("[data-document-outline]");
|
||||||
@ -57,6 +60,7 @@
|
|||||||
const primaryLocationSelect = document.querySelector("[data-primary-location-select]");
|
const primaryLocationSelect = document.querySelector("[data-primary-location-select]");
|
||||||
const saveSceneLinksButton = document.querySelector("[data-save-scene-links]");
|
const saveSceneLinksButton = document.querySelector("[data-save-scene-links]");
|
||||||
const sceneLinksStatus = document.querySelector("[data-scene-links-status]");
|
const sceneLinksStatus = document.querySelector("[data-scene-links-status]");
|
||||||
|
const linksEditingScene = document.querySelector("[data-links-editing-scene]");
|
||||||
const chapterCreateLink = document.querySelector("[data-chapter-create-link]");
|
const chapterCreateLink = document.querySelector("[data-chapter-create-link]");
|
||||||
const chapterCreateLinkChapter = document.querySelector("[data-chapter-create-link-chapter]");
|
const chapterCreateLinkChapter = document.querySelector("[data-chapter-create-link-chapter]");
|
||||||
const createPlotDirectorChapterButton = document.querySelector("[data-create-plotdirector-chapter]");
|
const createPlotDirectorChapterButton = document.querySelector("[data-create-plotdirector-chapter]");
|
||||||
@ -146,6 +150,13 @@
|
|||||||
let structurePreviewModel = null;
|
let structurePreviewModel = null;
|
||||||
let isApplyingStructureSync = false;
|
let isApplyingStructureSync = false;
|
||||||
let pendingApplyStructureSyncConfirmation = null;
|
let pendingApplyStructureSyncConfirmation = null;
|
||||||
|
let sceneTrackingMode = "Manual";
|
||||||
|
let sceneContextStale = false;
|
||||||
|
let selectionRefreshTimer = null;
|
||||||
|
let selectionHandlerRegistered = false;
|
||||||
|
let isAutoRefreshingScene = false;
|
||||||
|
let lastLinkedSceneTitle = "";
|
||||||
|
let lastLinkedChapterTitle = "";
|
||||||
let currentSceneLinks = {
|
let currentSceneLinks = {
|
||||||
characters: [],
|
characters: [],
|
||||||
assets: [],
|
assets: [],
|
||||||
@ -159,18 +170,51 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectTab = (tabName) => {
|
const readStoredText = (key) => {
|
||||||
|
try {
|
||||||
|
return window.localStorage.getItem(key) || "";
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeStoredText = (key, value) => {
|
||||||
|
try {
|
||||||
|
if (value) {
|
||||||
|
window.localStorage.setItem(key, String(value));
|
||||||
|
} else {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Some Office hosts can restrict storage. The pane still falls back to the Scene tab.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectTab = (tabName, persist = true) => {
|
||||||
|
const targetTab = validTabNames.has(tabName) && tabButtons.some((button) => button.dataset.tabButton === tabName)
|
||||||
|
? tabName
|
||||||
|
: "scene";
|
||||||
|
|
||||||
for (const button of tabButtons) {
|
for (const button of tabButtons) {
|
||||||
const active = button.dataset.tabButton === tabName;
|
const active = button.dataset.tabButton === targetTab;
|
||||||
button.classList.toggle("is-active", active);
|
button.classList.toggle("is-active", active);
|
||||||
button.setAttribute("aria-selected", active ? "true" : "false");
|
button.setAttribute("aria-selected", active ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const panel of tabPanels) {
|
for (const panel of tabPanels) {
|
||||||
const active = panel.dataset.tabPanel === tabName;
|
const active = panel.dataset.tabPanel === targetTab;
|
||||||
panel.classList.toggle("is-active", active);
|
panel.classList.toggle("is-active", active);
|
||||||
panel.hidden = !active;
|
panel.hidden = !active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (persist) {
|
||||||
|
writeStoredText(storageKeys.activeTab, targetTab);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreActiveTab = () => {
|
||||||
|
const storedTab = readStoredText(storageKeys.activeTab);
|
||||||
|
selectTab(validTabNames.has(storedTab) ? storedTab : "scene", false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setConnectionStatus = (message) => {
|
const setConnectionStatus = (message) => {
|
||||||
@ -311,18 +355,63 @@
|
|||||||
updateApplyStructureSyncButton();
|
updateApplyStructureSyncButton();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateLinksEditingScene = () => {
|
||||||
|
if (!linksEditingScene) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
linksEditingScene.textContent = resolvedSceneId
|
||||||
|
? `Editing links for: ${lastLinkedSceneTitle || detectedSceneTitle || "Current scene"}`
|
||||||
|
: "Link a PlotDirector scene first.";
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSceneActionButtons = () => {
|
||||||
|
const blocked = sceneContextStale || !resolvedSceneId;
|
||||||
|
if (syncSceneProgressButton) {
|
||||||
|
syncSceneProgressButton.disabled = blocked;
|
||||||
|
}
|
||||||
|
if (saveSceneLinksButton) {
|
||||||
|
saveSceneLinksButton.disabled = blocked;
|
||||||
|
}
|
||||||
|
if (primaryLocationSelect) {
|
||||||
|
primaryLocationSelect.disabled = blocked;
|
||||||
|
}
|
||||||
|
for (const container of [characterChips, assetChips]) {
|
||||||
|
for (const checkbox of container ? [...container.querySelectorAll("input[type='checkbox']")] : []) {
|
||||||
|
checkbox.disabled = blocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSceneTrackingState = (status) => {
|
||||||
|
if (status === "Live" || status === "Manual") {
|
||||||
|
sceneTrackingMode = status;
|
||||||
|
}
|
||||||
|
setText(sceneTrackingStatus, status || sceneTrackingMode || "Manual");
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSceneContextStale = (isStale, message = "") => {
|
||||||
|
sceneContextStale = !!isStale;
|
||||||
|
setText(sceneTrackingStatus, sceneContextStale ? "Stale" : sceneTrackingMode);
|
||||||
|
updateSceneActionButtons();
|
||||||
|
if (message) {
|
||||||
|
setDocumentMessage(message);
|
||||||
|
setSceneLinksStatus(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const setResolvedScene = (sceneId, chapterId = null, resolutionMethod = "") => {
|
const setResolvedScene = (sceneId, chapterId = null, resolutionMethod = "") => {
|
||||||
resolvedSceneId = Number.isInteger(sceneId) && sceneId > 0 ? sceneId : null;
|
resolvedSceneId = Number.isInteger(sceneId) && sceneId > 0 ? sceneId : null;
|
||||||
resolvedChapterId = Number.isInteger(chapterId) && chapterId > 0 ? chapterId : null;
|
resolvedChapterId = Number.isInteger(chapterId) && chapterId > 0 ? chapterId : null;
|
||||||
currentResolutionMethod = resolutionMethod || "";
|
currentResolutionMethod = resolutionMethod || "";
|
||||||
if (syncSceneProgressButton) {
|
updateSceneActionButtons();
|
||||||
syncSceneProgressButton.disabled = !resolvedSceneId;
|
setSyncStatus(sceneContextStale
|
||||||
}
|
? "Refresh Current Scene before syncing."
|
||||||
if (saveSceneLinksButton) {
|
: resolvedSceneId ? "Ready to sync." : "No resolved PlotDirector scene.");
|
||||||
saveSceneLinksButton.disabled = !resolvedSceneId;
|
setSceneLinksStatus(sceneContextStale
|
||||||
}
|
? "Refresh Current Scene before saving."
|
||||||
setSyncStatus(resolvedSceneId ? "Ready to sync." : "No resolved PlotDirector scene.");
|
: resolvedSceneId ? "Ready to save." : "Link a PlotDirector scene first.");
|
||||||
setSceneLinksStatus(resolvedSceneId ? "Ready to save." : "Link a PlotDirector scene first.");
|
updateLinksEditingScene();
|
||||||
updateAnchorDisplay();
|
updateAnchorDisplay();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -431,6 +520,8 @@
|
|||||||
hideChapterCreateLink();
|
hideChapterCreateLink();
|
||||||
hideSceneCreateLink();
|
hideSceneCreateLink();
|
||||||
currentResolutionMethod = "";
|
currentResolutionMethod = "";
|
||||||
|
lastLinkedSceneTitle = "";
|
||||||
|
lastLinkedChapterTitle = "";
|
||||||
currentResolutionAnchorType = detectedSceneAnchorId ? "SceneID Anchor" : detectedChapterAnchorId ? "ChapterID Anchor" : "Title Match";
|
currentResolutionAnchorType = detectedSceneAnchorId ? "SceneID Anchor" : detectedChapterAnchorId ? "ChapterID Anchor" : "Title Match";
|
||||||
storedAnchorInvalid = false;
|
storedAnchorInvalid = false;
|
||||||
currentSceneLinks = { characters: [], assets: [], locations: [], primaryLocationId: null };
|
currentSceneLinks = { characters: [], assets: [], locations: [], primaryLocationId: null };
|
||||||
@ -447,6 +538,7 @@
|
|||||||
setText(plotDirectorBlockedReason, "-");
|
setText(plotDirectorBlockedReason, "-");
|
||||||
setHidden(plotDirectorBlockedReasonRow, true);
|
setHidden(plotDirectorBlockedReasonRow, true);
|
||||||
updateAnchorDisplay();
|
updateAnchorDisplay();
|
||||||
|
updateLinksEditingScene();
|
||||||
};
|
};
|
||||||
|
|
||||||
const linkItemId = (item, type) => {
|
const linkItemId = (item, type) => {
|
||||||
@ -528,6 +620,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderCompanionData = (data) => {
|
const renderCompanionData = (data) => {
|
||||||
|
lastLinkedSceneTitle = data?.sceneTitle || detectedSceneTitle || "";
|
||||||
|
lastLinkedChapterTitle = data?.chapterTitle || detectedChapterTitle || "";
|
||||||
setText(plotDirectorSceneTitle, displayValue(data?.sceneTitle));
|
setText(plotDirectorSceneTitle, displayValue(data?.sceneTitle));
|
||||||
setText(plotDirectorRevisionStatus, displayValue(data?.revisionStatus || data?.revisionStatusName || "Not provided"));
|
setText(plotDirectorRevisionStatus, displayValue(data?.revisionStatus || data?.revisionStatusName || "Not provided"));
|
||||||
setText(plotDirectorEstimatedWords, displayValue(data?.estimatedWords));
|
setText(plotDirectorEstimatedWords, displayValue(data?.estimatedWords));
|
||||||
@ -553,10 +647,13 @@
|
|||||||
locations: Array.isArray(data?.locations) ? data.locations : [],
|
locations: Array.isArray(data?.locations) ? data.locations : [],
|
||||||
primaryLocationId: data?.primaryLocationId ?? null
|
primaryLocationId: data?.primaryLocationId ?? null
|
||||||
};
|
};
|
||||||
renderSceneLinkList(characterChips, currentSceneLinks.characters, "character", !!resolvedSceneId);
|
renderSceneLinkList(characterChips, currentSceneLinks.characters, "character", !!resolvedSceneId && !sceneContextStale);
|
||||||
renderSceneLinkList(assetChips, currentSceneLinks.assets, "asset", !!resolvedSceneId);
|
renderSceneLinkList(assetChips, currentSceneLinks.assets, "asset", !!resolvedSceneId && !sceneContextStale);
|
||||||
renderPrimaryLocationOptions(currentSceneLinks.locations, currentSceneLinks.primaryLocationId, !!resolvedSceneId);
|
renderPrimaryLocationOptions(currentSceneLinks.locations, currentSceneLinks.primaryLocationId, !!resolvedSceneId && !sceneContextStale);
|
||||||
setSceneLinksStatus(resolvedSceneId ? "Ready to save." : "No resolved PlotDirector scene.");
|
updateLinksEditingScene();
|
||||||
|
setSceneLinksStatus(sceneContextStale
|
||||||
|
? "Refresh Current Scene before saving."
|
||||||
|
: resolvedSceneId ? "Ready to save." : "No resolved PlotDirector scene.");
|
||||||
setHidden(plotDirectorSceneDetails, false);
|
setHidden(plotDirectorSceneDetails, false);
|
||||||
setHidden(writingBriefBlock, false);
|
setHidden(writingBriefBlock, false);
|
||||||
setHidden(plotDirectorLinkGroups, false);
|
setHidden(plotDirectorLinkGroups, false);
|
||||||
@ -667,6 +764,7 @@
|
|||||||
}
|
}
|
||||||
currentResolutionAnchorType = anchorType || "Title Match";
|
currentResolutionAnchorType = anchorType || "Title Match";
|
||||||
storedAnchorInvalid = false;
|
storedAnchorInvalid = false;
|
||||||
|
setSceneContextStale(false);
|
||||||
setPlotDirectorSceneStatus("Linked to PlotDirector");
|
setPlotDirectorSceneStatus("Linked to PlotDirector");
|
||||||
setDocumentMessage("");
|
setDocumentMessage("");
|
||||||
hideChapterCreateLink();
|
hideChapterCreateLink();
|
||||||
@ -1424,7 +1522,7 @@
|
|||||||
const message = errorText(error);
|
const message = errorText(error);
|
||||||
console.error("Unable to scan the Word document.", error);
|
console.error("Unable to scan the Word document.", error);
|
||||||
setCurrentStructure(null, null, null);
|
setCurrentStructure(null, null, null);
|
||||||
setDocumentMessage("Unable to scan the Word document.");
|
setDocumentMessage(`Unable to scan the Word document: ${message}`);
|
||||||
setDiagnostics({ lastScan: "Failure", lastError: message });
|
setDiagnostics({ lastScan: "Failure", lastError: message });
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
@ -1780,8 +1878,9 @@
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Word Companion diagnostics failed.", error);
|
console.error("Word Companion diagnostics failed.", error);
|
||||||
setDiagnostics({ lastScan: "Failure", lastError: errorText(error) });
|
const message = errorText(error);
|
||||||
setDocumentMessage("Unable to scan the Word document.");
|
setDiagnostics({ lastScan: "Failure", lastError: message });
|
||||||
|
setDocumentMessage(`Unable to scan the Word document: ${message}`);
|
||||||
} finally {
|
} finally {
|
||||||
if (runDiagnosticsButton) {
|
if (runDiagnosticsButton) {
|
||||||
runDiagnosticsButton.disabled = false;
|
runDiagnosticsButton.disabled = false;
|
||||||
@ -1852,12 +1951,58 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verifyCurrentWordSceneMatchesLinkedScene = async () => {
|
||||||
|
if (!resolvedSceneId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
|
setSceneContextStale(true, "The Word cursor is now in a different scene. Refresh Current Scene before saving.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const structure = await window.Word.run(async (context) => {
|
||||||
|
return await readDocumentStructure(context);
|
||||||
|
});
|
||||||
|
const currentChapterTitle = structure.currentChapter?.title || "";
|
||||||
|
const currentSceneTitle = structure.currentScene?.title || "";
|
||||||
|
const currentSceneAnchorId = structure.currentScene?.anchorId || null;
|
||||||
|
const currentChapterAnchorId = structure.currentChapter?.anchorId || null;
|
||||||
|
const anchoredSceneMatches = Number.isInteger(currentSceneAnchorId) && currentSceneAnchorId === resolvedSceneId;
|
||||||
|
const anchoredChapterMatches = Number.isInteger(currentChapterAnchorId) && currentChapterAnchorId === resolvedChapterId;
|
||||||
|
const titleSceneMatches = normalizeTitleForResolve(currentSceneTitle, "scene").toLocaleLowerCase()
|
||||||
|
=== normalizeTitleForResolve(lastLinkedSceneTitle || detectedSceneTitle, "scene").toLocaleLowerCase();
|
||||||
|
const titleChapterMatches = normalizeTitleForResolve(currentChapterTitle, "chapter").toLocaleLowerCase()
|
||||||
|
=== normalizeTitleForResolve(lastLinkedChapterTitle || detectedChapterTitle, "chapter").toLocaleLowerCase();
|
||||||
|
const matches = anchoredSceneMatches || (titleSceneMatches && (anchoredChapterMatches || titleChapterMatches));
|
||||||
|
|
||||||
|
if (!matches) {
|
||||||
|
setSceneContextStale(true, "The Word cursor is now in a different scene. Refresh Current Scene before saving.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSceneContextStale(false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unable to verify current Word scene before saving.", error);
|
||||||
|
setDiagnostics({ lastError: errorText(error) });
|
||||||
|
setSceneContextStale(true, "The Word cursor is now in a different scene. Refresh Current Scene before saving.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const saveSceneLinks = async () => {
|
const saveSceneLinks = async () => {
|
||||||
if (!resolvedSceneId) {
|
if (!resolvedSceneId) {
|
||||||
setSceneLinksStatus("Link a PlotDirector scene first.");
|
setSceneLinksStatus("Link a PlotDirector scene first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sceneContextStale || !(await verifyCurrentWordSceneMatchesLinkedScene())) {
|
||||||
|
setSceneLinksStatus("The Word cursor is now in a different scene. Refresh Current Scene before saving.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (saveSceneLinksButton) {
|
if (saveSceneLinksButton) {
|
||||||
saveSceneLinksButton.disabled = true;
|
saveSceneLinksButton.disabled = true;
|
||||||
saveSceneLinksButton.textContent = "Saving...";
|
saveSceneLinksButton.textContent = "Saving...";
|
||||||
@ -1878,7 +2023,7 @@
|
|||||||
setDiagnostics({ lastError: errorText(error) });
|
setDiagnostics({ lastError: errorText(error) });
|
||||||
} finally {
|
} finally {
|
||||||
if (saveSceneLinksButton) {
|
if (saveSceneLinksButton) {
|
||||||
saveSceneLinksButton.disabled = !resolvedSceneId;
|
saveSceneLinksButton.disabled = !resolvedSceneId || sceneContextStale;
|
||||||
saveSceneLinksButton.textContent = "Save Scene Links";
|
saveSceneLinksButton.textContent = "Save Scene Links";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2461,6 +2606,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sceneContextStale) {
|
||||||
|
setSyncStatus("Refresh Current Scene before syncing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Number.isInteger(detectedSceneWordCount)) {
|
if (!Number.isInteger(detectedSceneWordCount)) {
|
||||||
setSyncStatus("Unable to sync progress.");
|
setSyncStatus("Unable to sync progress.");
|
||||||
return;
|
return;
|
||||||
@ -2485,7 +2635,7 @@
|
|||||||
setSyncStatus("Unable to sync progress.");
|
setSyncStatus("Unable to sync progress.");
|
||||||
} finally {
|
} finally {
|
||||||
if (syncSceneProgressButton) {
|
if (syncSceneProgressButton) {
|
||||||
syncSceneProgressButton.disabled = !resolvedSceneId;
|
syncSceneProgressButton.disabled = !resolvedSceneId || sceneContextStale;
|
||||||
syncSceneProgressButton.textContent = "Sync Progress";
|
syncSceneProgressButton.textContent = "Sync Progress";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2755,6 +2905,144 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasCurrentWordContextChanged = (structure) => {
|
||||||
|
const chapterTitle = structure.currentChapter?.title || "";
|
||||||
|
const sceneTitle = structure.currentScene?.title || "";
|
||||||
|
const chapterAnchorId = structure.currentChapter?.anchorId || null;
|
||||||
|
const sceneAnchorId = structure.currentScene?.anchorId || null;
|
||||||
|
|
||||||
|
return normalizeTitleForResolve(chapterTitle, "chapter") !== normalizeTitleForResolve(detectedChapterTitle, "chapter")
|
||||||
|
|| normalizeTitleForResolve(sceneTitle, "scene") !== normalizeTitleForResolve(detectedSceneTitle, "scene")
|
||||||
|
|| chapterAnchorId !== detectedChapterAnchorId
|
||||||
|
|| sceneAnchorId !== detectedSceneAnchorId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshCurrentSceneFromSelection = async () => {
|
||||||
|
if (isAutoRefreshingScene || !isAuthenticated || !selectedBook()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
|
setDocumentMessage("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAutoRefreshingScene = true;
|
||||||
|
setSceneTrackingState("Refreshing");
|
||||||
|
setDocumentMessage("Updating current scene...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const structure = await window.Word.run(async (context) => {
|
||||||
|
return await readDocumentStructure(context);
|
||||||
|
});
|
||||||
|
const changed = hasCurrentWordContextChanged(structure);
|
||||||
|
setCurrentStructure(
|
||||||
|
structure.currentChapter?.title,
|
||||||
|
structure.currentScene?.title,
|
||||||
|
structure.currentScene?.wordCount,
|
||||||
|
structure.currentChapter?.anchorId,
|
||||||
|
structure.currentScene?.anchorId,
|
||||||
|
structure.currentChapter?.index
|
||||||
|
);
|
||||||
|
renderOutline(structure);
|
||||||
|
setDiagnostics({
|
||||||
|
lastScan: "Success",
|
||||||
|
lastError: "-",
|
||||||
|
paragraphs: String(structure.paragraphCount),
|
||||||
|
heading1: String(structure.heading1Count),
|
||||||
|
heading2: String(structure.heading2Count)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
setSceneContextStale(false);
|
||||||
|
setSceneTrackingState("Live");
|
||||||
|
setDocumentMessage("Current scene updated.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!detectedSceneTitle) {
|
||||||
|
await refreshPlotDirectorChapter();
|
||||||
|
} else {
|
||||||
|
await refreshPlotDirectorScene();
|
||||||
|
}
|
||||||
|
setSceneContextStale(false);
|
||||||
|
setSceneTrackingState("Live");
|
||||||
|
setDocumentMessage("Current scene updated.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unable to refresh current scene from Word selection.", error);
|
||||||
|
setDiagnostics({ lastScan: "Failure", lastError: errorText(error) });
|
||||||
|
setSceneContextStale(true, "The Word cursor is now in a different scene. Refresh Current Scene before saving.");
|
||||||
|
} finally {
|
||||||
|
isAutoRefreshingScene = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleSelectionRefresh = () => {
|
||||||
|
if (selectionRefreshTimer) {
|
||||||
|
window.clearTimeout(selectionRefreshTimer);
|
||||||
|
}
|
||||||
|
selectionRefreshTimer = window.setTimeout(refreshCurrentSceneFromSelection, 850);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDocumentSelectionChanged = () => {
|
||||||
|
scheduleSelectionRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSelectionTracking = () => {
|
||||||
|
if (!selectionHandlerRegistered
|
||||||
|
|| !window.Office?.context?.document
|
||||||
|
|| typeof window.Office.context.document.removeHandlerAsync !== "function"
|
||||||
|
|| !window.Office?.EventType?.DocumentSelectionChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.Office.context.document.removeHandlerAsync(
|
||||||
|
window.Office.EventType.DocumentSelectionChanged,
|
||||||
|
{ handler: handleDocumentSelectionChanged }
|
||||||
|
);
|
||||||
|
selectionHandlerRegistered = false;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Unable to remove Word selection tracking handler.", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupSelectionTracking = () => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.Office?.context?.document
|
||||||
|
|| typeof window.Office.context.document.addHandlerAsync !== "function"
|
||||||
|
|| !window.Office?.EventType?.DocumentSelectionChanged) {
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
|
setDocumentMessage("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.Office.context.document.addHandlerAsync(
|
||||||
|
window.Office.EventType.DocumentSelectionChanged,
|
||||||
|
handleDocumentSelectionChanged,
|
||||||
|
(result) => {
|
||||||
|
if (result?.status === window.Office.AsyncResultStatus.Succeeded) {
|
||||||
|
selectionHandlerRegistered = true;
|
||||||
|
setSceneTrackingState("Live");
|
||||||
|
} else {
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
|
setDocumentMessage("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
window.addEventListener("beforeunload", removeSelectionTracking);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Unable to register Word selection tracking handler.", error);
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
|
setDocumentMessage("Automatic scene tracking is unavailable in this Word version. Use Refresh Current Scene.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const refreshCurrentScene = async () => {
|
const refreshCurrentScene = async () => {
|
||||||
if (!selectedBook()) {
|
if (!selectedBook()) {
|
||||||
setDocumentMessage("Select a PlotDirector book first.");
|
setDocumentMessage("Select a PlotDirector book first.");
|
||||||
@ -2768,6 +3056,7 @@
|
|||||||
if (!scanned) {
|
if (!scanned) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setSceneContextStale(false);
|
||||||
|
|
||||||
if (!detectedSceneTitle) {
|
if (!detectedSceneTitle) {
|
||||||
await refreshPlotDirectorChapter();
|
await refreshPlotDirectorChapter();
|
||||||
@ -2880,6 +3169,7 @@
|
|||||||
for (const button of tabButtons) {
|
for (const button of tabButtons) {
|
||||||
button.addEventListener("click", () => selectTab(button.dataset.tabButton));
|
button.addEventListener("click", () => selectTab(button.dataset.tabButton));
|
||||||
}
|
}
|
||||||
|
restoreActiveTab();
|
||||||
|
|
||||||
projectSelect?.addEventListener("change", async () => {
|
projectSelect?.addEventListener("change", async () => {
|
||||||
const projectId = Number.parseInt(projectSelect.value || "", 10);
|
const projectId = Number.parseInt(projectSelect.value || "", 10);
|
||||||
@ -2949,6 +3239,7 @@
|
|||||||
if (!window.Office || typeof window.Office.onReady !== "function") {
|
if (!window.Office || typeof window.Office.onReady !== "function") {
|
||||||
setOfficeStatus("Word Companion ready");
|
setOfficeStatus("Word Companion ready");
|
||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
setDiagnostics({
|
setDiagnostics({
|
||||||
host: "Unavailable",
|
host: "Unavailable",
|
||||||
platform: "Unavailable",
|
platform: "Unavailable",
|
||||||
@ -2963,6 +3254,9 @@
|
|||||||
setOfficeStatus("Word Companion ready");
|
setOfficeStatus("Word Companion ready");
|
||||||
if (!wordHostAvailable) {
|
if (!wordHostAvailable) {
|
||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
|
} else {
|
||||||
|
setupSelectionTracking();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user