Phase 10D.1: Word Companion Diagnostics
This commit is contained in:
parent
2b77e85bbc
commit
234176b3ae
@ -75,6 +75,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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">
|
<section class="word-companion-summary" aria-label="Connection summary">
|
||||||
<div>
|
<div>
|
||||||
<span>Connection Status</span>
|
<span>Connection Status</span>
|
||||||
|
|||||||
@ -158,11 +158,22 @@ body {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.word-companion-structure-grid div {
|
.word-companion-structure-grid div,
|
||||||
|
.word-companion-diagnostics-grid div {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 2px;
|
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 {
|
.word-companion-outline {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|||||||
@ -21,11 +21,22 @@
|
|||||||
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]");
|
||||||
|
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";
|
const isAuthenticated = shell?.dataset.authenticated === "true";
|
||||||
let projects = [];
|
let projects = [];
|
||||||
let books = [];
|
let books = [];
|
||||||
let officeReady = false;
|
let officeReady = false;
|
||||||
let wordHostAvailable = false;
|
let wordHostAvailable = false;
|
||||||
|
let officeHost = "Unknown";
|
||||||
|
let officePlatform = "Unknown";
|
||||||
|
|
||||||
const setOfficeStatus = (message) => {
|
const setOfficeStatus = (message) => {
|
||||||
if (officeStatus) {
|
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) => {
|
const readStoredId = (key) => {
|
||||||
try {
|
try {
|
||||||
const value = window.localStorage.getItem(key);
|
const value = window.localStorage.getItem(key);
|
||||||
@ -175,6 +271,8 @@
|
|||||||
|
|
||||||
const buildDocumentStructure = (paragraphs, selectionParagraphs) => {
|
const buildDocumentStructure = (paragraphs, selectionParagraphs) => {
|
||||||
const chapters = [];
|
const chapters = [];
|
||||||
|
let heading1Count = 0;
|
||||||
|
let heading2Count = 0;
|
||||||
let chapter = null;
|
let chapter = null;
|
||||||
let scene = null;
|
let scene = null;
|
||||||
|
|
||||||
@ -185,6 +283,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isBuiltInHeading(paragraph, 1)) {
|
if (isBuiltInHeading(paragraph, 1)) {
|
||||||
|
heading1Count += 1;
|
||||||
chapter = {
|
chapter = {
|
||||||
index: chapters.length + 1,
|
index: chapters.length + 1,
|
||||||
paragraphIndex: index,
|
paragraphIndex: index,
|
||||||
@ -197,6 +296,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isBuiltInHeading(paragraph, 2)) {
|
if (isBuiltInHeading(paragraph, 2)) {
|
||||||
|
heading2Count += 1;
|
||||||
if (!chapter) {
|
if (!chapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -230,7 +330,10 @@
|
|||||||
return {
|
return {
|
||||||
chapters,
|
chapters,
|
||||||
currentChapter: detectedChapter,
|
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") {
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
setCurrentStructure(null, null, null);
|
setCurrentStructure(null, null, null);
|
||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
|
setDiagnostics({ lastScan: "Failure" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,9 +415,18 @@
|
|||||||
);
|
);
|
||||||
renderOutline(structure);
|
renderOutline(structure);
|
||||||
setDocumentMessage("");
|
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);
|
setCurrentStructure(null, null, null);
|
||||||
setDocumentMessage("Unable to scan the Word document.");
|
setDocumentMessage(`Unable to scan the Word document: ${message}`);
|
||||||
|
setDiagnostics({ lastScan: "Failure" });
|
||||||
} finally {
|
} finally {
|
||||||
if (refreshDocumentButton) {
|
if (refreshDocumentButton) {
|
||||||
refreshDocumentButton.disabled = false;
|
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 fetchJson = async (url) => {
|
||||||
const response = await window.fetch(url, {
|
const response = await window.fetch(url, {
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
@ -436,6 +591,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
||||||
|
runDiagnosticsButton?.addEventListener("click", runDiagnostics);
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
loadProjects();
|
loadProjects();
|
||||||
@ -448,23 +604,31 @@
|
|||||||
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.");
|
||||||
|
setDiagnostics({
|
||||||
|
host: "Unavailable",
|
||||||
|
platform: "Unavailable",
|
||||||
|
ready: "No",
|
||||||
|
wordApi: "No"
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Office.onReady()
|
updateOfficeReadiness()
|
||||||
.then((info) => {
|
.then(() => {
|
||||||
officeReady = true;
|
|
||||||
wordHostAvailable = !!window.Word
|
|
||||||
&& !!window.Office.HostType
|
|
||||||
&& info?.host === window.Office.HostType.Word;
|
|
||||||
setOfficeStatus("Word Companion ready");
|
setOfficeStatus("Word Companion ready");
|
||||||
if (!wordHostAvailable) {
|
if (!wordHostAvailable) {
|
||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
|
console.error("Office readiness check failed.", error);
|
||||||
setConnectionStatus("Unable to connect to PlotDirector.");
|
setConnectionStatus("Unable to connect to PlotDirector.");
|
||||||
setOfficeStatus("Word Companion unavailable");
|
setOfficeStatus("Word Companion unavailable");
|
||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
|
setDiagnostics({
|
||||||
|
ready: "No",
|
||||||
|
wordApi: "No",
|
||||||
|
lastScan: "Failure"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user