From 85a4415453a567aa5507504b965a21683389930f Mon Sep 17 00:00:00 2001 From: Hatton Date: Mon, 29 Apr 2024 20:18:53 -0600 Subject: [PATCH] wip save via api call wip save via callback --- src/BloomBrowserUI/bookEdit/editablePage.ts | 4 +- .../bookEdit/js/bloomEditing.ts | 10 +- src/BloomExe/Book/HtmlDom.cs | 7 + src/BloomExe/Edit/EditingModel.cs | 216 +++++++++--------- src/BloomExe/Edit/EditingView.cs | 32 --- .../web/controllers/EditingViewApi.cs | 18 ++ 6 files changed, 147 insertions(+), 140 deletions(-) diff --git a/src/BloomBrowserUI/bookEdit/editablePage.ts b/src/BloomBrowserUI/bookEdit/editablePage.ts index 3a326a831da4..1061994c8f49 100644 --- a/src/BloomBrowserUI/bookEdit/editablePage.ts +++ b/src/BloomBrowserUI/bookEdit/editablePage.ts @@ -41,7 +41,7 @@ export interface IPageFrameExports { // For example, editTabBundle.getEditablePageBundleExports().pageSelectionChanging() can be called. import { pageSelectionChanging, - getBodyContentForSavePage, + callApiWithSaveData, userStylesheetContent, pageUnloading, disconnectForGarbageCollection, @@ -54,7 +54,7 @@ import { } from "./js/bloomEditing"; export { pageSelectionChanging, - getBodyContentForSavePage, + callApiWithSaveData, userStylesheetContent, pageUnloading, disconnectForGarbageCollection, diff --git a/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts b/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts index 3af34c5b7294..1c5da1732f71 100644 --- a/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts +++ b/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts @@ -29,7 +29,7 @@ import "jquery.hotkeys"; //makes the on(keydown work with keynames) import "../../lib/jquery.resize"; // makes jquery resize work on all elements import { getEditTabBundleExports } from "./bloomFrames"; import { showInvisibles, hideInvisibles } from "./showInvisibles"; - +import { postJson } from "../../utils/bloomApi"; //promise may be needed to run tests with phantomjs //import promise = require('es6-promise'); //promise.Promise.polyfill(); @@ -1265,8 +1265,12 @@ export const pageSelectionChanging = () => { } }; -// Called from C# by a RunJavaScript() in EditingView.CleanHtmlAndCopyToPageDom via -// editTabBundle.getEditablePageBundleExports(). +export function callApiWithSaveData() { + postJson("editView/saveHtml", { + html: getBodyContentForSavePage(), + userStylesheetContent: userStylesheetContent() + }); +} export const getBodyContentForSavePage = () => { const bubbleEditingOn = theOneBubbleManager.isComicEditingOn; if (bubbleEditingOn) { diff --git a/src/BloomExe/Book/HtmlDom.cs b/src/BloomExe/Book/HtmlDom.cs index 7d2a662a8ff8..2ac1c575729a 100644 --- a/src/BloomExe/Book/HtmlDom.cs +++ b/src/BloomExe/Book/HtmlDom.cs @@ -58,6 +58,13 @@ public HtmlDom(XmlDocument domToClone) _dom = (XmlDocument)domToClone.Clone(); } + public static HtmlDom FromXmlNodeNoClone(XmlNode domToOwn) + { + var h = new HtmlDom(); + h.RawDom.DocumentElement.AppendChild(h.RawDom.ImportNode(domToOwn, true)); + return h; + } + /// /// Make a DOM out of the input /// diff --git a/src/BloomExe/Edit/EditingModel.cs b/src/BloomExe/Edit/EditingModel.cs index 225efc54e0d3..fff022b0427f 100644 --- a/src/BloomExe/Edit/EditingModel.cs +++ b/src/BloomExe/Edit/EditingModel.cs @@ -1260,29 +1260,39 @@ private bool CannotSavePage() int _saveCount = 0; object _saveCountLock = new object(); - public void SaveNow(bool forceFullSave = false) + public void SaveHtmlNow(string bodyHtml, string userCssContent) { - try - { - lock (_saveCountLock) - { - Debug.Assert( - _saveCount == 0, - $"Trying to save while already saving: possible empty marginBox cause? (save count={_saveCount})" - ); - _saveCount++; - } - SaveNowInternal(forceFullSave); - } - finally + var dom = XmlHtmlConverter.GetXmlDomFromHtml(bodyHtml, false); + var bodyDom = dom.SelectSingleNode("//body"); + var browserDomPage = bodyDom.SelectSingleNode( + "//body//div[contains(@class,'bloom-page')]" + ); + var htmlDom = HtmlDom.FromXmlNodeNoClone(browserDomPage); + + var styles = HtmlDom.CreateUserModifiedStyles(userCssContent); + + var head = htmlDom.RawDom.SelectSingleNode("//head"); + head.InnerXml = HtmlDom.CreateUserModifiedStyles(userCssContent); + + _pageSelection.CurrentSelection.Book.SavePage( + htmlDom, + true /*todo forceFullSave*/ + ); + _pageHasUnsavedDataDerivedChange = false; + CheckForBL2634("finished save"); + while (_tasksToDoAfterSaving.Count > 0) { - lock (_saveCountLock) - { - _saveCount--; - } + var task = _tasksToDoAfterSaving[0]; + _tasksToDoAfterSaving.RemoveAt(0); + task(); } } + public void SaveNow(bool forceFullSave = false) + { + SaveNowInternal(forceFullSave); + } + private void SaveNowInternal(bool forceFullSave = false) { if (_domForCurrentPage != null && !_inProcessOfSaving && !NavigatingSoSuspendSaving) @@ -1292,92 +1302,92 @@ private void SaveNowInternal(bool forceFullSave = false) // Check memory for the benefit of developers. MemoryManagement.CheckMemory(false, "before EditingModel.SaveNow()", false); #endif - try - { - _webSocketServer.SendString("pageThumbnailList", "saving", ""); - // CleanHtml already requires that we are on UI thread. But it's worth asserting here too in case that changes. - // If we weren't sure of that we would need locking for access to _tasksToDoAfterSaving and _inProcessOfSaving, - // and would need to be careful about whether any delayed tasks needed to be on the UI thread. - if (_view.InvokeRequired) - { - NonFatalProblem.Report( - ModalIf.Beta, - PassiveIf.Beta, - "SaveNow called on wrong thread", - null - ); - _view.Invoke((Action)(() => SaveNowInternal(forceFullSave))); - Logger.WriteMinorEvent( - "EditingModel.SaveNow() finished after Invoke to get on UI thread" - ); - return; - } - CheckForBL2634("beginning SaveNow"); - _inProcessOfSaving = true; - _tasksToDoAfterSaving.Clear(); - _view.GetHtmlFromBrowserAndCopyToPageDom(); - - //BL-1064 (and several other reports) were about not being able to save a page. The problem appears to be that - //this old code: - // CurrentBook.SavePage(_domForCurrentPage); - //would some times ask book X to save a page from book Y. - //We could never reproduce it at will, so this is to help with that... - if (this._pageSelection.CurrentSelection.Book != _currentlyDisplayedBook) - { - Debug.Fail("This is the BL-1064 Situation"); - Logger.WriteEvent( - "Warning: SaveNow() with a page that is not the current book. That should be ok, but it is the BL-1064 situation (though we now work around it)." - ); - } - //but meanwhile, the page knows its book, so we can see if it looks like a valid book and give a helpful - //error if, for example, it was deleted: - try - { - if (!_pageSelection.CurrentSelection.Book.IsSaveable) - { - Logger.WriteEvent( - "Error: SaveNow() found that this book had IsSaveable=='false'" - ); - Logger.WriteEvent( - "Book path was {0}", - _pageSelection.CurrentSelection.Book.FolderPath - ); - throw new ApplicationException( - "Bloom tried to save a page to a book that was not in a position to be updated." - ); - } - } - catch (ObjectDisposedException err) // in case even calling CanUpdate gave an error - { - Logger.WriteEvent("Error: SaveNow() found that this book was disposed."); - throw err; - } - catch (Exception err) // in case even calling CanUpdate gave an error - { - Logger.WriteEvent("Error: SaveNow():CanUpdate threw an exception"); - throw err; - } - CheckForBL2634("save"); - //OK, looks safe, time to save. - var newPageData = GetPageData(_domForCurrentPage.RawDom); - _pageSelection.CurrentSelection.Book.SavePage( - _domForCurrentPage, - forceFullSave || NeedToDoFullSave(newPageData) - ); - _pageHasUnsavedDataDerivedChange = false; - CheckForBL2634("finished save"); - while (_tasksToDoAfterSaving.Count > 0) - { - var task = _tasksToDoAfterSaving[0]; - _tasksToDoAfterSaving.RemoveAt(0); - task(); - } - } - finally - { - _inProcessOfSaving = false; - } + _webSocketServer.SendString("pageThumbnailList", "saving", ""); + + // CleanHtml already requires that we are on UI thread. But it's worth asserting here too in case that changes. + // If we weren't sure of that we would need locking for access to _tasksToDoAfterSaving and _inProcessOfSaving, + // and would need to be careful about whether any delayed tasks needed to be on the UI thread. + // if (_view.InvokeRequired) + // { + // NonFatalProblem.Report( + // ModalIf.Beta, + // PassiveIf.Beta, + // "SaveNow called on wrong thread", + // null + // ); + // _view.Invoke((Action)(() => SaveNowInternal(forceFullSave))); + // Logger.WriteMinorEvent( + // "EditingModel.SaveNow() finished after Invoke to get on UI thread" + // ); + // return; + // } + CheckForBL2634("beginning SaveNow"); + _inProcessOfSaving = true; + _tasksToDoAfterSaving.Clear(); + // TODO: do the toolbox removal, check that things are loaded, etc. + var script = + @"console.log('saving'); editTabBundle.getEditablePageBundleExports().callApiWithSaveData();"; + _view.Browser.RunJavascriptAsync(script); + + // _view.GetHtmlFromBrowserAndCopyToPageDom(); + + // //BL-1064 (and several other reports) were about not being able to save a page. The problem appears to be that + // //this old code: + // // CurrentBook.SavePage(_domForCurrentPage); + // //would some times ask book X to save a page from book Y. + // //We could never reproduce it at will, so this is to help with that... + // if (this._pageSelection.CurrentSelection.Book != _currentlyDisplayedBook) + // { + // Debug.Fail("This is the BL-1064 Situation"); + // Logger.WriteEvent( + // "Warning: SaveNow() with a page that is not the current book. That should be ok, but it is the BL-1064 situation (though we now work around it)." + // ); + // } + // //but meanwhile, the page knows its book, so we can see if it looks like a valid book and give a helpful + // //error if, for example, it was deleted: + // try + // { + // if (!_pageSelection.CurrentSelection.Book.IsSaveable) + // { + // Logger.WriteEvent( + // "Error: SaveNow() found that this book had IsSaveable=='false'" + // ); + // Logger.WriteEvent( + // "Book path was {0}", + // _pageSelection.CurrentSelection.Book.FolderPath + // ); + // throw new ApplicationException( + // "Bloom tried to save a page to a book that was not in a position to be updated." + // ); + // } + // } + // catch (ObjectDisposedException err) // in case even calling CanUpdate gave an error + // { + // Logger.WriteEvent("Error: SaveNow() found that this book was disposed."); + // throw err; + // } + // catch (Exception err) // in case even calling CanUpdate gave an error + // { + // Logger.WriteEvent("Error: SaveNow():CanUpdate threw an exception"); + // throw err; + // } + // CheckForBL2634("save"); + // //OK, looks safe, time to save. + // var newPageData = GetPageData(_domForCurrentPage.RawDom); + // _pageSelection.CurrentSelection.Book.SavePage( + // _domForCurrentPage, + // forceFullSave || NeedToDoFullSave(newPageData) + // ); + // _pageHasUnsavedDataDerivedChange = false; + // CheckForBL2634("finished save"); + // while (_tasksToDoAfterSaving.Count > 0) + // { + // var task = _tasksToDoAfterSaving[0]; + // _tasksToDoAfterSaving.RemoveAt(0); + // task(); + // } + #if MEMORYCHECK // Check memory for the benefit of developers. MemoryManagement.CheckMemory(false, "after EditingModel.SaveNow()", false); diff --git a/src/BloomExe/Edit/EditingView.cs b/src/BloomExe/Edit/EditingView.cs index 1a9ca0478e41..eccd1fcc3540 100644 --- a/src/BloomExe/Edit/EditingView.cs +++ b/src/BloomExe/Edit/EditingView.cs @@ -1481,38 +1481,6 @@ public void UpdateAllThumbnails() _pageListView.UpdateAllThumbnails(); } - /// - /// this started as an experiment, where our textareas were not being read when we saved because of the need - /// to change the picture - /// - public async Task GetHtmlFromBrowserAndCopyToPageDom() - { - // NOTE: these calls to may lead to API calls from the JS. These are async, so the actions - // that JS might perform may not actually happen until well after this method. We ran into a problem in - // BL-9912 where the Leveled Reader Tool was prompted by some of this to call us back with a save to the - // tool state, but by then the editingModel had cleared out its knowledge of what book it had previously - // been editing, so there was an null. - var script = - @" -if (typeof(editTabBundle) !=='undefined' && typeof(editTabBundle.getToolboxBundleExports()) !=='undefined') - editTabBundle.getToolboxBundleExports().removeToolboxMarkup(); -if (typeof(editTabBundle) !=='undefined' && typeof(editTabBundle.getEditablePageBundleExports()) !=='undefined') - editTabBundle.getEditablePageBundleExports().getBodyContentForSavePage() + '' + editTabBundle.getEditablePageBundleExports().userStylesheetContent();"; - var combinedData = RunJavascriptWithStringResult_Sync_Dangerous(script); - string bodyHtml = null; - string userCssContent = null; - if (combinedData != null) - { - var endHtml = combinedData.IndexOf("", StringComparison.Ordinal); - if (endHtml > 0) - { - bodyHtml = combinedData.Substring(0, endHtml); - userCssContent = combinedData.Substring(endHtml + "".Length); - } - } - _browser1.ReadEditedHtmlNow(bodyHtml, userCssContent); // TODO this makes no sense being in browser. Has nothing to do with the browser. - } - private void _copyButton_Click(object sender, EventArgs e) { ExecuteCommandSafely(_copyCommand); diff --git a/src/BloomExe/web/controllers/EditingViewApi.cs b/src/BloomExe/web/controllers/EditingViewApi.cs index f0a3267fcd04..27d9fcaab194 100644 --- a/src/BloomExe/web/controllers/EditingViewApi.cs +++ b/src/BloomExe/web/controllers/EditingViewApi.cs @@ -67,6 +67,13 @@ public void RegisterWithApiHandler(BloomApiHandler apiHandler) HandleDuplicatePageMany, true ); + apiHandler.RegisterEndpointHandler( + "editView/saveHtml", + HandleSaveHtml, + true /*review*/ + , + true /*review*/ + ); apiHandler.RegisterEndpointHandler("editView/topics", HandleTopics, false); apiHandler.RegisterEndpointHandler("editView/changeImage", HandleChangeImage, true); apiHandler.RegisterEndpointHandler("editView/cutImage", HandleCutImage, true); @@ -231,6 +238,17 @@ dynamic messageBundle } } + private void HandleSaveHtml(ApiRequest request) + { + // the post is an object of the form {html: string, userStyles:string} + // after we get it we call the SaveHtmlNow method on the model + dynamic data = request.RequiredPostDynamic(); + var html = (string)data.html; + var userStylesheetContent = (string)data.userStylesheetContent; + View.Model.SaveHtmlNow(html, userStylesheetContent); + request.PostSucceeded(); + } + private void HandleChangeImage(ApiRequest request) { dynamic data = DynamicJson.Parse(request.RequiredPostJson());