Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions src/BloomExe/Book/Book.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,28 @@ private void EnsureUpToDateMemoryUnprotected(IProgress progress)
);
_bookData.MergeBrandingSettings(CollectionSettings.Subscription.BrandingKey);
_bookData.SynchronizeDataItemsThroughoutDOM();
// After SynchronizeDataItemsThroughoutDOM restores the content of custom layout pages
// from the data-div, the bloom-editables may reflect old language settings (e.g. when
// making a derivative or changing languages). Re-prepare those pages so they have
// bloom-editables for the current languages and the correct content-order classes.
foreach (
var customPage in OurHtmlDom
.SafeSelectNodes(
"//div[contains(@class,'bloom-page') and contains(@class,'bloom-customLayout')]"
)
.Cast<SafeXmlElement>()
)
{
TranslationGroupManager.PrepareElementsInPageOrDocument(customPage, _bookData);
TranslationGroupManager.UpdateContentLanguageClasses(
customPage,
_bookData,
BookInfo.AppearanceSettings,
Language1Tag,
Language2Tag,
Language3Tag
);
}
licenseMetadata = GetLicenseMetadata();
// I think we should only mess with tags if we are updating the book for real.
var oldTagsPath = Path.Combine(Storage.FolderPath, "tags.txt");
Expand Down Expand Up @@ -2541,14 +2563,7 @@ public void BringXmatterHtmlUpToDate(HtmlDom bookDOM)
// Various things, especially publication, don't work with unknown page sizes.
Layout layout = Layout.FromDomAndChoices(bookDOM, Layout.A5Portrait, fileLocator);
var oldIds = new List<string>();
var customLayoutIds = bookDOM
.SafeSelectNodes(
"//div[contains(@class, 'bloom-page') and @data-custom-layout-id and contains(concat(' ', normalize-space(@class), ' '), ' bloom-customLayout ')]"
)
.Cast<SafeXmlElement>()
.Select(page => page.GetAttribute("data-custom-layout-id"))
.Where(id => !string.IsNullOrEmpty(id))
.ToHashSet();
var customLayoutIds = XMatterHelper.GatherCustomLayoutIds(bookDOM);
XMatterHelper.RemoveExistingXMatter(bookDOM, oldIds);
// this says, if you can't figure out the page size, use the one we got before we removed the xmatter...
// still requiring it to be a valid layout.
Expand All @@ -2560,18 +2575,7 @@ public void BringXmatterHtmlUpToDate(HtmlDom bookDOM)
_bookData.MetadataLanguage1Tag,
oldIds
);
foreach (
var page in bookDOM
.SafeSelectNodes(
"//div[contains(@class, 'bloom-page') and @data-custom-layout-id]"
)
.Cast<SafeXmlElement>()
)
{
var customLayoutId = page.GetAttribute("data-custom-layout-id");
if (customLayoutIds.Contains(customLayoutId))
page.AddClass("bloom-customLayout");
}
XMatterHelper.RestoreCustomLayoutClasses(bookDOM, customLayoutIds);
var dataBookLangs = bookDOM.GatherDataBookLanguages();
TranslationGroupManager.PrepareDataBookTranslationGroups(bookDOM.RawDom, dataBookLangs);

Expand Down
27 changes: 23 additions & 4 deletions src/BloomExe/Book/BookStarter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ string parentCollectionPath
var newBookFolder = Path.Combine(parentCollectionPath, initialBookName);
CopyFolder(sourceBookFolder, newBookFolder);
BookStorage.RemoveLocalOnlyFiles(newBookFolder);
//if something bad happens from here on out, we need to delete that folder we just made
//if something bad happens from here on out, we need to delete that folder we just made
try
{
var oldNamedFile = Path.Combine(
Expand All @@ -93,7 +93,11 @@ string parentCollectionPath
RobustFile.Move(oldNamedFile, newNamedFile);

//the destination may change here...
newBookFolder = SetupNewDocumentContents(sourceBookFolder, newBookFolder, newBookInstanceId);
newBookFolder = SetupNewDocumentContents(
sourceBookFolder,
newBookFolder,
newBookInstanceId
);

if (OnNextRunSimulateFailureMakingBook)
throw new ApplicationException("Simulated failure for unit test");
Expand Down Expand Up @@ -166,7 +170,11 @@ private string GetMetaValue(SafeXmlDocument Dom, string name, string defaultValu
return defaultValue;
}

private string SetupNewDocumentContents(string sourceFolderPath, string initialPath, string newBookInstanceId)
private string SetupNewDocumentContents(
string sourceFolderPath,
string initialPath,
string newBookInstanceId
)
{
// This bookInfo is temporary, just used to make the (also temporary) BookStorage we
// use here in this method. I don't think it actually matters what its save context is.
Expand Down Expand Up @@ -243,6 +251,10 @@ private string SetupNewDocumentContents(string sourceFolderPath, string initialP
// class to figure out which BookData keys to remove.
ClearUnneededOriginalContentFromDerivative(storage.Dom, bookData);

// Preserve the bloom-customLayout class through xmatter replacement, so that
// EnsureUpToDate can later decide whether to keep or remove it based on the subscription.
var customLayoutIds = XMatterHelper.GatherCustomLayoutIds(storage.Dom);

// For a new book, we discard any old xmatter Ids, so the new book will have its own page IDs.
// One way this is helpful is caching cover images by page ID, so each book has a different cover page ID.
XMatterHelper.RemoveExistingXMatter(storage.Dom, new List<string>());
Expand Down Expand Up @@ -273,6 +285,9 @@ private string SetupNewDocumentContents(string sourceFolderPath, string initialP

InjectXMatter(initialPath, storage, sizeAndOrientation);

// Restore bloom-customLayout to any pages that had it before xmatter replacement.
XMatterHelper.RestoreCustomLayoutClasses(storage.Dom, customLayoutIds);
Comment on lines 285 to +289
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 GatherCustomLayoutIds/RestoreCustomLayoutClasses may be a no-op in BookStarter for xmatter pages

The newly added GatherCustomLayoutIds at BookStarter.cs:256 collects IDs from pages with bloom-customLayout before xmatter removal. After InjectXMatter at line 286, the new xmatter pages come from generic templates that lack data-custom-layout-id attributes. Therefore RestoreCustomLayoutClasses at line 289 searches for @data-custom-layout-id on pages (XPath at XMatterHelper.cs:562) but won't find any on the freshly injected template pages. This makes the Gather/Restore pair effectively a no-op for xmatter pages in the BookStarter path. The bloom-customLayout class and custom content would only be properly restored later when the book is fully loaded via EnsureUpToDateSynchronizeDataItemsThroughoutDOM. This is harmless (no incorrect behavior), but worth noting that the protection is not actually active in this code path. The same pattern in Book.cs:BringXmatterHtmlUpToDate works because SynchronizeDataItemsThroughoutDOM runs shortly after and restores data-custom-layout-id to the pages before they're used.

(Refers to lines 254-289)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


SetLineageAndId(storage, sourceFolderPath, newBookInstanceId);

if (makingTranslation)
Expand Down Expand Up @@ -416,7 +431,11 @@ private static void ProcessXMatterMetaTags(BookStorage storage)
storage.Dom.RemoveMetaElement("xmatter-for-children");
}

private void SetLineageAndId(BookStorage storage, string sourceFolderPath, string newBookInstanceId)
private void SetLineageAndId(
BookStorage storage,
string sourceFolderPath,
string newBookInstanceId
)
{
string parentId = null;
string lineage = null;
Expand Down
34 changes: 21 additions & 13 deletions src/BloomExe/Book/TranslationGroupManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -485,23 +485,31 @@ private static void UpdateContentLanguageClassesOnElement(
string[] dataDefaultLanguages
)
{
HashSet<string> classesToKeep = null;
HtmlDom.RemoveClassesBeginningWith(editable, "bloom-content");
if (editable.ParentWithClass("bloom-customLayout") != null)
{
// on a custom page, every bloom-editable is the only thing visible
// in its translationGroup, and visibility is not controlled by the
// appearance system. So we will never add these classes. However,
// they might have been copied there when setting up the custom layout.
// To avoid a distracting and possibly annoying change of appearance
// when transitioning to a custom layout, we will keep whatever class
// from this group the element had when the conversion happened.
// It's also important not to remove them in the copy in the data-div;
// On a custom layout page, assign bloom-contentFirst/Second/Third based on the
// data-default-languages attribute of the parent group and whether the element
// is visible. This ensures correctness after language changes or derivative creation.
// It's also important not to remove these from the copy in the data-div;
// we prevent that by renaming bloom-editable and bloom-translationGroup.
classesToKeep = new HashSet<string>(
new[] { "bloom-contentFirst", "bloom-contentSecond", "bloom-contentThird" }
);
if (IsVisible(editable))
{
var group = editable.ParentNode as SafeXmlElement;
switch (group?.GetAttribute("data-default-languages")?.Trim())
{
case "V":
editable.AddClass("bloom-contentFirst");
break;
case "N1":
editable.AddClass("bloom-contentSecond");
break;
case "N2":
editable.AddClass("bloom-contentThird");
break;
}
}
}
HtmlDom.RemoveClassesBeginningWith(editable, "bloom-content", classesToKeep);
var lang = editable.GetAttribute("lang");

//These bloom-content* classes are used by some stylesheet rules, primarily to boost the font-size of some languages.
Expand Down
34 changes: 34 additions & 0 deletions src/BloomExe/Book/XMatterHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,40 @@ SafeXmlElement div in dom.SafeSelectNodes(
}
}

/// <summary>
/// Collect the data-custom-layout-id values of any pages that currently have bloom-customLayout,
/// so that <see cref="RestoreCustomLayoutClasses"/> can re-apply the class after xmatter replacement.
/// </summary>
public static HashSet<string> GatherCustomLayoutIds(HtmlDom dom)
{
return dom.SafeSelectNodes(
"//div[contains(@class, 'bloom-page') and @data-custom-layout-id and contains(concat(' ', normalize-space(@class), ' '), ' bloom-customLayout ')]"
)
.Cast<SafeXmlElement>()
.Select(page => page.GetAttribute("data-custom-layout-id"))
.Where(id => !string.IsNullOrEmpty(id))
.ToHashSet();
}

/// <summary>
/// Re-apply bloom-customLayout to any pages whose data-custom-layout-id was collected by
/// <see cref="GatherCustomLayoutIds"/> before xmatter was replaced.
/// </summary>
public static void RestoreCustomLayoutClasses(HtmlDom dom, HashSet<string> customLayoutIds)
{
foreach (
var page in dom.SafeSelectNodes(
"//div[contains(@class, 'bloom-page') and @data-custom-layout-id]"
)
.Cast<SafeXmlElement>()
)
{
var id = page.GetAttribute("data-custom-layout-id");
if (customLayoutIds.Contains(id))
page.AddClass("bloom-customLayout");
}
}

/// <summary>
/// This will return a different name if we recognize the submitted name and we know that we have changed it or retired it.
/// </summary>
Expand Down