Phase 10D.1: Word Companion Diagnostics
This commit is contained in:
parent
2b77e85bbc
commit
234176b3ae
@ -75,6 +75,47 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-section" aria-label="Diagnostics">
|
||||
<div class="word-companion-section-header">
|
||||
<span>Diagnostics</span>
|
||||
<button type="button" data-run-diagnostics>Run Diagnostics</button>
|
||||
</div>
|
||||
<div class="word-companion-diagnostics-grid">
|
||||
<div>
|
||||
<span>Office Host</span>
|
||||
<strong data-diagnostic-host>Unknown</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Office Platform</span>
|
||||
<strong data-diagnostic-platform>Unknown</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Office Ready</span>
|
||||
<strong data-diagnostic-ready>No</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Word API Available</span>
|
||||
<strong data-diagnostic-word-api>No</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Last Scan Result</span>
|
||||
<strong data-diagnostic-last-scan>Not run</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Paragraphs Found</span>
|
||||
<strong data-diagnostic-paragraphs>-</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Heading 1 Found</span>
|
||||
<strong data-diagnostic-heading1>-</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Heading 2 Found</span>
|
||||
<strong data-diagnostic-heading2>-</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-summary" aria-label="Connection summary">
|
||||
<div>
|
||||
<span>Connection Status</span>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user