Add safe Word manuscript cleanup
This commit is contained in:
parent
884a96d166
commit
451e384966
@ -80,7 +80,12 @@ var tests = new (string Name, Action Test)[]
|
|||||||
("Writing Intent route uses canonical story context and planning data", WritingIntentRouteUsesCanonicalStoryContextAndPlanningData),
|
("Writing Intent route uses canonical story context and planning data", WritingIntentRouteUsesCanonicalStoryContextAndPlanningData),
|
||||||
("Writing Intent client uses shared writing follow helper", WritingIntentClientUsesSharedWritingFollowHelper),
|
("Writing Intent client uses shared writing follow helper", WritingIntentClientUsesSharedWritingFollowHelper),
|
||||||
("Writing mode pages are isolated from global Word navigation", WritingModePagesAreIsolatedFromGlobalWordNavigation),
|
("Writing mode pages are isolated from global Word navigation", WritingModePagesAreIsolatedFromGlobalWordNavigation),
|
||||||
("Writing pair shares canonical follow infrastructure", WritingPairSharesCanonicalFollowInfrastructure)
|
("Writing pair shares canonical follow infrastructure", WritingPairSharesCanonicalFollowInfrastructure),
|
||||||
|
("Word manuscript cleanup classifies PlotDirector markers by tag only", WordManuscriptCleanupClassifiesPlotDirectorMarkersByTagOnly),
|
||||||
|
("Word manuscript cleanup preserves content controls content", WordManuscriptCleanupPreservesContentControlContent),
|
||||||
|
("Word manuscript cleanup UI separates unlink from clean unlink", WordManuscriptCleanupUiSeparatesUnlinkFromCleanUnlink),
|
||||||
|
("Word manuscript unlink keeps Story Intelligence data untouched", WordManuscriptUnlinkKeepsStoryIntelligenceDataUntouched),
|
||||||
|
("Word manuscript cleanup remains relinkable and idempotent", WordManuscriptCleanupRemainsRelinkableAndIdempotent)
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var test in tests)
|
foreach (var test in tests)
|
||||||
@ -264,6 +269,94 @@ static void WritingPairSharesCanonicalFollowInfrastructure()
|
|||||||
Assert(helper.Contains("document.addEventListener(window.PlotDirectorStoryPosition?.eventName", StringComparison.Ordinal), "Both pages should react to the same canonical browser event.");
|
Assert(helper.Contains("document.addEventListener(window.PlotDirectorStoryPosition?.eventName", StringComparison.Ordinal), "Both pages should react to the same canonical browser event.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void WordManuscriptCleanupClassifiesPlotDirectorMarkersByTagOnly()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-host.js"));
|
||||||
|
var classifierStart = script.IndexOf("const classifyPlotDirectorContentControl", StringComparison.Ordinal);
|
||||||
|
var classifierEnd = script.IndexOf("window.PlotDirectorWordMarkers", classifierStart, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
Assert(classifierStart >= 0 && classifierEnd > classifierStart, "Word host should expose a stable PlotDirector marker classifier.");
|
||||||
|
var classifier = script[classifierStart..classifierEnd];
|
||||||
|
Assert(classifier.Contains("const tag = String(control?.tag || \"\").trim().toUpperCase()", StringComparison.Ordinal), "Marker cleanup should classify content controls from the tag field.");
|
||||||
|
Assert(script.Contains("chapter: \"PD-CHAPTER-\"", StringComparison.Ordinal), "Chapter marker prefix is missing.");
|
||||||
|
Assert(script.Contains("scene: \"PD-SCENE-\"", StringComparison.Ordinal), "Scene marker prefix is missing.");
|
||||||
|
Assert(classifier.Contains("tag.startsWith(plotDirectorMarkerPrefixes.chapter)", StringComparison.Ordinal), "Classifier should use the chapter tag prefix.");
|
||||||
|
Assert(classifier.Contains("tag.startsWith(plotDirectorMarkerPrefixes.scene)", StringComparison.Ordinal), "Classifier should use the scene tag prefix.");
|
||||||
|
Assert(!classifier.Contains("title", StringComparison.OrdinalIgnoreCase), "Marker cleanup must not delete arbitrary controls by display title.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WordManuscriptCleanupPreservesContentControlContent()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-host.js"));
|
||||||
|
|
||||||
|
Assert(script.Contains("const removePlotDirectorContentControlsPreservingContent", StringComparison.Ordinal), "Clean unlink should have a dedicated marker removal path.");
|
||||||
|
Assert(script.Contains("context.document.contentControls", StringComparison.Ordinal), "Clean unlink should inspect document content controls.");
|
||||||
|
Assert(script.Contains("control.delete(true);", StringComparison.Ordinal), "PlotDirector marker cleanup must call Word.ContentControl.delete(true) to preserve visible content.");
|
||||||
|
Assert(!script.Contains("control.delete(false)", StringComparison.Ordinal), "PlotDirector marker cleanup must not delete marker content.");
|
||||||
|
Assert(!script.Contains("control.delete();", StringComparison.Ordinal), "PlotDirector marker cleanup must pass keepContent explicitly.");
|
||||||
|
Assert(script.Contains("await inspectPlotDirectorMarkers();", StringComparison.Ordinal), "Clean unlink should verify marker counts after removal.");
|
||||||
|
Assert(script.Contains("latestMarkerState.totalCount > 0", StringComparison.Ordinal), "Clean unlink should stop when PlotDirector markers remain.");
|
||||||
|
Assert(script.Contains("cleanup verification failed", StringComparison.Ordinal), "Clean unlink should fail closed when marker verification cannot complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WordManuscriptCleanupUiSeparatesUnlinkFromCleanUnlink()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var view = File.ReadAllText(Path.Combine(root, "Views/WordCompanionHost/Index.cshtml"));
|
||||||
|
var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-host.js"));
|
||||||
|
|
||||||
|
Assert(view.Contains("data-marker-binding-status", StringComparison.Ordinal), "Maintenance UI should report document binding status.");
|
||||||
|
Assert(view.Contains("data-marker-chapter-count", StringComparison.Ordinal), "Maintenance UI should report chapter marker counts.");
|
||||||
|
Assert(view.Contains("data-marker-scene-count", StringComparison.Ordinal), "Maintenance UI should report scene marker counts.");
|
||||||
|
Assert(view.Contains("Unlink from PlotDirector", StringComparison.Ordinal), "Existing unlink behavior should remain visible as a separate action.");
|
||||||
|
Assert(view.Contains("Hidden PlotDirector anchors will be left in place", StringComparison.Ordinal), "Existing unlink dialog should state markers are retained.");
|
||||||
|
Assert(view.Contains("Clean and unlink document", StringComparison.Ordinal), "Full cleanup should be a separate explicit action.");
|
||||||
|
Assert(view.Contains("data-clean-unlink-word-document", StringComparison.Ordinal), "Clean unlink button is missing.");
|
||||||
|
Assert(view.Contains("data-clean-unlink-word-document-dialog", StringComparison.Ordinal), "Clean unlink confirmation dialog is missing.");
|
||||||
|
Assert(view.Contains("Visible manuscript text, headings, formatting, comments, and tracked changes should remain", StringComparison.Ordinal), "Clean unlink dialog should describe content preservation.");
|
||||||
|
Assert(script.Contains("requestUnlinkWordDocumentConfirmation", StringComparison.Ordinal), "Existing unlink confirmation path should remain.");
|
||||||
|
Assert(script.Contains("requestCleanUnlinkWordDocumentConfirmation", StringComparison.Ordinal), "Clean unlink should use its own confirmation path.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WordManuscriptUnlinkKeepsStoryIntelligenceDataUntouched()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var unlinkSql = File.ReadAllText(Path.Combine(root, "Sql/108_Phase16Q_ManuscriptUnlink.sql"));
|
||||||
|
var sceneResultSql = File.ReadAllText(Path.Combine(root, "Sql/115_Phase20I_StoryIntelligenceResultPersistence.sql"));
|
||||||
|
var importCommitSql = File.ReadAllText(Path.Combine(root, "Sql/121_Phase20V_StoryIntelligenceImportCommit.sql"));
|
||||||
|
|
||||||
|
Assert(unlinkSql.Contains("CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_UnlinkBook", StringComparison.Ordinal), "Server unlink procedure is missing.");
|
||||||
|
Assert(unlinkSql.Contains("IsLinkedToManuscript = 0", StringComparison.Ordinal), "Server unlink should clear manuscript link flags.");
|
||||||
|
Assert(unlinkSql.Contains("ManuscriptDocumentID = NULL", StringComparison.Ordinal), "Server unlink should clear manuscript document references.");
|
||||||
|
Assert(unlinkSql.Contains("IsActive = 0", StringComparison.Ordinal), "Server unlink should deactivate only the manuscript document binding row.");
|
||||||
|
Assert(!unlinkSql.Contains("StoryIntelligence", StringComparison.Ordinal), "Server unlink must not modify Story Intelligence tables.");
|
||||||
|
Assert(!unlinkSql.Contains("DELETE FROM dbo.Chapters", StringComparison.OrdinalIgnoreCase), "Server unlink must not delete chapters.");
|
||||||
|
Assert(!unlinkSql.Contains("DELETE FROM dbo.Scenes", StringComparison.OrdinalIgnoreCase), "Server unlink must not delete scenes.");
|
||||||
|
Assert(sceneResultSql.Contains("StartParagraph", StringComparison.Ordinal) && sceneResultSql.Contains("EndParagraph", StringComparison.Ordinal), "Story Intelligence scene result positions should remain database-backed.");
|
||||||
|
Assert(importCommitSql.Contains("SourceStartParagraph", StringComparison.Ordinal) && importCommitSql.Contains("SourceEndParagraph", StringComparison.Ordinal), "Story Intelligence source positions should remain database-backed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WordManuscriptCleanupRemainsRelinkableAndIdempotent()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/word-companion-host.js"));
|
||||||
|
var service = File.ReadAllText(Path.Combine(root, "Services/WordCompanionService.cs"));
|
||||||
|
var repository = File.ReadAllText(Path.Combine(root, "Data/Repositories.cs"));
|
||||||
|
var saveSql = File.ReadAllText(Path.Combine(root, "Sql/094_Phase16B_ManuscriptBindingLinkedStructures.sql"));
|
||||||
|
var unlinkSql = File.ReadAllText(Path.Combine(root, "Sql/108_Phase16Q_ManuscriptUnlink.sql"));
|
||||||
|
|
||||||
|
Assert(script.Contains("const binding = currentDocumentBinding();", StringComparison.Ordinal), "Clean unlink should tolerate missing local binding metadata.");
|
||||||
|
Assert(script.Contains("if (binding?.documentGuid && Number.isInteger(binding.bookId) && binding.bookId > 0)", StringComparison.Ordinal), "Server unlink should be skipped safely when no binding exists.");
|
||||||
|
Assert(script.Contains("await removeDocumentBinding();", StringComparison.Ordinal), "Clean unlink should remove local binding settings after marker verification.");
|
||||||
|
Assert(service.Contains("documentGuid = Guid.NewGuid();", StringComparison.Ordinal), "Relinking should mint a new document GUID when needed.");
|
||||||
|
Assert(service.Contains("manuscriptDocuments.SaveAsync(request.BookId, documentGuid, RequireUserId())", StringComparison.Ordinal), "Relinking should still flow through the manuscript document repository.");
|
||||||
|
Assert(repository.Contains("\"dbo.ManuscriptDocument_Save\"", StringComparison.Ordinal), "Relinking should still use the manuscript document save procedure.");
|
||||||
|
Assert(saveSql.Contains("ManuscriptDocument_Save", StringComparison.Ordinal) && saveSql.Contains("IsActive", StringComparison.Ordinal), "Server save procedure should support a new active document binding.");
|
||||||
|
Assert(unlinkSql.Contains("@DocumentGuid IS NULL", StringComparison.Ordinal) && unlinkSql.Contains("00000000-0000-0000-0000-000000000000", StringComparison.Ordinal), "Server unlink should tolerate repeated or metadata-free unlink requests.");
|
||||||
|
}
|
||||||
|
|
||||||
static void StripsMarkdownFence()
|
static void StripsMarkdownFence()
|
||||||
{
|
{
|
||||||
var result = StoryIntelligenceJsonRepair.Repair("""
|
var result = StoryIntelligenceJsonRepair.Repair("""
|
||||||
|
|||||||
@ -270,10 +270,28 @@
|
|||||||
<span>Manuscript Maintenance</span>
|
<span>Manuscript Maintenance</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="word-companion-message">Use these tools when manuscript headings, scene boundaries, or document links need attention.</p>
|
<p class="word-companion-message">Use these tools when manuscript headings, scene boundaries, or document links need attention.</p>
|
||||||
|
<div class="word-companion-marker-status" data-marker-status>
|
||||||
|
<div><span>Document binding</span><strong data-marker-binding-status>Unknown</strong></div>
|
||||||
|
<div><span>Chapter markers</span><strong data-marker-chapter-count>-</strong></div>
|
||||||
|
<div><span>Scene markers</span><strong data-marker-scene-count>-</strong></div>
|
||||||
|
</div>
|
||||||
|
<div class="word-companion-maintenance-group">
|
||||||
|
<div>
|
||||||
|
<strong>Unlink from PlotDirector</strong>
|
||||||
|
<p>Disconnect this document while leaving hidden PlotDirector anchors in place for possible reuse.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="word-companion-danger-action" data-unlink-word-document hidden>Unlink from PlotDirector</button>
|
||||||
|
</div>
|
||||||
|
<div class="word-companion-maintenance-group word-companion-maintenance-group-strong">
|
||||||
|
<div>
|
||||||
|
<strong>Clean and unlink document</strong>
|
||||||
|
<p>Remove PlotDirector hidden anchors and binding metadata from this Word document while preserving manuscript text.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="word-companion-danger-action" data-clean-unlink-word-document>Clean and unlink</button>
|
||||||
|
</div>
|
||||||
<div class="word-companion-actions word-companion-maintenance-actions">
|
<div class="word-companion-actions word-companion-maintenance-actions">
|
||||||
<button type="button" data-attach-plotdirector-ids disabled>Attach PlotDirector IDs</button>
|
<button type="button" data-attach-plotdirector-ids disabled>Attach PlotDirector IDs</button>
|
||||||
<button type="button" data-refresh-document>Refresh Local Cache</button>
|
<button type="button" data-refresh-document>Refresh Local Cache</button>
|
||||||
<button type="button" class="word-companion-danger-action" data-unlink-word-document hidden>Unlink this Word document</button>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="word-companion-section word-companion-structure-preview" aria-label="Structure Sync Preview">
|
<section class="word-companion-section word-companion-structure-preview" aria-label="Structure Sync Preview">
|
||||||
@ -539,15 +557,38 @@
|
|||||||
<div class="word-companion-section-header">
|
<div class="word-companion-section-header">
|
||||||
<span>Unlink Word Document?</span>
|
<span>Unlink Word Document?</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="word-companion-dialog-copy">This will remove PlotDirector binding metadata from this Word document.</p>
|
<p class="word-companion-dialog-copy">This will remove PlotDirector binding metadata from this Word document and unlink this Book in PlotDirector.</p>
|
||||||
<p class="word-companion-dialog-copy">If PlotDirector is reachable, it will also unlink this Book in PlotDirector.</p>
|
<p class="word-companion-dialog-copy">Hidden PlotDirector anchors will be left in place for possible reuse.</p>
|
||||||
<p class="word-companion-dialog-copy">Your manuscript text will not be changed.</p>
|
<p class="word-companion-dialog-copy">Your manuscript text will not be changed.</p>
|
||||||
|
<div class="word-companion-marker-status word-companion-dialog-marker-status">
|
||||||
|
<div><span>Chapter markers kept</span><strong data-unlink-dialog-chapter-count>-</strong></div>
|
||||||
|
<div><span>Scene markers kept</span><strong data-unlink-dialog-scene-count>-</strong></div>
|
||||||
|
</div>
|
||||||
<div class="word-companion-dialog-actions">
|
<div class="word-companion-dialog-actions">
|
||||||
<button type="button" data-unlink-word-document-confirm>Continue</button>
|
<button type="button" data-unlink-word-document-confirm>Continue</button>
|
||||||
<button type="button" data-unlink-word-document-cancel>Cancel</button>
|
<button type="button" data-unlink-word-document-cancel>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<dialog class="word-companion-dialog" data-clean-unlink-word-document-dialog>
|
||||||
|
<form method="dialog">
|
||||||
|
<div class="word-companion-section-header">
|
||||||
|
<span>Clean and Unlink?</span>
|
||||||
|
</div>
|
||||||
|
<p class="word-companion-dialog-copy">This will remove PlotDirector hidden chapter and scene anchors from this Word document.</p>
|
||||||
|
<p class="word-companion-dialog-copy">Visible manuscript text, headings, formatting, comments, and tracked changes should remain in the document.</p>
|
||||||
|
<p class="word-companion-dialog-copy">PlotDirector binding metadata will be removed and the Book will be unlinked in PlotDirector.</p>
|
||||||
|
<div class="word-companion-marker-status word-companion-dialog-marker-status">
|
||||||
|
<div><span>Chapter markers removed</span><strong data-clean-dialog-chapter-count>-</strong></div>
|
||||||
|
<div><span>Scene markers removed</span><strong data-clean-dialog-scene-count>-</strong></div>
|
||||||
|
</div>
|
||||||
|
<div class="word-companion-dialog-actions">
|
||||||
|
<button type="button" data-clean-unlink-word-document-confirm>Clean and unlink</button>
|
||||||
|
<button type="button" data-clean-unlink-word-document-cancel>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
}
|
}
|
||||||
</main>
|
</main>
|
||||||
@if (Model.IsAuthenticated)
|
@if (Model.IsAuthenticated)
|
||||||
|
|||||||
@ -601,6 +601,67 @@ body {
|
|||||||
background: #fef2f2;
|
background: #fef2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-companion-marker-status {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-marker-status div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
border: 1px solid rgba(111, 75, 45, 0.16);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: #fffdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-marker-status span {
|
||||||
|
color: var(--companion-muted);
|
||||||
|
font-size: 0.76rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-dialog-marker-status {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-maintenance-group {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
border: 1px solid rgba(111, 75, 45, 0.16);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 9px;
|
||||||
|
background: #fffdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-maintenance-group-strong {
|
||||||
|
border-color: #fecaca;
|
||||||
|
background: #fff8f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-maintenance-group p {
|
||||||
|
margin: 3px 0 0;
|
||||||
|
color: var(--companion-muted);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-maintenance-group button {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 31px;
|
||||||
|
border: 1px solid var(--companion-line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: #fffdf8;
|
||||||
|
color: var(--companion-accent-dark);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
.word-companion-message:empty {
|
.word-companion-message:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
2
PlotLine/wwwroot/css/word-companion.min.css
vendored
2
PlotLine/wwwroot/css/word-companion.min.css
vendored
File diff suppressed because one or more lines are too long
@ -44,6 +44,9 @@
|
|||||||
const unlinkWordDocumentDialog = document.querySelector("[data-unlink-word-document-dialog]");
|
const unlinkWordDocumentDialog = document.querySelector("[data-unlink-word-document-dialog]");
|
||||||
const unlinkWordDocumentConfirm = document.querySelector("[data-unlink-word-document-confirm]");
|
const unlinkWordDocumentConfirm = document.querySelector("[data-unlink-word-document-confirm]");
|
||||||
const unlinkWordDocumentCancel = document.querySelector("[data-unlink-word-document-cancel]");
|
const unlinkWordDocumentCancel = document.querySelector("[data-unlink-word-document-cancel]");
|
||||||
|
const cleanUnlinkWordDocumentDialog = document.querySelector("[data-clean-unlink-word-document-dialog]");
|
||||||
|
const cleanUnlinkWordDocumentConfirm = document.querySelector("[data-clean-unlink-word-document-confirm]");
|
||||||
|
const cleanUnlinkWordDocumentCancel = document.querySelector("[data-clean-unlink-word-document-cancel]");
|
||||||
const officeStatus = document.querySelector("[data-office-status]");
|
const officeStatus = document.querySelector("[data-office-status]");
|
||||||
const connectionStatus = document.querySelector("[data-connection-status]");
|
const connectionStatus = document.querySelector("[data-connection-status]");
|
||||||
const summaryConnection = document.querySelector("[data-summary-connection]");
|
const summaryConnection = document.querySelector("[data-summary-connection]");
|
||||||
@ -75,6 +78,14 @@
|
|||||||
const anchorSceneId = document.querySelector("[data-anchor-scene-id]");
|
const anchorSceneId = document.querySelector("[data-anchor-scene-id]");
|
||||||
const attachPlotDirectorIdsButton = document.querySelector("[data-attach-plotdirector-ids]");
|
const attachPlotDirectorIdsButton = document.querySelector("[data-attach-plotdirector-ids]");
|
||||||
const unlinkWordDocumentButton = document.querySelector("[data-unlink-word-document]");
|
const unlinkWordDocumentButton = document.querySelector("[data-unlink-word-document]");
|
||||||
|
const cleanUnlinkWordDocumentButton = document.querySelector("[data-clean-unlink-word-document]");
|
||||||
|
const markerBindingStatus = document.querySelector("[data-marker-binding-status]");
|
||||||
|
const markerChapterCount = document.querySelector("[data-marker-chapter-count]");
|
||||||
|
const markerSceneCount = document.querySelector("[data-marker-scene-count]");
|
||||||
|
const unlinkDialogChapterCount = document.querySelector("[data-unlink-dialog-chapter-count]");
|
||||||
|
const unlinkDialogSceneCount = document.querySelector("[data-unlink-dialog-scene-count]");
|
||||||
|
const cleanDialogChapterCount = document.querySelector("[data-clean-dialog-chapter-count]");
|
||||||
|
const cleanDialogSceneCount = document.querySelector("[data-clean-dialog-scene-count]");
|
||||||
const plotDirectorSceneDetails = document.querySelector("[data-plotdirector-scene-details]");
|
const plotDirectorSceneDetails = document.querySelector("[data-plotdirector-scene-details]");
|
||||||
const plotDirectorSceneTitle = document.querySelector("[data-plotdirector-scene-title]");
|
const plotDirectorSceneTitle = document.querySelector("[data-plotdirector-scene-title]");
|
||||||
const plotDirectorRevisionStatus = document.querySelector("[data-plotdirector-revision-status]");
|
const plotDirectorRevisionStatus = document.querySelector("[data-plotdirector-revision-status]");
|
||||||
@ -253,6 +264,34 @@
|
|||||||
locations: [],
|
locations: [],
|
||||||
primaryLocationId: null
|
primaryLocationId: null
|
||||||
};
|
};
|
||||||
|
let latestMarkerState = {
|
||||||
|
chapterCount: 0,
|
||||||
|
sceneCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
available: false,
|
||||||
|
message: "Not inspected"
|
||||||
|
};
|
||||||
|
|
||||||
|
const plotDirectorMarkerPrefixes = {
|
||||||
|
chapter: "PD-CHAPTER-",
|
||||||
|
scene: "PD-SCENE-"
|
||||||
|
};
|
||||||
|
|
||||||
|
const classifyPlotDirectorContentControl = (control) => {
|
||||||
|
const tag = String(control?.tag || "").trim().toUpperCase();
|
||||||
|
if (tag.startsWith(plotDirectorMarkerPrefixes.chapter)) {
|
||||||
|
return "chapter";
|
||||||
|
}
|
||||||
|
if (tag.startsWith(plotDirectorMarkerPrefixes.scene)) {
|
||||||
|
return "scene";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
window.PlotDirectorWordMarkers = {
|
||||||
|
classify: classifyPlotDirectorContentControl
|
||||||
|
};
|
||||||
|
|
||||||
const runtimeDebug = (message) => {
|
const runtimeDebug = (message) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -458,6 +497,112 @@
|
|||||||
setHidden(unlinkWordDocumentButton, !firstRunDocumentBinding);
|
setHidden(unlinkWordDocumentButton, !firstRunDocumentBinding);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateMarkerStatusDisplay = (state = latestMarkerState) => {
|
||||||
|
const binding = currentDocumentBinding();
|
||||||
|
setText(markerBindingStatus, binding ? "Linked" : "Not linked");
|
||||||
|
setText(markerChapterCount, state.available ? String(state.chapterCount) : "-");
|
||||||
|
setText(markerSceneCount, state.available ? String(state.sceneCount) : "-");
|
||||||
|
setText(unlinkDialogChapterCount, state.available ? String(state.chapterCount) : "-");
|
||||||
|
setText(unlinkDialogSceneCount, state.available ? String(state.sceneCount) : "-");
|
||||||
|
setText(cleanDialogChapterCount, state.available ? String(state.chapterCount) : "-");
|
||||||
|
setText(cleanDialogSceneCount, state.available ? String(state.sceneCount) : "-");
|
||||||
|
if (cleanUnlinkWordDocumentButton) {
|
||||||
|
cleanUnlinkWordDocumentButton.disabled = !wordHostAvailable || !officeReady;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const inspectPlotDirectorMarkers = async () => {
|
||||||
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
|
latestMarkerState = {
|
||||||
|
chapterCount: 0,
|
||||||
|
sceneCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
available: false,
|
||||||
|
message: "Word document access is unavailable."
|
||||||
|
};
|
||||||
|
updateMarkerStatusDisplay();
|
||||||
|
return latestMarkerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
latestMarkerState = await window.Word.run(async (context) => {
|
||||||
|
const controls = context.document.contentControls;
|
||||||
|
controls.load("items/tag,title");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
let chapterCount = 0;
|
||||||
|
let sceneCount = 0;
|
||||||
|
for (const control of controls.items || []) {
|
||||||
|
const markerType = classifyPlotDirectorContentControl(control);
|
||||||
|
if (markerType === "chapter") {
|
||||||
|
chapterCount += 1;
|
||||||
|
} else if (markerType === "scene") {
|
||||||
|
sceneCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
chapterCount,
|
||||||
|
sceneCount,
|
||||||
|
totalCount: chapterCount + sceneCount,
|
||||||
|
available: true,
|
||||||
|
message: "Marker inspection complete."
|
||||||
|
};
|
||||||
|
});
|
||||||
|
updateMarkerStatusDisplay(latestMarkerState);
|
||||||
|
return latestMarkerState;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unable to inspect PlotDirector markers.", error);
|
||||||
|
latestMarkerState = {
|
||||||
|
chapterCount: 0,
|
||||||
|
sceneCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
available: false,
|
||||||
|
message: `Unable to inspect markers: ${errorText(error)}`
|
||||||
|
};
|
||||||
|
updateMarkerStatusDisplay();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePlotDirectorContentControlsPreservingContent = async () => {
|
||||||
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
|
throw new Error("Word document access is unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await window.Word.run(async (context) => {
|
||||||
|
const controls = context.document.contentControls;
|
||||||
|
controls.load("items/tag,title");
|
||||||
|
await context.sync();
|
||||||
|
|
||||||
|
let chapterCount = 0;
|
||||||
|
let sceneCount = 0;
|
||||||
|
for (const control of controls.items || []) {
|
||||||
|
const markerType = classifyPlotDirectorContentControl(control);
|
||||||
|
if (!markerType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markerType === "chapter") {
|
||||||
|
chapterCount += 1;
|
||||||
|
} else if (markerType === "scene") {
|
||||||
|
sceneCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
control.delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.sync();
|
||||||
|
return {
|
||||||
|
chapterCount,
|
||||||
|
sceneCount,
|
||||||
|
totalCount: chapterCount + sceneCount,
|
||||||
|
available: true,
|
||||||
|
message: "PlotDirector markers removed."
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const setSyncStatus = (message) => {
|
const setSyncStatus = (message) => {
|
||||||
setText(syncStatus, message);
|
setText(syncStatus, message);
|
||||||
};
|
};
|
||||||
@ -1165,6 +1310,7 @@
|
|||||||
setText(linkedProjectTitle, runtimeProjectTitle());
|
setText(linkedProjectTitle, runtimeProjectTitle());
|
||||||
setText(linkedBookTitle, runtimeBookTitle());
|
setText(linkedBookTitle, runtimeBookTitle());
|
||||||
}
|
}
|
||||||
|
updateMarkerStatusDisplay();
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedProject = () => {
|
const selectedProject = () => {
|
||||||
@ -1557,6 +1703,18 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const saveWordDocumentIfAvailable = () => new Promise((resolve) => {
|
||||||
|
const document = window.Office?.context?.document;
|
||||||
|
if (!document || typeof document.saveAsync !== "function") {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.saveAsync((result) => {
|
||||||
|
resolve(result.status === window.Office.AsyncResultStatus.Succeeded);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const ensureDocumentGuid = () => {
|
const ensureDocumentGuid = () => {
|
||||||
if (firstRunDocumentBinding?.documentGuid) {
|
if (firstRunDocumentBinding?.documentGuid) {
|
||||||
return firstRunDocumentBinding.documentGuid;
|
return firstRunDocumentBinding.documentGuid;
|
||||||
@ -2202,6 +2360,7 @@
|
|||||||
|
|
||||||
await context.sync();
|
await context.sync();
|
||||||
});
|
});
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const friendlyScanError = (error) => {
|
const friendlyScanError = (error) => {
|
||||||
@ -2924,6 +3083,7 @@
|
|||||||
refreshDocumentButton.disabled = false;
|
refreshDocumentButton.disabled = false;
|
||||||
refreshDocumentButton.textContent = "Refresh Document Structure";
|
refreshDocumentButton.textContent = "Refresh Document Structure";
|
||||||
}
|
}
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3138,6 +3298,7 @@
|
|||||||
|
|
||||||
await context.sync();
|
await context.sync();
|
||||||
});
|
});
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resultSummaryText = (summary) =>
|
const resultSummaryText = (summary) =>
|
||||||
@ -3409,7 +3570,7 @@
|
|||||||
|
|
||||||
const requestUnlinkWordDocumentConfirmation = () => new Promise((resolve) => {
|
const requestUnlinkWordDocumentConfirmation = () => new Promise((resolve) => {
|
||||||
if (!unlinkWordDocumentDialog || typeof unlinkWordDocumentDialog.showModal !== "function") {
|
if (!unlinkWordDocumentDialog || typeof unlinkWordDocumentDialog.showModal !== "function") {
|
||||||
resolve(window.confirm("This will remove PlotDirector binding metadata from this Word document.\n\nIf PlotDirector is reachable, it will also unlink this Book in PlotDirector.\n\nYour manuscript text will not be changed.\n\nContinue?"));
|
resolve(window.confirm(`This will remove PlotDirector binding metadata from this Word document and unlink this Book in PlotDirector.\n\nHidden PlotDirector anchors will be left in place for possible reuse.\n\nCurrent marker count:\n${latestMarkerState.chapterCount} chapter markers\n${latestMarkerState.sceneCount} scene markers\n\nYour manuscript text will not be changed.\n\nContinue?`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3433,6 +3594,33 @@
|
|||||||
unlinkWordDocumentDialog.showModal();
|
unlinkWordDocumentDialog.showModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const requestCleanUnlinkWordDocumentConfirmation = () => new Promise((resolve) => {
|
||||||
|
const message = `This will remove PlotDirector hidden anchors from this Word document while preserving manuscript text.\n\nThis will remove:\n${latestMarkerState.chapterCount} PlotDirector chapter markers\n${latestMarkerState.sceneCount} PlotDirector scene markers\nthe document's PlotDirector binding\n\nThe document can be linked again later if desired.\n\nContinue?`;
|
||||||
|
if (!cleanUnlinkWordDocumentDialog || typeof cleanUnlinkWordDocumentDialog.showModal !== "function") {
|
||||||
|
resolve(window.confirm(message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanup = (confirmed) => {
|
||||||
|
cleanUnlinkWordDocumentConfirm?.removeEventListener("click", confirmHandler);
|
||||||
|
cleanUnlinkWordDocumentCancel?.removeEventListener("click", cancelHandler);
|
||||||
|
cleanUnlinkWordDocumentDialog?.removeEventListener("cancel", cancelEventHandler);
|
||||||
|
cleanUnlinkWordDocumentDialog?.close();
|
||||||
|
resolve(confirmed);
|
||||||
|
};
|
||||||
|
const confirmHandler = () => cleanup(true);
|
||||||
|
const cancelHandler = () => cleanup(false);
|
||||||
|
const cancelEventHandler = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
cleanup(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanUnlinkWordDocumentConfirm?.addEventListener("click", confirmHandler);
|
||||||
|
cleanUnlinkWordDocumentCancel?.addEventListener("click", cancelHandler);
|
||||||
|
cleanUnlinkWordDocumentDialog?.addEventListener("cancel", cancelEventHandler);
|
||||||
|
cleanUnlinkWordDocumentDialog.showModal();
|
||||||
|
});
|
||||||
|
|
||||||
const importFirstRunManuscript = async () => {
|
const importFirstRunManuscript = async () => {
|
||||||
const project = selectedFirstRunProject();
|
const project = selectedFirstRunProject();
|
||||||
const book = selectedFirstRunBook();
|
const book = selectedFirstRunBook();
|
||||||
@ -3740,6 +3928,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await inspectPlotDirectorMarkers();
|
||||||
|
} catch {
|
||||||
|
setDocumentMessage("Marker counts are unavailable. You can still unlink without removing hidden anchors.");
|
||||||
|
}
|
||||||
|
|
||||||
const confirmed = await requestUnlinkWordDocumentConfirmation();
|
const confirmed = await requestUnlinkWordDocumentConfirmation();
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
setDocumentMessage("Unlink cancelled.");
|
setDocumentMessage("Unlink cancelled.");
|
||||||
@ -3758,6 +3952,7 @@
|
|||||||
bookId: binding.bookId
|
bookId: binding.bookId
|
||||||
});
|
});
|
||||||
await removeDocumentBinding();
|
await removeDocumentBinding();
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
await returnToUnboundOnboarding("Manuscript unlinked.");
|
await returnToUnboundOnboarding("Manuscript unlinked.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to unlink manuscript binding.", error);
|
console.error("Unable to unlink manuscript binding.", error);
|
||||||
@ -3766,6 +3961,7 @@
|
|||||||
if (removeLocalOnly) {
|
if (removeLocalOnly) {
|
||||||
try {
|
try {
|
||||||
await removeDocumentBinding();
|
await removeDocumentBinding();
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
await returnToUnboundOnboarding("Word metadata removed. Unlink the Book manually in PlotDirector if it still shows as linked.");
|
await returnToUnboundOnboarding("Word metadata removed. Unlink the Book manually in PlotDirector if it still shows as linked.");
|
||||||
} catch (metadataError) {
|
} catch (metadataError) {
|
||||||
console.error("Unable to remove Word document metadata.", metadataError);
|
console.error("Unable to remove Word document metadata.", metadataError);
|
||||||
@ -3777,12 +3973,100 @@
|
|||||||
} finally {
|
} finally {
|
||||||
if (unlinkWordDocumentButton) {
|
if (unlinkWordDocumentButton) {
|
||||||
unlinkWordDocumentButton.disabled = false;
|
unlinkWordDocumentButton.disabled = false;
|
||||||
unlinkWordDocumentButton.textContent = "Unlink this Word document";
|
unlinkWordDocumentButton.textContent = "Unlink from PlotDirector";
|
||||||
}
|
}
|
||||||
updateUnlinkWordDocumentButton();
|
updateUnlinkWordDocumentButton();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanAndUnlinkWordDocument = async () => {
|
||||||
|
console.debug("Word Companion clean and unlink clicked");
|
||||||
|
const binding = currentDocumentBinding();
|
||||||
|
|
||||||
|
if (!officeReady || !wordHostAvailable || !window.Word || typeof window.Word.run !== "function") {
|
||||||
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDocumentMessage("Inspecting PlotDirector markers...");
|
||||||
|
try {
|
||||||
|
await inspectPlotDirectorMarkers();
|
||||||
|
} catch (error) {
|
||||||
|
setDocumentMessage(`Unable to inspect markers: ${errorText(error)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await requestCleanUnlinkWordDocumentConfirmation();
|
||||||
|
if (!confirmed) {
|
||||||
|
setDocumentMessage("Clean and unlink cancelled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanUnlinkWordDocumentButton) {
|
||||||
|
cleanUnlinkWordDocumentButton.disabled = true;
|
||||||
|
cleanUnlinkWordDocumentButton.textContent = "Cleaning...";
|
||||||
|
}
|
||||||
|
if (unlinkWordDocumentButton) {
|
||||||
|
unlinkWordDocumentButton.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setDocumentMessage("Removing PlotDirector hidden anchors...");
|
||||||
|
await removePlotDirectorContentControlsPreservingContent();
|
||||||
|
await saveWordDocumentIfAvailable();
|
||||||
|
try {
|
||||||
|
await inspectPlotDirectorMarkers();
|
||||||
|
} catch (verificationError) {
|
||||||
|
console.error("Unable to verify PlotDirector marker cleanup.", verificationError);
|
||||||
|
setDocumentMessage(`PlotDirector markers were removed, but cleanup verification failed: ${errorText(verificationError)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestMarkerState.available && latestMarkerState.totalCount > 0) {
|
||||||
|
setDocumentMessage(`Cleanup stopped: ${latestMarkerState.totalCount} PlotDirector marker${latestMarkerState.totalCount === 1 ? "" : "s"} remain.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDocumentMessage("Removing PlotDirector binding metadata...");
|
||||||
|
try {
|
||||||
|
await removeDocumentBinding();
|
||||||
|
} catch (metadataError) {
|
||||||
|
console.error("Unable to remove Word document metadata after marker cleanup.", metadataError);
|
||||||
|
setDocumentMessage(`PlotDirector markers were removed, but Word binding metadata remains: ${errorText(metadataError)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverMessage = "";
|
||||||
|
if (binding?.documentGuid && Number.isInteger(binding.bookId) && binding.bookId > 0) {
|
||||||
|
try {
|
||||||
|
await postJson("/api/word-companion/manuscript/unlink", {
|
||||||
|
documentGuid: binding.documentGuid,
|
||||||
|
bookId: binding.bookId
|
||||||
|
});
|
||||||
|
} catch (serverError) {
|
||||||
|
console.error("Word document cleaned, but PlotDirector server unlink failed.", serverError);
|
||||||
|
serverMessage = " Word document is clean, but unlink the Book manually in PlotDirector if it still shows as linked.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
|
await returnToUnboundOnboarding(`Word document cleaned and unlinked.${serverMessage}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unable to clean PlotDirector markers.", error);
|
||||||
|
setDocumentMessage(`Cleanup failed before unlinking: ${errorText(error)}`);
|
||||||
|
} finally {
|
||||||
|
if (cleanUnlinkWordDocumentButton) {
|
||||||
|
cleanUnlinkWordDocumentButton.disabled = false;
|
||||||
|
cleanUnlinkWordDocumentButton.textContent = "Clean and unlink";
|
||||||
|
}
|
||||||
|
if (unlinkWordDocumentButton) {
|
||||||
|
unlinkWordDocumentButton.disabled = false;
|
||||||
|
}
|
||||||
|
updateUnlinkWordDocumentButton();
|
||||||
|
updateMarkerStatusDisplay();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const patchJson = (url, body) => fetchJson(url, {
|
const patchJson = (url, body) => fetchJson(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@ -3953,6 +4237,7 @@
|
|||||||
ensureAnchorControl(sceneParagraph, "PlotDirector Scene", `PD-SCENE-${resolvedSceneId}`, "PD-SCENE");
|
ensureAnchorControl(sceneParagraph, "PlotDirector Scene", `PD-SCENE-${resolvedSceneId}`, "PD-SCENE");
|
||||||
await context.sync();
|
await context.sync();
|
||||||
});
|
});
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
|
|
||||||
detectedChapterAnchorId = resolvedChapterId;
|
detectedChapterAnchorId = resolvedChapterId;
|
||||||
detectedSceneAnchorId = resolvedSceneId;
|
detectedSceneAnchorId = resolvedSceneId;
|
||||||
@ -4005,6 +4290,7 @@
|
|||||||
ensureAnchorControl(chapterParagraph, "PlotDirector Chapter", `PD-CHAPTER-${resolvedChapterId}`, "PD-CHAPTER");
|
ensureAnchorControl(chapterParagraph, "PlotDirector Chapter", `PD-CHAPTER-${resolvedChapterId}`, "PD-CHAPTER");
|
||||||
await context.sync();
|
await context.sync();
|
||||||
});
|
});
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
|
|
||||||
detectedChapterAnchorId = resolvedChapterId;
|
detectedChapterAnchorId = resolvedChapterId;
|
||||||
storedAnchorInvalid = false;
|
storedAnchorInvalid = false;
|
||||||
@ -5701,6 +5987,7 @@
|
|||||||
saveSceneLinksButton?.addEventListener("click", saveSceneLinks);
|
saveSceneLinksButton?.addEventListener("click", saveSceneLinks);
|
||||||
attachPlotDirectorIdsButton?.addEventListener("click", attachPlotDirectorIds);
|
attachPlotDirectorIdsButton?.addEventListener("click", attachPlotDirectorIds);
|
||||||
unlinkWordDocumentButton?.addEventListener("click", unlinkThisWordDocument);
|
unlinkWordDocumentButton?.addEventListener("click", unlinkThisWordDocument);
|
||||||
|
cleanUnlinkWordDocumentButton?.addEventListener("click", cleanAndUnlinkWordDocument);
|
||||||
createPlotDirectorChapterButton?.addEventListener("click", createPlotDirectorChapter);
|
createPlotDirectorChapterButton?.addEventListener("click", createPlotDirectorChapter);
|
||||||
linkExistingChapterButton?.addEventListener("click", openLinkExistingChapterDialog);
|
linkExistingChapterButton?.addEventListener("click", openLinkExistingChapterDialog);
|
||||||
linkChapterSearch?.addEventListener("input", renderLinkChapterOptions);
|
linkChapterSearch?.addEventListener("input", renderLinkChapterOptions);
|
||||||
@ -5760,6 +6047,7 @@
|
|||||||
setDocumentMessage("Word document access is unavailable.");
|
setDocumentMessage("Word document access is unavailable.");
|
||||||
setSceneTrackingState("Manual");
|
setSceneTrackingState("Manual");
|
||||||
} else {
|
} else {
|
||||||
|
await inspectPlotDirectorMarkers().catch(() => null);
|
||||||
setupSelectionTracking();
|
setupSelectionTracking();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
26
PlotLine/wwwroot/js/word-companion-host.min.js
vendored
26
PlotLine/wwwroot/js/word-companion-host.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user