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 @@
-
+
+
+
+