diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 4370ffb0fd4..9b404e056ea 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -368,7 +368,7 @@ var Discuss = AbstractAction.extend({ */ destroy: function () { if (this.$buttons) { - this.$buttons.off().destroy(); + this.$buttons.off().remove(); } this._super.apply(this, arguments); }, diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py index b0c6daec3bd..70c83a5bb64 100644 --- a/addons/mass_mailing/__manifest__.py +++ b/addons/mass_mailing/__manifest__.py @@ -44,5 +44,8 @@ 'demo': [ 'data/mass_mailing_demo.xml', ], + 'qweb': [ + 'static/src/xml/*.xml', + ], 'application': True, } diff --git a/addons/mass_mailing/controllers/__init__.py b/addons/mass_mailing/controllers/__init__.py index 83d13fa601d..5d4b25db9c0 100644 --- a/addons/mass_mailing/controllers/__init__.py +++ b/addons/mass_mailing/controllers/__init__.py @@ -2,4 +2,3 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import main -from . import web_editor diff --git a/addons/mass_mailing/controllers/web_editor.py b/addons/mass_mailing/controllers/web_editor.py deleted file mode 100644 index 88e9f768690..00000000000 --- a/addons/mass_mailing/controllers/web_editor.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import http -from odoo.http import request -from odoo.addons.web_editor.controllers.main import Web_Editor - - -class Web_Editor(Web_Editor): - - @http.route(["/website_mass_mailing/field/popup_content"], type='http', auth="user") - def mass_mailing_FieldTextHtmlPopupTemplate(self, model=None, res_id=None, field=None, callback=None, **kwargs): - kwargs['snippets'] = '/website/snippets' - kwargs['template'] = 'mass_mailing.FieldTextHtmlPopupContent' - return self.FieldTextHtml(model, res_id, field, callback, **kwargs) - - @http.route('/mass_mailing/field/email_template', type='http', auth="user") - def mass_mailing_FieldTextHtmlEmailTemplate(self, model=None, res_id=None, field=None, callback=None, **kwargs): - kwargs['snippets'] = '/mass_mailing/snippets' - kwargs['template'] = 'mass_mailing.FieldTextHtmlInline' - return self.FieldTextHtmlInline(model, res_id, field, callback, **kwargs) - - @http.route(['/mass_mailing/snippets'], type='json', auth="user", website=True) - def mass_mailing_snippets(self): - values = {'company_id': request.env['res.users'].browse(request.uid).company_id} - return request.env.ref('mass_mailing.email_designer_snippets').render(values) diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml index b8e403a1a6c..8819dd20202 100644 --- a/addons/mass_mailing/data/mass_mailing_demo.xml +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -85,65 +85,115 @@ email Info <info@yourcompany.example.com> - - - -
- - - - - - - - - - - - - - - -
- - - -
- Your Company - - YourCompanyny -
-
-
-
-
-

- Great stories have personality. Consider telling a great story that provides personality. - Writing a story with personality for potential clients will assist with making a relationship connection. - This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.
- Great stories are for everyone even when only written for just one person. - If you try to write with a wide general audience in mind, your story will ring false and be bland. - No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest. -

-

- - Click here for your special promotion ! - -

-
-
-
- YourCompany
-
- Unsubscribe - | - Contact -
-
-
- Powered by Odoo -
+
+ + + + + + + +
+ + +
+
+ + + + + + +
+

+ Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience. +
Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest. +

+
+
+
+ +
+ + + + + + +
+ Powered by + + Odoo +
+
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index f9d95c893d9..a5121e3d773 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -456,7 +456,9 @@ class MassMailing(models.Model): default=lambda self: self.env['mail.message']._get_default_from()) sent_date = fields.Datetime(string='Sent Date', oldname='date', copy=False) schedule_date = fields.Datetime(string='Schedule in the Future') - body_html = fields.Html(string='Body', sanitize_attributes=False) + # don't translate 'body_arch', the translations are only on 'body_html' + body_arch = fields.Html(string='Body', translate=False) + body_html = fields.Html(string='Body converted to be send by mail', sanitize_attributes=False) attachment_ids = fields.Many2many('ir.attachment', 'mass_mailing_ir_attachments_rel', 'mass_mailing_id', 'attachment_id', string='Attachments') keep_archives = fields.Boolean(string='Keep Archives') @@ -621,7 +623,6 @@ class MassMailing(models.Model): else: mailing_domain.append((0, '=', 1)) self.mailing_domain = repr(mailing_domain) - self.body_html = "on_change_model_and_list" @api.onchange('subject') def _onchange_subject(self): diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js new file mode 100644 index 00000000000..404ed6e554b --- /dev/null +++ b/addons/mass_mailing/static/src/js/mass_mailing.js @@ -0,0 +1,37 @@ +odoo.define('mass_mailing.mass_mailing', function (require) { +"use strict"; + +var KanbanRecord = require('web.KanbanRecord'); +var KanbanColumn = require('web.KanbanColumn'); + +KanbanRecord.include({ + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _openRecord: function () { + if (this.modelName === 'mail.mass_mailing.campaign') { + this.$('.oe_mailings').click(); + } else if (this.modelName === 'mail.mass_mailing.list' && + this.$('.o_mailing_list_kanban_boxes a')) { + this.$('.o_mailing_list_kanban_boxes a').first().click(); + } else { + this._super.apply(this, arguments); + } + }, +}); + +KanbanColumn.include({ + init: function () { + this._super.apply(this, arguments); + if (this.modelName === 'mail.mass_mailing') { + this.draggable = false; + } + }, +}); + +}); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_editor.js b/addons/mass_mailing/static/src/js/mass_mailing_editor.js deleted file mode 100644 index c7e95198005..00000000000 --- a/addons/mass_mailing/static/src/js/mass_mailing_editor.js +++ /dev/null @@ -1,434 +0,0 @@ -odoo.define('mass_mailing.editor', function (require) { -"use strict"; - -require('web.dom_ready'); -var ajax = require('web.ajax'); -var core = require('web.core'); -var rte = require('web_editor.rte'); -var options = require('web_editor.snippets.options'); -var snippets_editor = require('web_editor.snippet.editor'); - -var $editable_area = $('#editable_area'); -var odoo_top = window.top.odoo; - -// Snippet option for resizing image and column width inline like excel -options.registry.sizing_x = options.Class.extend({ - /** - * @override - */ - start: function () { - var def = this._super.apply(this, arguments); - - this.containerWidth = this.$target.parent().closest("td, table, div").width(); - - var self = this; - var offset, sib_offset, target_width, sib_width; - - this.$overlay.find(".o_handle.e, .o_handle.w").removeClass("readonly"); - this.isIMG = this.$target.is("img"); - if (this.isIMG) { - this.$overlay.find(".o_handle.w").addClass("readonly"); - this.$overlay.find(".oe_snippet_move, .oe_snippet_clone").addClass('d-none'); - } - - var $body = $(document.body); - this.$overlay.find(".o_handle").on('mousedown', function (event) { - event.preventDefault(); - var $handle = $(this); - var compass = false; - - _.each(['n', 's', 'e', 'w'], function (handler) { - if ($handle.hasClass(handler)) { compass = handler; } - }); - if (self.isIMG) { compass = "image"; } - - $body.on("mousemove.mass_mailing_width_x", function (event) { - event.preventDefault(); - offset = self.$target.offset().left; - target_width = self.get_max_width(self.$target); - if (compass === 'e' && self.$target.next().offset()) { - sib_width = self.get_max_width(self.$target.next()); - sib_offset = self.$target.next().offset().left; - self.change_width(event, self.$target, target_width, offset, true); - self.change_width(event, self.$target.next(), sib_width, sib_offset, false); - } - if (compass === 'w' && self.$target.prev().offset()) { - sib_width = self.get_max_width(self.$target.prev()); - sib_offset = self.$target.prev().offset().left; - self.change_width(event, self.$target, target_width, offset, false); - self.change_width(event, self.$target.prev(), sib_width, sib_offset, true); - } - if (compass === 'image') { - self.change_width(event, self.$target, target_width, offset, true); - } - }); - $body.one("mouseup", function () { - $body.off('.mass_mailing_width_x'); - }); - }); - - return def; - }, - change_width: function (event, target, target_width, offset, grow) { - target.css("width", grow ? (event.pageX - offset) : (offset + target_width - event.pageX)); - this.trigger_up('cover_update'); - }, - get_int_width: function (el) { - return parseInt($(el).css("width"), 10); - }, - get_max_width: function ($el) { - return this.containerWidth - _.reduce(_.map($el.siblings(), this.get_int_width), function (memo, w) { return memo + w; }); - }, - onFocus: function () { - this._super.apply(this, arguments); - - if (this.$target.is("td, th")) { - this.$overlay.find(".o_handle.e, .o_handle.w").toggleClass("readonly", this.$target.siblings().length === 0); - } - }, -}); - -options.registry.table_item = options.Class.extend({ - onClone: function (options) { - this._super.apply(this, arguments); - - // If we cloned a td or th element... - if (options.isCurrent && this.$target.is("td, th")) { - // ... and that the td or th element was alone on its row ... - if (this.$target.siblings().length === 1) { - var $tr = this.$target.parent(); - $tr.clone().empty().insertAfter($tr).append(this.$target); // ... move the clone in a new row instead - return; - } - - // ... if not, if the clone neighbor is an empty cell, remove this empty cell (like if the clone content had been put in that cell) - var $next = this.$target.next(); - if ($next.length && $next.text().trim() === "") { - $next.remove(); - return; - } - - // ... if not, insert an empty col in each other row, at the index of the clone - var width = this.$target.width(); - var $trs = this.$target.closest("table").children("thead, tbody, tfoot").addBack().children("tr").not(this.$target.parent()); - _.each($trs.children(":nth-child(" + this.$target.index() + ")"), function (col) { - $(col).after($("", {style: "width: " + width + "px;"})); - }); - } - }, - onRemove: function () { - this._super.apply(this, arguments); - - // If we are removing a td or th element which was not alone on its row ... - if (this.$target.is("td, th") && this.$target.siblings().length > 0) { - var $trs = this.$target.closest("table").children("thead, tbody, tfoot").addBack().children("tr").not(this.$target.parent()); - if ($trs.length) { // ... if there are other rows in the table ... - var $last_tds = $trs.children(":last-child"); - if (_.reduce($last_tds, function (memo, td) { return memo + (td.innerHTML || ""); }, "").trim() === "") { - $last_tds.remove(); // ... remove the potential full empty column in the table - } else { - this.$target.parent().append(""); // ... else, if there is no full empty column, append an empty col in the current row - } - } - } - }, -}); - -var fn_popover_update = $.summernote.eventHandler.modules.popover.update; -$.summernote.eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) { - fn_popover_update.call(this, $popover, oStyle, isAirMode); - $("span.o_table_handler, div.note-table").remove(); -}; - -ajax.loadXML("/mass_mailing/static/src/xml/mass_mailing.xml", core.qweb); - -snippets_editor.Class.include({ - _computeSnippetTemplates: function (html) { - var self = this; - var ret = this._super.apply(this, arguments); - - var $themes = this.$("#email_designer_themes").children(); - if ($themes.length === 0) return ret; - - /** - * Initialize theme parameters. - */ - var all_classes = ""; - var themes_params = _.map($themes, function (theme) { - var $theme = $(theme); - var name = $theme.data("name"); - var classname = "o_" + name + "_theme"; - all_classes += " " + classname; - var images_info = _.defaults($theme.data("imagesInfo") || {}, {all: {}}); - _.each(images_info, function (info) { - info = _.defaults(info, images_info.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 (images_info[filename]) { - return images_info[filename]; - } - return images_info.all; - } - }; - }); - $themes.parent().remove(); - - var $body = $(document.body); - var $snippets = this.$(".oe_snippet"); - var $snippets_menu = this.$el.find("#snippets_menu"); - - /** - * 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: themes_params - })); - var first_choice; - check_if_must_force_theme_choice(); - - /** - * 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(); - odoo_top[window.callback+"_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 selected_theme = false; - $dropdown.on("mouseenter", ".dropdown-item", function (e) { - if (first_choice) return; - e.preventDefault(); - var theme_params = themes_params[$(e.currentTarget).index()]; - switch_theme(theme_params); - }); - $dropdown.on("click", ".dropdown-item", function (e) { - e.preventDefault(); - var theme_params = themes_params[$(e.currentTarget).index()]; - if (first_choice) { - switch_theme(theme_params); - $body.removeClass("o_force_mail_theme_choice"); - first_choice = false; - - if ($mail_themes_upgrade.length) { - $dropdown.remove(); - $snippets_menu.empty(); - } - } - - switch_images(theme_params, $snippets); - - selected_theme = theme_params; - - // Notify form view - odoo_top[window.callback+"_downup"]($editable_area.addClass("o_dirty").html()); - }); - - /** - * 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 () { - check_selected_theme(); - $dropdown.find(".dropdown-item").removeClass("selected").filter(function () { - return ($(this).has(".o_thumb[style=\""+ "background-image: url(" + (selected_theme && selected_theme.img) + "_small.png)"+ "\"]").length > 0); - }).addClass("selected"); - }); - $dropdown.on("hidden.bs.dropdown", function () { - switch_theme(selected_theme); - }); - - /** - * On page load, check the selected theme and force switching to it (body needs the - * theme style for its edition toolbar). - */ - check_selected_theme(); - $body.addClass(selected_theme.className); - switch_images(selected_theme, $snippets); - - $dropdown.insertAfter($snippets_menu); - - return ret; - - function check_if_must_force_theme_choice() { - first_choice = editable_area_is_empty(); - $body.toggleClass("o_force_mail_theme_choice", first_choice); - } - - function editable_area_is_empty($layout) { - $layout = $layout || $editable_area.find(".o_layout"); - var $mail_wrapper = $layout.children(".o_mail_wrapper"); - var $mail_wrapper_content = $mail_wrapper.find('.o_mail_wrapper_td'); - if (!$mail_wrapper_content.length) { // compatibility - $mail_wrapper_content = $mail_wrapper; - } - return ( - $editable_area.html().trim() === "" - || ($layout.length > 0 && ($layout.html().trim() === "" || $mail_wrapper_content.length > 0 && $mail_wrapper_content.html().trim() === "")) - ); - } - - function check_selected_theme() { - var $layout = $editable_area.find(".o_layout"); - if ($layout.length === 0) { - selected_theme = false; - } else { - _.each(themes_params, function (theme_params) { - if ($layout.hasClass(theme_params.className)) { - selected_theme = theme_params; - } - }); - } - } - - function switch_images(theme_params, $container) { - if (!theme_params) return; - $container.find("img").each(function () { - var $img = $(this); - var src = $img.attr("src"); - - var m = src.match(/^\/web\/image\/\w+\.s_default_image_(?:theme_[a-z]+_)?(.+)$/); - if (!m) { - m = src.match(/^\/\w+\/static\/src\/img\/(?:theme_[a-z]+\/)?s_default_image_(.+)\.[a-z]+$/); - } - if (!m) return; - - var file = m[1]; - var img_info = theme_params.get_image_info(file); - - if (img_info.format) { - src = "/" + img_info.module + "/static/src/img/theme_" + theme_params.name + "/s_default_image_" + file + "." + img_info.format; - } else { - src = "/web/image/" + img_info.module + ".s_default_image_theme_" + theme_params.name + "_" + file; - } - - $img.attr("src", src); - }); - } - - function switch_theme(theme_params) { - if (!theme_params || switch_theme.last === theme_params) return; - switch_theme.last = theme_params; - - $body.removeClass(all_classes).addClass(theme_params.className); - - var $old_layout = $editable_area.find('.o_layout'); - - var $new_wrapper; - var $new_wrapper_content; - if (theme_params.nowrap) { - $new_wrapper = $('
', {class: 'oe_structure'}); - $new_wrapper_content = $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 = $('', {class: 'o_mail_wrapper'}); - $new_wrapper_content = $('').append( - $('
', {class: 'o_mail_no_options o_mail_wrapper_td oe_structure'}); - $new_wrapper.append($('
', {class: 'o_mail_no_resize o_not_editable', contenteditable: 'false'}), - $new_wrapper_content, - $('', {class: 'o_mail_no_resize o_not_editable', contenteditable: 'false'}) - )); - } - var $new_layout = $('
', {class: 'o_layout ' + theme_params.className}).append($new_wrapper); - - var $contents; - if (first_choice) { - $contents = theme_params.template; - } else if ($old_layout.length) { - $contents = ($old_layout.hasClass('oe_structure') ? $old_layout : $old_layout.find('.oe_structure').first()).contents(); - } else { - $contents = $editable_area.contents(); - } - - $new_wrapper_content.append($contents); - switch_images(theme_params, $new_wrapper_content); - $editable_area.empty().append($new_layout); - $old_layout.remove(); - - if (first_choice) { - self._registerDefaultTexts($new_wrapper_content); - if(theme_params.name == 'basic') { - $editable_area.focusIn(); - } - } - self._disableUndroppableSnippets(); - } - }, - cleanForSave: function () { - this._super.apply(this, arguments); - // remove font-family from all elements for plain text theme (just like gmail) - var $basicTheme = this.$editable.find('.o_basic_theme'); - if($basicTheme.length && this.$editable.data('oe-model') === 'mail.mass_mailing') { - this.$editable.find('*').css('font-family', ''); - } - } -}); - -var callback = window ? window["callback"] : undefined; -odoo_top[callback+"_updown"] = function (value, fields_values, field_name) { - if (!window || window.closed) { - delete odoo_top[callback+"_updown"]; - return; - } - - var $editable = $("#editable_area"); - var _val = $editable.prop("innerHTML"); - var editor_enable = $('body').hasClass('editor_enable'); - value = value || ""; - - if (value !==_val) { - if (editor_enable) { - if (value !== fields_values[field_name]) { - rte.history.recordUndo($editable); - } - core.bus.trigger('deactivate_snippet'); - } - - if (value.indexOf('on_change_model_and_list') === -1) { - $editable.html(value); - - if (editor_enable) { - if (value !== fields_values[field_name]) { - $editable.trigger("content_changed"); - } - } - } - } - - if (fields_values.mailing_model && editor_enable) { - if (value.indexOf('on_change_model_and_list') !== -1) { - odoo_top[callback+"_downup"](_val); - } - } -}; - -if ($editable_area.html().indexOf('on_change_model_and_list') !== -1) { - $editable_area.empty(); -} -// Adding compatibility for the outlook compliance of mailings. -// Commit of such compatibility : a14f89c8663c9cafecb1cc26918055e023ecbe42 -options.registry.background.include({ - start: function () { - this._super(); - var $table_target = this.$target.find('table:first'); - if ($table_target) { - this.$target = $table_target; - } - } -}); -}); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_field_text_html.js b/addons/mass_mailing/static/src/js/mass_mailing_field_text_html.js deleted file mode 100644 index a14b9588613..00000000000 --- a/addons/mass_mailing/static/src/js/mass_mailing_field_text_html.js +++ /dev/null @@ -1,29 +0,0 @@ -odoo.define('mass_mailing.field_text_html', function (require) { - -var FieldTextHtml = require('web_editor.backend').FieldTextHtml; -var fieldRegistry = require('web.field_registry'); - -var MassMailingFieldTextHtml = FieldTextHtml.extend({ - /** - * 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' - ]); - } -}); - -fieldRegistry.add('mass_mailing_html_frame', MassMailingFieldTextHtml); - -return MassMailingFieldTextHtml; - -}); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_snippets.js b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js new file mode 100644 index 00000000000..fe8093b17d9 --- /dev/null +++ b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js @@ -0,0 +1,142 @@ +odoo.define('mass_mailing.snippets.options', function (require) { +"use strict"; + +var options = 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({ + /** + * @override + */ + start: function () { + var def = this._super.apply(this, arguments); + + this.containerWidth = this.$target.parent().closest("td, table, div").width(); + + var self = this; + var offset, sib_offset, target_width, sib_width; + + this.$overlay.find(".o_handle.e, .o_handle.w").removeClass("readonly"); + this.isIMG = this.$target.is("img"); + if (this.isIMG) { + this.$overlay.find(".o_handle.w").addClass("readonly"); + this.$overlay.find(".oe_snippet_move, .oe_snippet_clone").addClass('d-none'); + } + + var $body = $(this.ownerDocument.body); + this.$overlay.find(".o_handle").on('mousedown', function (event) { + event.preventDefault(); + var $handle = $(this); + var compass = false; + + _.each(['n', 's', 'e', 'w'], function (handler) { + if ($handle.hasClass(handler)) { compass = handler; } + }); + if (self.isIMG) { compass = "image"; } + + $body.on("mousemove.mass_mailing_width_x", function (event) { + event.preventDefault(); + offset = self.$target.offset().left; + target_width = self.get_max_width(self.$target); + if (compass === 'e' && self.$target.next().offset()) { + sib_width = self.get_max_width(self.$target.next()); + sib_offset = self.$target.next().offset().left; + self.change_width(event, self.$target, target_width, offset, true); + self.change_width(event, self.$target.next(), sib_width, sib_offset, false); + } + if (compass === 'w' && self.$target.prev().offset()) { + sib_width = self.get_max_width(self.$target.prev()); + sib_offset = self.$target.prev().offset().left; + self.change_width(event, self.$target, target_width, offset, false); + self.change_width(event, self.$target.prev(), sib_width, sib_offset, true); + } + if (compass === 'image') { + self.change_width(event, self.$target, target_width, offset, true); + } + }); + $body.one("mouseup", function () { + $body.off('.mass_mailing_width_x'); + }); + }); + + return def; + }, + change_width: function (event, target, target_width, offset, grow) { + target.css("width", grow ? (event.pageX - offset) : (offset + target_width - event.pageX)); + this.trigger_up('cover_update'); + }, + get_int_width: function (el) { + return parseInt($(el).css("width"), 10); + }, + get_max_width: function ($el) { + return this.containerWidth - _.reduce(_.map($el.siblings(), this.get_int_width), function (memo, w) { return memo + w; }); + }, + onFocus: function () { + this._super.apply(this, arguments); + + if (this.$target.is("td, th")) { + this.$overlay.find(".o_handle.e, .o_handle.w").toggleClass("readonly", this.$target.siblings().length === 0); + } + }, +}); + +options.registry.mass_mailing_table_item = options.Class.extend({ + onClone: function (options) { + this._super.apply(this, arguments); + + // If we cloned a td or th element... + if (options.isCurrent && this.$target.is("td, th")) { + // ... and that the td or th element was alone on its row ... + if (this.$target.siblings().length === 1) { + var $tr = this.$target.parent(); + $tr.clone().empty().insertAfter($tr).append(this.$target); // ... move the clone in a new row instead + return; + } + + // ... if not, if the clone neighbor is an empty cell, remove this empty cell (like if the clone content had been put in that cell) + var $next = this.$target.next(); + if ($next.length && $next.text().trim() === "") { + $next.remove(); + return; + } + + // ... if not, insert an empty col in each other row, at the index of the clone + var width = this.$target.width(); + var $trs = this.$target.closest("table").children("thead, tbody, tfoot").addBack().children("tr").not(this.$target.parent()); + _.each($trs.children(":nth-child(" + this.$target.index() + ")"), function (col) { + $(col).after($("
", {style: "width: " + width + "px;"})); + }); + } + }, + onRemove: function () { + this._super.apply(this, arguments); + + // If we are removing a td or th element which was not alone on its row ... + if (this.$target.is("td, th") && this.$target.siblings().length > 0) { + var $trs = this.$target.closest("table").children("thead, tbody, tfoot").addBack().children("tr").not(this.$target.parent()); + if ($trs.length) { // ... if there are other rows in the table ... + var $last_tds = $trs.children(":last-child"); + if (_.reduce($last_tds, function (memo, td) { return memo + (td.innerHTML || ""); }, "").trim() === "") { + $last_tds.remove(); // ... remove the potential full empty column in the table + } else { + this.$target.parent().append(""); // ... else, if there is no full empty column, append an empty col in the current row + } + } + } + }, +}); + +// Adding compatibility for the outlook compliance of mailings. +// Commit of such compatibility : a14f89c8663c9cafecb1cc26918055e023ecbe42 +options.registry.background = options.registry.background.extend({ + start: function () { + this._super(); + if (this.snippets && this.snippets.split('.')[0] === "mass_mailing") { + var $table_target = this.$target.find('table:first'); + if ($table_target.length) { + this.$target = $table_target; + } + } + } +}); +}); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_widget.js b/addons/mass_mailing/static/src/js/mass_mailing_widget.js new file mode 100644 index 00000000000..e633ad2b4a1 --- /dev/null +++ b/addons/mass_mailing/static/src/js/mass_mailing_widget.js @@ -0,0 +1,505 @@ +odoo.define('mass_mailing.FieldHtml', function (require) { +'use strict'; + +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 _t = core._t; + + +var MassMailingFieldHtml = FieldHtml.extend({ + xmlDependencies: (FieldHtml.prototype.xmlDependencies || []).concat(["/mass_mailing/static/src/xml/mass_mailing.xml"]), + + custom_events: _.extend({}, FieldHtml.prototype.custom_events, { + snippets_loaded: '_onSnippetsLoaded', + }), + + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + if (!this.nodeOptions.snippets) { + this.nodeOptions.snippets = 'mass_mailing.email_designer_snippets'; + } + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Commit the change in 'style-inline' on an other field + * nodeOptions: + * - inline-field: fieldName to save the html value converted into inline code + * + * @override + */ + commitChanges: function () { + var self = this; + var fieldName = this.nodeOptions['inline-field']; + + if (this.$content.find('.o_basic_theme').length) { + this.$content.find('*').css('font-family', ''); + } + + var $editable = this.wysiwyg.getEditable(); + + return this.wysiwyg.save().then(function (isDirty) { + self._isDirty = 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(); + } + }); + }, + /** + * 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' + ]); + }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Add generateOptions options to change wysiwyg configuration: + * - Remove table menu in toolbar + * - Remove table popover + * - Add rules for isEditableNode, the TD must content a not TABLE node as + * children to be editable. + * + * @override + */ + _getWysiwygOptions: function () { + var options = this._super(); + options.generateOptions = function (options) { + options.toolbar = _.filter(options.toolbar, function (item) { + return item[0] !== 'table'; + }); + delete options.popover.table; + + var isEditableNode = options.isEditableNode; + options.isEditableNode = function (node) { + if (node.tagName === "TD" && !$(node).children('*:not(table):first').length) { + return false; + } + return isEditableNode.call(this, node); + }; + + return options; + }; + return options; + }, + /** + * 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_mailWrapper"); + var $mailWrapperContent = $mailWrapper.find('.o_mailWrapper_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 = "


"; + return value === "" || value === blankEditable; + }, + /** + * @override + */ + _renderEdit: function () { + this._isFromInline = !this.value; + if (!this.value) { + this.value = this.recordData[this.nodeOptions['inline-field']]; + } + return this._super.apply(this, arguments); + }, + + /** + * @override + * @returns {JQuery} + */ + _renderTranslateButton: function () { + var fieldName = this.nodeOptions['inline-field']; + if (_t.database.multi_lang && this.record.fields[fieldName].translate && this.res_id) { + return $('