Connection Status
diff --git a/PlotLine/wwwroot/css/word-companion.css b/PlotLine/wwwroot/css/word-companion.css
index db3b6fc..11fbe7b 100644
--- a/PlotLine/wwwroot/css/word-companion.css
+++ b/PlotLine/wwwroot/css/word-companion.css
@@ -158,11 +158,22 @@ body {
gap: 8px;
}
-.word-companion-structure-grid div {
+.word-companion-structure-grid div,
+.word-companion-diagnostics-grid div {
display: grid;
gap: 2px;
}
+.word-companion-diagnostics-grid {
+ display: grid;
+ gap: 8px;
+ grid-template-columns: 1fr 1fr;
+}
+
+.word-companion-diagnostics-grid strong {
+ font-size: 0.88rem;
+}
+
.word-companion-outline {
display: grid;
gap: 8px;
diff --git a/PlotLine/wwwroot/js/word-companion-host.js b/PlotLine/wwwroot/js/word-companion-host.js
index 15a0504..837975e 100644
--- a/PlotLine/wwwroot/js/word-companion-host.js
+++ b/PlotLine/wwwroot/js/word-companion-host.js
@@ -21,11 +21,22 @@
const documentMessage = document.querySelector("[data-document-message]");
const syncNote = document.querySelector("[data-sync-note]");
const documentOutline = document.querySelector("[data-document-outline]");
+ const runDiagnosticsButton = document.querySelector("[data-run-diagnostics]");
+ const diagnosticHost = document.querySelector("[data-diagnostic-host]");
+ const diagnosticPlatform = document.querySelector("[data-diagnostic-platform]");
+ const diagnosticReady = document.querySelector("[data-diagnostic-ready]");
+ const diagnosticWordApi = document.querySelector("[data-diagnostic-word-api]");
+ const diagnosticLastScan = document.querySelector("[data-diagnostic-last-scan]");
+ const diagnosticParagraphs = document.querySelector("[data-diagnostic-paragraphs]");
+ const diagnosticHeading1 = document.querySelector("[data-diagnostic-heading1]");
+ const diagnosticHeading2 = document.querySelector("[data-diagnostic-heading2]");
const isAuthenticated = shell?.dataset.authenticated === "true";
let projects = [];
let books = [];
let officeReady = false;
let wordHostAvailable = false;
+ let officeHost = "Unknown";
+ let officePlatform = "Unknown";
const setOfficeStatus = (message) => {
if (officeStatus) {
@@ -72,6 +83,91 @@
}
};
+ const setText = (element, value) => {
+ if (element) {
+ element.textContent = value;
+ }
+ };
+
+ const setDiagnostics = (values) => {
+ const hasValue = (name) => Object.prototype.hasOwnProperty.call(values, name);
+
+ if (hasValue("host")) {
+ setText(diagnosticHost, values.host);
+ }
+ if (hasValue("platform")) {
+ setText(diagnosticPlatform, values.platform);
+ }
+ if (hasValue("ready")) {
+ setText(diagnosticReady, values.ready);
+ }
+ if (hasValue("wordApi")) {
+ setText(diagnosticWordApi, values.wordApi);
+ }
+ if (hasValue("lastScan")) {
+ setText(diagnosticLastScan, values.lastScan);
+ }
+ if (hasValue("paragraphs")) {
+ setText(diagnosticParagraphs, values.paragraphs);
+ }
+ if (hasValue("heading1")) {
+ setText(diagnosticHeading1, values.heading1);
+ }
+ if (hasValue("heading2")) {
+ setText(diagnosticHeading2, values.heading2);
+ }
+ };
+
+ const errorText = (error) => {
+ const parts = [];
+
+ if (error?.name) {
+ parts.push(error.name);
+ }
+ if (error?.code && error.code !== error.name) {
+ parts.push(error.code);
+ }
+ if (error?.message) {
+ parts.push(error.message);
+ }
+ if (error?.debugInfo?.errorLocation) {
+ parts.push(`Location: ${error.debugInfo.errorLocation}`);
+ }
+
+ return parts.length > 0 ? parts.join(": ") : String(error);
+ };
+
+ const updateOfficeReadiness = async () => {
+ if (!window.Office || typeof window.Office.onReady !== "function") {
+ officeReady = false;
+ wordHostAvailable = false;
+ officeHost = "Unavailable";
+ officePlatform = "Unavailable";
+ setDiagnostics({
+ host: officeHost,
+ platform: officePlatform,
+ ready: "No",
+ wordApi: "No"
+ });
+ return null;
+ }
+
+ const info = await window.Office.onReady();
+ officeReady = true;
+ officeHost = info?.host || "Unknown";
+ officePlatform = info?.platform || "Unknown";
+ wordHostAvailable = !!window.Word
+ && !!window.Office.HostType
+ && info?.host === window.Office.HostType.Word;
+ setDiagnostics({
+ host: officeHost,
+ platform: officePlatform,
+ ready: "Yes",
+ wordApi: wordHostAvailable ? "Yes" : "No"
+ });
+ return info;
+ };
+
const readStoredId = (key) => {
try {
const value = window.localStorage.getItem(key);
@@ -175,6 +271,8 @@
const buildDocumentStructure = (paragraphs, selectionParagraphs) => {
const chapters = [];
+ let heading1Count = 0;
+ let heading2Count = 0;
let chapter = null;
let scene = null;
@@ -185,6 +283,7 @@
}
if (isBuiltInHeading(paragraph, 1)) {
+ heading1Count += 1;
chapter = {
index: chapters.length + 1,
paragraphIndex: index,
@@ -197,6 +296,7 @@
}
if (isBuiltInHeading(paragraph, 2)) {
+ heading2Count += 1;
if (!chapter) {
return;
}
@@ -230,7 +330,10 @@
return {
chapters,
currentChapter: detectedChapter,
- currentScene: detectedScene
+ currentScene: detectedScene,
+ paragraphCount: paragraphs.length,
+ heading1Count,
+ heading2Count
};
};
@@ -283,6 +386,7 @@
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
setCurrentStructure(null, null, null);
setDocumentMessage("Word document access is unavailable.");
+ setDiagnostics({ lastScan: "Failure" });
return;
}
@@ -311,9 +415,18 @@
);
renderOutline(structure);
setDocumentMessage("");
- } catch {
+ setDiagnostics({
+ lastScan: "Success",
+ paragraphs: String(structure.paragraphCount),
+ heading1: String(structure.heading1Count),
+ heading2: String(structure.heading2Count)
+ });
+ } catch (error) {
+ const message = errorText(error);
+ console.error("Unable to scan the Word document.", error);
setCurrentStructure(null, null, null);
- setDocumentMessage("Unable to scan the Word document.");
+ setDocumentMessage(`Unable to scan the Word document: ${message}`);
+ setDiagnostics({ lastScan: "Failure" });
} finally {
if (refreshDocumentButton) {
refreshDocumentButton.disabled = false;
@@ -322,6 +435,48 @@
}
};
+ const runDiagnostics = async () => {
+ if (runDiagnosticsButton) {
+ runDiagnosticsButton.disabled = true;
+ runDiagnosticsButton.textContent = "Running...";
+ }
+
+ try {
+ await updateOfficeReadiness();
+
+ if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
+ setDiagnostics({
+ lastScan: "Failure",
+ paragraphs: "-",
+ heading1: "-",
+ heading2: "-"
+ });
+ return;
+ }
+
+ const paragraphCount = await window.Word.run(async (context) => {
+ const paragraphs = context.document.body.paragraphs;
+ paragraphs.load("items");
+ await context.sync();
+ return paragraphs.items.length;
+ });
+
+ setDiagnostics({
+ lastScan: "Success",
+ paragraphs: String(paragraphCount)
+ });
+ } catch (error) {
+ console.error("Word Companion diagnostics failed.", error);
+ setDiagnostics({ lastScan: "Failure" });
+ setDocumentMessage(`Unable to scan the Word document: ${errorText(error)}`);
+ } finally {
+ if (runDiagnosticsButton) {
+ runDiagnosticsButton.disabled = false;
+ runDiagnosticsButton.textContent = "Run Diagnostics";
+ }
+ }
+ };
+
const fetchJson = async (url) => {
const response = await window.fetch(url, {
credentials: "same-origin",
@@ -436,6 +591,7 @@
});
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
+ runDiagnosticsButton?.addEventListener("click", runDiagnostics);
if (isAuthenticated) {
loadProjects();
@@ -448,23 +604,31 @@
if (!window.Office || typeof window.Office.onReady !== "function") {
setOfficeStatus("Word Companion ready");
setDocumentMessage("Word document access is unavailable.");
+ setDiagnostics({
+ host: "Unavailable",
+ platform: "Unavailable",
+ ready: "No",
+ wordApi: "No"
+ });
return;
}
- window.Office.onReady()
- .then((info) => {
- officeReady = true;
- wordHostAvailable = !!window.Word
- && !!window.Office.HostType
- && info?.host === window.Office.HostType.Word;
+ updateOfficeReadiness()
+ .then(() => {
setOfficeStatus("Word Companion ready");
if (!wordHostAvailable) {
setDocumentMessage("Word document access is unavailable.");
}
})
- .catch(() => {
+ .catch((error) => {
+ console.error("Office readiness check failed.", error);
setConnectionStatus("Unable to connect to PlotDirector.");
setOfficeStatus("Word Companion unavailable");
setDocumentMessage("Word document access is unavailable.");
+ setDiagnostics({
+ ready: "No",
+ wordApi: "No",
+ lastScan: "Failure"
+ });
});
})();