Phase 10D manual document structure.
This commit is contained in:
parent
bb774a5066
commit
2b77e85bbc
@ -28,14 +28,20 @@
|
||||
}
|
||||
</header>
|
||||
|
||||
<section class="word-companion-section" aria-label="Project">
|
||||
<span>Project</span>
|
||||
<strong>(Not connected)</strong>
|
||||
<section class="word-companion-section" aria-label="Project selection">
|
||||
<label for="word-companion-project">Project</label>
|
||||
<select id="word-companion-project" data-project-select disabled>
|
||||
<option value="">Loading projects...</option>
|
||||
</select>
|
||||
<p class="word-companion-message" data-project-message></p>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-section" aria-label="Book">
|
||||
<span>Book</span>
|
||||
<strong>(Not connected)</strong>
|
||||
<section class="word-companion-section" aria-label="Book selection">
|
||||
<label for="word-companion-book">Book</label>
|
||||
<select id="word-companion-book" data-book-select disabled>
|
||||
<option value="">Select a project first</option>
|
||||
</select>
|
||||
<p class="word-companion-message" data-book-message></p>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-section" aria-label="Current Scene">
|
||||
@ -43,6 +49,47 @@
|
||||
<strong>(Not connected)</strong>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-section word-companion-document" aria-label="Document Structure">
|
||||
<div class="word-companion-section-header">
|
||||
<span>Document Structure</span>
|
||||
<button type="button" data-refresh-document>Refresh Document Structure</button>
|
||||
</div>
|
||||
<div class="word-companion-structure-grid">
|
||||
<div>
|
||||
<span>Current Chapter</span>
|
||||
<strong data-current-chapter>Not detected</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Current Scene</span>
|
||||
<strong data-current-scene>Not detected</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Current Scene Word Count</span>
|
||||
<strong data-current-word-count>-</strong>
|
||||
</div>
|
||||
</div>
|
||||
<p class="word-companion-message" data-document-message></p>
|
||||
<p class="word-companion-message" data-sync-note></p>
|
||||
<div class="word-companion-outline" data-document-outline>
|
||||
<p>No structure scanned.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-summary" aria-label="Connection summary">
|
||||
<div>
|
||||
<span>Connection Status</span>
|
||||
<strong data-summary-connection>@statusText</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Current Project</span>
|
||||
<strong data-summary-project>(Not connected)</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Current Book</span>
|
||||
<strong data-summary-book>(Not connected)</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="word-companion-footer">
|
||||
<span data-office-status>Word Companion loading</span>
|
||||
</footer>
|
||||
@ -50,4 +97,3 @@
|
||||
<script src="~/js/word-companion-host.js" asp-append-version="true"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@ body {
|
||||
|
||||
.word-companion-status,
|
||||
.word-companion-user,
|
||||
.word-companion-message,
|
||||
.word-companion-footer {
|
||||
margin: 0;
|
||||
color: var(--companion-muted);
|
||||
@ -81,29 +82,139 @@ body {
|
||||
|
||||
.word-companion-section {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--companion-line);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: var(--companion-panel);
|
||||
}
|
||||
|
||||
.word-companion-section span {
|
||||
.word-companion-section span,
|
||||
.word-companion-section label,
|
||||
.word-companion-summary span {
|
||||
color: var(--companion-muted);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.word-companion-section label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.word-companion-section select {
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
border: 1px solid var(--companion-line);
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
background: #ffffff;
|
||||
color: var(--companion-ink);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.word-companion-section select:disabled {
|
||||
background: var(--companion-soft);
|
||||
color: var(--companion-muted);
|
||||
}
|
||||
|
||||
.word-companion-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.word-companion-section-header button {
|
||||
min-height: 32px;
|
||||
border: 1px solid var(--companion-accent);
|
||||
border-radius: 6px;
|
||||
padding: 5px 9px;
|
||||
background: var(--companion-accent);
|
||||
color: #ffffff;
|
||||
font: inherit;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.word-companion-section-header button:disabled {
|
||||
border-color: var(--companion-line);
|
||||
background: var(--companion-soft);
|
||||
color: var(--companion-muted);
|
||||
}
|
||||
|
||||
.word-companion-message:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.word-companion-section strong {
|
||||
min-width: 0;
|
||||
font-size: 0.98rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.word-companion-structure-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.word-companion-structure-grid div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.word-companion-outline {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
border-top: 1px solid var(--companion-line);
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.word-companion-outline p {
|
||||
margin: 0;
|
||||
color: var(--companion-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.word-companion-outline-chapter {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.word-companion-outline-chapter ul {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.word-companion-outline-chapter li {
|
||||
color: var(--companion-ink);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.word-companion-summary {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
border: 1px solid rgba(47, 111, 99, 0.18);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: var(--companion-soft);
|
||||
}
|
||||
|
||||
.word-companion-summary div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.word-companion-summary strong {
|
||||
min-width: 0;
|
||||
font-size: 0.92rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.word-companion-footer {
|
||||
border-top: 1px solid var(--companion-line);
|
||||
margin-top: auto;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,31 @@
|
||||
(() => {
|
||||
const storageKeys = {
|
||||
projectId: "plotdirector.word.projectId",
|
||||
bookId: "plotdirector.word.bookId"
|
||||
};
|
||||
|
||||
const shell = document.querySelector(".word-companion-shell");
|
||||
const officeStatus = document.querySelector("[data-office-status]");
|
||||
const connectionStatus = document.querySelector("[data-connection-status]");
|
||||
const summaryConnection = document.querySelector("[data-summary-connection]");
|
||||
const summaryProject = document.querySelector("[data-summary-project]");
|
||||
const summaryBook = document.querySelector("[data-summary-book]");
|
||||
const projectSelect = document.querySelector("[data-project-select]");
|
||||
const bookSelect = document.querySelector("[data-book-select]");
|
||||
const projectMessage = document.querySelector("[data-project-message]");
|
||||
const bookMessage = document.querySelector("[data-book-message]");
|
||||
const refreshDocumentButton = document.querySelector("[data-refresh-document]");
|
||||
const currentChapter = document.querySelector("[data-current-chapter]");
|
||||
const currentScene = document.querySelector("[data-current-scene]");
|
||||
const currentWordCount = document.querySelector("[data-current-word-count]");
|
||||
const documentMessage = document.querySelector("[data-document-message]");
|
||||
const syncNote = document.querySelector("[data-sync-note]");
|
||||
const documentOutline = document.querySelector("[data-document-outline]");
|
||||
const isAuthenticated = shell?.dataset.authenticated === "true";
|
||||
let projects = [];
|
||||
let books = [];
|
||||
let officeReady = false;
|
||||
let wordHostAvailable = false;
|
||||
|
||||
const setOfficeStatus = (message) => {
|
||||
if (officeStatus) {
|
||||
@ -12,20 +37,434 @@
|
||||
if (connectionStatus) {
|
||||
connectionStatus.textContent = message;
|
||||
}
|
||||
if (summaryConnection) {
|
||||
summaryConnection.textContent = message;
|
||||
}
|
||||
};
|
||||
|
||||
const setProjectMessage = (message) => {
|
||||
if (projectMessage) {
|
||||
projectMessage.textContent = message;
|
||||
}
|
||||
};
|
||||
|
||||
const setBookMessage = (message) => {
|
||||
if (bookMessage) {
|
||||
bookMessage.textContent = message;
|
||||
}
|
||||
};
|
||||
|
||||
const setDocumentMessage = (message) => {
|
||||
if (documentMessage) {
|
||||
documentMessage.textContent = message;
|
||||
}
|
||||
};
|
||||
|
||||
const setCurrentStructure = (chapterTitle, sceneTitle, wordCount) => {
|
||||
if (currentChapter) {
|
||||
currentChapter.textContent = chapterTitle || "Not detected";
|
||||
}
|
||||
if (currentScene) {
|
||||
currentScene.textContent = sceneTitle || "Not detected";
|
||||
}
|
||||
if (currentWordCount) {
|
||||
currentWordCount.textContent = Number.isInteger(wordCount) ? String(wordCount) : "-";
|
||||
}
|
||||
};
|
||||
|
||||
const readStoredId = (key) => {
|
||||
try {
|
||||
const value = window.localStorage.getItem(key);
|
||||
const id = Number.parseInt(value || "", 10);
|
||||
return Number.isInteger(id) && id > 0 ? id : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const writeStoredId = (key, value) => {
|
||||
try {
|
||||
if (value) {
|
||||
window.localStorage.setItem(key, String(value));
|
||||
} else {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
} catch {
|
||||
// Some Office hosts can restrict storage. Selection still works for the current pane session.
|
||||
}
|
||||
};
|
||||
|
||||
const resetSelect = (select, message) => {
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
select.innerHTML = "";
|
||||
const option = document.createElement("option");
|
||||
option.value = "";
|
||||
option.textContent = message;
|
||||
select.append(option);
|
||||
select.disabled = true;
|
||||
};
|
||||
|
||||
const addOptions = (select, placeholder, rows, idKey, titleKey) => {
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
select.innerHTML = "";
|
||||
const placeholderOption = document.createElement("option");
|
||||
placeholderOption.value = "";
|
||||
placeholderOption.textContent = placeholder;
|
||||
select.append(placeholderOption);
|
||||
|
||||
for (const row of rows) {
|
||||
const option = document.createElement("option");
|
||||
option.value = String(row[idKey]);
|
||||
option.textContent = row[titleKey];
|
||||
select.append(option);
|
||||
}
|
||||
|
||||
select.disabled = rows.length === 0;
|
||||
};
|
||||
|
||||
const selectedProject = () => {
|
||||
const projectId = Number.parseInt(projectSelect?.value || "", 10);
|
||||
return projects.find((project) => project.projectId === projectId) || null;
|
||||
};
|
||||
|
||||
const selectedBook = () => {
|
||||
const bookId = Number.parseInt(bookSelect?.value || "", 10);
|
||||
return books.find((book) => book.bookId === bookId) || null;
|
||||
};
|
||||
|
||||
const updateSummary = () => {
|
||||
const project = selectedProject();
|
||||
const book = selectedBook();
|
||||
|
||||
if (summaryProject) {
|
||||
summaryProject.textContent = project?.title || "(Not connected)";
|
||||
}
|
||||
if (summaryBook) {
|
||||
summaryBook.textContent = book?.title || "(Not connected)";
|
||||
}
|
||||
if (syncNote) {
|
||||
syncNote.textContent = book ? "" : "Select a PlotDirector book before syncing in a future step.";
|
||||
}
|
||||
};
|
||||
|
||||
const normalizedStyleName = (paragraph) => {
|
||||
const style = paragraph?.styleBuiltIn || paragraph?.style || "";
|
||||
return String(style).replace(/\s+/g, "").toLowerCase();
|
||||
};
|
||||
|
||||
const isBuiltInHeading = (paragraph, level) => {
|
||||
const style = normalizedStyleName(paragraph);
|
||||
return style === `heading${level}` || style.endsWith(`.heading${level}`) || style.includes(`heading${level}`);
|
||||
};
|
||||
|
||||
const countWords = (text) => {
|
||||
const words = String(text || "").trim().split(/\s+/).filter(Boolean);
|
||||
return words.length;
|
||||
};
|
||||
|
||||
const paragraphMatches = (left, right) => {
|
||||
return String(left?.text || "").trim() === String(right?.text || "").trim()
|
||||
&& normalizedStyleName(left) === normalizedStyleName(right);
|
||||
};
|
||||
|
||||
const buildDocumentStructure = (paragraphs, selectionParagraphs) => {
|
||||
const chapters = [];
|
||||
let chapter = null;
|
||||
let scene = null;
|
||||
|
||||
paragraphs.forEach((paragraph, index) => {
|
||||
const title = String(paragraph.text || "").trim();
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBuiltInHeading(paragraph, 1)) {
|
||||
chapter = {
|
||||
index: chapters.length + 1,
|
||||
paragraphIndex: index,
|
||||
title,
|
||||
scenes: []
|
||||
};
|
||||
chapters.push(chapter);
|
||||
scene = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBuiltInHeading(paragraph, 2)) {
|
||||
if (!chapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
scene = {
|
||||
index: chapter.scenes.length + 1,
|
||||
paragraphIndex: index,
|
||||
title,
|
||||
wordCount: 0
|
||||
};
|
||||
chapter.scenes.push(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene) {
|
||||
scene.wordCount += countWords(title);
|
||||
}
|
||||
});
|
||||
|
||||
const selectedParagraph = selectionParagraphs.find((paragraph) => String(paragraph.text || "").trim());
|
||||
const selectedIndex = selectedParagraph
|
||||
? paragraphs.findIndex((paragraph) => paragraphMatches(paragraph, selectedParagraph))
|
||||
: -1;
|
||||
const detectedChapter = selectedIndex >= 0
|
||||
? chapters.filter((item) => item.paragraphIndex <= selectedIndex).at(-1) || null
|
||||
: null;
|
||||
const detectedScene = detectedChapter && selectedIndex >= 0
|
||||
? detectedChapter.scenes.filter((item) => item.paragraphIndex <= selectedIndex).at(-1) || null
|
||||
: null;
|
||||
|
||||
return {
|
||||
chapters,
|
||||
currentChapter: detectedChapter,
|
||||
currentScene: detectedScene
|
||||
};
|
||||
};
|
||||
|
||||
const renderOutline = (structure) => {
|
||||
if (!documentOutline) {
|
||||
return;
|
||||
}
|
||||
|
||||
documentOutline.innerHTML = "";
|
||||
|
||||
if (structure.chapters.length === 0) {
|
||||
const message = document.createElement("p");
|
||||
message.textContent = "No chapters detected. Use Heading 1 for chapter titles.";
|
||||
documentOutline.append(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasScenes = structure.chapters.some((chapter) => chapter.scenes.length > 0);
|
||||
if (!hasScenes) {
|
||||
const message = document.createElement("p");
|
||||
message.textContent = "No scenes detected. Use Heading 2 for scene titles.";
|
||||
documentOutline.append(message);
|
||||
}
|
||||
|
||||
for (const chapter of structure.chapters) {
|
||||
const chapterBlock = document.createElement("div");
|
||||
chapterBlock.className = "word-companion-outline-chapter";
|
||||
|
||||
const chapterTitle = document.createElement("strong");
|
||||
chapterTitle.textContent = `Chapter ${chapter.index}: ${chapter.title}`;
|
||||
chapterBlock.append(chapterTitle);
|
||||
|
||||
if (chapter.scenes.length > 0) {
|
||||
const sceneList = document.createElement("ul");
|
||||
for (const sceneItem of chapter.scenes) {
|
||||
const sceneRow = document.createElement("li");
|
||||
sceneRow.textContent = `Scene ${sceneItem.index}: ${sceneItem.title}`;
|
||||
sceneList.append(sceneRow);
|
||||
}
|
||||
chapterBlock.append(sceneList);
|
||||
}
|
||||
|
||||
documentOutline.append(chapterBlock);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshDocumentStructure = async () => {
|
||||
setDocumentMessage("");
|
||||
|
||||
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||
setCurrentStructure(null, null, null);
|
||||
setDocumentMessage("Word document access is unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshDocumentButton) {
|
||||
refreshDocumentButton.disabled = true;
|
||||
refreshDocumentButton.textContent = "Scanning...";
|
||||
}
|
||||
setDocumentMessage("Scanning document structure...");
|
||||
|
||||
try {
|
||||
const structure = await window.Word.run(async (context) => {
|
||||
const bodyParagraphs = context.document.body.paragraphs;
|
||||
const selectionParagraphs = context.document.getSelection().paragraphs;
|
||||
|
||||
bodyParagraphs.load("text,styleBuiltIn,style");
|
||||
selectionParagraphs.load("text,styleBuiltIn,style");
|
||||
await context.sync();
|
||||
|
||||
return buildDocumentStructure(bodyParagraphs.items, selectionParagraphs.items);
|
||||
});
|
||||
|
||||
setCurrentStructure(
|
||||
structure.currentChapter?.title,
|
||||
structure.currentScene?.title,
|
||||
structure.currentScene?.wordCount
|
||||
);
|
||||
renderOutline(structure);
|
||||
setDocumentMessage("");
|
||||
} catch {
|
||||
setCurrentStructure(null, null, null);
|
||||
setDocumentMessage("Unable to scan the Word document.");
|
||||
} finally {
|
||||
if (refreshDocumentButton) {
|
||||
refreshDocumentButton.disabled = false;
|
||||
refreshDocumentButton.textContent = "Refresh Document Structure";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchJson = async (url) => {
|
||||
const response = await window.fetch(url, {
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const loadBooks = async (projectId, preferredBookId) => {
|
||||
if (!projectId) {
|
||||
books = [];
|
||||
resetSelect(bookSelect, "Select a project first");
|
||||
setBookMessage("");
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
return;
|
||||
}
|
||||
|
||||
resetSelect(bookSelect, "Loading books...");
|
||||
setBookMessage("");
|
||||
|
||||
try {
|
||||
const data = await fetchJson(`/api/word-companion/projects/${projectId}/books`);
|
||||
books = Array.isArray(data.books) ? data.books : [];
|
||||
|
||||
if (books.length === 0) {
|
||||
resetSelect(bookSelect, "No books available.");
|
||||
setBookMessage("No books available.");
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
return;
|
||||
}
|
||||
|
||||
addOptions(bookSelect, "Choose a book", books, "bookId", "title");
|
||||
const validBook = books.find((book) => book.bookId === preferredBookId);
|
||||
if (validBook && bookSelect) {
|
||||
bookSelect.value = String(validBook.bookId);
|
||||
writeStoredId(storageKeys.bookId, validBook.bookId);
|
||||
} else {
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
}
|
||||
updateSummary();
|
||||
} catch {
|
||||
books = [];
|
||||
resetSelect(bookSelect, "Unable to load books.");
|
||||
setBookMessage("Unable to load books.");
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
}
|
||||
};
|
||||
|
||||
const loadProjects = async () => {
|
||||
resetSelect(projectSelect, "Loading projects...");
|
||||
resetSelect(bookSelect, "Select a project first");
|
||||
setProjectMessage("");
|
||||
setBookMessage("");
|
||||
|
||||
try {
|
||||
const data = await fetchJson("/api/word-companion/projects");
|
||||
projects = Array.isArray(data.projects) ? data.projects : [];
|
||||
|
||||
if (projects.length === 0) {
|
||||
resetSelect(projectSelect, "No projects available.");
|
||||
setProjectMessage("No projects available.");
|
||||
writeStoredId(storageKeys.projectId, null);
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
return;
|
||||
}
|
||||
|
||||
addOptions(projectSelect, "Choose a project", projects, "projectId", "title");
|
||||
const storedProjectId = readStoredId(storageKeys.projectId);
|
||||
const validProject = projects.find((project) => project.projectId === storedProjectId);
|
||||
if (validProject && projectSelect) {
|
||||
projectSelect.value = String(validProject.projectId);
|
||||
writeStoredId(storageKeys.projectId, validProject.projectId);
|
||||
await loadBooks(validProject.projectId, readStoredId(storageKeys.bookId));
|
||||
} else {
|
||||
writeStoredId(storageKeys.projectId, null);
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
}
|
||||
} catch {
|
||||
projects = [];
|
||||
books = [];
|
||||
resetSelect(projectSelect, "Unable to connect to PlotDirector.");
|
||||
resetSelect(bookSelect, "Select a project first");
|
||||
setConnectionStatus("Unable to connect to PlotDirector.");
|
||||
setProjectMessage("Unable to connect to PlotDirector.");
|
||||
writeStoredId(storageKeys.projectId, null);
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
}
|
||||
};
|
||||
|
||||
projectSelect?.addEventListener("change", async () => {
|
||||
const projectId = Number.parseInt(projectSelect.value || "", 10);
|
||||
writeStoredId(storageKeys.projectId, Number.isInteger(projectId) && projectId > 0 ? projectId : null);
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
await loadBooks(projectId, null);
|
||||
});
|
||||
|
||||
bookSelect?.addEventListener("change", () => {
|
||||
const bookId = Number.parseInt(bookSelect.value || "", 10);
|
||||
writeStoredId(storageKeys.bookId, Number.isInteger(bookId) && bookId > 0 ? bookId : null);
|
||||
updateSummary();
|
||||
});
|
||||
|
||||
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
||||
|
||||
if (isAuthenticated) {
|
||||
loadProjects();
|
||||
} else {
|
||||
resetSelect(projectSelect, "Please sign in to PlotDirector.");
|
||||
resetSelect(bookSelect, "Please sign in to PlotDirector.");
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
if (!window.Office || typeof window.Office.onReady !== "function") {
|
||||
setOfficeStatus("Word Companion ready");
|
||||
setDocumentMessage("Word document access is unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.Office.onReady()
|
||||
.then(() => {
|
||||
.then((info) => {
|
||||
officeReady = true;
|
||||
wordHostAvailable = !!window.Word
|
||||
&& !!window.Office.HostType
|
||||
&& info?.host === window.Office.HostType.Word;
|
||||
setOfficeStatus("Word Companion ready");
|
||||
if (!wordHostAvailable) {
|
||||
setDocumentMessage("Word document access is unavailable.");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setConnectionStatus("Unable to connect to PlotDirector.");
|
||||
setOfficeStatus("Word Companion unavailable");
|
||||
setDocumentMessage("Word document access is unavailable.");
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user