From bb37bd915eb84abcdb741ffe4f77765cd18f9f90 Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Fri, 8 Apr 2022 14:10:05 +0000 Subject: [PATCH] [FIX] mass_mailing: do tab style manipulations on start mass_mailing When starting the mass_mailing design option, some manipulations are done on the `#design-element` style element. This can result in sudden style changes when clicking the design tab. This commit makes it so these manipulations are done as soon as mass_mailing starts. In order to do that, constants were shared from mass_mailing_snippets. closes odoo/odoo#88318 Related: odoo/enterprise#25753 Signed-off-by: David Monjoie (dmo) --- addons/mass_mailing/__manifest__.py | 1 + .../src/js/mass_mailing_design_constants.js | 149 +++++++++++++++++ .../static/src/js/mass_mailing_snippets.js | 150 +----------------- .../static/src/js/mass_mailing_widget.js | 3 + 4 files changed, 161 insertions(+), 142 deletions(-) create mode 100644 addons/mass_mailing/static/src/js/mass_mailing_design_constants.js diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py index 1212bc181b7..4cb50d1cf12 100644 --- a/addons/mass_mailing/__manifest__.py +++ b/addons/mass_mailing/__manifest__.py @@ -88,6 +88,7 @@ 'mass_mailing/static/src/js/mailing_contact_view_list.js', 'mass_mailing/static/src/js/mailing_filter_widget.js', 'mass_mailing/static/src/js/mass_mailing.js', + 'mass_mailing/static/src/js/mass_mailing_design_constants.js', 'mass_mailing/static/src/js/mass_mailing_widget.js', 'mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js', 'mass_mailing/static/src/js/unsubscribe.js', diff --git a/addons/mass_mailing/static/src/js/mass_mailing_design_constants.js b/addons/mass_mailing/static/src/js/mass_mailing_design_constants.js new file mode 100644 index 00000000000..1ddcf17cb4a --- /dev/null +++ b/addons/mass_mailing/static/src/js/mass_mailing_design_constants.js @@ -0,0 +1,149 @@ +/** @odoo-module alias=mass_mailing.design_constants**/ + +export const CSS_PREFIX = '.o_mail_wrapper'; +export const DEFAULT_CSS_OBJECT = { + h1: { + 'font-size': '35px', + color: '#212529', + 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', + }, + h2: { + 'font-size': '28px', + color: '#212529', + 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', + }, + h3: { + 'font-size': '24.5px', + color: '#212529', + 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', + }, + 'p, p > *, li, li > *': { + 'font-size': '14px', + color: '#212529', + 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', + }, + 'a:not(.btn), a.btn-link': { + 'text-decoration-line': 'none', + color: '#276e72', + }, + 'a.btn-primary, a.btn-outline-primary, a.btn-fill-primary': { + 'font-size': '12.25px', + color: '#FFFFFF', + 'background-color': '#35979c', + 'border-color': '#35979c', + 'border-style': 'solid', + 'border-width': '1px', + }, + 'a.btn-secondary, a.btn-outline-secondary, a.btn-fill-secondary': { + 'font-size': '12.25px', + color: '#FFFFFF', + 'background-color': '#685563', + 'border-color': '#685563', + 'border-style': 'solid', + 'border-width': '1px', + }, + hr: { + 'border-top-color': '#212529', + 'border-top-style': 'solid', + 'border-top-width': '1px', + width: '100%', + } +}; +export const DEFAULT_CSS = Object.keys(DEFAULT_CSS_OBJECT).map( // selectors + key => key.split(',').map( // each individual comma separated selector + selector => `${CSS_PREFIX} ${selector.trim()} {\n ${ // selector { + Object.keys(DEFAULT_CSS_OBJECT[key]).map( // css properties + prop => `${prop}: ${DEFAULT_CSS_OBJECT[key][prop]}`) // [prop: value] + .join(';\n ')}\n}` // prop: value; + ).join('\n') +).join('\n'); // css text + +export const BTN_SIZE_STYLES = { + 'btn-sm': { + 'padding': '0.25rem 0.5rem', + 'font-size': '0.875rem', + 'line-height': '1.5rem', + }, + 'btn-lg': { + 'padding': '0.5rem 1rem', + 'font-size': '1.25rem', + 'line-height': '1.5rem', + }, + 'btn-md': { + 'padding': false, // Property must be removed. + 'font-size': '14px', + 'line-height': false, // Property must be removed. + }, +}; +export const DEFAULT_BUTTON_SIZE = 'btn-md'; +export const PRIORITY_STYLES = { + 'h1': ['font-family'], + 'h2': ['font-family'], + 'h3': ['font-family'], + 'p': ['font-family'], + 'a:not(.btn)': [], + 'a.btn-primary': [], + 'a.btn-secondary': [], + 'hr': [], +}; +export const RE_CSS_TEXT_MATCH = /([^{]+)([^}]+)/; + +/** + * Take a css text and splits each comma-separated selector into separate + * styles, applying the css prefix to each. Return the modified css text. + * + * @param {string} [css] + * @returns {string} + */ +export const splitCss = css => { + const styleElement = document.createElement('style'); + styleElement.textContent = css; + // Temporarily insert the style element in the dom to have a stylesheet. + document.head.appendChild(styleElement); + const rules = [...styleElement.sheet.cssRules]; + styleElement.remove(); + const result = rules.map(rule => { + const [, fullSelector, styles] = rule.cssText.match(RE_CSS_TEXT_MATCH); + return fullSelector.split(',').map(selector => { + if (!selector.trim().startsWith(CSS_PREFIX)) { + selector = `${CSS_PREFIX} ${selector.trim()}`; + } + return `${selector.trim()} {${styles.replace('{', '').trim()}}`; + }).join(''); + }).join(''); + return result; +}; +export const getFontName = fontFamily => fontFamily.split(',')[0].replace(/"/g, '').replace(/([a-z])([A-Z])/g, (v, a, b) => `${a} ${b}`).trim(); +export const normalizeFontFamily = fontFamily => fontFamily.replace(/"/g, '').replace(/, /g, ','); +export const initializeDesignTabCss = $editable => { + let styleElement = $editable.get(0).ownerDocument.querySelector('#design-element'); + if (styleElement) { + styleElement.textContent = splitCss(styleElement.textContent); + } else { + // If a style element can't be found, create one and initialize it. + styleElement = document.createElement('style'); + styleElement.setAttribute('id', 'design-element'); + // The style element needs to be within the layout of the email in + // order to be saved along with it. + $editable.find('.o_layout').prepend(styleElement); + styleElement.textContent = splitCss(DEFAULT_CSS); + } +}; + +export const FONT_FAMILIES = [ + 'Arial, "Helvetica Neue", Helvetica, sans-serif', // name: "Arial" + '"Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace', // name: "Courier New" + 'Georgia, Times, "Times New Roman", serif', // name: "Georgia" + '"Helvetica Neue", Helvetica, Arial, sans-serif', // name: "Helvetica Neue" + '"Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif', // name: "Lucida Grande" + 'Tahoma, Verdana, Segoe, sans-serif', // name: "Tahoma" + 'TimesNewRoman, "Times New Roman", Times, Baskerville, Georgia, serif', // name: "Times New Roman" + '"Trebuchet MS", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Tahoma, sans-serif', // name: "Trebuchet MS" + 'Verdana, Geneva, sans-serif', // name: "Verdana" +].map(fontFamily => normalizeFontFamily(fontFamily)); + +export default { + CSS_PREFIX, DEFAULT_CSS_OBJECT, DEFAULT_CSS, BTN_SIZE_STYLES, + DEFAULT_BUTTON_SIZE, PRIORITY_STYLES, RE_CSS_TEXT_MATCH, FONT_FAMILIES, + splitCss, getFontName, normalizeFontFamily, initializeDesignTabCss, +} diff --git a/addons/mass_mailing/static/src/js/mass_mailing_snippets.js b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js index 75b53d34a3c..be135a8ec73 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_snippets.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js @@ -5,111 +5,12 @@ const options = require('web_editor.snippets.options'); const {ColorpickerWidget} = require('web.Colorpicker'); const SelectUserValueWidget = options.userValueWidgetsRegistry['we-select']; const weUtils = require('web_editor.utils'); +const { + CSS_PREFIX, BTN_SIZE_STYLES, + DEFAULT_BUTTON_SIZE, PRIORITY_STYLES, FONT_FAMILIES, + getFontName, normalizeFontFamily, initializeDesignTabCss +} = require('mass_mailing.design_constants'); -//-------------------------------------------------------------------------- -// Constants -//-------------------------------------------------------------------------- - -const _getFontName = fontFamily => fontFamily.split(',')[0].replace(/"/g, '').replace(/([a-z])([A-Z])/g, (v, a, b) => `${a} ${b}`).trim(); -const _normalizeFontFamily = fontFamily => fontFamily.replace(/"/g, '').replace(/, /g, ','); -const FONT_FAMILIES = [ - 'Arial, "Helvetica Neue", Helvetica, sans-serif', // name: "Arial" - '"Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace', // name: "Courier New" - 'Georgia, Times, "Times New Roman", serif', // name: "Georgia" - '"Helvetica Neue", Helvetica, Arial, sans-serif', // name: "Helvetica Neue" - '"Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif', // name: "Lucida Grande" - 'Tahoma, Verdana, Segoe, sans-serif', // name: "Tahoma" - 'TimesNewRoman, "Times New Roman", Times, Baskerville, Georgia, serif', // name: "Times New Roman" - '"Trebuchet MS", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Tahoma, sans-serif', // name: "Trebuchet MS" - 'Verdana, Geneva, sans-serif', // name: "Verdana" -].map(fontFamily => _normalizeFontFamily(fontFamily)); -const CSS_PREFIX = '.o_mail_wrapper'; -const DEFAULT_CSS_OBJECT = { - h1: { - 'font-size': '35px', - color: '#212529', - 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', - }, - h2: { - 'font-size': '28px', - color: '#212529', - 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', - }, - h3: { - 'font-size': '24.5px', - color: '#212529', - 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', - }, - 'p, p > *, li, li > *': { - 'font-size': '14px', - color: '#212529', - 'font-family': 'Arial,Helvetica Neue,Helvetica,sans-serif', - }, - 'a:not(.btn), a.btn-link': { - 'text-decoration-line': 'none', - color: '#276e72', - }, - 'a.btn-primary, a.btn-outline-primary, a.btn-fill-primary': { - 'font-size': '12.25px', - color: '#FFFFFF', - 'background-color': '#35979c', - 'border-color': '#35979c', - 'border-style': 'solid', - 'border-width': '1px', - }, - 'a.btn-secondary, a.btn-outline-secondary, a.btn-fill-secondary': { - 'font-size': '12.25px', - color: '#FFFFFF', - 'background-color': '#685563', - 'border-color': '#685563', - 'border-style': 'solid', - 'border-width': '1px', - }, - hr: { - 'border-top-color': '#212529', - 'border-top-style': 'solid', - 'border-top-width': '1px', - width: '100%', - } -}; -const DEFAULT_CSS = Object.keys(DEFAULT_CSS_OBJECT).map( // selectors - key => key.split(',').map( // each individual comma separated selector - selector => `${CSS_PREFIX} ${selector.trim()} {\n ${ // selector { - Object.keys(DEFAULT_CSS_OBJECT[key]).map( // css properties - prop => `${prop}: ${DEFAULT_CSS_OBJECT[key][prop]}`) // [prop: value] - .join(';\n ')}\n}` // prop: value; - ).join('\n') -).join('\n'); // css text - -const BTN_SIZE_STYLES = { - 'btn-sm': { - 'padding': '0.25rem 0.5rem', - 'font-size': '0.875rem', - 'line-height': '1.5rem', - }, - 'btn-lg': { - 'padding': '0.5rem 1rem', - 'font-size': '1.25rem', - 'line-height': '1.5rem', - }, - 'btn-md': { - 'padding': false, // Property must be removed. - 'font-size': '14px', - 'line-height': false, // Property must be removed. - }, -}; -const DEFAULT_BUTTON_SIZE = 'btn-md'; -const PRIORITY_STYLES = { - 'h1': ['font-family'], - 'h2': ['font-family'], - 'h3': ['font-family'], - 'p': ['font-family'], - 'a:not(.btn)': [], - 'a.btn-primary': [], - 'a.btn-secondary': [], - 'hr': [], -}; -const RE_CSS_TEXT_MATCH = /([^{]+)([^}]+)/; //-------------------------------------------------------------------------- // Options @@ -261,7 +162,7 @@ options.userValueWidgetsRegistry['we-fontfamilypicker'] = SelectUserValueWidget. button.dataset.customizeCssProperty = fontFamily; button.dataset.cssProperty = 'font-family'; button.dataset.selectorText = this.el.dataset.selectorText; - button.textContent = _getFontName(fontFamily); + button.textContent = getFontName(fontFamily); this.menuEl.appendChild(button); }; return res; @@ -284,18 +185,8 @@ options.registry.DesignTab = options.Class.extend({ const res = await this._super(...arguments); const $editable = this.options.wysiwyg.getEditable(); this.document = $editable[0].ownerDocument; + initializeDesignTabCss($editable); this.styleElement = this.document.querySelector('#design-element'); - if (this.styleElement) { - this.styleElement.textContent = this._splitCss(this.styleElement.textContent); - } else { - // If a style element can't be found, create one and initialize it. - this.styleElement = document.createElement('style'); - this.styleElement.setAttribute('id', 'design-element'); - // The style element needs to be within the layout of the email in - // order to be saved along with it. - $editable.find('.o_layout').prepend(this.styleElement); - this.styleElement.textContent = this._splitCss(DEFAULT_CSS); - } // When editing a stylesheet, its content is not updated so it won't be // saved along with the mailing. Therefore we need to write its cssText // into it. However, when doing that we lose its reference. So we need @@ -436,7 +327,7 @@ options.registry.DesignTab = options.Class.extend({ if (params.possibleValues && params.possibleValues[1] === FONT_FAMILIES[0]) { // For font-family, we need to normalize it so it // matches an option value. - return rule && _normalizeFontFamily(rule.style.getPropertyValue('font-family')); + return rule && normalizeFontFamily(rule.style.getPropertyValue('font-family')); } else { return rule && rule.style.getPropertyValue(params.cssProperty); } @@ -476,31 +367,6 @@ options.registry.DesignTab = options.Class.extend({ _getRule(selectorText) { return [...(this.styleSheet.cssRules || this.styleSheet.rules)].find(rule => rule.selectorText === selectorText); }, - /** - * Take a css text and splits each comma-separated selector into separate - * styles, applying the css prefix to each. Return the modified css text. - * - * @param {string} css - * @returns {string} - */ - _splitCss(css) { - const styleElement = this.document.createElement('style'); - styleElement.textContent = css; - // Temporarily insert the style element in the dom to have a stylesheet. - document.head.appendChild(styleElement); - const rules = [...styleElement.sheet.cssRules]; - styleElement.remove(); - const result = rules.map(rule => { - const [, fullSelector, styles] = rule.cssText.match(RE_CSS_TEXT_MATCH); - return fullSelector.split(',').map(selector => { - if (!selector.trim().startsWith(CSS_PREFIX)) { - selector = `${CSS_PREFIX} ${selector.trim()}`; - } - return `${selector.trim()} {${styles.replace('{', '').trim()}}`; - }).join(''); - }).join(''); - return result; - }, }); }); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_widget.js b/addons/mass_mailing/static/src/js/mass_mailing_widget.js index d68a6ed75aa..72b5258cb99 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_widget.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_widget.js @@ -6,6 +6,7 @@ var core = require('web.core'); var FieldHtml = require('web_editor.field.html'); var fieldRegistry = require('web.field_registry'); var convertInline = require('web_editor.convertInline'); +const { initializeDesignTabCss } = require('mass_mailing.design_constants'); var _t = core._t; @@ -125,6 +126,7 @@ var MassMailingFieldHtml = FieldHtml.extend({ this.$content.find('.o_layout').addBack().data('name', 'Mailing'); // We don't want to drop snippets directly within the wysiwyg. this.$content.removeClass('o_editable'); + initializeDesignTabCss(this.wysiwyg.getEditable()); }, /** * Returns true if the editable area is empty. @@ -289,6 +291,7 @@ var MassMailingFieldHtml = FieldHtml.extend({ if (themeParams.name === 'basic') { this.$content[0].focus(); } + initializeDesignTabCss(this.wysiwyg.getEditable()); this.wysiwyg.trigger('reload_snippet_dropzones'); this.trigger_up('iframe_updated', { $iframe: this.wysiwyg.$iframe }); this.wysiwyg.odooEditor.historyStep(true);