Phase 16Q - Manuscript Unlink and Recovery Tools
This commit is contained in:
parent
db6f0992c1
commit
7356a1c6b1
@ -91,4 +91,15 @@ public sealed class BooksController(IBookService books) : Controller
|
|||||||
TempData["ArchiveMessage"] = "Book archived. You can restore it from Archived Items.";
|
TempData["ArchiveMessage"] = "Book archived. You can restore it from Archived Items.";
|
||||||
return RedirectToAction("Details", "Projects", new { id = projectId });
|
return RedirectToAction("Details", "Projects", new { id = projectId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> UnlinkManuscript(int id)
|
||||||
|
{
|
||||||
|
var result = await books.UnlinkManuscriptAsync(id);
|
||||||
|
TempData["ManuscriptMessage"] = result is null
|
||||||
|
? "Unable to unlink manuscript. It may already be unlinked, or you may not have access."
|
||||||
|
: result.Message;
|
||||||
|
return RedirectToAction(nameof(Details), new { id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -198,6 +198,13 @@ public sealed class WordCompanionController(
|
|||||||
return response is null ? BadRequest() : Ok(response);
|
return response is null ? BadRequest() : Ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("manuscript/unlink")]
|
||||||
|
public async Task<IActionResult> UnlinkManuscript(WordCompanionManuscriptUnlinkRequest request)
|
||||||
|
{
|
||||||
|
var response = await wordCompanion.UnlinkManuscriptAsync(request);
|
||||||
|
return response is null ? BadRequest() : Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("manuscript/analyse")]
|
[HttpPost("manuscript/analyse")]
|
||||||
public async Task<IActionResult> AnalyseManuscript(WordCompanionManuscriptAnalyseRequest request)
|
public async Task<IActionResult> AnalyseManuscript(WordCompanionManuscriptAnalyseRequest request)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -65,6 +65,7 @@ public interface IManuscriptDocumentRepository
|
|||||||
Task<ManuscriptDocumentModel?> GetByBookAsync(int bookId, int userId);
|
Task<ManuscriptDocumentModel?> GetByBookAsync(int bookId, int userId);
|
||||||
Task<ManuscriptDocumentModel?> GetByGuidAsync(Guid documentGuid, int userId);
|
Task<ManuscriptDocumentModel?> GetByGuidAsync(Guid documentGuid, int userId);
|
||||||
Task<ManuscriptDocumentModel?> SaveAsync(int bookId, Guid documentGuid, int userId);
|
Task<ManuscriptDocumentModel?> SaveAsync(int bookId, Guid documentGuid, int userId);
|
||||||
|
Task<ManuscriptDocumentUnlinkResult?> UnlinkBookAsync(int bookId, Guid? documentGuid, int userId);
|
||||||
Task UpdateLastSyncAsync(int manuscriptDocumentId, int userId);
|
Task UpdateLastSyncAsync(int manuscriptDocumentId, int userId);
|
||||||
Task UpdateLastOpenedAsync(int manuscriptDocumentId, int userId);
|
Task UpdateLastOpenedAsync(int manuscriptDocumentId, int userId);
|
||||||
}
|
}
|
||||||
@ -2347,6 +2348,15 @@ public sealed class ManuscriptDocumentRepository(ISqlConnectionFactory connectio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ManuscriptDocumentUnlinkResult?> UnlinkBookAsync(int bookId, Guid? documentGuid, int userId)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<ManuscriptDocumentUnlinkResult>(
|
||||||
|
"dbo.ManuscriptDocument_UnlinkBook",
|
||||||
|
new { BookID = bookId, DocumentGuid = documentGuid, UserID = userId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateLastSyncAsync(int manuscriptDocumentId, int userId)
|
public async Task UpdateLastSyncAsync(int manuscriptDocumentId, int userId)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
|||||||
@ -269,6 +269,15 @@ public sealed class ManuscriptDocumentModel
|
|||||||
public int? CreatedByUserID { get; set; }
|
public int? CreatedByUserID { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ManuscriptDocumentUnlinkResult
|
||||||
|
{
|
||||||
|
public int BookID { get; set; }
|
||||||
|
public int ManuscriptDocumentID { get; set; }
|
||||||
|
public int ChaptersUnlinked { get; set; }
|
||||||
|
public int ScenesUnlinked { get; set; }
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class CharacterAlias
|
public sealed class CharacterAlias
|
||||||
{
|
{
|
||||||
public int CharacterAliasID { get; set; }
|
public int CharacterAliasID { get; set; }
|
||||||
|
|||||||
@ -381,6 +381,21 @@ public sealed class WordCompanionManuscriptLinkResponse
|
|||||||
public string Message { get; init; } = "Manuscript linked.";
|
public string Message { get; init; } = "Manuscript linked.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class WordCompanionManuscriptUnlinkRequest
|
||||||
|
{
|
||||||
|
public int BookId { get; set; }
|
||||||
|
public Guid? DocumentGuid { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class WordCompanionManuscriptUnlinkResponse
|
||||||
|
{
|
||||||
|
public int BookId { get; init; }
|
||||||
|
public int ManuscriptDocumentId { get; init; }
|
||||||
|
public int ChaptersUnlinked { get; init; }
|
||||||
|
public int ScenesUnlinked { get; init; }
|
||||||
|
public string Message { get; init; } = "Manuscript unlinked.";
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class WordCompanionCreateBookRequest
|
public sealed class WordCompanionCreateBookRequest
|
||||||
{
|
{
|
||||||
public string? Title { get; set; }
|
public string? Title { get; set; }
|
||||||
|
|||||||
@ -101,6 +101,7 @@ public interface IBookService
|
|||||||
Task<BookEditViewModel?> GetEditAsync(int bookId);
|
Task<BookEditViewModel?> GetEditAsync(int bookId);
|
||||||
Task PopulateEditContextAsync(BookEditViewModel model);
|
Task PopulateEditContextAsync(BookEditViewModel model);
|
||||||
Task<BookSaveResult> SaveAsync(BookEditViewModel model);
|
Task<BookSaveResult> SaveAsync(BookEditViewModel model);
|
||||||
|
Task<ManuscriptDocumentUnlinkResult?> UnlinkManuscriptAsync(int bookId);
|
||||||
Task ArchiveAsync(int bookId);
|
Task ArchiveAsync(int bookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1272,6 +1273,13 @@ public sealed class BookService(
|
|||||||
return new(bookId, true);
|
return new(bookId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<ManuscriptDocumentUnlinkResult?> UnlinkManuscriptAsync(int bookId)
|
||||||
|
{
|
||||||
|
return currentUser.UserId is int userId
|
||||||
|
? manuscriptDocuments.UnlinkBookAsync(bookId, null, userId)
|
||||||
|
: Task.FromResult<ManuscriptDocumentUnlinkResult?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<SelectListItem> BuildStatusOptions(string? selectedStatus)
|
private static IReadOnlyList<SelectListItem> BuildStatusOptions(string? selectedStatus)
|
||||||
=> BookStatuses.All
|
=> BookStatuses.All
|
||||||
.Select(status => new SelectListItem(status, status, string.Equals(status, selectedStatus, StringComparison.OrdinalIgnoreCase)))
|
.Select(status => new SelectListItem(status, status, string.Equals(status, selectedStatus, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
|||||||
@ -30,6 +30,7 @@ public interface IWordCompanionService
|
|||||||
Task<WordCompanionRuntimeBookResponse?> GetRuntimeBookAsync(int bookId, Guid documentGuid);
|
Task<WordCompanionRuntimeBookResponse?> GetRuntimeBookAsync(int bookId, Guid documentGuid);
|
||||||
Task<WordCompanionManuscriptBookResponse?> GetManuscriptForBookAsync(int bookId);
|
Task<WordCompanionManuscriptBookResponse?> GetManuscriptForBookAsync(int bookId);
|
||||||
Task<WordCompanionManuscriptLinkResponse?> LinkManuscriptAsync(WordCompanionManuscriptLinkRequest request);
|
Task<WordCompanionManuscriptLinkResponse?> LinkManuscriptAsync(WordCompanionManuscriptLinkRequest request);
|
||||||
|
Task<WordCompanionManuscriptUnlinkResponse?> UnlinkManuscriptAsync(WordCompanionManuscriptUnlinkRequest request);
|
||||||
Task<WordCompanionManuscriptAnalyseResponse?> AnalyseManuscriptAsync(WordCompanionManuscriptAnalyseRequest request);
|
Task<WordCompanionManuscriptAnalyseResponse?> AnalyseManuscriptAsync(WordCompanionManuscriptAnalyseRequest request);
|
||||||
Task<WordCompanionManuscriptImportResponse?> ImportManuscriptAsync(WordCompanionManuscriptImportRequest request);
|
Task<WordCompanionManuscriptImportResponse?> ImportManuscriptAsync(WordCompanionManuscriptImportRequest request);
|
||||||
Task<WordCompanionDiscoverCharactersResponse?> DiscoverCharacterCandidatesAsync(WordCompanionDiscoverCharactersRequest request);
|
Task<WordCompanionDiscoverCharactersResponse?> DiscoverCharacterCandidatesAsync(WordCompanionDiscoverCharactersRequest request);
|
||||||
@ -434,6 +435,26 @@ public sealed class WordCompanionService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<WordCompanionManuscriptUnlinkResponse?> UnlinkManuscriptAsync(WordCompanionManuscriptUnlinkRequest request)
|
||||||
|
{
|
||||||
|
if (request.BookId <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await manuscriptDocuments.UnlinkBookAsync(request.BookId, request.DocumentGuid, RequireUserId());
|
||||||
|
return result is null
|
||||||
|
? null
|
||||||
|
: new WordCompanionManuscriptUnlinkResponse
|
||||||
|
{
|
||||||
|
BookId = result.BookID,
|
||||||
|
ManuscriptDocumentId = result.ManuscriptDocumentID,
|
||||||
|
ChaptersUnlinked = result.ChaptersUnlinked,
|
||||||
|
ScenesUnlinked = result.ScenesUnlinked,
|
||||||
|
Message = result.Message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public Task<WordCompanionManuscriptAnalyseResponse?> AnalyseManuscriptAsync(WordCompanionManuscriptAnalyseRequest request)
|
public Task<WordCompanionManuscriptAnalyseResponse?> AnalyseManuscriptAsync(WordCompanionManuscriptAnalyseRequest request)
|
||||||
{
|
{
|
||||||
if (request.ProjectId <= 0
|
if (request.ProjectId <= 0
|
||||||
|
|||||||
68
PlotLine/Sql/108_Phase16Q_ManuscriptUnlink.sql
Normal file
68
PlotLine/Sql/108_Phase16Q_ManuscriptUnlink.sql
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ManuscriptDocument_UnlinkBook
|
||||||
|
@BookID int,
|
||||||
|
@DocumentGuid uniqueidentifier = NULL,
|
||||||
|
@UserID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @ManuscriptDocumentID int;
|
||||||
|
DECLARE @ChaptersUnlinked int = 0;
|
||||||
|
DECLARE @ScenesUnlinked int = 0;
|
||||||
|
|
||||||
|
SELECT @ManuscriptDocumentID = md.ManuscriptDocumentID
|
||||||
|
FROM dbo.ManuscriptDocuments md
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = md.BookID
|
||||||
|
INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
|
||||||
|
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
|
||||||
|
WHERE md.BookID = @BookID
|
||||||
|
AND md.IsActive = 1
|
||||||
|
AND b.IsArchived = 0
|
||||||
|
AND p.IsArchived = 0
|
||||||
|
AND pua.UserID = @UserID
|
||||||
|
AND pua.IsActive = 1
|
||||||
|
AND (@DocumentGuid IS NULL OR @DocumentGuid = '00000000-0000-0000-0000-000000000000' OR md.DocumentGuid = @DocumentGuid);
|
||||||
|
|
||||||
|
IF @ManuscriptDocumentID IS NULL
|
||||||
|
RETURN;
|
||||||
|
|
||||||
|
UPDATE dbo.Chapters
|
||||||
|
SET IsLinkedToManuscript = 0,
|
||||||
|
ManuscriptDocumentID = NULL,
|
||||||
|
ExternalReference = NULL,
|
||||||
|
LinkedUtc = NULL,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
WHERE BookID = @BookID
|
||||||
|
AND ManuscriptDocumentID = @ManuscriptDocumentID;
|
||||||
|
|
||||||
|
SET @ChaptersUnlinked = @@ROWCOUNT;
|
||||||
|
|
||||||
|
UPDATE s
|
||||||
|
SET IsLinkedToManuscript = 0,
|
||||||
|
ManuscriptDocumentID = NULL,
|
||||||
|
ExternalReference = NULL,
|
||||||
|
LinkedUtc = NULL,
|
||||||
|
UpdatedDate = SYSUTCDATETIME()
|
||||||
|
FROM dbo.Scenes s
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
WHERE c.BookID = @BookID
|
||||||
|
AND s.ManuscriptDocumentID = @ManuscriptDocumentID;
|
||||||
|
|
||||||
|
SET @ScenesUnlinked = @@ROWCOUNT;
|
||||||
|
|
||||||
|
UPDATE dbo.ManuscriptDocuments
|
||||||
|
SET IsActive = 0
|
||||||
|
WHERE ManuscriptDocumentID = @ManuscriptDocumentID;
|
||||||
|
|
||||||
|
SELECT @BookID AS BookID,
|
||||||
|
@ManuscriptDocumentID AS ManuscriptDocumentID,
|
||||||
|
@ChaptersUnlinked AS ChaptersUnlinked,
|
||||||
|
@ScenesUnlinked AS ScenesUnlinked,
|
||||||
|
N'Manuscript unlinked. Chapters, scenes, and story data were kept.' AS Message;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
@ -16,6 +16,10 @@
|
|||||||
{
|
{
|
||||||
<div class="alert alert-info">@archiveMessage</div>
|
<div class="alert alert-info">@archiveMessage</div>
|
||||||
}
|
}
|
||||||
|
@if (TempData["ManuscriptMessage"] is string manuscriptMessage)
|
||||||
|
{
|
||||||
|
<div class="alert alert-info">@manuscriptMessage</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="page-heading">
|
<div class="page-heading">
|
||||||
<div>
|
<div>
|
||||||
@ -101,6 +105,10 @@
|
|||||||
{
|
{
|
||||||
<strong>Manuscript linked</strong>
|
<strong>Manuscript linked</strong>
|
||||||
<span class="muted">Last synced: @(Model.ManuscriptDocument.LastSyncUtc?.ToString("dd MMM yyyy HH:mm") ?? "Never")</span>
|
<span class="muted">Last synced: @(Model.ManuscriptDocument.LastSyncUtc?.ToString("dd MMM yyyy HH:mm") ?? "Never")</span>
|
||||||
|
<form asp-action="UnlinkManuscript" method="post" class="mt-2" data-confirm-message="This will unlink the Word manuscript from this Book in PlotDirector. It will not delete chapters, scenes, characters, assets, locations, or manuscript text. If the Word document is still open, you should also unlink it from the Word Companion so the hidden document metadata is removed. Continue?">
|
||||||
|
<input type="hidden" name="id" value="@Model.Book.BookID" />
|
||||||
|
<button type="submit" class="btn btn-outline-danger btn-sm">Unlink Manuscript</button>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
<span class="muted">Manuscript Status</span>
|
<span class="muted">Manuscript Status</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -170,6 +170,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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" class="word-companion-danger-action" data-unlink-word-document hidden>Unlink this Word document</button>
|
||||||
<p class="word-companion-message" data-sync-note></p>
|
<p class="word-companion-message" data-sync-note></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -558,6 +558,16 @@ body {
|
|||||||
color: var(--companion-muted);
|
color: var(--companion-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-companion-current-scene > .word-companion-danger-action {
|
||||||
|
border-color: #fecaca;
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-companion-current-scene > .word-companion-danger-action:hover:not(:disabled) {
|
||||||
|
border-color: #ef4444;
|
||||||
|
background: #fef2f2;
|
||||||
|
}
|
||||||
|
|
||||||
.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
@ -67,6 +67,7 @@
|
|||||||
const anchorChapterId = document.querySelector("[data-anchor-chapter-id]");
|
const anchorChapterId = document.querySelector("[data-anchor-chapter-id]");
|
||||||
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 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]");
|
||||||
@ -426,6 +427,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateUnlinkWordDocumentButton = () => {
|
||||||
|
setHidden(unlinkWordDocumentButton, !firstRunDocumentBinding);
|
||||||
|
};
|
||||||
|
|
||||||
const setSyncStatus = (message) => {
|
const setSyncStatus = (message) => {
|
||||||
setText(syncStatus, message);
|
setText(syncStatus, message);
|
||||||
};
|
};
|
||||||
@ -1152,6 +1157,7 @@
|
|||||||
const setFirstRunMode = (visible) => {
|
const setFirstRunMode = (visible) => {
|
||||||
setHidden(firstRunWizard, !visible);
|
setHidden(firstRunWizard, !visible);
|
||||||
setHidden(tabsNav, visible);
|
setHidden(tabsNav, visible);
|
||||||
|
updateUnlinkWordDocumentButton();
|
||||||
for (const panel of tabPanels) {
|
for (const panel of tabPanels) {
|
||||||
setHidden(panel, visible ? true : panel.dataset.tabPanel !== "scene" && !panel.classList.contains("is-active"));
|
setHidden(panel, visible ? true : panel.dataset.tabPanel !== "scene" && !panel.classList.contains("is-active"));
|
||||||
}
|
}
|
||||||
@ -1357,6 +1363,30 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeDocumentBinding = () => new Promise((resolve, reject) => {
|
||||||
|
const settings = window.Office?.context?.document?.settings;
|
||||||
|
if (!settings) {
|
||||||
|
reject(new Error("Word document settings are unavailable."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.values(documentMetadataKeys)) {
|
||||||
|
if (typeof settings.remove === "function") {
|
||||||
|
settings.remove(key);
|
||||||
|
} else {
|
||||||
|
settings.set(key, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.saveAsync((result) => {
|
||||||
|
if (result.status === window.Office.AsyncResultStatus.Succeeded) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(result.error || new Error("Unable to remove Word document metadata."));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const ensureDocumentGuid = () => {
|
const ensureDocumentGuid = () => {
|
||||||
if (firstRunDocumentBinding?.documentGuid) {
|
if (firstRunDocumentBinding?.documentGuid) {
|
||||||
return firstRunDocumentBinding.documentGuid;
|
return firstRunDocumentBinding.documentGuid;
|
||||||
@ -3143,6 +3173,70 @@
|
|||||||
setFirstRunMode(true);
|
setFirstRunMode(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const returnToUnboundOnboarding = async (message) => {
|
||||||
|
firstRunDocumentBinding = null;
|
||||||
|
clearRuntimeCaches();
|
||||||
|
resetPlotDirectorScene("Not detected");
|
||||||
|
setSceneTrackingState("Manual");
|
||||||
|
setConnectionStatus("Connected");
|
||||||
|
syncFirstRunProjectOptions();
|
||||||
|
if (selectedFirstRunProject()) {
|
||||||
|
await loadBooks(selectedFirstRunProject().projectId, null);
|
||||||
|
} else {
|
||||||
|
resetSelect(firstRunBookSelect, "Select a project first");
|
||||||
|
}
|
||||||
|
resetFirstRunPreview(message || "Select a Project and Book to begin.");
|
||||||
|
setFirstRunMode(true);
|
||||||
|
updateUnlinkWordDocumentButton();
|
||||||
|
};
|
||||||
|
|
||||||
|
const unlinkThisWordDocument = async () => {
|
||||||
|
const binding = firstRunDocumentBinding || readDocumentBinding();
|
||||||
|
if (!binding) {
|
||||||
|
setDocumentMessage("This Word document is not linked.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = 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?");
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlinkWordDocumentButton) {
|
||||||
|
unlinkWordDocumentButton.disabled = true;
|
||||||
|
unlinkWordDocumentButton.textContent = "Unlinking...";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await postJson("/api/word-companion/manuscript/unlink", {
|
||||||
|
documentGuid: binding.documentGuid,
|
||||||
|
bookId: binding.bookId
|
||||||
|
});
|
||||||
|
await removeDocumentBinding();
|
||||||
|
await returnToUnboundOnboarding("Word document unlinked. Select a Project and Book to begin.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unable to unlink manuscript binding.", error);
|
||||||
|
const removeLocalOnly = window.confirm("PlotDirector could not be updated, but you can still remove the binding from this Word document.\n\nYou may need to unlink the Book manually in PlotDirector later.\n\nRemove Word metadata only?");
|
||||||
|
if (removeLocalOnly) {
|
||||||
|
try {
|
||||||
|
await removeDocumentBinding();
|
||||||
|
await returnToUnboundOnboarding("Word metadata removed. Unlink the Book manually in PlotDirector if it still shows as linked.");
|
||||||
|
} catch (metadataError) {
|
||||||
|
console.error("Unable to remove Word document metadata.", metadataError);
|
||||||
|
setDocumentMessage(`Unable to remove Word metadata: ${errorText(metadataError)}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setDocumentMessage(`Unable to unlink manuscript: ${errorText(error)}`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (unlinkWordDocumentButton) {
|
||||||
|
unlinkWordDocumentButton.disabled = false;
|
||||||
|
unlinkWordDocumentButton.textContent = "Unlink this Word document";
|
||||||
|
}
|
||||||
|
updateUnlinkWordDocumentButton();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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" },
|
||||||
@ -5056,6 +5150,7 @@
|
|||||||
syncSceneProgressButton?.addEventListener("click", () => syncSceneProgress({ source: "manual" }));
|
syncSceneProgressButton?.addEventListener("click", () => syncSceneProgress({ source: "manual" }));
|
||||||
saveSceneLinksButton?.addEventListener("click", saveSceneLinks);
|
saveSceneLinksButton?.addEventListener("click", saveSceneLinks);
|
||||||
attachPlotDirectorIdsButton?.addEventListener("click", attachPlotDirectorIds);
|
attachPlotDirectorIdsButton?.addEventListener("click", attachPlotDirectorIds);
|
||||||
|
unlinkWordDocumentButton?.addEventListener("click", unlinkThisWordDocument);
|
||||||
createPlotDirectorChapterButton?.addEventListener("click", createPlotDirectorChapter);
|
createPlotDirectorChapterButton?.addEventListener("click", createPlotDirectorChapter);
|
||||||
linkExistingChapterButton?.addEventListener("click", openLinkExistingChapterDialog);
|
linkExistingChapterButton?.addEventListener("click", openLinkExistingChapterDialog);
|
||||||
linkChapterSearch?.addEventListener("input", renderLinkChapterOptions);
|
linkChapterSearch?.addEventListener("input", renderLinkChapterOptions);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user