[REF] mass_mailing: adapt to new jabberwock editor
This commit is contained in:
@@ -178,7 +178,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
|
||||
// have no way of redispatching 'genuine' key events. Resent events
|
||||
// don't trigger native event handlers of elements. So this means that
|
||||
// our fake events will not appear in eg. an <input> element.
|
||||
if ((this.element_is_editable(e.target) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true")
|
||||
// Don't catch events targeting a shadow DOM because we must keep the
|
||||
// behavior specific to the content.
|
||||
if (((this.element_is_editable(e.target) || e.target.shadowRoot) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true")
|
||||
return;
|
||||
|
||||
// Catch and buffer the event
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
odoo.define('mass_mailing.snippets.options', function (require) {
|
||||
"use strict";
|
||||
|
||||
var options = require('web_editor.snippets.options');
|
||||
var snippetOptions = require('web_editor.snippets.options');
|
||||
|
||||
// Snippet option for resizing image and column width inline like excel
|
||||
options.registry.mass_mailing_sizing_x = options.Class.extend({
|
||||
snippetOptions.registry.mass_mailing_sizing_x = snippetOptions.SnippetOptionWidget.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -79,7 +79,7 @@ options.registry.mass_mailing_sizing_x = options.Class.extend({
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.mass_mailing_table_item = options.Class.extend({
|
||||
snippetOptions.registry.mass_mailing_table_item = snippetOptions.SnippetOptionWidget.extend({
|
||||
onClone: function (options) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
@@ -127,7 +127,7 @@ options.registry.mass_mailing_table_item = options.Class.extend({
|
||||
|
||||
// Adding compatibility for the outlook compliance of mailings.
|
||||
// Commit of such compatibility : a14f89c8663c9cafecb1cc26918055e023ecbe42
|
||||
options.registry.BackgroundImage = options.registry.BackgroundImage.extend({
|
||||
snippetOptions.registry.BackgroundImage = snippetOptions.registry.BackgroundImage.extend({
|
||||
start: function () {
|
||||
this._super();
|
||||
if (this.snippets && this.snippets.split('.')[0] === "mass_mailing") {
|
||||
|
||||
@@ -5,21 +5,28 @@ var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var FieldHtml = require('web_editor.field.html');
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var convertInline = require('web_editor.convertInline');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
var MassMailingFieldHtml = FieldHtml.extend({
|
||||
xmlDependencies: (FieldHtml.prototype.xmlDependencies || []).concat(["/mass_mailing/static/src/xml/mass_mailing.xml"]),
|
||||
jsLibs: [
|
||||
'/mass_mailing/static/src/js/mass_mailing_snippets.js',
|
||||
],
|
||||
assetLibs: ['web_editor.compiled_assets_wysiwyg'],
|
||||
jsLibs: ['/mass_mailing/static/src/js/mass_mailing_snippets.js'],
|
||||
|
||||
custom_events: _.extend({}, FieldHtml.prototype.custom_events, {
|
||||
snippets_loaded: '_onSnippetsLoaded',
|
||||
}),
|
||||
|
||||
events: {
|
||||
'click .o_we_show_themes_btn': '_onShowThemesClick',
|
||||
'click .o_mail_theme_selector a': '_onChangeThemeClick',
|
||||
'mouseenter .o_mail_theme_selector a': '_onChangeThemeMouseEnter',
|
||||
'mouseleave .o_mail_theme_selector a': '_onChangeThemeMouseLeave',
|
||||
'click .o_we_fullscreen_btn': '_onFullScreenClick',
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -41,103 +48,34 @@ var MassMailingFieldHtml = FieldHtml.extend({
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
commitChanges: function () {
|
||||
commitChanges: async function () {
|
||||
var self = this;
|
||||
if (config.isDebug() && this.mode === 'edit') {
|
||||
var layoutInfo = $.summernote.core.dom.makeLayoutInfo(this.wysiwyg.$editor);
|
||||
$.summernote.pluginEvents.codeview(undefined, undefined, layoutInfo, false);
|
||||
}
|
||||
if (this.mode === 'readonly' || !this.isRendered) {
|
||||
return this._super();
|
||||
}
|
||||
var fieldName = this.nodeOptions['inline-field'];
|
||||
|
||||
if (this.$content.find('.o_basic_theme').length) {
|
||||
this.$content.find('*').css('font-family', '');
|
||||
await this._super();
|
||||
if (this.mode === 'readonly' || !this.wysiwyg) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $editable = this.wysiwyg.getEditable();
|
||||
const isDirty = this._isDirty;
|
||||
const changes = {};
|
||||
changes[this.nodeOptions['inline-field']] = await this.wysiwyg.getValue('text/mail');
|
||||
|
||||
return this.wysiwyg.saveModifiedImages(this.$content).then(function () {
|
||||
return self.wysiwyg.save().then(function (result) {
|
||||
self._isDirty = result.isDirty;
|
||||
|
||||
convertInline.attachmentThumbnailToLinkImg($editable);
|
||||
convertInline.fontToImg($editable);
|
||||
convertInline.classToStyle($editable);
|
||||
|
||||
self.trigger_up('field_changed', {
|
||||
dataPointID: self.dataPointID,
|
||||
changes: _.object([fieldName], [self._unWrap($editable.html())])
|
||||
});
|
||||
|
||||
if (self._isDirty && self.mode === 'edit') {
|
||||
return self._doAction();
|
||||
}
|
||||
});
|
||||
self.trigger_up('field_changed', {
|
||||
dataPointID: self.dataPointID,
|
||||
changes: changes,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* The html_frame widget is opened in an iFrame that has its URL encoded
|
||||
* with all the key/values returned by this method.
|
||||
*
|
||||
* Some fields can get very long values and we want to omit them for the URL building.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
getDatarecord: function () {
|
||||
return _.omit(this._super(), [
|
||||
'mailing_domain',
|
||||
'contact_list_ids',
|
||||
'body_html',
|
||||
'attachment_ids'
|
||||
]);
|
||||
|
||||
if (isDirty && self.mode === 'edit') {
|
||||
return self._doAction();
|
||||
}
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns true if must force the user to choose a theme.
|
||||
*
|
||||
* @private
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_checkIfMustForceThemeChoice: function () {
|
||||
var firstChoice = this._editableAreaIsEmpty();
|
||||
this.$content.closest('body').toggleClass("o_force_mail_theme_choice", firstChoice);
|
||||
return firstChoice;
|
||||
},
|
||||
/**
|
||||
* Returns true if the editable area is empty.
|
||||
*
|
||||
* @private
|
||||
* @param {JQuery} [$layout]
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_editableAreaIsEmpty: function ($layout) {
|
||||
$layout = $layout || this.$content.find(".o_layout");
|
||||
var $mailWrapper = $layout.children(".o_mail_wrapper");
|
||||
var $mailWrapperContent = $mailWrapper.find('.o_mail_wrapper_td');
|
||||
if (!$mailWrapperContent.length) { // compatibility
|
||||
$mailWrapperContent = $mailWrapper;
|
||||
}
|
||||
var value;
|
||||
if ($mailWrapperContent.length > 0) {
|
||||
value = $mailWrapperContent.html();
|
||||
} else if ($layout.length) {
|
||||
value = $layout.html();
|
||||
} else {
|
||||
value = this.wysiwyg.getValue();
|
||||
}
|
||||
var blankEditable = "<p><br></p>";
|
||||
return value === "" || value === blankEditable;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
this._isFromInline = !!this.value;
|
||||
if (!this.value) {
|
||||
this.value = this.recordData[this.nodeOptions['inline-field']];
|
||||
}
|
||||
@@ -166,25 +104,6 @@ var MassMailingFieldHtml = FieldHtml.extend({
|
||||
}
|
||||
return $();
|
||||
},
|
||||
/**
|
||||
* Returns the selected theme, if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} themesParams
|
||||
* @returns {false|Object}
|
||||
*/
|
||||
_getSelectedTheme: function (themesParams) {
|
||||
var $layout = this.$content.find(".o_layout");
|
||||
var selectedTheme = false;
|
||||
if ($layout.length !== 0) {
|
||||
_.each(themesParams, function (themeParams) {
|
||||
if ($layout.hasClass(themeParams.className)) {
|
||||
selectedTheme = themeParams;
|
||||
}
|
||||
});
|
||||
}
|
||||
return selectedTheme;
|
||||
},
|
||||
/**
|
||||
* Swap the previous theme's default images with the new ones.
|
||||
* (Redefine the `src` attribute of all images in a $container, depending on the theme parameters.)
|
||||
@@ -221,81 +140,187 @@ var MassMailingFieldHtml = FieldHtml.extend({
|
||||
$img.attr("src", src);
|
||||
});
|
||||
},
|
||||
_getContent: async function () {
|
||||
return $(this.$('jw-shadow')[0].shadowRoot).find(':not(style,link)');
|
||||
},
|
||||
/**
|
||||
* Switch themes or import first theme.
|
||||
* Reload snippet when choose a template.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createWysiwygIntance: async function () {
|
||||
await this._super();
|
||||
const onCommitCheckSnippets = (params) => {
|
||||
if (params.commandNames.includes('applyTemplate')) {
|
||||
setTimeout(() => {
|
||||
// use setTimeout to reload snippets after the redraw
|
||||
this.wysiwyg.snippetsMenu.trigger('reload_snippet_dropzones');
|
||||
});
|
||||
}
|
||||
};
|
||||
this.wysiwyg.editor.dispatcher.registerCommandHook('@commit', onCommitCheckSnippets);
|
||||
},
|
||||
/**
|
||||
* Add templates & themes.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_getWysiwygOptions: async function () {
|
||||
this.needShadow = true;
|
||||
const options = await this._super();
|
||||
const self = this;
|
||||
|
||||
// Get the snippets to have the templates and themes container.
|
||||
const $snippets = $(await this._rpc({
|
||||
model: 'ir.ui.view',
|
||||
method: 'render_public_asset',
|
||||
args: [options.snippets, {}],
|
||||
kwargs: {
|
||||
context: options.recordInfo.context,
|
||||
},
|
||||
}));
|
||||
|
||||
// Create templates and themes components.
|
||||
const themes = [{
|
||||
id: 'default',
|
||||
label: _t('Default'),
|
||||
render(editor) {
|
||||
return editor.plugins.get(self.wysiwyg.JWEditorLib.Parser).parse('text/html',
|
||||
'<div class="oe_structure"><t-placeholder/></div>');
|
||||
},
|
||||
}];
|
||||
const components = [];
|
||||
const templateConfigurations = {};
|
||||
$snippets.find("#email_designer_themes").children().each(function () {
|
||||
const $template = $(this);
|
||||
const data = $template.data();
|
||||
const templateId = 'template-' + data.name;
|
||||
const themeId = 'theme-' + data.name;
|
||||
const nowrap = !!$template.data('nowrap');
|
||||
components.push({
|
||||
id: templateId,
|
||||
async render(editor) {
|
||||
const valueAndTheme = self._getValueAndTheme($template.html());
|
||||
const html = '<t-theme name="' + themeId + '">' + valueAndTheme.value + '</t-theme>';
|
||||
return editor.plugins.get(self.wysiwyg.JWEditorLib.Parser).parse('text/html', html);
|
||||
},
|
||||
});
|
||||
templateConfigurations[templateId] = {
|
||||
componentId: templateId,
|
||||
zoneId: 'editable',
|
||||
label: data.name,
|
||||
thumbnail: data.img + '_large.png',
|
||||
thumbnailZoneId: 'container',
|
||||
};
|
||||
themes.push({
|
||||
id: themeId,
|
||||
data: data,
|
||||
label: data.name,
|
||||
render(editor) {
|
||||
const parserPlugin = editor.plugins.get(self.wysiwyg.JWEditorLib.Parser);
|
||||
if (nowrap) {
|
||||
return parserPlugin.parse('text/html',
|
||||
'<div class="o_layout oe_structure" contenteditable="true"><t-placeholder/></div>');
|
||||
} else {
|
||||
// This wrapper structure is the only way to have a responsive
|
||||
// and centered fixed-width content column on all mail clients
|
||||
return parserPlugin.parse('text/html',
|
||||
'<div class="o_layout o_' + data.name + '_theme">' +
|
||||
'<table class="o_mail_wrapper">' +
|
||||
'<tr>' +
|
||||
'<td class="o_mail_no_resize o_not_editable"></td>' +
|
||||
'<td class="o_mail_no_options o_mail_wrapper_td oe_structure" contenteditable="true"><t-placeholder/></td>' +
|
||||
'<td class="o_mail_no_resize o_not_editable"></td>' +
|
||||
'</tr>' +
|
||||
'</table>' +
|
||||
'</div>');
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Add the templates and themes as options.
|
||||
options.templates = {
|
||||
components: components,
|
||||
templateConfigurations: templateConfigurations,
|
||||
};
|
||||
options.themes = themes;
|
||||
|
||||
// Get the current theme.
|
||||
const valueAndTheme = this._getValueAndTheme(options.value);
|
||||
if (valueAndTheme.themeId) {
|
||||
options.value = '<t-theme name="' + valueAndTheme.themeId + '">' + valueAndTheme.value + '</t-theme>';
|
||||
} else if (options.value.length) {
|
||||
options.value = '<t-theme>' + valueAndTheme.value + '</t-theme>';
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
/**
|
||||
* Returns the selected theme, if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Boolean} firstChoice true if this is the first chosen theme (going from no theme to a theme)
|
||||
* @param {Object} themeParams
|
||||
* @param {string} value
|
||||
* @returns {[string, string]} [value, themeId]
|
||||
*/
|
||||
_switchThemes: function (firstChoice, themeParams) {
|
||||
if (!themeParams || this.switchThemeLast === themeParams) {
|
||||
return;
|
||||
}
|
||||
this.switchThemeLast = themeParams;
|
||||
|
||||
this.$content.closest('body').removeClass(this._allClasses).addClass(themeParams.className);
|
||||
|
||||
var $old_layout = this.$content.find('.o_layout');
|
||||
|
||||
var $new_wrapper;
|
||||
var $newWrapperContent;
|
||||
if (themeParams.nowrap) {
|
||||
$new_wrapper = $('<div/>', {
|
||||
class: 'oe_structure'
|
||||
});
|
||||
$newWrapperContent = $new_wrapper;
|
||||
} else {
|
||||
// This wrapper structure is the only way to have a responsive
|
||||
// and centered fixed-width content column on all mail clients
|
||||
$new_wrapper = $('<table/>', {
|
||||
class: 'o_mail_wrapper'
|
||||
});
|
||||
$newWrapperContent = $('<td/>', {
|
||||
class: 'o_mail_no_options o_mail_wrapper_td oe_structure'
|
||||
});
|
||||
$new_wrapper.append($('<tr/>').append(
|
||||
$('<td/>', {
|
||||
class: 'o_mail_no_resize o_not_editable',
|
||||
contenteditable: 'false'
|
||||
}),
|
||||
$newWrapperContent,
|
||||
$('<td/>', {
|
||||
class: 'o_mail_no_resize o_not_editable',
|
||||
contenteditable: 'false'
|
||||
})
|
||||
));
|
||||
}
|
||||
var $newLayout = $('<div/>', {
|
||||
class: 'o_layout ' + themeParams.className
|
||||
}).append($new_wrapper);
|
||||
|
||||
var $contents;
|
||||
if (firstChoice) {
|
||||
$contents = themeParams.template;
|
||||
} else if ($old_layout.length) {
|
||||
$contents = ($old_layout.hasClass('oe_structure') ? $old_layout : $old_layout.find('.oe_structure').first()).contents();
|
||||
} else {
|
||||
$contents = this.$content.find('.o_editable').contents();
|
||||
}
|
||||
|
||||
$newWrapperContent.append($contents);
|
||||
this._switchImages(themeParams, $newWrapperContent);
|
||||
this.$content.find('.o_editable').empty().append($newLayout);
|
||||
$old_layout.remove();
|
||||
|
||||
if (firstChoice) {
|
||||
$newWrapperContent.find('*').addBack()
|
||||
.contents()
|
||||
.filter(function () {
|
||||
return this.nodeType === 3 && this.textContent.match(/\S/);
|
||||
}).parent().addClass('o_default_snippet_text');
|
||||
|
||||
if (themeParams.name == 'basic') {
|
||||
this.$content.focusIn();
|
||||
_getValueAndTheme: function (value) {
|
||||
const $value = $(value);
|
||||
let $layout = $value.hasClass("o_layout") ? $value : $value.find(".o_layout");
|
||||
let themeId;
|
||||
if ($layout.length) {
|
||||
let $contents = $layout.contents();
|
||||
const classNameThemeId = [].find.call($layout[0].classList, className => className.includes('_theme'));
|
||||
themeId = classNameThemeId && ('theme-' + classNameThemeId.slice(2, -6));
|
||||
const $td = $contents.find('.o_mail_wrapper_td');
|
||||
if ($td.length) {
|
||||
$contents = $td.contents();
|
||||
} else if ($layout.length) {
|
||||
$contents = $layout.contents();
|
||||
}
|
||||
value = $contents.get().map(node => node.outerHTML || node.textContent).join('');
|
||||
}
|
||||
this.wysiwyg.trigger('reload_snippet_dropzones');
|
||||
return {value: value, themeId: themeId};
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_closeThemes: function () {
|
||||
this.$el.find('#o_scroll, .o_snippet_search_filter').removeClass('d-none');
|
||||
this.$el.find('.o_mail_theme_selector').addClass('d-none');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_previewUpdateTheme: function (themeId) {
|
||||
const theme = this.wysiwyg.options.themes.find(theme => theme.id === themeId);
|
||||
const $layout = $(this.el.querySelector('jw-shadow::shadow /deep/ .o_layout'));
|
||||
$layout.attr('class', 'o_layout o_' + theme.data.name + '_theme');
|
||||
|
||||
const themeName = theme.data.name;
|
||||
const imagesInfo = theme.data.imagesInfo;
|
||||
$layout.find('img').each(function () {
|
||||
const $img = $(this);
|
||||
let src = $img.attr('src');
|
||||
|
||||
const isLogo = src.includes('logo.');
|
||||
let moduleName = 'mass_mailing';
|
||||
let format = 'jpg';
|
||||
if (imagesInfo.logo && imagesInfo.logo.module && isLogo) {
|
||||
moduleName = imagesInfo.logo.module;
|
||||
} else if (imagesInfo.all && imagesInfo.all.module) {
|
||||
moduleName = imagesInfo.all.module;
|
||||
}
|
||||
if (imagesInfo.logo && imagesInfo.logo.format && isLogo) {
|
||||
format = imagesInfo.logo.format;
|
||||
} else if (imagesInfo.all && imagesInfo.all.format) {
|
||||
format = imagesInfo.all.format;
|
||||
}
|
||||
|
||||
src = src.replace(
|
||||
/^\/[^\/]+\/(.+)\/theme_[^/]+\/(.*)\.\w+$/,
|
||||
'/' + moduleName + '/$1/theme_' + themeName + '/$2.' + format);
|
||||
$img.attr('src', src);
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -303,191 +328,101 @@ var MassMailingFieldHtml = FieldHtml.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_onLoadWysiwyg: function () {
|
||||
if (this._isFromInline) {
|
||||
this._fromInline();
|
||||
}
|
||||
if (this.snippetsLoaded) {
|
||||
this._onSnippetsLoaded(this.snippetsLoaded);
|
||||
}
|
||||
this._super();
|
||||
_onSnippetsLoaded: function () {
|
||||
this.$el.find("#email_designer_themes").remove();
|
||||
this.$el.find('#oe_snippets').append($(QWeb.render('mass_mailing.theme_selector', {themes: this.wysiwyg.options.themes})));
|
||||
this.$el.find('#snippets_menu').append($('<button type="button" class="o_we_show_themes_btn"><span>' + _t('Select a theme') + '</span></button>'));
|
||||
this.$el.find('#oe_snippets').before($('<button type="button" class="o_we_fullscreen_btn"><span class="fa fa-expand"></span></button>'));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onSnippetsLoaded: function (ev) {
|
||||
var self = this;
|
||||
if (!this.$content) {
|
||||
this.snippetsLoaded = ev;
|
||||
return;
|
||||
}
|
||||
var $snippetsSideBar = ev.data;
|
||||
var $themes = $snippetsSideBar.find("#email_designer_themes").children();
|
||||
var $snippets = $snippetsSideBar.find(".oe_snippet");
|
||||
var $snippets_menu = $snippetsSideBar.find("#snippets_menu");
|
||||
_onChangeThemeClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
const themeId = $(ev.target.closest('a')).data('id');
|
||||
const theme = this.wysiwyg.options.themes.find(theme => theme.id === themeId);
|
||||
const layoutPlugin = this.wysiwyg.editor.plugins.get(this.wysiwyg.JWEditorLib.Layout)
|
||||
const domEngine = layoutPlugin.engines.dom;
|
||||
const themeNode = domEngine.components.main[0].firstDescendant(node => node.themeName);
|
||||
if (themeId !== themeNode.themeName) {
|
||||
const changeTheme = () => {
|
||||
themeNode.themeName = themeId;
|
||||
|
||||
if (config.device.isMobile) {
|
||||
$snippetsSideBar.hide();
|
||||
this.$content.attr('style', 'padding-left: 0px !important');
|
||||
}
|
||||
const themeName = theme.data.name;
|
||||
const imagesInfo = theme.data.imagesInfo;
|
||||
const imageNodes = themeNode.descendants(this.wysiwyg.JWEditorLib.ImageNode);
|
||||
|
||||
if ($themes.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const imageNode of imageNodes) {
|
||||
const attributes = imageNode.modifiers.get(this.wysiwyg.JWEditorLib.Attributes);
|
||||
let src = attributes.get('src');
|
||||
|
||||
/**
|
||||
* Initialize theme parameters.
|
||||
*/
|
||||
this._allClasses = "";
|
||||
var themesParams = _.map($themes, function (theme) {
|
||||
var $theme = $(theme);
|
||||
var name = $theme.data("name");
|
||||
var classname = "o_" + name + "_theme";
|
||||
self._allClasses += " " + classname;
|
||||
var imagesInfo = _.defaults($theme.data("imagesInfo") || {}, {
|
||||
all: {}
|
||||
});
|
||||
_.each(imagesInfo, function (info) {
|
||||
info = _.defaults(info, imagesInfo.all, {
|
||||
module: "mass_mailing",
|
||||
format: "jpg"
|
||||
});
|
||||
});
|
||||
return {
|
||||
name: name,
|
||||
className: classname || "",
|
||||
img: $theme.data("img") || "",
|
||||
template: $theme.html().trim(),
|
||||
nowrap: !!$theme.data('nowrap'),
|
||||
get_image_info: function (filename) {
|
||||
if (imagesInfo[filename]) {
|
||||
return imagesInfo[filename];
|
||||
const isLogo = src.includes('logo.');
|
||||
let moduleName = 'mass_mailing';
|
||||
let format = 'jpg';
|
||||
if (imagesInfo.logo && imagesInfo.logo.module && isLogo) {
|
||||
moduleName = imagesInfo.logo.module;
|
||||
} else if (imagesInfo.all && imagesInfo.all.module) {
|
||||
moduleName = imagesInfo.all.module;
|
||||
}
|
||||
return imagesInfo.all;
|
||||
if (imagesInfo.logo && imagesInfo.logo.format && isLogo) {
|
||||
format = imagesInfo.logo.format;
|
||||
} else if (imagesInfo.all && imagesInfo.all.format) {
|
||||
format = imagesInfo.all.format;
|
||||
}
|
||||
|
||||
src = src.replace(
|
||||
/^\/[^\/]+\/(.+)\/theme_[^/]+\/(.*)\.\w+$/,
|
||||
'/' + moduleName + '/$1/theme_' + themeName + '/$2.' + format);
|
||||
attributes.set('src', src);
|
||||
}
|
||||
};
|
||||
});
|
||||
$themes.parent().remove();
|
||||
|
||||
/**
|
||||
* Create theme selection screen and check if it must be forced opened.
|
||||
* Reforce it opened if the last snippet is removed.
|
||||
*/
|
||||
var $dropdown = $(core.qweb.render("mass_mailing.theme_selector", {
|
||||
themes: themesParams
|
||||
})).dropdown();
|
||||
|
||||
var firstChoice = this._checkIfMustForceThemeChoice();
|
||||
|
||||
/**
|
||||
* Add proposition to install enterprise themes if not installed.
|
||||
*/
|
||||
var $mail_themes_upgrade = $dropdown.find(".o_mass_mailing_themes_upgrade");
|
||||
$mail_themes_upgrade.on("click", function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
self.do_action("mass_mailing.action_mass_mailing_configuration");
|
||||
});
|
||||
|
||||
/**
|
||||
* Switch theme when a theme button is hovered. Confirm change if the theme button
|
||||
* is pressed.
|
||||
*/
|
||||
var selectedTheme = false;
|
||||
$dropdown.on("mouseenter", ".dropdown-item", function (e) {
|
||||
if (firstChoice) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var themeParams = themesParams[$(e.currentTarget).index()];
|
||||
self._switchThemes(firstChoice, themeParams);
|
||||
});
|
||||
$dropdown.on("mouseleave", ".dropdown-item", function (e) {
|
||||
self._switchThemes(false, selectedTheme);
|
||||
});
|
||||
$dropdown.on("click", '[data-toggle="dropdown"]', function (e) {
|
||||
var $menu = $dropdown.find('.dropdown-menu');
|
||||
var isVisible = $menu.hasClass('show');
|
||||
if (isVisible) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
$menu.removeClass('show');
|
||||
}
|
||||
});
|
||||
|
||||
$dropdown.on("click", ".dropdown-item", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
var themeParams = themesParams[$(e.currentTarget).index()];
|
||||
if (firstChoice) {
|
||||
self._switchThemes(firstChoice, themeParams);
|
||||
self.$content.closest('body').removeClass("o_force_mail_theme_choice");
|
||||
firstChoice = false;
|
||||
|
||||
if ($mail_themes_upgrade.length) {
|
||||
$dropdown.remove();
|
||||
$snippets_menu.empty();
|
||||
}
|
||||
}
|
||||
|
||||
self._switchImages(themeParams, $snippets);
|
||||
|
||||
selectedTheme = themeParams;
|
||||
|
||||
// Notify form view
|
||||
self.wysiwyg.getEditable().trigger('change');
|
||||
$dropdown.find('.dropdown-menu').removeClass('show');
|
||||
$dropdown.find('.dropdown-item.selected').removeClass('selected');
|
||||
$dropdown.find('.dropdown-item:eq(' + themesParams.indexOf(selectedTheme) + ')').addClass('selected');
|
||||
});
|
||||
|
||||
/**
|
||||
* If the user opens the theme selection screen, indicates which one is active and
|
||||
* saves the information...
|
||||
* ... then when the user closes check if the user confirmed its choice and restore
|
||||
* previous state if this is not the case.
|
||||
*/
|
||||
$dropdown.on("shown.bs.dropdown", function () {
|
||||
selectedTheme = self._getSelectedTheme(themesParams);
|
||||
$dropdown.find(".dropdown-item").removeClass("selected").filter(function () {
|
||||
return ($(this).has(".o_thumb[style=\"" + "background-image: url(" + (selectedTheme && selectedTheme.img) + "_small.png)" + "\"]").length > 0);
|
||||
}).addClass("selected");
|
||||
});
|
||||
$dropdown.on("hidden.bs.dropdown", function () {
|
||||
self._switchThemes(firstChoice, selectedTheme);
|
||||
});
|
||||
|
||||
/**
|
||||
* On page load, check the selected theme and force switching to it (body needs the
|
||||
* theme style for its edition toolbar).
|
||||
*/
|
||||
selectedTheme = this._getSelectedTheme(themesParams);
|
||||
if (selectedTheme) {
|
||||
this.$content.closest('body').addClass(selectedTheme.className);
|
||||
$dropdown.find('.dropdown-item:eq(' + themesParams.indexOf(selectedTheme) + ')').addClass('selected');
|
||||
this._switchImages(selectedTheme, $snippets);
|
||||
} else if (this.$content.find('.o_layout').length) {
|
||||
themesParams.push({
|
||||
name: 'o_mass_mailing_no_theme',
|
||||
className: 'o_mass_mailing_no_theme',
|
||||
img: "",
|
||||
template: this.$content.find('.o_layout').addClass('o_mass_mailing_no_theme').clone().find('oe_structure').empty().end().html().trim(),
|
||||
nowrap: true,
|
||||
get_image_info: function () {}
|
||||
});
|
||||
selectedTheme = this._getSelectedTheme(themesParams);
|
||||
this.wysiwyg.editor.execCommand(changeTheme);
|
||||
}
|
||||
|
||||
$dropdown.insertAfter($snippets_menu);
|
||||
this._closeThemes();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeThemeMouseEnter: function (ev) {
|
||||
const themeId = $(ev.target.closest('a')).data('id');
|
||||
this._previewUpdateTheme(themeId);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeThemeMouseLeave: function (ev) {
|
||||
const layoutPlugin = this.wysiwyg.editor.plugins.get(this.wysiwyg.JWEditorLib.Layout)
|
||||
const domEngine = layoutPlugin.engines.dom;
|
||||
const themeNode = domEngine.components.main[0].firstDescendant(node => node.themeName);
|
||||
this._previewUpdateTheme(themeNode.themeName);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onFullScreenClick: function (ev) {
|
||||
if (this.el.classList.contains('jw-fullscreen')) {
|
||||
document.body.classList.remove('jw-fullscreen');
|
||||
this.el.classList.remove('jw-fullscreen');
|
||||
} else {
|
||||
document.body.classList.add('jw-fullscreen');
|
||||
this.el.classList.add('jw-fullscreen');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onShowThemesClick: function () {
|
||||
this.$el.find('#o_scroll, .o_snippet_search_filter, .o_mail_theme_selector').toggleClass('d-none');
|
||||
this.$el.one('click', '.o_we_add_snippet_btn, .o_we_customize_snippet_btn', () => this._closeThemes());
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onTranslate: function (ev) {
|
||||
_onTranslate: function () {
|
||||
this.trigger_up('translate', {
|
||||
fieldName: this.nodeOptions['inline-field'],
|
||||
id: this.dataPointID,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
.o_field_widget.o_mail_body {
|
||||
height: 55vh;
|
||||
display: flex;
|
||||
|
||||
.o_mail_theme_selector {
|
||||
overflow: auto;
|
||||
|
||||
> a {
|
||||
.o_thumb {
|
||||
display: none;
|
||||
background-size: cover;
|
||||
padding-top: 45%;
|
||||
width: 90%;
|
||||
margin: 10px auto;
|
||||
|
||||
&.logo {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_we_fullscreen_btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.jw-fullscreen .jw-fullscreen {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.oe_structure {
|
||||
width: 100%;
|
||||
@media (max-width: map-get($grid-breakpoints, 'lg')) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.o_basic_theme .o_main_sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oe_form_field_html .o_we_website_top_actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
.o_form_view .o_form_sheet .o_notebook .tab-content .tab-pane .o_mail_body {
|
||||
// cancel padding of form_sheet
|
||||
margin-top: -$o-sheet-cancel-tpadding;
|
||||
margin-left: -$o-sheet-cancel-hpadding;
|
||||
margin-right: -$o-sheet-cancel-hpadding;
|
||||
margin-bottom: -$o-sheet-cancel-bpadding;
|
||||
}
|
||||
|
||||
.o_mail_theme_selector {
|
||||
> a {
|
||||
@include o-position-absolute(0, 0, auto, 0);
|
||||
height: $o-we-toolbar-height;
|
||||
line-height: $o-we-toolbar-height;
|
||||
border-radius: 0;
|
||||
background-color: $o-we-sidebar-bg;
|
||||
color: #212629;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
color: #4e525b;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.show > a {
|
||||
color: white;
|
||||
background-color: #212629;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
top: -3px !important;
|
||||
left: -5px !important;
|
||||
right: -2px !important;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
overflow: auto;
|
||||
background-color: $o-we-sidebar-bg;
|
||||
z-index: 1050;
|
||||
|
||||
&.show {
|
||||
bottom: 31px !important;
|
||||
top: -2px !important;
|
||||
right: 3px !important;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 10px 10px;
|
||||
&:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_thumb {
|
||||
display: none;
|
||||
background-size: cover;
|
||||
padding-top: 50%;
|
||||
border: 1px solid $o-we-border-color;
|
||||
|
||||
|
||||
&.logo {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: $o-we-sidebar-bg;
|
||||
|
||||
.o_thumb {
|
||||
border: 1px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected .o_thumb {
|
||||
border: 2px solid $o-brand-odoo;
|
||||
background-color: $o-we-sidebar-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.o_force_mail_theme_choice {
|
||||
#oe_snippets {
|
||||
width: 100%;
|
||||
|
||||
.o_mail_theme_selector {
|
||||
.dropdown-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
display: block;
|
||||
|
||||
.dropdown-item {
|
||||
margin: 0;
|
||||
float: left;
|
||||
clear: none;
|
||||
width: 100%;
|
||||
max-width: 25%;
|
||||
transition: all 0.3s ease 0s;
|
||||
|
||||
&:first-child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.o_thumb {
|
||||
display: none;
|
||||
padding-top: 107%;
|
||||
border: 1px solid #4e525b;
|
||||
border-top: 1px solid $o-we-border-color;
|
||||
box-shadow: 0 5px 10px rgba(black, 0.8);
|
||||
will-change: transform;
|
||||
backface-visibility: hidden;
|
||||
transition: all 0.3s ease 0s;
|
||||
|
||||
&.small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
&.small {
|
||||
display: none;
|
||||
}
|
||||
&.large {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #212629;
|
||||
|
||||
.o_thumb {
|
||||
box-shadow: 0 5px 30px 1px rgba(black, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
&.o_mass_mailing_themes_upgrade .o_thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
border: 1px dashed white;
|
||||
opacity: 0.2;
|
||||
|
||||
> .fa {
|
||||
@include o-position-absolute(0, 0, 0, 0);
|
||||
text-align: center;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
|
||||
&::before {
|
||||
vertical-align: middle;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.note-editor {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.editor_enable.o_basic_theme.o_in_iframe {
|
||||
padding-right: 0px !important;
|
||||
|
||||
#web_editor-top-edit .note-popover .popover {
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
#oe_snippets {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.note-editable .o_layout {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
#web_editor-toolbars .popover .note-color {
|
||||
.note-back-color-preview .dropdown-toggle,
|
||||
.note-fore-color-preview .dropdown-toggle {
|
||||
height: $o-we-toolbar-height - 1;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_structure {
|
||||
width: 100%;
|
||||
@media (max-width: map-get($grid-breakpoints, 'lg')) {
|
||||
width: auto;
|
||||
margin-right: $o-we-sidebar-width;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
:root {
|
||||
font-size: 14px;
|
||||
}
|
||||
.oe_structure {
|
||||
width: 100%;
|
||||
@media (max-width: map-get($grid-breakpoints, 'lg')) {
|
||||
width: auto;
|
||||
margin-left: $o-we-sidebar-width;
|
||||
}
|
||||
}
|
||||
.note-editable {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<div t-name="mass_mailing.theme_selector" class="o_mail_theme_selector">
|
||||
<a role="button" href="#" class="btn btn-sm dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-paint-brush"/> Change Style
|
||||
</a>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<t t-foreach="themes" t-as="theme">
|
||||
<a t-att-id="theme.name" role="menuitem" href="#" class="dropdown-item">
|
||||
<div class="o_thumb small" t-attf-style="background-image: url(#{theme.img}_small.png)"/>
|
||||
<div class="o_thumb large" t-attf-style="background-image: url(#{theme.img}_large.png)"/>
|
||||
<div class="o_thumb logo" t-attf-style="background-image: url(#{theme.img}_logo.png)"/>
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="themes.length === 1">
|
||||
<a role="menuitem" href="#" class="dropdown-item o_mass_mailing_themes_upgrade">
|
||||
<div class="o_thumb"><i class="fa fa-plus" role="img" aria-label="Upgrade theme" title="Upgrade theme"/></div>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
<div t-name="mass_mailing.theme_selector" class="o_mail_theme_selector d-none">
|
||||
<t t-foreach="themes" t-as="theme" t-if="theme.data">
|
||||
<a t-att-data-id="theme.id" t-attf-class="o_{{theme.data.name}}_theme" role="menuitem" href="#">
|
||||
<div class="o_thumb small" t-attf-style="background-image: url(#{theme.data.img}_small.png)"/>
|
||||
<div class="o_thumb large" t-attf-style="background-image: url(#{theme.data.img}_large.png)"/>
|
||||
<div class="o_thumb logo" t-attf-style="background-image: url(#{theme.data.img}_logo.png)"/>
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="themes.length === 1">
|
||||
<a role="menuitem" href="#" class="dropdown-item o_mass_mailing_themes_upgrade">
|
||||
<div class="o_thumb"><i class="fa fa-plus" role="img" aria-label="Upgrade theme" title="Upgrade theme"/></div>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
</templates>
|
||||
|
||||
@@ -3,11 +3,9 @@ odoo.define('mass_mailing.field_html_tests', function (require) {
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var FormView = require('web.FormView');
|
||||
var FieldHtml = require('web_editor.field.html');
|
||||
var MassMailingFieldHtml = require('mass_mailing.FieldHtml');
|
||||
var testUtils = require('web.test_utils');
|
||||
var weTestUtils = require('web_editor.test_utils');
|
||||
var Wysiwyg = require('web_editor.wysiwyg');
|
||||
const loader = require('web_editor.loader');
|
||||
|
||||
|
||||
QUnit.module('mass_mailing', {}, function () {
|
||||
@@ -37,21 +35,19 @@ QUnit.module('field html', {
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
testUtils.mock.patch(ajax, {
|
||||
loadAsset: function (xmlId) {
|
||||
if (xmlId === 'web_editor.compiled_assets_wysiwyg') {
|
||||
return Promise.resolve({
|
||||
jsLibs: [],
|
||||
cssLibs: [],
|
||||
cssContents: []
|
||||
});
|
||||
}
|
||||
if (xmlId === 'template.assets') {
|
||||
return Promise.resolve({
|
||||
cssLibs: [],
|
||||
cssContents: ['.field_body {background-color: red;}']
|
||||
});
|
||||
}
|
||||
if (xmlId === 'template.assets_all_style') {
|
||||
return Promise.resolve({
|
||||
cssLibs: $('link[href]:not([type="image/x-icon"])').map(function () {
|
||||
return $(this).attr('href');
|
||||
}).get(),
|
||||
cssContents: ['.field_body {background-color: red;}']
|
||||
cssContents: ['body {background-color: red;}']
|
||||
});
|
||||
}
|
||||
throw 'Wrong template';
|
||||
@@ -89,7 +85,7 @@ QUnit.test('save arch and html', async function (assert) {
|
||||
var $fieldReadonly = form.$('.oe_form_field[name="body_html"]');
|
||||
var $fieldEdit = form.$('.oe_form_field[name="body_arch"]');
|
||||
|
||||
assert.strictEqual($fieldReadonly.css('display'), 'block', "should display the readonly mode");
|
||||
assert.strictEqual($fieldReadonly.css('display'), 'flex', "should display the readonly mode");
|
||||
assert.strictEqual($fieldEdit.css('display'), 'none', "should hide the edit mode");
|
||||
|
||||
await testUtils.form.clickEdit(form);
|
||||
@@ -98,7 +94,7 @@ QUnit.test('save arch and html', async function (assert) {
|
||||
$fieldEdit = form.$('.oe_form_field[name="body_arch"]');
|
||||
|
||||
assert.strictEqual($fieldReadonly.css('display'), 'none', "should hide the readonly mode");
|
||||
assert.strictEqual($fieldEdit.css('display'), 'block', "should display the edit mode");
|
||||
assert.strictEqual($fieldEdit.css('display'), 'flex', "should display the edit mode");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
<odoo>
|
||||
<!-- Snippets & Themes Menu -->
|
||||
<template id="email_designer_snippets" inherit_id="web_editor.snippets" primary="True" groups="base.group_user">
|
||||
<xpath expr="//div[@id='snippets_menu']" position="inside">
|
||||
<button type="button" disabled="disabled">Select a template</button>
|
||||
</xpath>
|
||||
<xpath expr="//t[@id='default_snippets']" position="replace">
|
||||
<t id="default_snippets">
|
||||
<t t-set="company_id" t-value="res_company"/>
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
odoo.define('web_editor.convertInline', function (require) {
|
||||
'use strict';
|
||||
|
||||
var FieldHtml = require('web_editor.field.html');
|
||||
|
||||
/**
|
||||
* Returns the css rules which applies on an element, tweaked so that they are
|
||||
* browser/mail client ok.
|
||||
*
|
||||
* @param {DOMElement} a
|
||||
* @returns {Object} css property name -> css property value
|
||||
*/
|
||||
function getMatchedCSSRules(a) {
|
||||
var i, r, k;
|
||||
var doc = a.ownerDocument;
|
||||
var rulesCache = a.ownerDocument._rulesCache || (a.ownerDocument._rulesCache = []);
|
||||
|
||||
if (!rulesCache.length) {
|
||||
var sheets = doc.styleSheets;
|
||||
for (i = sheets.length-1 ; i >= 0 ; i--) {
|
||||
var rules;
|
||||
// try...catch because browser may not able to enumerate rules for cross-domain sheets
|
||||
try {
|
||||
rules = sheets[i].rules || sheets[i].cssRules;
|
||||
} catch (e) {
|
||||
console.warn("Can't read the css rules of: " + sheets[i].href, e);
|
||||
continue;
|
||||
}
|
||||
if (rules) {
|
||||
for (r = rules.length-1; r >= 0; r--) {
|
||||
var selectorText = rules[r].selectorText;
|
||||
if (selectorText &&
|
||||
rules[r].cssText &&
|
||||
selectorText !== '*' &&
|
||||
selectorText.indexOf(':hover') === -1 &&
|
||||
selectorText.indexOf(':before') === -1 &&
|
||||
selectorText.indexOf(':after') === -1 &&
|
||||
selectorText.indexOf(':active') === -1 &&
|
||||
selectorText.indexOf(':link') === -1 &&
|
||||
selectorText.indexOf('::') === -1 &&
|
||||
selectorText.indexOf("'") === -1) {
|
||||
var st = selectorText.split(/\s*,\s*/);
|
||||
for (k = 0 ; k < st.length ; k++) {
|
||||
rulesCache.push({ 'selector': st[k], 'style': rules[r].style });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rulesCache.reverse();
|
||||
}
|
||||
|
||||
var css = [];
|
||||
var style;
|
||||
a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;
|
||||
for (r = 0; r < rulesCache.length; r++) {
|
||||
if (a.matches(rulesCache[r].selector)) {
|
||||
style = rulesCache[r].style;
|
||||
if (style.parentRule) {
|
||||
var style_obj = {};
|
||||
var len;
|
||||
for (k = 0, len = style.length ; k < len ; k++) {
|
||||
if (style[k].indexOf('animation') !== -1) {
|
||||
continue;
|
||||
}
|
||||
style_obj[style[k]] = style[style[k].replace(/-(.)/g, function (a, b) { return b.toUpperCase(); })];
|
||||
if (new RegExp(style[k] + '\s*:[^:;]+!important' ).test(style.cssText)) {
|
||||
style_obj[style[k]] += ' !important';
|
||||
}
|
||||
}
|
||||
rulesCache[r].style = style = style_obj;
|
||||
}
|
||||
css.push([rulesCache[r].selector, style]);
|
||||
}
|
||||
}
|
||||
|
||||
function specificity(selector) {
|
||||
// http://www.w3.org/TR/css3-selectors/#specificity
|
||||
var a = 0;
|
||||
selector = selector.replace(/#[a-z0-9_-]+/gi, function () { a++; return ''; });
|
||||
var b = 0;
|
||||
selector = selector.replace(/(\.[a-z0-9_-]+)|(\[.*?\])/gi, function () { b++; return ''; });
|
||||
var c = 0;
|
||||
selector = selector.replace(/(^|\s+|:+)[a-z0-9_-]+/gi, function (a) { if (a.indexOf(':not(')===-1) c++; return ''; });
|
||||
return a*100 + b*10 + c;
|
||||
}
|
||||
css.sort(function (a, b) { return specificity(a[0]) - specificity(b[0]); });
|
||||
|
||||
style = {};
|
||||
_.each(css, function (v,k) {
|
||||
_.each(v[1], function (v,k) {
|
||||
if (v && _.isString(v) && k.indexOf('-webkit') === -1 && (!style[k] || style[k].indexOf('important') === -1 || v.indexOf('important') !== -1)) {
|
||||
style[k] = v;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
_.each(style, function (v,k) {
|
||||
if (v.indexOf('important') !== -1) {
|
||||
style[k] = v.slice(0, v.length-11);
|
||||
}
|
||||
});
|
||||
|
||||
if (style.display === 'block') {
|
||||
delete style.display;
|
||||
}
|
||||
|
||||
// The css generates all the attributes separately and not in simplified form.
|
||||
// In order to have a better compatibility (outlook for example) we simplify the css tags.
|
||||
// e.g. border-left-style: none; border-bottom-s .... will be simplified in border-style = none
|
||||
_.each([
|
||||
{property: 'margin'},
|
||||
{property: 'padding'},
|
||||
{property: 'border', propertyEnd: '-style', defaultValue: 'none'},
|
||||
], function (propertyInfo) {
|
||||
var p = propertyInfo.property;
|
||||
var e = propertyInfo.propertyEnd || '';
|
||||
var defVal = propertyInfo.defaultValue || 0;
|
||||
|
||||
if (style[p+'-top'+e] || style[p+'-right'+e] || style[p+'-bottom'+e] || style[p+'-left'+e]) {
|
||||
if (style[p+'-top'+e] === style[p+'-right'+e] && style[p+'-top'+e] === style[p+'-bottom'+e] && style[p+'-top'+e] === style[p+'-left'+e]) {
|
||||
// keep => property: [top/right/bottom/left value];
|
||||
style[p+e] = style[p+'-top'+e];
|
||||
}
|
||||
else {
|
||||
// keep => property: [top value] [right value] [bottom value] [left value];
|
||||
style[p+e] = (style[p+'-top'+e] || defVal) + ' ' + (style[p+'-right'+e] || defVal) + ' ' + (style[p+'-bottom'+e] || defVal) + ' ' + (style[p+'-left'+e] || defVal);
|
||||
if (style[p+e].indexOf('inherit') !== -1 || style[p+e].indexOf('initial') !== -1) {
|
||||
// keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
|
||||
delete style[p+e];
|
||||
return;
|
||||
}
|
||||
}
|
||||
delete style[p+'-top'+e];
|
||||
delete style[p+'-right'+e];
|
||||
delete style[p+'-bottom'+e];
|
||||
delete style[p+'-left'+e];
|
||||
}
|
||||
});
|
||||
|
||||
if (style['border-bottom-left-radius']) {
|
||||
style['border-radius'] = style['border-bottom-left-radius'];
|
||||
delete style['border-bottom-left-radius'];
|
||||
delete style['border-bottom-right-radius'];
|
||||
delete style['border-top-left-radius'];
|
||||
delete style['border-top-right-radius'];
|
||||
}
|
||||
|
||||
// if the border styling is initial we remove it to simplify the css tags for compatibility.
|
||||
// Also, since we do not send a css style tag, the initial value of the border is useless.
|
||||
_.each(_.keys(style), function (k) {
|
||||
if (k.indexOf('border') !== -1 && style[k] === 'initial') {
|
||||
delete style[k];
|
||||
}
|
||||
});
|
||||
|
||||
// text-decoration rule is decomposed in -line, -color and -style. This is
|
||||
// however not supported by many browser/mail clients and the editor does
|
||||
// not allow to change -color and -style rule anyway
|
||||
if (style['text-decoration-line']) {
|
||||
style['text-decoration'] = style['text-decoration-line'];
|
||||
delete style['text-decoration-line'];
|
||||
delete style['text-decoration-color'];
|
||||
delete style['text-decoration-style'];
|
||||
}
|
||||
|
||||
// text-align inheritance does not seem to get past <td> elements on some
|
||||
// mail clients
|
||||
if (style['text-align'] === 'inherit') {
|
||||
var $el = $(a).parent();
|
||||
do {
|
||||
var align = $el.css('text-align');
|
||||
if (_.indexOf(['left', 'right', 'center', 'justify'], align) >= 0) {
|
||||
style['text-align'] = align;
|
||||
break;
|
||||
}
|
||||
$el = $el.parent();
|
||||
} while ($el.length && !$el.is('html'));
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts font icons to images.
|
||||
*
|
||||
* @param {jQuery} $editable - the element in which the font icons have to be
|
||||
* converted to images
|
||||
*/
|
||||
function fontToImg($editable) {
|
||||
var fonts = odoo.__DEBUG__.services["wysiwyg.fonts"];
|
||||
|
||||
$editable.find('.fa').each(function () {
|
||||
var $font = $(this);
|
||||
var icon, content;
|
||||
_.find(fonts.fontIcons, function (font) {
|
||||
return _.find(fonts.getCssSelectors(font.parser), function (data) {
|
||||
if ($font.is(data.selector.replace(/::?before/g, ''))) {
|
||||
icon = data.names[0].split('-').shift();
|
||||
content = data.css.match(/content:\s*['"]?(.)['"]?/)[1];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (content) {
|
||||
var color = $font.css('color').replace(/\s/g, '');
|
||||
$font.replaceWith($('<img/>', {
|
||||
src: _.str.sprintf('/web_editor/font_to_img/%s/%s/%s', content.charCodeAt(0), window.encodeURI(color), Math.max(1, Math.round($font.height()))),
|
||||
'data-class': $font.attr('class'),
|
||||
'data-style': $font.attr('style'),
|
||||
class: $font.attr('class').replace(new RegExp('(^|\\s+)' + icon + '(-[^\\s]+)?', 'gi'), ''), // remove inline font-awsome style
|
||||
style: $font.attr('style'),
|
||||
}).css({height: 'auto', width: 'auto'}));
|
||||
} else {
|
||||
$font.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts images which were the result of a font icon convertion to a font
|
||||
* icon again.
|
||||
*
|
||||
* @param {jQuery} $editable - the element in which the images will be converted
|
||||
* back to font icons
|
||||
*/
|
||||
function imgToFont($editable) {
|
||||
$editable.find('img[src*="/web_editor/font_to_img/"]').each(function () {
|
||||
var $img = $(this);
|
||||
$img.replaceWith($('<span/>', {
|
||||
class: $img.data('class'),
|
||||
style: $img.data('style')
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility function to apply function over descendants elements
|
||||
*
|
||||
* This is needed until the following issue of jQuery is solved:
|
||||
* https://github.com./jquery/sizzle/issues/403
|
||||
*
|
||||
* @param {Element} node The root Element node
|
||||
* @param {Function} func The function applied over descendants
|
||||
*/
|
||||
function applyOverDescendants(node, func) {
|
||||
node = node.firstChild;
|
||||
while (node) {
|
||||
if (node.nodeType === 1) {
|
||||
func(node);
|
||||
applyOverDescendants(node, func);
|
||||
}
|
||||
var $node = $(node);
|
||||
if (node.nodeName === 'A' && $node.hasClass('btn') && !$node.children().length && $(node).parents('.o_outlook_hack').length) {
|
||||
node = $(node).parents('.o_outlook_hack')[0];
|
||||
}
|
||||
else if (node.nodeName === 'IMG' && $node.parent('p').hasClass('o_outlook_hack')) {
|
||||
node = $node.parent()[0];
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts css style to inline style (leave the classes on elements but forces
|
||||
* the style they give as inline style).
|
||||
*
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
function classToStyle($editable) {
|
||||
applyOverDescendants($editable[0], function (node) {
|
||||
var $target = $(node);
|
||||
var css = getMatchedCSSRules(node);
|
||||
var style = $target.attr('style') || '';
|
||||
_.each(css, function (v,k) {
|
||||
if (!(new RegExp('(^|;)\s*' + k).test(style))) {
|
||||
style = k+':'+v+';'+style;
|
||||
}
|
||||
});
|
||||
if (_.isEmpty(style)) {
|
||||
$target.removeAttr('style');
|
||||
} else {
|
||||
$target.attr('style', style);
|
||||
}
|
||||
// Apple Mail
|
||||
if (node.nodeName === 'TD' && !node.childNodes.length) {
|
||||
node.innerHTML = ' ';
|
||||
}
|
||||
|
||||
// Outlook
|
||||
if (node.nodeName === 'A' && $target.hasClass('btn') && !$target.hasClass('btn-link') && !$target.children().length) {
|
||||
var $hack = $('<table class="o_outlook_hack" style="display: inline-table;vertical-align:middle"><tr><td></td></tr></table>');
|
||||
$hack.find('td')
|
||||
.attr('height', $target.outerHeight())
|
||||
.css({
|
||||
'text-align': $target.parent().css('text-align'),
|
||||
'margin': $target.css('padding'),
|
||||
'border-radius': $target.css('border-radius'),
|
||||
'background-color': $target.css('background-color'),
|
||||
});
|
||||
$target.after($hack);
|
||||
$target.appendTo($hack.find('td'));
|
||||
// the space add a line when it's a table but it's invisible when it's a link
|
||||
node = $hack[0].previousSibling;
|
||||
if (node && node.nodeType === Node.TEXT_NODE && !node.textContent.match(/\S/)) {
|
||||
$(node).remove();
|
||||
}
|
||||
node = $hack[0].nextSibling;
|
||||
if (node && node.nodeType === Node.TEXT_NODE && !node.textContent.match(/\S/)) {
|
||||
$(node).remove();
|
||||
}
|
||||
}
|
||||
else if (node.nodeName === 'IMG' && $target.is('.mx-auto.d-block')) {
|
||||
$target.wrap('<p class="o_outlook_hack" style="text-align:center;margin:0"/>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the inline style which is not necessary (because, for example, a
|
||||
* class on an element will induce the same style).
|
||||
*
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
function styleToClass($editable) {
|
||||
// Outlook revert
|
||||
$editable.find('.o_outlook_hack').each(function () {
|
||||
$(this).after($('a,img', this));
|
||||
}).remove();
|
||||
|
||||
var $c = $('<span/>').appendTo($editable[0].ownerDocument.body);
|
||||
|
||||
applyOverDescendants($editable[0], function (node) {
|
||||
var $target = $(node);
|
||||
var css = getMatchedCSSRules(node);
|
||||
var style = '';
|
||||
_.each(css, function (v,k) {
|
||||
if (!(new RegExp('(^|;)\s*' + k).test(style))) {
|
||||
style = k+':'+v+';'+style;
|
||||
}
|
||||
});
|
||||
css = ($c.attr('style', style).attr('style') || '').split(/\s*;\s*/);
|
||||
style = ($target.attr('style') || '').replace(/\s*:\s*/, ':').replace(/\s*;\s*/, ';');
|
||||
_.each(css, function (v) {
|
||||
style = style.replace(v, '');
|
||||
});
|
||||
style = style.replace(/;+(\s;)*/g, ';').replace(/^;/g, '');
|
||||
if (style !== '') {
|
||||
$target.attr('style', style);
|
||||
} else {
|
||||
$target.removeAttr('style');
|
||||
}
|
||||
});
|
||||
$c.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts css display for attachment link to real image.
|
||||
* Without this post process, the display depends on the css and the picture
|
||||
* does not appear when we use the html without css (to send by email for e.g.)
|
||||
*
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
function attachmentThumbnailToLinkImg($editable) {
|
||||
$editable.find('a[href*="/web/content/"][data-mimetype]').filter(':empty, :containsExact( )').each(function () {
|
||||
var $link = $(this);
|
||||
var $img = $('<img/>')
|
||||
.attr('src', $link.css('background-image').replace(/(^url\(['"])|(['"]\)$)/g, ''))
|
||||
.css('height', Math.max(1, $link.height()) + 'px')
|
||||
.css('width', Math.max(1, $link.width()) + 'px');
|
||||
$link.prepend($img);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert attachmentThumbnailToLinkImg changes
|
||||
*
|
||||
* @see attachmentThumbnailToLinkImg
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
function linkImgToAttachmentThumbnail($editable) {
|
||||
$editable.find('a[href*="/web/content/"][data-mimetype] > img').remove();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
|
||||
FieldHtml.include({
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
commitChanges: function () {
|
||||
if (this.nodeOptions['style-inline'] && this.mode === "edit") {
|
||||
this._toInline();
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts CSS dependencies to CSS-independent HTML.
|
||||
* - CSS display for attachment link -> real image
|
||||
* - Font icons -> images
|
||||
* - CSS styles -> inline styles
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toInline: function () {
|
||||
var $editable = this.wysiwyg.getEditable();
|
||||
var html = this.wysiwyg.getValue();
|
||||
$editable.html(html);
|
||||
|
||||
attachmentThumbnailToLinkImg($editable);
|
||||
fontToImg($editable);
|
||||
classToStyle($editable);
|
||||
this.wysiwyg.setValue($editable.html(), {
|
||||
notifyChange: false,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Revert _toInline changes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_fromInline: function () {
|
||||
var $editable = this.wysiwyg.getEditable();
|
||||
var html = this.wysiwyg.getValue();
|
||||
$editable.html(html);
|
||||
|
||||
styleToClass($editable);
|
||||
imgToFont($editable);
|
||||
linkImgToAttachmentThumbnail($editable);
|
||||
this.wysiwyg.setValue($editable.html(), {
|
||||
notifyChange: false,
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handler
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onLoadWysiwyg: function () {
|
||||
if (this.nodeOptions['style-inline'] && this.mode === "edit") {
|
||||
this._fromInline();
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
fontToImg: fontToImg,
|
||||
imgToFont: imgToFont,
|
||||
classToStyle: classToStyle,
|
||||
styleToClass: styleToClass,
|
||||
attachmentThumbnailToLinkImg: attachmentThumbnailToLinkImg,
|
||||
linkImgToAttachmentThumbnail: linkImgToAttachmentThumbnail,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user