[IMP] website, *: prevent hardcoded font sizes
*: web_editor, mass_mailing This commit changes the way the font size selector works. Before this commit, the font size selector applied a hardcoded font size using the style attribute of the selected element. The purpose of this commit is to change this to apply a class on the selected element, making it responsive and customizable. In all Odoo applications, the font size selector will now apply a class on the current selection. An exception is made for mass mailing where the class would not make much sense as not related to the custom heading sizes (probably needs to be refactored in the future to use the standard font-size classes) and fonts cannot be responsive in mails anyway (so there would be a difference between preview and sent mail). Those classes are: - `display-N-fs` with N in [1 => 4]. The sizes are stored in the `$display-font-sizes` map. - `hN-fs` with N in [1 => 6]. The sizes are stored in the `$hN-font-size` variables. - `small` for the small font size. The size is stored in the `$small-font-size` variable. The font size selector shows the value of the class (which is dynamic) that will be applied. In the website application, the value of each class is configurable thanks to a previous commit. The user can choose the size of each font size class in the website settings. Note that many alternatives were considered for this feature, this is the chosen compromise. For the record, here is a very short summary of the alternatives: - Doing nothing: voted as the worse idea. Users see a font-size selector they will use it one way or another. The font-size won't be responsive, breaking their mobile website. And there are real use cases you could not do: a big "promotion" paragraph on your product page? Not possible: you either break your mobile page (using the font size option) or possibly hurt your SEO (using the font-style option and turning your paragraph into an h1). - Removing the font-size selector: we did not want the loss of the feature as there are correct usecases to use it (as mentioned above). - Using the Bootstrap hN and display-N classes instead of making new ones. Closed to be the chosen idea but discarded because those classes comes with colors (that the user can configure) and it would feel weird to have the color change when changing the font-size. Also, in the end, we also did not want the line-height, margins, etc of those classes (only the font-size). - Instead of X new classes, have only one: o-fs, which would be applied alongside a bootstrap hN or display-N class, when chosen by the user, to cancel the unwanted style of those. It works but it forces us to always have an added `<span>` to apply the font-size, which we don't want in the future (mainly because of display-N classes, see next commit). It is actually very needed to be able to use proper line-height when reducing the font. - Using a combination of an inline `em` font-size + a class to clamp it on mobile. Was probably the best next idea but rejected for several reasons. The main one probably being the inconsistency when changing the whole size of a title/paragraph (not part of it) and later changing the related theme size later. E.g. have an `<h1>` followed by a `<p>`. The h1 is 40px, the paragraph is 20px. Force the title to 20px, because you want it smaller, same size as the paragraph. Real use case but also users could simply use the font-size controls by mistake. We would thus apply 0.5em to do that. Later, change the theme font-size of h1 to 36px (small change). The 0.5em one is now 18px, smaller than the following paragraph. Preventing that would require more checks which would "break" other things / possibilities. - Probably others that were forgotten. In the end, there was no good or bad answer. "Anything works", as long as the feature "I want this text smaller/bigger" is there. The surrounding features are always compromise (some users would expect some behavior, some users would expect others). This commit focused on solving the unresponsiveness of those custom font-sizes, which was a problem for many users. task-1958098 Part-of: odoo/odoo#129791 Co-authored-by: qsm-odoo <qsm@odoo.com>
This commit is contained in:
committed by
qsm-odoo
co-authored by
qsm-odoo
parent
9d753397f6
commit
194f73a9bb
@@ -61,6 +61,7 @@ export class MassMailingHtmlField extends HtmlField {
|
||||
this.wysiwyg.odooEditor.toolbarHide();
|
||||
},
|
||||
dropImageAsAttachment: false,
|
||||
useResponsiveFontSizes: false,
|
||||
...this.props.wysiwygOptions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 || '<p><br></p>';
|
||||
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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -84,18 +84,19 @@
|
||||
</div>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">default</a></li>
|
||||
<li><a class="dropdown-item" href="#">8</a></li>
|
||||
<li><a class="dropdown-item" href="#">9</a></li>
|
||||
<li><a class="dropdown-item" href="#">10</a></li>
|
||||
<li><a class="dropdown-item" href="#">11</a></li>
|
||||
<li><a class="dropdown-item" href="#">12</a></li>
|
||||
<li><a class="dropdown-item" href="#">14</a></li>
|
||||
<li><a class="dropdown-item" href="#">18</a></li>
|
||||
<li><a class="dropdown-item" href="#">24</a></li>
|
||||
<li><a class="dropdown-item" href="#">36</a></li>
|
||||
<li><a class="dropdown-item" href="#">48</a></li>
|
||||
<li><a class="dropdown-item" href="#">62</a></li>
|
||||
<!-- See `FONT_SIZE_CLASSES` -->
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="display-1-font-size" data-apply-class="display-1-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Display 1</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="display-2-font-size" data-apply-class="display-2-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Display 2</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="display-3-font-size" data-apply-class="display-3-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Display 3</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="display-4-font-size" data-apply-class="display-4-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Display 4</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="h1-font-size" data-apply-class="h1-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Heading 1</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="h2-font-size" data-apply-class="h2-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Heading 2</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="h3-font-size" data-apply-class="h3-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Heading 3</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="h4-font-size" data-apply-class="h4-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Heading 4</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="h5-font-size" data-apply-class="h5-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Heading 5</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="h6-font-size" data-apply-class="h6-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Heading 6</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="font-size-base" data-apply-class="base-fs" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Normal</span></a></li>
|
||||
<li><a class="dropdown-item d-flex justify-content-between align-items-center" data-dynamic-value="small-font-size" data-apply-class="small" href="#">? <span class="d-none o_we_font_size_badge badge rounded-pill bg-light text-dark ms-2">Small</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]);
|
||||
@@ -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)
|
||||
|
||||
@@ -1623,11 +1623,19 @@
|
||||
</we-row>
|
||||
</div>
|
||||
<div data-js="OptionsTab" data-selector="theme-paragraph" data-no-check="true">
|
||||
<we-input string="Font size"
|
||||
data-customize-website-variable="null"
|
||||
data-variable="font-size-base"
|
||||
data-unit="px"
|
||||
data-save-unit="rem"/>
|
||||
<we-collapse>
|
||||
<we-input string="Font size"
|
||||
data-customize-website-variable="null"
|
||||
data-variable="font-size-base"
|
||||
data-unit="px"
|
||||
data-save-unit="rem"/>
|
||||
<we-input string="Small"
|
||||
class="o_we_sublevel_1"
|
||||
data-customize-website-variable="null"
|
||||
data-variable="small-font-size"
|
||||
data-unit="px"
|
||||
data-save-unit="rem"/>
|
||||
</we-collapse>
|
||||
<we-fontfamilypicker string="Font family" data-variable="font"/>
|
||||
<!-- "× ": \u00D7\u2000 -->
|
||||
<we-input string="Line height"
|
||||
|
||||
Reference in New Issue
Block a user