diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js index caf74dbad8f..f66a94fddf1 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js @@ -61,6 +61,7 @@ export class MassMailingHtmlField extends HtmlField { this.wysiwyg.odooEditor.toolbarHide(); }, dropImageAsAttachment: false, + useResponsiveFontSizes: false, ...this.props.wysiwygOptions, }; } diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index 00bba82bb27..0d06cd5739f 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -78,6 +78,7 @@ import { EMAIL_REGEX, prepareUpdate, boundariesOut, + getFontSizeDisplayValue, } from './utils/utils.js'; import { editorCommands } from './commands/commands.js'; import { Powerbox } from './powerbox/Powerbox.js'; @@ -259,6 +260,12 @@ export class OdooEditor extends EventTarget { allowCommandVideo: true, renderingClasses: [], allowInlineAtRoot: false, + useResponsiveFontSizes: true, + showResponsiveFontSizesBadges: false, + // TODO probably move `getCSSVariableValue` and + // `convertNumericToUnit` as odoo-editor utils to avoid this + getCSSVariableValue: () => null, + convertNumericToUnit: x => x, }, options, ); @@ -733,7 +740,8 @@ export class OdooEditor extends EventTarget { // Otherwise, if we lost the selection in the editable, restore it. this.historyResetLatestComputedSelection(true); } - }) + }); + // Handle the font size input. if (fontSizeInput) { const debouncedOnInputChange = (() => { @@ -742,37 +750,86 @@ export class OdooEditor extends EventTarget { clearTimeout(handle); handle = setTimeout(() => { handle = null; - setFontSize(fontSizeInput.value); + const fontSize = parseInt(fontSizeInput.value); + if (fontSize > 0) { + if (!this.isSelectionInEditable()) { + this.historyResetLatestComputedSelection(true); + } + this.execCommand("setFontSize", `${fontSize}px`); + fontSizeInput.blur(); + } resolve(); }, 50); }); })(); - const setFontSize = fontSize => { - if (fontSize === 'default' || parseInt(fontSize) > 0) { - if (!this.isSelectionInEditable()) { - this.historyResetLatestComputedSelection(true); - } - this.execCommand('setFontSize', fontSize === 'default' ? undefined : parseInt(fontSize) + 'px'); - fontSizeInput.blur(); - } - } this.addDomListener(fontSizeInput, 'change', debouncedOnInputChange); - // Handle the font size dropdown. - const fontSizeDropdown = this.toolbar.querySelector('#font-size'); - if (fontSizeDropdown) { - fontSizeDropdown.querySelectorAll('.dropdown-item').forEach(item => { - this.addDomListener(item, 'mousedown', ev => setFontSize(ev.target.textContent)); - this.addDomListener(item, 'keydown', ev => { - if (ev.key === 'Enter') { - setFontSize(ev.target.textContent); - } - }); - }); - } } + + // Handle the font size dropdown. + const fontSizeDropdown = this.toolbar.querySelector('#font-size'); + if (fontSizeDropdown) { + this.computeFontSizeSelectorValues(fontSizeDropdown); + + const applyFontSizeChoice = optionEl => { + if (!this.isSelectionInEditable()) { + this.historyResetLatestComputedSelection(true); + } + if (this.options.useResponsiveFontSizes) { + const fontSizeClassName = optionEl.dataset.applyClass; + this.execCommand("setFontSize", undefined); + this.execCommand("setFontSizeClassName", fontSizeClassName); + } else { + this.execCommand("setFontSize", `${optionEl.dataset.value}px`); + } + }; + fontSizeDropdown.querySelectorAll('.dropdown-item').forEach(item => { + this.addDomListener(item, 'mousedown', ev => { + applyFontSizeChoice(ev.currentTarget); + }); + this.addDomListener(item, 'keydown', ev => { + if (ev.key !== 'Enter') { + return; + } + applyFontSizeChoice(ev.currentTarget); + }); + }); + } + this._updateToolbar(); } + /** + * Sets the px value for every font size dropdown item. + */ + computeFontSizeSelectorValues(fontSizeDropdownEl) { + fontSizeDropdownEl = fontSizeDropdownEl || this.toolbar.querySelector("#font-size"); + + let previousItem = null; + let previousValue = -1; + const style = this.document.defaultView.getComputedStyle(this.document.body); + for (const itemEl of fontSizeDropdownEl.querySelectorAll("[data-dynamic-value]")) { + const variableName = itemEl.dataset.dynamicValue; + const strValue = this.options.getCSSVariableValue(variableName, style); + const remValue = parseFloat(strValue); + const pxValue = this.options.convertNumericToUnit(remValue, "rem", "px"); + // Change the text node value only to preserve the badge element + const roundedValue = Math.round(pxValue); + itemEl.dataset.value = roundedValue; + itemEl.firstChild.textContent = roundedValue; + + // If same value as the previous one, hide the previous one + if (previousItem) { + previousItem.parentElement.classList.toggle('d-none', Math.abs(pxValue - previousValue) < 0.001); + } + previousItem = itemEl; + previousValue = pxValue; + } + + for (const badgeEl of fontSizeDropdownEl.querySelectorAll(".o_we_font_size_badge")) { + badgeEl.classList.toggle("d-none", !this.options.showResponsiveFontSizesBadges); + } + } + resetContent(value) { value = value || '


'; this.editable.innerHTML = value; @@ -3028,9 +3085,6 @@ export class OdooEditor extends EventTarget { } } if (sel.rangeCount) { - const closestStartContainer = closestElement(sel.getRangeAt(0).startContainer); - const selectionStartStyle = closestStartContainer && getComputedStyle(closestStartContainer); - // queryCommandState does not take stylesheets into account for (const format of ['bold', 'italic', 'underline', 'strikeThrough', 'switchDirection']) { const formatButton = this.toolbar.querySelector(`#${format.toLowerCase()}`); @@ -3039,10 +3093,14 @@ export class OdooEditor extends EventTarget { } } - const fontSizeValue = this.toolbar.querySelector('#fontSizeCurrentValue'); - if (fontSizeValue) { - fontSizeValue.value = /\d+/.exec(selectionStartStyle.fontSize).pop(); + const fontSizeEl = this.toolbar.querySelector("#fontSizeCurrentValue"); + if (fontSizeEl) { + fontSizeEl.value = Math.round(getFontSizeDisplayValue(sel, + this.options.getCSSVariableValue, + this.options.convertNumericToUnit + )); } + const table = getInSelection(this.document, 'table'); const toolbarButton = this.toolbar.querySelector('.toolbar-edit-table'); if (toolbarButton) { @@ -3118,6 +3176,7 @@ export class OdooEditor extends EventTarget { this._positionToolbar(); } } + updateToolbarPosition() { if ( this.autohideToolbar && @@ -3127,6 +3186,7 @@ export class OdooEditor extends EventTarget { this._positionToolbar(); } } + _positionToolbar() { const OFFSET = 10; let isBottom = false; diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js index 995ec0f9a86..bff7f560336 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js @@ -365,6 +365,7 @@ export const editorCommands = { underline: editor => formatSelection(editor, 'underline'), strikeThrough: editor => formatSelection(editor, 'strikeThrough'), setFontSize: (editor, size) => formatSelection(editor, 'fontSize', {applyStyle: true, formatProps: {size}}), + setFontSizeClassName: (editor, className) => formatSelection(editor, 'setFontSizeClassName', {formatProps: {className}}), switchDirection: editor => { getDeepRange(editor.editable, { splitText: true, select: true, correctTripleClick: true }); const selection = editor.document.getSelection(); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js index 7df1708377a..d5c4225128f 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js @@ -61,6 +61,12 @@ export const PHONE_REGEX = /^(tel:(?:\/\/)?)?\+?[\d\s.\-()\/]{3,25}$/; export const PROTECTED_BLOCK_TAG = ['TR','TD','TABLE','TBODY','UL','OL','LI']; +/** + * Array of all the classes used by the editor to change the font size. + */ +export const FONT_SIZE_CLASSES = ["display-1-fs", "display-2-fs", "display-3-fs", "display-4-fs", "h1-fs", + "h2-fs", "h3-fs", "h4-fs", "h5-fs", "h6-fs", "base-fs", "small"]; + //------------------------------------------------------------------------------ // Position and sizes //------------------------------------------------------------------------------ @@ -946,9 +952,24 @@ const formatsSpecs = { fontSize: { isFormatted: isFontSize, hasStyle: (node) => node.style && node.style['font-size'], - addStyle: (node, props) => node.style['font-size'] = props.size, + addStyle: (node, props) => { + node.style['font-size'] = props.size; + node.classList.remove(...FONT_SIZE_CLASSES); + }, removeStyle: (node) => removeStyle(node, 'font-size'), }, + setFontSizeClassName: { + isFormatted: hasClass, + hasStyle: (node, props) => FONT_SIZE_CLASSES + .find(cls => node.classList.contains(cls)), + addStyle: (node, props) => node.classList.add(props.className), + removeStyle: (node) => { + node.classList.remove(...FONT_SIZE_CLASSES); + if (node.classList.length === 0) { + node.removeAttribute("class"); + } + }, + }, switchDirection: { isFormatted: isDirectionSwitched, } @@ -1049,8 +1070,10 @@ export const formatSelection = (editor, formatName, {applyStyle, formatProps} = let parentNode = selectedTextNode.parentElement; // Remove the format on all inline ancestors until a block or an element - // with a class (in case the formating comes from the class). - while (parentNode && (!isBlock(parentNode) && !(parentNode.classList && parentNode.classList.length))) { + // with a class that is not related to font size (in case the formatting + // comes from the class). + while (parentNode && (!isBlock(parentNode) && (parentNode.classList.length === 0 || + [...parentNode.classList].every(cls => FONT_SIZE_CLASSES.includes(cls))))) { const isUselessZws = parentNode.tagName === 'SPAN' && parentNode.hasAttribute('data-oe-zws-empty-inline') && parentNode.getAttributeNames().length === 1; @@ -1283,6 +1306,18 @@ export function isFontSize(node, props) { const element = closestElement(node); return getComputedStyle(element)['font-size'] === props.size; } +/** + * Return true if the given node classlist contains `props.className`. + * + * @param {Object} props + * @param {Node} node A node to compare the font-size against. + * @param {String} props.className The name of the class. + * @returns {boolean} + */ +export function hasClass(node, props) { + const element = closestElement(node); + return element.classList.contains(props.className); +} /** * Return true if the given node appears in a different direction than that of * the editable ('ltr' or 'rtl'). @@ -1780,6 +1815,58 @@ export function isColorGradient(value) { return value && value.includes('-gradient('); } +/** + * Finds the font size to display for the current selection. We cannot rely + * on the computed font-size only as font-sizes are responsive and we always + * want to display the desktop (integer when possible) one. + * + * @private + * @todo probably move `getCSSVariableValue` and `convertNumericToUnit` as + * odoo-editor utils. + * @param {Selection} sel The current selection. + * @returns {Float} The font size to display. + */ +export function getFontSizeDisplayValue(sel, getCSSVariableValue, convertNumericToUnit) { + const tagNameRelatedToFontSize = ["h1", "h2", "h3", "h4", "h5", "h6"]; + const closestStartContainerEl = closestElement(sel.getRangeAt(0).startContainer); + const closestFontSizedEl = closestStartContainerEl.closest(` + [style*='font-size'], + ${FONT_SIZE_CLASSES.map(className => `.${className}`)}, + ${tagNameRelatedToFontSize} + `); + let remValue; + if (closestFontSizedEl) { + const customFontSize = closestFontSizedEl.style.fontSize; + if (customFontSize) { + // Use the computed value to always convert to px. However, this + // currently does not check that the inline font-size is the one + // actually having an effect (there could be an !important CSS rule + // forcing something else). + // TODO align with the behavior of the rest of the editor snippet + // options. + return parseFloat(getComputedStyle(closestStartContainerEl).fontSize); + } + // It's a class font size or a hN tag. We don't return the computed + // font size because it can be different from the one displayed in + // the toolbar because it's responsive. + const fontSizeClass = FONT_SIZE_CLASSES.find( + className => closestFontSizedEl.classList.contains(className)); + if (fontSizeClass) { + const fsName = fontSizeClass.substring(0, fontSizeClass.length - 3); // Without -fs + remValue = parseFloat(getCSSVariableValue(`${fsName}-font-size`)); + } else { + const tagName = closestFontSizedEl.tagName.toLowerCase(); + remValue = parseFloat(getCSSVariableValue(`${tagName}-font-size`)); + } + } + // It's default font size (no font size class / style). + if (remValue === undefined) { + remValue = parseFloat(getCSSVariableValue("font-size-base")); + } + const pxValue = convertNumericToUnit(remValue, "rem", "px"); + return pxValue || parseFloat(getComputedStyle(closestStartContainerEl).fontSize); +} + //------------------------------------------------------------------------------ // DOM Modification //------------------------------------------------------------------------------ @@ -2730,8 +2817,8 @@ export function peek(arr) { return arr[arr.length - 1]; } /** - * Check user OS - * @returns {boolean} + * Check user OS + * @returns {boolean} */ export function isMacOS() { return window.navigator.userAgent.includes('Mac'); diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index bcfd6866fd2..200e91bd913 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -273,7 +273,11 @@ export class Wysiwyg extends Component { autostart: true, dropImageAsAttachment: true, editorPlugins: [], - } + useResponsiveFontSizes: true, + showResponsiveFontSizesBadges: false, + getCSSVariableValue: weUtils.getCSSVariableValue, + convertNumericToUnit: weUtils.convertNumericToUnit, + }; init() { this.id = ++id; this.options = this._getEditorOptions(this.props.options); @@ -447,6 +451,10 @@ export class Wysiwyg extends Component { renderingClasses: ['o_dirty', 'o_transform_removal', 'oe_edited_link', 'o_menu_loading'], dropImageAsAttachment: options.dropImageAsAttachment, foldSnippets: !!options.foldSnippets, + useResponsiveFontSizes: options.useResponsiveFontSizes, + showResponsiveFontSizesBadges: options.showResponsiveFontSizesBadges, + getCSSVariableValue: options.getCSSVariableValue, + convertNumericToUnit: options.convertNumericToUnit, }, editorCollaborationOptions)); this.odooEditor.addEventListener('contentChanged', function () { diff --git a/addons/web_editor/static/src/scss/web_editor.common.scss b/addons/web_editor/static/src/scss/web_editor.common.scss index 97161f9818b..6dc7f016b6a 100644 --- a/addons/web_editor/static/src/scss/web_editor.common.scss +++ b/addons/web_editor/static/src/scss/web_editor.common.scss @@ -78,6 +78,19 @@ @include print-variable('o-we-content-to-translate-color', $o-we-content-to-translate-color); @include print-variable('o-we-translated-content-color', $o-we-translated-content-color); @include print-variable('o-system-fonts', $o-system-fonts); + + @include print-variable('display-1-font-size', map-get($display-font-sizes, 1)); + @include print-variable('display-2-font-size', map-get($display-font-sizes, 2)); + @include print-variable('display-3-font-size', map-get($display-font-sizes, 3)); + @include print-variable('display-4-font-size', map-get($display-font-sizes, 4)); + @include print-variable('h1-font-size', $h1-font-size); + @include print-variable('h2-font-size', $h2-font-size); + @include print-variable('h3-font-size', $h3-font-size); + @include print-variable('h4-font-size', $h4-font-size); + @include print-variable('h5-font-size', $h5-font-size); + @include print-variable('h6-font-size', $h6-font-size); + @include print-variable('font-size-base', $font-size-base); + @include print-variable('small-font-size', $small-font-size); } html, body { @@ -237,6 +250,33 @@ img.ms-auto, img.mx-auto { width: auto; } +@for $index from 1 through 4 { + .display-#{$index}-fs { + font-size: map-get($display-font-sizes, $index); + } +} +.h1-fs { + font-size: $h1-font-size; +} +.h2-fs { + font-size: $h2-font-size; +} +.h3-fs { + font-size: $h3-font-size; +} +.h4-fs { + font-size: $h4-font-size; +} +.h5-fs { + font-size: $h5-font-size; +} +.h6-fs { + font-size: $h6-font-size; +} +.base-fs { + font-size: $font-size-base; +} + div.media_iframe_video { margin: 0 auto; text-align: center; diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml index 79a243f7664..4ed62eb704e 100644 --- a/addons/web_editor/static/src/xml/editor.xml +++ b/addons/web_editor/static/src/xml/editor.xml @@ -84,18 +84,19 @@ diff --git a/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js b/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js index 8650ed4e3ff..e4119517629 100644 --- a/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js +++ b/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js @@ -376,6 +376,7 @@ export class WysiwygAdapterComponent extends Wysiwyg { getReadOnlyAreas: this._getReadOnlyAreas.bind(this), getUnremovableElements: this._getUnremovableElements.bind(this), direction: this.websiteService.currentWebsite.metadata.direction, + showResponsiveFontSizesBadges: true, ...options, }); } diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js index 3f0607f71f5..f6a4d095399 100644 --- a/addons/website/static/src/js/editor/snippets.editor.js +++ b/addons/website/static/src/js/editor/snippets.editor.js @@ -263,6 +263,10 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({ `)); this._toggleAnimatedTextButton(); this._toggleHighlightAnimatedTextButton(); + + // As the toolbar displays css variable that are customizable by users, + // we have the recompute the font size selector values. + this.options.wysiwyg.odooEditor.computeFontSizeSelectorValues(); }, /** * Activates the button to animate text if the selection is in an diff --git a/addons/website/static/src/scss/bootstrap_overridden.scss b/addons/website/static/src/scss/bootstrap_overridden.scss index 277e6f9d79a..d267645a69f 100644 --- a/addons/website/static/src/scss/bootstrap_overridden.scss +++ b/addons/website/static/src/scss/bootstrap_overridden.scss @@ -130,6 +130,8 @@ $display-font-sizes: ( 6: o-website-value('display-6-font-size') or 2.5rem ) !default; +$small-font-size: o-website-value('small-font-size') !default; + // H2~H6 font families are custom variables. $headings-font-family: $o-theme-headings-font !default; $h2-font-family: $o-theme-h2-font !default; diff --git a/addons/website/static/src/scss/primary_variables.scss b/addons/website/static/src/scss/primary_variables.scss index 185c225958c..30f979d58bf 100644 --- a/addons/website/static/src/scss/primary_variables.scss +++ b/addons/website/static/src/scss/primary_variables.scss @@ -1966,6 +1966,13 @@ $o-base-website-values-palette: ( 'display-3-font-size': null, // Default to BS 'display-4-font-size': null, // Default to BS + // Forced to use `rem` by default instead of Bootstrap default using `em` as + // the editor would not be working with `em` values for now. This will have + // the drawback of changing the default small behavior of bootstrap which + // can normally be put inside titles etc... but that's not the way the + // editor will allow to use it for now. This is a compromise. + 'small-font-size': 0.875rem, + 'google-fonts': null, 'google-local-fonts': null, diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index aa346713210..9d608fee609 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -79,6 +79,8 @@ $-seen-urls: (); $i: $i + 1; } + // Note: font-size are not needed as already added by web_editor for the + // font-size dropdown @include print-variable('paragraph-margin-top', $paragraph-margin-top); @include print-variable('paragraph-margin-bottom', $paragraph-margin-bottom); @include print-variable('h2-line-height', $h2-line-height); @@ -90,16 +92,6 @@ $-seen-urls: (); @include print-variable('display-2-line-height', $display-2-line-height); @include print-variable('display-3-line-height', $display-3-line-height); @include print-variable('display-4-line-height', $display-4-line-height); - @include print-variable('h1-font-size', $h1-font-size); - @include print-variable('h2-font-size', $h2-font-size); - @include print-variable('h3-font-size', $h3-font-size); - @include print-variable('h4-font-size', $h4-font-size); - @include print-variable('h5-font-size', $h5-font-size); - @include print-variable('h6-font-size', $h6-font-size); - @include print-variable('display-1-font-size', map-get($display-font-sizes, 1)); - @include print-variable('display-2-font-size', map-get($display-font-sizes, 2)); - @include print-variable('display-3-font-size', map-get($display-font-sizes, 3)); - @include print-variable('display-4-font-size', map-get($display-font-sizes, 4)); // We cannot rely on $o-theme-hx-font because they contain the list of // fallback fonts (unicode...). $-headings-or-main-font: o-website-value('headings-font') or o-website-value('font'); diff --git a/addons/website/static/tests/tours/website_text_font_size.js b/addons/website/static/tests/tours/website_text_font_size.js new file mode 100644 index 00000000000..7f94deb1ca2 --- /dev/null +++ b/addons/website/static/tests/tours/website_text_font_size.js @@ -0,0 +1,119 @@ +/** @odoo-module **/ + +import wTourUtils from '@website/js/tours/tour_utils'; +import {FONT_SIZE_CLASSES} from '@web_editor/js/editor/odoo-editor/src/utils/utils'; + +const classNameInfo = new Map(); +classNameInfo.set("display-1-fs", {scssVariableName: "display-1-font-size", start: 80, end: 90}); +classNameInfo.set("display-2-fs", {scssVariableName: "display-2-font-size", start: 72, end: 80}); +classNameInfo.set("display-3-fs", {scssVariableName: "display-3-font-size", start: 64, end: 70}); +classNameInfo.set("display-4-fs", {scssVariableName: "display-4-font-size", start: 56, end: 60}); +classNameInfo.set("h1-fs", {scssVariableName: "h1-font-size", start: 48, end: 50}); +classNameInfo.set("h2-fs", {scssVariableName: "h2-font-size", start: 40, end: 42}); +classNameInfo.set("h3-fs", {scssVariableName: "h3-font-size", start: 32, end: 38}); +classNameInfo.set("h4-fs", {scssVariableName: "h4-font-size", start: 24, end: 34}); +classNameInfo.set("h5-fs", {scssVariableName: "h5-font-size", start: 20, end: 30}); +classNameInfo.set("h6-fs", {scssVariableName: "h6-font-size", start: 16, end: 26}); +classNameInfo.set("base-fs", {scssVariableName: "font-size-base", start: 16, end: 26}); +classNameInfo.set("small", {scssVariableName: "small-font-size", start: 14, end: 24}); + +function checkComputedFontSize(fontSizeClass, stage) { + return { + content: `Check that the computed font size for ${fontSizeClass} is correct`, + trigger: `iframe #wrap .s_text_block .${fontSizeClass}`, + run: function () { + const computedFontSize = parseInt(getComputedStyle(this.$anchor[0]).fontSize); + const expectedFontSize = classNameInfo.get(fontSizeClass)[stage]; + const gapBetweenSizes = Math.abs(computedFontSize - expectedFontSize); + const gapTolerance = 7; // Because the font size is responsive. + if (gapBetweenSizes > gapTolerance) { + console.error(`When applied class ${fontSizeClass}, the font size is ` + + `${computedFontSize} instead of ~${expectedFontSize}`); + } + } + }; +} + +function getFontSizeTestSteps(fontSizeClass) { + return [ + wTourUtils.dragNDrop({id: "s_text_block", name: "Text"}), + { + content: `[${fontSizeClass}] Click on the text block first paragraph (to auto select)`, + trigger: "iframe .s_text_block p", + }, { + content: `Open the font size dropdown to select ${fontSizeClass}`, + trigger: "#font-size button", + }, { + content: `Select ${fontSizeClass} in the dropdown`, + trigger: `a[data-apply-class="${fontSizeClass}"]:contains(${classNameInfo.get(fontSizeClass).start})`, + }, + checkComputedFontSize(fontSizeClass, "start"), + wTourUtils.goToTheme(), + { + content: `Open the collapse to see the font size of ${fontSizeClass}`, + trigger: `we-collapse:has(we-input[data-variable="` + + `${classNameInfo.get(fontSizeClass).scssVariableName}"]) we-toggler`, + }, { + content: `Check that the setting for ${fontSizeClass} is correct`, + trigger: `we-input[data-variable="${classNameInfo.get(fontSizeClass).scssVariableName}"]` + + ` input:propValue("${classNameInfo.get(fontSizeClass).start}")`, + isCheck: true, + }, { + content: `Change the setting value of ${fontSizeClass}`, + trigger: `[data-variable="${classNameInfo.get(fontSizeClass).scssVariableName}"] input`, + run: `text_blur ${classNameInfo.get(fontSizeClass).end}`, + }, { + content: `[${fontSizeClass}] Go to blocks tab`, + trigger: ".o_we_add_snippet_btn", + }, { + content: `[${fontSizeClass}] Wait to be in blocks tab`, + trigger: ".o_we_add_snippet_btn.active", + }, + wTourUtils.goToTheme(), + { + content: `Check that the setting of ${fontSizeClass} has been updated`, + trigger: `we-input[data-variable="${classNameInfo.get(fontSizeClass).scssVariableName}"]` + + ` input:propValue("${classNameInfo.get(fontSizeClass).end}")`, + isCheck: true, + }, { + content: `Close the collapse to hide the font size of ${fontSizeClass}`, + trigger: `we-collapse:has(we-input[data-variable=` + + `"${classNameInfo.get(fontSizeClass).scssVariableName}"]) we-toggler`, + extra_trigger: `body:not(:has(.o_we_ui_loading))`, + }, + checkComputedFontSize(fontSizeClass, "end"), + { + content: `Click again on the text with class ${fontSizeClass}`, + trigger: `iframe #wrap .s_text_block .${fontSizeClass}`, + }, { + content: `Remove the text snippet containing the text with class ${fontSizeClass}`, + trigger: `.oe_snippet_remove`, + } + ]; +} + +function getAllFontSizesTestSteps() { + const steps = []; + for (const fontSizeClass of FONT_SIZE_CLASSES) { + if (fontSizeClass === 'h6-fs') { + // That option is hidden by default because same value as base-fs + continue; + } + steps.push(...getFontSizeTestSteps(fontSizeClass)); + } + return steps; +} + +wTourUtils.registerWebsitePreviewTour("website_text_font_size", { + test: true, + url: "/", + edition: true, +}, () => [ + ...getAllFontSizesTestSteps(), + // The last step has to be a check. + { + content: "Verify that the text block has been deleted", + trigger: "iframe #wrap:not(:has(.s_text_block))", + isCheck: true, + }, +]); diff --git a/addons/website/tests/test_ui.py b/addons/website/tests/test_ui.py index 30bb3b96ef0..cb448f0cdcc 100644 --- a/addons/website/tests/test_ui.py +++ b/addons/website/tests/test_ui.py @@ -443,3 +443,6 @@ class TestUi(odoo.tests.HttpCase): def test_website_media_dialog_image_shape(self): self.start_tour("/", 'website_media_dialog_image_shape', login='admin') + + def test_website_text_font_size(self): + self.start_tour('/@/', 'website_text_font_size', login='admin', timeout=300) diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml index f2101920d7b..48a29434693 100644 --- a/addons/website/views/snippets/snippets.xml +++ b/addons/website/views/snippets/snippets.xml @@ -1623,11 +1623,19 @@
- + + + +