diff --git a/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx b/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx index e0b35450fb84..4c9ea432382b 100644 --- a/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx +++ b/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx @@ -71,8 +71,10 @@ import { kBloomButtonClass, kBloomCanvasSelector, } from "../toolbox/canvas/canvasElementUtils"; -import { getString, post, useApiObject } from "../../utils/bloomApi"; +import { wrapWithRequestPageContentDelay } from "./bloomEditing"; +import { get, post, useApiObject } from "../../utils/bloomApi"; import { ILanguageNameValues } from "../bookSettings/FieldVisibilityGroup"; +import OverflowChecker from "../OverflowChecker/OverflowChecker"; interface IMenuItemWithSubmenu extends ILocalizableMenuItemProps { subMenu?: ILocalizableMenuItemProps[]; @@ -938,6 +940,76 @@ const CanvasElementContextControls: React.FunctionComponent<{ // So until I get a better idea, I'm just putting in a hard-coded list. const fieldsControlledByAppearanceSystem = ["bookTitle"]; +function adjustAutoSizeForVisibleEditableInTranslationGroup(tg: HTMLElement) { + const visibleEditable = tg.getElementsByClassName( + "bloom-editable bloom-visibility-code-on", + )[0] as HTMLElement; + if (!visibleEditable) { + return; + } + OverflowChecker.AdjustSizeOrMarkOverflow(visibleEditable); +} + +function setEditableContentFromKnownDataBookValueIfAny( + editable: HTMLElement, + dataBook: string | null, + tg: HTMLElement, +) { + if (!dataBook) { + return; + } + wrapWithRequestPageContentDelay( + () => + new Promise((resolve, reject) => { + get( + `editView/getDataBookValue?lang=${editable.getAttribute("lang")}&dataBook=${dataBook}`, + (result) => { + try { + const content = result.data; + // content comes from a source that looked empty, we don't want to overwrite something the user may + // already have typed here. + // But it may well have something in it, because we usually have an empty paragraph to start with. + // To test whether it looks empty, we put the text into a newly created element and then + // see whether it's textContent is empty. + // The logic of overwriting something which the user has typed here is that if we keep what's here, + // then the user may never know that there was already something in that field. But if we overwrite, then + // the user can always correct it back to what he just typed. + const temp = document.createElement("div"); + temp.innerHTML = content || ""; + if (temp.textContent.trim() !== "") + editable.innerHTML = content; + adjustAutoSizeForVisibleEditableInTranslationGroup( + tg, + ); + resolve(); + } catch (error) { + reject(error); + } + }, + (error) => { + reject(error); + }, + ); + }), + "setCanvasFieldValueFromDataBook", + ); +} + +function applyAppearanceClassForEditable(editable: HTMLElement) { + editable.classList.remove( + "bloom-contentFirst", + "bloom-contentSecond", + "bloom-contentThird", + ); + if (editable.classList.contains("bloom-content1")) { + editable.classList.add("bloom-contentFirst"); + } else if (editable.classList.contains("bloom-contentNational1")) { + editable.classList.add("bloom-contentSecond"); + } else if (editable.classList.contains("bloom-contentNational2")) { + editable.classList.add("bloom-contentThird"); + } +} + function makeLanguageMenuItem( ce: HTMLElement, menuOptions: IMenuItemWithSubmenu[], @@ -962,7 +1034,7 @@ function makeLanguageMenuItem( tg.setAttribute("data-default-languages", dataDefaultLang); const editables = Array.from( tg.getElementsByClassName("bloom-editable"), - ); + ) as HTMLElement[]; if (editables.length === 0) return; // not able to handle this yet. let editableInLang = editables.find( (e) => e.getAttribute("lang") === langCode, @@ -1013,11 +1085,18 @@ function makeLanguageMenuItem( } } + setEditableContentFromKnownDataBookValueIfAny( + editableInLang, + dataBookValue, + tg, + ); + // and conversely remove them from the others for (const editable of editables) { // Ensure visibility code is off for others. editable.classList.remove("bloom-visibility-code-on"); } + adjustAutoSizeForVisibleEditableInTranslationGroup(tg); setMenuOpen(false); }; @@ -1183,6 +1262,37 @@ function makeFieldTypeMenuItem( } }; + const removeConflictingStyleClasses = ( + fieldType: { + editableClasses: string[]; + classes: string[]; + }, + editables: HTMLElement[], + ) => { + const newStyleClasses = new Set( + [...fieldType.classes, ...fieldType.editableClasses].filter((c) => + c.endsWith("-style"), + ), + ); + if (newStyleClasses.size === 0) { + return; + } + + const stripStyleClasses = (element: HTMLElement) => { + Array.from(element.classList).forEach((className) => { + if ( + className.endsWith("-style") && + !newStyleClasses.has(className) + ) { + element.classList.remove(className); + } + }); + }; + + stripStyleClasses(tg); + editables.forEach((editable) => stripStyleClasses(editable)); + }; + const activeType = tg .getElementsByClassName("bloom-editable bloom-visibility-code-on")[0] ?.getAttribute("data-book"); @@ -1194,9 +1304,21 @@ function makeFieldTypeMenuItem( clearFieldTypeClasses(); for (const editable of Array.from( tg.getElementsByClassName("bloom-editable"), - )) { + ) as HTMLElement[]) { editable.removeAttribute("data-book"); + // There's a bit of guess-work involved in what would be most helpful here. + // clearFieldTypeClasses removes any field-type-specific style class, + // and we generally expect a bloom-editable to have some style class. + // Should it be Normal-style or Bubble-style? Bubble-style is the default + // for canvas elements, so I decided to go with that. + const hasStyleClass = Array.from(editable.classList).some( + (className) => className.endsWith("-style"), + ); + if (!hasStyleClass) { + editable.classList.add("Bubble-style"); + } } + adjustAutoSizeForVisibleEditableInTranslationGroup(tg); setMenuOpen(false); }, icon: !activeType && , @@ -1210,7 +1332,7 @@ function makeFieldTypeMenuItem( clearFieldTypeClasses(); const editables = Array.from( tg.getElementsByClassName("bloom-editable"), - ); + ) as HTMLElement[]; if (fieldType.readOnly) { const readOnlyDiv = document.createElement("div"); readOnlyDiv.setAttribute( @@ -1232,34 +1354,37 @@ function makeFieldTypeMenuItem( // Reload the page to get the derived content loaded. post("common/saveChangesAndRethinkPageEvent", () => {}); } else { + removeConflictingStyleClasses(fieldType, editables); tg.classList.add(...fieldType.classes); for (const editable of editables) { editable.classList.add(...fieldType.editableClasses); editable.setAttribute("data-book", fieldType.dataBook); + if ( + fieldsControlledByAppearanceSystem.includes( + fieldType.dataBook, + ) + ) { + applyAppearanceClassForEditable(editable); + } else { + editable.classList.remove( + "bloom-contentFirst", + "bloom-contentSecond", + "bloom-contentThird", + ); + } if ( editable.classList.contains( "bloom-visibility-code-on", ) ) { - getString( - `editView/getDataBookValue?lang=${editable.getAttribute("lang")}&dataBook=${fieldType.dataBook}`, - (content) => { - // content comes from a source that looked empty, we don't want to overwrite something the user may - // already have typed here. - // But it may well have something in it, because we usually have an empty paragraph to start with. - // To test whether it looks empty, we put the text into a newly created element and then - // see whether it's textContent is empty. - // The logic of overwriting something which the user has typed here is that if we keep what's here, - // then the user may never know that there was already something in that field. But if we overwrite, then - // the user can always correct it back to what he just typed. - const temp = document.createElement("div"); - temp.innerHTML = content || ""; - if (temp.textContent.trim() !== "") - editable.innerHTML = content; - }, + setEditableContentFromKnownDataBookValueIfAny( + editable, + fieldType.dataBook, + tg, ); } } + adjustAutoSizeForVisibleEditableInTranslationGroup(tg); } setMenuOpen(false); },