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
2 changes: 2 additions & 0 deletions DistFiles/localization/en/BloomMediumPriority.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@
<trans-unit id="BookSettings.CoverIsImage" sil:dynamic="true">
<source xml:lang="en">Fill the front cover with a single image</source>
<note>BookSettings.CoverIsImage</note>
<note>Obsolete as of Bloom 6.4</note>
</trans-unit>
<trans-unit id="BookSettings.CoverIsImage.Description" sil:dynamic="true">
<source xml:lang="en">Using this option turns on the [Print Bleed](https://en.wikipedia.org/wiki/Bleed_%28printing%29) indicators on paper layouts. See [Full Page Cover Images](https://docs.bloomlibrary.org/full-page-cover-images) for information on sizing your image to fit.</source>
Expand All @@ -778,6 +779,7 @@
<trans-unit id="BookSettings.CoverIsImage.Description.V2" sil:dynamic="true">
<source xml:lang="en">Replace the front cover content with a single full-bleed image. See [Full Page Cover Images](https://docs.bloomlibrary.org/full-page-cover-images) for information on sizing your image to fit.</source>
<note>BookSettings.CoverIsImage.Description.V2</note>
<note>Obsolete as of Bloom 6.4</note>
</trans-unit>
<trans-unit id="BookSettings.FullBleed" sil:dynamic="true">
<source xml:lang="en">Use full bleed page layout</source>
Expand Down
42 changes: 39 additions & 3 deletions src/BloomBrowserUI/bookEdit/StyleEditor/StyleEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,34 @@ export default class StyleEditor {

private uiLang: string;

// We allow read-only fields to have a style dialog on custom-layout pages
// for elements that have a -style class to edit.
public static shouldAllowNonEditableStyleDialogTarget(
targetBox: HTMLElement,
): boolean {
if (targetBox.classList.contains("bloom-editable")) {
return false;
}
if (StyleEditor.GetStyleClassFromElement(targetBox) === null) {
return false;
}
return (
targetBox.closest(
".bloom-page.bloom-customLayout[data-custom-layout-id]",
) !== null
);
}

// When we allow an element that's not a bloom-editable to have a format dialog,
// we always use the default (not langauge-dependent) version of the rules,
// since we don't have the structure of a translation group containing multiple
// bloom-editables that might need different formatting.
private targetUsesLanguageIndependentRules(
targetBox: HTMLElement,
): boolean {
return StyleEditor.shouldAllowNonEditableStyleDialogTarget(targetBox);
}

public AttachToBox(targetBox: HTMLElement) {
this.uiLang = theOneLocalizationManager.getCurrentUILocale();

Expand Down Expand Up @@ -1498,7 +1526,11 @@ export default class StyleEditor {
// BL-5616 This also applies if the textbox's default language is '*',
// like it is for an Arithmetic Equation.
const tag = $(this.boxBeingEdited).attr("lang");
if (this.shouldSetDefaultRule() || tag === "*") {
if (
this.shouldSetDefaultRule() ||
this.targetUsesLanguageIndependentRules(this.boxBeingEdited) ||
tag === "*"
) {
theOneLocalizationManager
.asyncGetText(
"BookEditor.DefaultForText",
Expand Down Expand Up @@ -2217,11 +2249,15 @@ export default class StyleEditor {
if (!styleName) {
return null; // bizarre, since we put up the dialog
}
const langAttrValue = StyleEditor.GetLangValueOrNull(target);
const useLanguageIndependentRule =
ignoreLanguage || this.targetUsesLanguageIndependentRules(target);
const langAttrValue = useLanguageIndependentRule
? null
: StyleEditor.GetLangValueOrNull(target);
return this.GetOrCreateRuleForStyle(
styleName,
langAttrValue,
ignoreLanguage,
useLanguageIndependentRule,
forChildPara,
);
}
Expand Down
41 changes: 0 additions & 41 deletions src/BloomBrowserUI/bookEdit/bookSettings/BookSettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,6 @@ export const BookSettingsDialog: React.FunctionComponent<{
"BookSettings.Gutter.DefaultLabel",
);

const coverIsImageLabel = useL10n(
"Fill the front cover with a single image",
"BookSettings.CoverIsImage",
);
const coverIsImageDescription = useL10n(
"Replace the front cover content with a single full-bleed image. See [Full Page Cover Images](https://docs.bloomlibrary.org/full-page-cover-images) for information on sizing your image to fit.",
"BookSettings.CoverIsImage.Description.V2",
);

const fullBleedLabel = useL10n(
"Use full bleed page layout",
"BookSettings.FullBleed",
Expand Down Expand Up @@ -389,9 +380,6 @@ export const BookSettingsDialog: React.FunctionComponent<{
setMigratedTheme("");
};

const tierAllowsFullPageCoverImage =
useGetFeatureStatus("fullPageCoverImage")?.enabled;

const tierAllowsFullBleed = useGetFeatureStatus("PrintshopReady")?.enabled;

function saveSettingsAndCloseDialog() {
Expand Down Expand Up @@ -490,35 +478,6 @@ export const BookSettingsDialog: React.FunctionComponent<{
</ConfigrStatic>
)}
<ConfigrGroup label={whatToShowOnCoverLabel}>
<div>
<ConfigrBoolean
label={coverIsImageLabel}
description={coverIsImageDescription}
{...getAdditionalProps<boolean>(
`coverIsImage`,
)}
disabled={
appearanceDisabled ||
!tierAllowsFullPageCoverImage
}
/>
<div
css={css`
display: flex;
padding-bottom: 5px;
font-size: 12px;
font-weight: bold;
`}
>
<BloomSubscriptionIndicatorIconAndText
feature="fullPageCoverImage"
css={css`
margin-left: auto;
`}
disabled={appearanceDisabled}
/>
</div>
</div>
<FieldVisibilityGroup
field="cover-title"
labelFrame="Show Title in {0}"
Expand Down
61 changes: 58 additions & 3 deletions src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
} from "../toolbox/canvas/canvasElementUtils";
import { getString, post, useApiObject } from "../../utils/bloomApi";
import { ILanguageNameValues } from "../bookSettings/FieldVisibilityGroup";
import StyleEditor from "../StyleEditor/StyleEditor";

interface IMenuItemWithSubmenu extends ILocalizableMenuItemProps {
subMenu?: ILocalizableMenuItemProps[];
Expand All @@ -85,6 +86,24 @@ function isNavigationButton(canvasElement: HTMLElement) {
return canvasElement.classList.contains(kBloomButtonClass);
}

function getFormatTargetElement(
canvasElement: HTMLElement,
): HTMLElement | undefined {
const editable = canvasElement.getElementsByClassName(
"bloom-editable bloom-visibility-code-on",
)[0] as HTMLElement | undefined;
if (editable) {
return editable;
}

const candidates = Array.from(
canvasElement.querySelectorAll("[class]"),
) as HTMLElement[];
return candidates.find((candidate) =>
StyleEditor.shouldAllowNonEditableStyleDialogTarget(candidate),
);
}

// This is the controls bar that appears beneath a canvas element when it is selected. It contains buttons
// for the most common operations that apply to the canvas element in its current state, and a menu for less common
// operations.
Expand Down Expand Up @@ -356,6 +375,8 @@ const CanvasElementContextControls: React.FunctionComponent<{
const editableTextElement = props.canvasElement.getElementsByClassName(
"bloom-editable bloom-visibility-code-on",
)[0] as HTMLElement;
const formatTargetElement = getFormatTargetElement(props.canvasElement);
const showFormatButton = !!formatTargetElement && !isNavButton;

if (isNavButton) {
menuOptions.splice(0, 0, {
Expand Down Expand Up @@ -456,6 +477,13 @@ const CanvasElementContextControls: React.FunctionComponent<{
languageNameValues,
noneLabel,
);
} else if (formatTargetElement && !isNavButton) {
menuOptions.push({
l10nId: "EditTab.Toolbox.ComicTool.Options.Format",
english: "Format",
onClick: () => GetEditor().runFormatDialog(formatTargetElement),
icon: <CogIcon css={getMenuIconCss()} />,
});
}

const runMetadataDialog = () => {
Expand Down Expand Up @@ -617,15 +645,15 @@ const CanvasElementContextControls: React.FunctionComponent<{
)}
</Fragment>
)}
{editableTextElement && !isNavButton && (
{showFormatButton && (
<ButtonWithTooltip
tipL10nKey="EditTab.Toolbox.ComicTool.Options.Format"
icon={CogIcon}
relativeSize={0.8}
onClick={() => {
if (!props.canvasElement) return;
GetEditor().runFormatDialog(
editableTextElement,
formatTargetElement,
);
}}
/>
Expand All @@ -650,7 +678,7 @@ const CanvasElementContextControls: React.FunctionComponent<{
</Fragment>
)}
{(!(hasImage && isPlaceHolder) &&
!editableTextElement &&
!showFormatButton &&
!(hasVideo && !videoAlreadyChosen)) || (
// Add a spacer if there is any button before these
<div
Expand Down Expand Up @@ -1751,6 +1779,33 @@ function addImageMenuOptions(
theOneCanvasElementManager.updateCanvasElementForChangedImage(
bgImg,
);

// We want to make it active. However, if it used to be a placeholder, it was
// previously hidden. This interferes with making the measurements to move the
// control frame (at least), so we make a few attempts to activate it.
// Don't make a long timeout here, because it could override some other
// selection that the user quickly makes.
const activateConvertedBackground = () => {
requestAnimationFrame(() => {
theOneCanvasElementManager.setActiveElement(
bgImageCe,
);
});
};
activateConvertedBackground();
if (!haveRealBgImage) {
const fallbackHandle = setTimeout(() => {
activateConvertedBackground();
}, 120);
bgImg.addEventListener(
"load",
() => {
clearTimeout(fallbackHandle);
activateConvertedBackground();
},
{ once: true },
);
}
setMenuOpen(false);
},
});
Expand Down
74 changes: 74 additions & 0 deletions src/BloomBrowserUI/bookEdit/toolbox/canvas/customXmatterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ export async function convertXmatterPageToCustom(
// where to put its new canvas element.
ceContent = elem.cloneNode(true) as HTMLElement;
}

preserveHintMetadata(elem as HTMLElement, ceContent as HTMLElement);

// make a new canvas element to hold this. Not sure what problems it will
// cause when it's NOT a TG, but we have at least topic and language name that
// never are and are commonly on the front cover.
Expand Down Expand Up @@ -232,6 +235,77 @@ async function getLanguageNameValues(): Promise<ILanguageNameValues> {
.data as ILanguageNameValues;
}

function copyHintAttributes(source: Element, target: HTMLElement): void {
[
"data-hint",
"data-i18n",
"data-link-text",
"data-link-target",
"data-functiononhintclick",
].forEach((attr) => {
const value = source.getAttribute(attr);
if (value && !target.hasAttribute(attr)) {
target.setAttribute(attr, value);
}
});
}

function preserveHintMetadata(
sourceElement: HTMLElement,
convertedElement: HTMLElement,
): void {
// If we've already preserved label content or explicit hint metadata, leave it alone.
if (
convertedElement.querySelector("label.bubble") ||
convertedElement.hasAttribute("data-hint")
) {
return;
}

const translationGroup = sourceElement.closest(
".bloom-translationGroup",
) as HTMLElement | null;
if (!translationGroup) {
copyHintAttributes(sourceElement, convertedElement);
if (
!convertedElement.hasAttribute("data-hint") &&
sourceElement.parentElement
) {
copyHintAttributes(sourceElement.parentElement, convertedElement);
}
return;
}

copyHintAttributes(translationGroup, convertedElement);

const label = translationGroup.querySelector("label.bubble");
if (!label) {
if (
!convertedElement.hasAttribute("data-hint") &&
translationGroup.parentElement
) {
copyHintAttributes(
translationGroup.parentElement,
convertedElement,
);
}
return;
}

const labelText = label.textContent?.trim();
if (labelText && !convertedElement.hasAttribute("data-hint")) {
convertedElement.setAttribute("data-hint", labelText);
}
copyHintAttributes(label, convertedElement);

if (
!convertedElement.hasAttribute("data-hint") &&
translationGroup.parentElement
) {
copyHintAttributes(translationGroup.parentElement, convertedElement);
}
}

function setDataDefault(
ceContent: HTMLElement,
lang: string,
Expand Down
20 changes: 2 additions & 18 deletions src/BloomExe/Book/AppearanceSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,6 @@ public string FirstPossiblyOffendingCssFile
// The default here is rarely if ever relevant. Usually a newly created instance will be initialized from a folder, and the default will be overwritten,
// either to whatever we find in appearance.json, or to "legacy-5-6" if there is no appearance.json.
new StringPropertyDef("cssThemeName", "cssThemeName", "default"),
// BooleanPropertyDef, not CssXDef because this is not a css variable.
new BooleanPropertyDef(
"coverIsImage",
"coverIsImage",
defaultValue: false,
requiresXmatterUpdate: true,
valueRequiredIfLegacyTheme: false
), // If true, cover page is just a full bleed image.
// If true, book uses full bleed page layout in edit mode. Printing that way is still optional.
new BooleanPropertyDef("fullBleed", "fullBleed", defaultValue: false),
// Does not correspond to a css variable. We will set the relevant page number css variables based on this setting.
Expand Down Expand Up @@ -183,11 +175,8 @@ public string CssThemeName
// Some setting's values are not allowed in legacy mode.
// REVIEW:
// This concept of forcing a value based on the legacy theme was introduced at the time coverIsImage was added.
// And currently (Dec 2024), it is the only property that has a valueRequiredIfLegacyTheme.
// But I think the properties which existed before that and which get disabled by setting the theme
// to legacy should also be set. e.g. cover-topic-show
// Without this, the user can set the theme to non-legacy, change the property to whatever he wants,
// then change the theme back to legacy.
// Since then that was removed and (as of March 2026) no property has a valueRequiredIfLegacyTheme.
// Possibly more should have it.
private void SetRequiredValuesIfLegacyTheme()
{
if (CssThemeName != "legacy-5-6") // Can't use UsingLegacy here because it includes logic about the syncing of files which we don't want.
Expand Down Expand Up @@ -222,11 +211,6 @@ private void SetProperty(KeyValuePair<string, object> property)
Properties[property.Key] = property.Value;
}

public bool CoverIsImage
{
get { return _properties.coverIsImage; }
}

public bool FullBleed
{
get { return _properties.fullBleed; }
Expand Down
Loading