diff --git a/.gitignore b/.gitignore
index 31a7afd7d31d..a5b23c4a541e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -179,3 +179,5 @@ src/content/.yarn/*
!src/content/.yarn/releases
!src/content/.yarn/sdks
!src/content/.yarn/versions
+
+.vite/vitest/results.json
diff --git a/src/BloomBrowserUI/bookEdit/editablePage.ts b/src/BloomBrowserUI/bookEdit/editablePage.ts
index 3a326a831da4..0728241f7c4e 100644
--- a/src/BloomBrowserUI/bookEdit/editablePage.ts
+++ b/src/BloomBrowserUI/bookEdit/editablePage.ts
@@ -11,7 +11,6 @@ import { theOneBubbleManager, BubbleManager } from "./js/bubbleManager";
// This allows strong typing to be done for exported functions
export interface IPageFrameExports {
- pageSelectionChanging(): void;
pageUnloading(): void;
disconnectForGarbageCollection(): void;
copySelection(): void;
@@ -38,10 +37,9 @@ export interface IPageFrameExports {
}
// This exports the functions that should be accessible from other IFrames or from C#.
-// For example, editTabBundle.getEditablePageBundleExports().pageSelectionChanging() can be called.
+// For example, editTabBundle.getEditablePageBundleExports().saveRequested() can be called.
import {
- pageSelectionChanging,
- getBodyContentForSavePage,
+ saveRequested,
userStylesheetContent,
pageUnloading,
disconnectForGarbageCollection,
@@ -53,8 +51,7 @@ import {
attachToCkEditor
} from "./js/bloomEditing";
export {
- pageSelectionChanging,
- getBodyContentForSavePage,
+ saveRequested,
userStylesheetContent,
pageUnloading,
disconnectForGarbageCollection,
diff --git a/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts b/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts
index 3af34c5b7294..d6ba123ba1aa 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();
@@ -1251,9 +1251,7 @@ export function localizeCkeditorTooltips(bar: JQuery) {
});
}
-// This is invoked from C# when we are about to change pages. The C# code will save the changes
-// to the page after we return from this (hopefully; certainly in debugging this is the case).
-export const pageSelectionChanging = () => {
+function removeOrigami() {
// We are mirroring the origami layoutToggleClickHandler() here, in case the user changes
// pages while the origami toggle in on.
// The DOM here is for just one page, so there's only ever one marginBox.
@@ -1263,10 +1261,16 @@ export const pageSelectionChanging = () => {
for (let i = 0; i < textLabels.length; i++) {
textLabels[i].remove();
}
-};
+}
-// Called from C# by a RunJavaScript() in EditingView.CleanHtmlAndCopyToPageDom via
-// editTabBundle.getEditablePageBundleExports().
+export function saveRequested(forceFullSave: boolean) {
+ removeOrigami();
+ postJson("editView/saveHtml", {
+ forceFullSave: forceFullSave,
+ 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 be8b18c7b631..29eccc9ae2e3 100644
--- a/src/BloomExe/Edit/EditingModel.cs
+++ b/src/BloomExe/Edit/EditingModel.cs
@@ -158,7 +158,7 @@ ITemplateFinder sourceCollectionsList
collectionClosingEvent.Subscribe(o =>
{
if (Visible)
- SaveNow();
+ RequestBrowserToSave();
});
localizationChangedEvent.Subscribe(o =>
{
@@ -166,7 +166,7 @@ ITemplateFinder sourceCollectionsList
//shown so the view has never been full constructed, so we're not in a good state to do a refresh
if (Visible)
{
- SaveNow();
+ RequestBrowserToSave();
_view.UpdateButtonLocalizations();
RefreshDisplayOfCurrentPage(true);
//_view.UpdateDisplay();
@@ -214,7 +214,7 @@ private void OnTabAboutToChange(TabChangedDetails details)
{
if (details.From == _view)
{
- SaveNow();
+ RequestBrowserToSave();
_view.RunJavascriptAsync(
"if (typeof(editTabBundle) !=='undefined' && typeof(editTabBundle.getEditablePageBundleExports()) !=='undefined') {editTabBundle.getEditablePageBundleExports().disconnectForGarbageCollection();}"
);
@@ -310,7 +310,7 @@ private void DuplicatePageInternal(IPage page, int numberOfTimesToDuplicate = 1)
{
try
{
- SaveNow(); //ensure current page is saved first
+ RequestBrowserToSave(); //ensure current page is saved first
_domForCurrentPage = null; //prevent us trying to save it later, as the page selection changes
_currentlyDisplayedBook.DuplicatePage(page, numberOfTimesToDuplicate);
// Book.DuplicatePage() updates the page list so we don't need to do it here.
@@ -354,7 +354,7 @@ internal void DeletePage(IPage page)
try
{
// BL-4035 Save any style changes to the book before deleting the page.
- SaveNow();
+ RequestBrowserToSave();
}
catch (Exception saveError)
{
@@ -551,7 +551,7 @@ public IEnumerable GetSizeAndOrientationChoices()
public void SetLayout(Layout layout)
{
- SaveNow();
+ RequestBrowserToSave();
var changedOrientation =
CurrentBook.GetLayout().SizeAndOrientation.IsLandScape
!= layout.SizeAndOrientation.IsLandScape;
@@ -590,7 +590,7 @@ public void ContentLanguagesSelectionChanged()
CurrentBook.SetMultilingualContentLanguages(contentLanguages); // set langs before saving page
// The language choice is saved in the data-div, so we must do a full save even if this
// page doesn't contain anything else that has non-local effects.
- SaveNow(true);
+ RequestBrowserToSave(true);
CurrentBook.PrepareForEditing();
RefreshDisplayOfCurrentPage();
_view.UpdatePageList(true); //counting on this to redo the thumbnails
@@ -718,14 +718,17 @@ public void ViewVisibleNowDoSlowStuff()
// completed saving it.
int _selChangeCount = 0;
object _selChangeLock = new object();
+
private void OnPageSelectionChanging(object sender, EventArgs eventArgs)
{
try
{
lock (_selChangeLock)
{
- Debug.Assert(_selChangeCount == 0,
- $"Multiple active OnPageSelectionChanging calls, possible empty marginBox cause? (count={_selChangeLock})");
+ Debug.Assert(
+ _selChangeCount == 0,
+ $"Multiple active OnPageSelectionChanging calls, possible empty marginBox cause? (count={_selChangeLock})"
+ );
_selChangeCount++;
}
OnPageSelectionChangingInternal(sender, eventArgs);
@@ -738,6 +741,7 @@ private void OnPageSelectionChanging(object sender, EventArgs eventArgs)
}
}
}
+
private void OnPageSelectionChangingInternal(object sender, EventArgs eventArgs)
{
CheckForBL2634("start of page selection changing--should have old IDs");
@@ -749,10 +753,7 @@ private void OnPageSelectionChangingInternal(object sender, EventArgs eventArgs)
)
{
_view.ChangingPages = true;
- _view.RunJavascriptWithStringResult_Sync_Dangerous(
- "if (typeof(editTabBundle) !=='undefined' && typeof(editTabBundle.getEditablePageBundleExports()) !=='undefined') {editTabBundle.getEditablePageBundleExports().pageSelectionChanging();}"
- );
- FinishSavingPage();
+ RequestSavePage();
_view.RunJavascriptAsync(
"if (typeof(editTabBundle) !=='undefined' && typeof(editTabBundle.getEditablePageBundleExports()) !=='undefined') {editTabBundle.getEditablePageBundleExports().disconnectForGarbageCollection();}"
);
@@ -1142,7 +1143,7 @@ private void EnsureLevelAttrCorrect()
_currentlyDisplayedBook.BookInfo.MetaData.LeveledReaderLevel.ToString();
if (correctLevel != currentLevel)
{
- SaveNow();
+ RequestBrowserToSave();
_currentlyDisplayedBook.OurHtmlDom.Body.SetAttribute(
"data-leveledreaderlevel",
correctLevel
@@ -1209,25 +1210,20 @@ internal void RethinkPageAndReloadIt(bool forceFullSave = false)
{
if (CannotSavePage())
return;
- FinishSavingPage(forceFullSave);
+ RequestSavePage(forceFullSave);
RefreshDisplayOfCurrentPage();
}
///
- /// Called from a JavaScript event after it has done everything appropriate in JS land towards saving a page,
- /// in the process of wrapping up this page before moving to another.
- /// The main point is that any changes on this page get saved back to the main document.
- /// In case it is an origami page, there is some special stuff to do as commented below.
- /// (Argument is required for JS callback, not used).
+ /// Called as a result of page selection changing or other event that requests a save and reload.
///
- /// true if it was aborted (nothing to save or refresh)
- private void FinishSavingPage(bool forceFullSave = false)
+ private void RequestSavePage(bool forceFullSave = false)
{
if (CannotSavePage())
return;
var stopwatch = Stopwatch.StartNew();
- SaveNow(forceFullSave);
+ RequestBrowserToSave(forceFullSave);
stopwatch.Stop();
Debug.WriteLine("Save Now Elapsed Time: {0} ms", stopwatch.ElapsedMilliseconds);
}
@@ -1253,128 +1249,50 @@ private bool CannotSavePage()
private System.Windows.Forms.Timer _developerFileWatcherQuietTimer;
private bool _weHaveSeenAJsonChange;
- int _saveCount = 0;
- object _saveCountLock = new object();
- public void SaveNow(bool forceFullSave = false)
+ public void SavePageHtml(string bodyHtml, string userCssContent, bool forceFullSave)
{
- try
- {
- lock (_saveCountLock)
- {
- Debug.Assert(_saveCount == 0,
- $"Trying to save while already saving: possible empty marginBox cause? (save count={_saveCount})");
- _saveCount++;
- }
- SaveNowInternal(forceFullSave);
- }
- finally
+ // extract the page and convert it to xml
+ 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);
+
+ // stick the custom styles in the head
+ var styles = HtmlDom.CreateUserModifiedStyles(userCssContent);
+ var head = htmlDom.RawDom.SelectSingleNode("//head");
+ head.InnerXml = HtmlDom.CreateUserModifiedStyles(userCssContent);
+
+ // see if there were data-div changes which require a full save
+ var newPageData = GetPageData(browserDomPage);
+ var fullSave = forceFullSave || NeedToDoFullSave(newPageData);
+
+ // save it
+ _pageSelection.CurrentSelection.Book.SavePage(htmlDom, fullSave);
+ _pageHasUnsavedDataDerivedChange = false;
+
+ // something to do with deleting?
+ while (_tasksToDoAfterSaving.Count > 0)
{
- lock (_saveCountLock)
- {
- _saveCount--;
- }
+ var task = _tasksToDoAfterSaving[0];
+ _tasksToDoAfterSaving.RemoveAt(0);
+ task();
}
}
- private void SaveNowInternal(bool forceFullSave = false)
+
+ public void RequestBrowserToSave(bool forceFullSave = false)
{
if (_domForCurrentPage != null && !_inProcessOfSaving && !NavigatingSoSuspendSaving)
{
- Logger.WriteMinorEvent("EditingModel.SaveNow() starting");
-#if MEMORYCHECK
- // 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.CleanHtmlAndCopyToPageDom();
-
- //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;
- }
-#if MEMORYCHECK
- // Check memory for the benefit of developers.
- MemoryManagement.CheckMemory(false, "after EditingModel.SaveNow()", false);
-#endif
- Logger.WriteMinorEvent("EditingModel.SaveNow() finished");
+ Logger.WriteMinorEvent("EditingModel.RequestSave() starting");
+ // show the saving message to the user
+ _webSocketServer.SendString("pageThumbnailList", "saving", "");
+ _tasksToDoAfterSaving.Clear(); // review
+ // review do we really need to be checking to see if things are loaded? If they are not, then there is nothing to save, and this doesn't thow.
+ var script =
+ $"console.log('saving'); editTabBundle.getToolboxBundleExports().removeToolboxMarkup();editTabBundle.getEditablePageBundleExports().saveRequested({forceFullSave.ToString().ToLowerInvariant()});";
+ _view.Browser.RunJavascriptAsync(script);
}
else
{
@@ -1490,7 +1408,7 @@ IProgress progress
);
// We need to save so that when asked by the thumbnailer, the book will give the proper image
- SaveNow();
+ RequestBrowserToSave();
// BL-3717: if we cleanup unused image files whenever we change a picture then Cut can lose
// all of an image's metadata (because the actual file is missing from the book folder when we go to
@@ -1681,14 +1599,14 @@ public void PreserveHtmlOfElement(string elementHtml)
public void ShowAddPageDialog()
{
- SaveNow(); // At least in template mode, the current page shows in the Add Page dialog, and should be current.
+ RequestBrowserToSave(); // At least in template mode, the current page shows in the Add Page dialog, and should be current.
_view.ShowAddPageDialog();
}
internal void ChangePageLayout(IPage page)
{
PageChangingLayout = page;
- SaveNow(); // need to preserve any typing they've done but not yet saved
+ RequestBrowserToSave(); // need to preserve any typing they've done but not yet saved
_view.ShowChangeLayoutDialog();
}
@@ -1743,7 +1661,7 @@ public bool GetClipboardHasPage()
public void CopyPage(IPage page)
{
- SaveNow(); // need to preserve any typing they've done but not yet saved (BL-4512)
+ RequestBrowserToSave(); // need to preserve any typing they've done but not yet saved (BL-4512)
// We have to clone this so that if the user changes the page after doing the copy,
// when they paste they get the page as it was, not as it is now.
_pageDivFromCopyPage = (XmlElement)page.GetDivNodeForThisPage().CloneNode(true);
diff --git a/src/BloomExe/Edit/EditingView.cs b/src/BloomExe/Edit/EditingView.cs
index bb8b94ff0abf..ee81d837021d 100644
--- a/src/BloomExe/Edit/EditingView.cs
+++ b/src/BloomExe/Edit/EditingView.cs
@@ -796,7 +796,7 @@ public void OnPasteImage(int imgIndex)
{
// BL-11709: It's just possible that the element we are pasting into has not yet been saved,
// which will cause problems. So do a save before pasting.
- _model.SaveNow();
+ _model.RequestBrowserToSave();
using (var measure = PerformanceMeasurement.Global.Measure("Paste Image"))
{
@@ -1046,7 +1046,7 @@ public void OnChangeImage(int imgIndex)
{
// Make sure any new image overlays on the page are saved, so our imgIndex will select the
// right one.
- Model.SaveNow();
+ Model.RequestBrowserToSave();
var imageElement = GetImageNode(imgIndex);
if (imageElement == null)
@@ -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 void CleanHtmlAndCopyToPageDom()
- {
- // 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.ReadEditableAreasNow(bodyHtml, userCssContent);
- }
-
private void _copyButton_Click(object sender, EventArgs e)
{
ExecuteCommandSafely(_copyCommand);
@@ -1870,7 +1838,7 @@ protected override void OnLoad(EventArgs e)
private void SaveWhenIdle(object o, EventArgs eventArgs)
{
Application.Idle -= SaveWhenIdle; // don't need to do again till next Deactivate.
- _model.SaveNow();
+ _model.RequestBrowserToSave();
// Restore any tool state removed by CleanHtmlAndCopyToPageDom(), which is called by _model.SaveNow().
RunJavascriptAsync(
"if (typeof(editTabBundle) !=='undefined') {editTabBundle.getToolboxBundleExports().applyToolboxStateToPage();}"
@@ -2121,7 +2089,7 @@ internal void SetZoomControl(ZoomControl zoomCtl)
private void _bookSettingsButton_Click(object sender, EventArgs e)
{
- _model.SaveNow();
+ _model.RequestBrowserToSave();
// Open the book settings dialog to the context-specific group.
var groupIndex = _model.CurrentPage.IsCoverPage ? 0 : 1;
diff --git a/src/BloomExe/Edit/PageSelection.cs b/src/BloomExe/Edit/PageSelection.cs
index 8b976030fae7..0d839d718a2a 100644
--- a/src/BloomExe/Edit/PageSelection.cs
+++ b/src/BloomExe/Edit/PageSelection.cs
@@ -28,7 +28,10 @@ public bool SelectPage(IPage page, bool prepareAlreadyDone = false)
#endif
//enhance... make pre-change event cancellable
if (!prepareAlreadyDone)
+ {
+ // this leads to the saving of the current page
PrepareToSelectPage();
+ }
_currentSelection = page;
InvokeSelectionChanged();
diff --git a/src/BloomExe/Edit/WebThumbNailList.cs b/src/BloomExe/Edit/WebThumbNailList.cs
index 178fcc3f639e..5426c938cbd9 100644
--- a/src/BloomExe/Edit/WebThumbNailList.cs
+++ b/src/BloomExe/Edit/WebThumbNailList.cs
@@ -348,7 +348,7 @@ internal void PageMoved(IPage movedPage, int newPageIndex)
WebSocketServer.SendString("pageThumbnailList", "pageListNeedsReset", "");
return;
}
- Model.SaveNow();
+ Model.RequestBrowserToSave();
var relocatePageInfo = new RelocatePageInfo(movedPage, newPageIndex);
RelocatePageEvent.Raise(relocatePageInfo);
UpdateItems(movedPage.Book.GetPages());
diff --git a/src/BloomExe/IBrowser.cs b/src/BloomExe/IBrowser.cs
index d9d833aac3f2..6f6503b80303 100644
--- a/src/BloomExe/IBrowser.cs
+++ b/src/BloomExe/IBrowser.cs
@@ -1,10 +1,7 @@
using Bloom.Api;
using Bloom.Book;
-using Bloom.ErrorReporter;
using Bloom.ToPalaso;
-using L10NSharp;
using SIL.IO;
-using SIL.Reporting;
using System;
using System.Diagnostics;
using System.Drawing;
@@ -288,127 +285,6 @@ public void OnOpenPageInEdge(object sender, EventArgs e)
// intentionally letting any errors just escape, give us an error
}
- public void ReadEditableAreasNow(string bodyHtml, string userCssContent)
- {
- if (Url != "about:blank")
- {
- LoadPageDomFromBrowser(bodyHtml, userCssContent);
- }
- }
-
- ///
- /// What's going on here: the browser is just editing/displaying a copy of one page of the document.
- /// So we need to copy any changes back to the real DOM.
- /// We're now obtaining the new content another way, so this code doesn't have any reason
- /// to be in this class...but we're aiming for a minimal change, maximal safety fix for 4.9
- ///
- private void LoadPageDomFromBrowser(string bodyHtml, string userCssContent)
- {
- Debug.Assert(!InvokeRequired);
- if (_pageEditDom == null)
- return;
-
- try
- {
- // unlikely, but if we somehow couldn't get the new content, better keep the old.
- // This MIGHT be able to happen in some cases of very fast page clicking, where
- // the page isn't fully enough loaded to expose the functions we use to get the
- // content. In that case, the user can't have made changes, so not saving is fine.
- if (string.IsNullOrEmpty(bodyHtml))
- return;
-
- var content = bodyHtml;
- XmlDocument dom;
-
- //todo: deal with exception that can come out of this
- dom = XmlHtmlConverter.GetXmlDomFromHtml(content, false);
- var bodyDom = dom.SelectSingleNode("//body");
-
- if (_pageEditDom == null)
- return;
-
- var destinationDomPage = _pageEditDom.SelectSingleNode(
- "//body//div[contains(@class,'bloom-page')]"
- );
- if (destinationDomPage == null)
- return;
- var expectedPageId = destinationDomPage.Attributes["id"].Value;
-
- var browserDomPage = bodyDom.SelectSingleNode(
- "//body//div[contains(@class,'bloom-page')]"
- );
- if (browserDomPage == null)
- return; //why? but I've seen it happen
-
- var thisPageId = browserDomPage.Attributes["id"].Value;
- if (expectedPageId != thisPageId)
- {
- SIL.Reporting.ErrorReport.NotifyUserOfProblem(
- LocalizationManager.GetString(
- "Browser.ProblemSaving",
- "There was a problem while saving. Please return to the previous page and make sure it looks correct."
- )
- );
- return;
- }
-
- // We've seen pages get emptied out, and we don't know why. This is a safety check.
- // See BL-13078, BL-13120, BL-13123, and BL-13143 for examples.
- if (BookStorage.CheckForEmptyMarginBoxOnPage(browserDomPage as XmlElement))
- {
- // This has been logged and reported to the user. We don't want to save the empty page.
- return;
- }
-
- _pageEditDom.GetElementsByTagName("body")[0].InnerXml = bodyDom.InnerXml;
-
- SaveCustomizedCssRules(userCssContent);
-
- //enhance: we have jscript for this: cleanup()... but running jscript in this method was leading the browser to show blank screen
- // foreach (XmlElement j in _editDom.SafeSelectNodes("//div[contains(@class, 'ui-tooltip')]"))
- // {
- // j.ParentNode.RemoveChild(j);
- // }
- // foreach (XmlAttribute j in _editDom.SafeSelectNodes("//@ariasecondary-describedby | //@aria-describedby"))
- // {
- // j.OwnerElement.RemoveAttributeNode(j);
- // }
- }
- catch (Exception e)
- {
- Bloom.Utils.MiscUtils.SuppressUnusedExceptionVarWarning(e);
- Debug.Fail(
- "Debug Mode Only: Error while trying to read changes to CSSRules. In Release, this just gets swallowed. Will now re-throw the exception."
- );
-#if DEBUG
- throw;
-#endif
- }
-
- try
- {
- XmlHtmlConverter.ThrowIfHtmlHasErrors(_pageEditDom.OuterXml);
- }
- catch (Exception e)
- {
- //var exceptionWithHtmlContents = new Exception(content);
- ErrorReport.NotifyUserOfProblem(
- e,
- "Sorry, Bloom choked on something on this page (validating page).{1}{1}+{0}",
- e.Message,
- Environment.NewLine
- );
- }
- }
-
- private void SaveCustomizedCssRules(string userCssContent)
- {
- // Yes, this wipes out everything else in the head. At this point, the only things
- // we need in _pageEditDom are the user defined style sheet and the bloom-page element in the body.
- _pageEditDom.GetElementsByTagName("head")[0].InnerXml =
- HtmlDom.CreateUserModifiedStyles(userCssContent);
- }
-
[Obsolete(
"This method is dangerous because it has to loop Application.DoEvents(). RunJavaScriptAsync() is preferred."
)]
diff --git a/src/BloomExe/Workspace/WorkspaceView.cs b/src/BloomExe/Workspace/WorkspaceView.cs
index 79d6b2d762be..c056f5cd9236 100644
--- a/src/BloomExe/Workspace/WorkspaceView.cs
+++ b/src/BloomExe/Workspace/WorkspaceView.cs
@@ -1458,7 +1458,7 @@ private void StartProblemReport(object sender, EventArgs e)
{
if (_editTab.IsSelected)
{
- _editingView.Model.SaveNow();
+ _editingView.Model.RequestBrowserToSave();
}
}
catch
diff --git a/src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs b/src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs
index 2b788e4ab523..578c95dfd72c 100644
--- a/src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs
+++ b/src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs
@@ -68,7 +68,7 @@ private void HandleBookCopyrightAndLicense(ApiRequest request)
{
case HttpMethods.Get:
//in case we were in this dialog already and made changes which haven't found their way out to the book yet
- Model.SaveNow();
+ Model.RequestBrowserToSave();
var intellectualPropertyData = GetJsonFromMetadata(
Model.CurrentBook.GetLicenseMetadata(),
@@ -115,7 +115,7 @@ private void HandleImageCopyrightAndLicense(ApiRequest request)
request.ReplyWithJson(intellectualPropertyData);
break;
case HttpMethods.Post:
- View.Model.SaveNow(); // Saved DOM must be up to date with possibly new imageUrl
+ View.Model.RequestBrowserToSave(); // Saved DOM must be up to date with possibly new imageUrl
metadata = GetMetadataFromJson(request, forBook: false);
bool askUserToCopyToAllImages = View.SaveImageMetadata(metadata);
diff --git a/src/BloomExe/web/controllers/EditingViewApi.cs b/src/BloomExe/web/controllers/EditingViewApi.cs
index f0a3267fcd04..5d7af9e46198 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,18 @@ 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 forceFullSave = (bool)data.forceFullSave;
+ var html = (string)data.html;
+ var userStylesheetContent = (string)data.userStylesheetContent;
+ View.Model.SavePageHtml(html, userStylesheetContent, forceFullSave);
+ request.PostSucceeded();
+ }
+
private void HandleChangeImage(ApiRequest request)
{
dynamic data = DynamicJson.Parse(request.RequiredPostJson());
diff --git a/src/BloomExe/web/controllers/SignLanguageApi.cs b/src/BloomExe/web/controllers/SignLanguageApi.cs
index 5fdaa1d03db2..0df2bd29bdee 100644
--- a/src/BloomExe/web/controllers/SignLanguageApi.cs
+++ b/src/BloomExe/web/controllers/SignLanguageApi.cs
@@ -572,7 +572,7 @@ public void CheckForChangedVideoOnActivate(object sender, EventArgs eventArgs)
// We might modify the current page, but the user may also have modified it
// without doing anything to cause a Save before the deactivate. So save their
// changes before we go to work on it.
- Model.SaveNow();
+ Model.RequestBrowserToSave();
foreach (var videoPath in filesModifiedSinceDeactivate)
{