From f296992317e96562c66bd7ad59a5080d6c551ed5 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 10 Jan 2019 11:07:17 +0000 Subject: [PATCH] [IMP] web_editor,*: Refactoring the wysiwyg editor and 'html' field * Creating a new structure by transforming all the plugins in the library using the odoo inheritance system. Plugins are easier to implement with the AbstractPlugin to add Odoo behaviors. * From now on, the methods of the library (in this case Summernote) can no longer be called by other modules or files. Only the wysiwyg widgets can access it, to simplify the updating process. The wysiwyg object serves as an interface. * Depending on the options the snippets will be loaded or not, the editor will be in an iframe or not... all of this is transparent from the outside. * Regarding iframes, all controllers related to editing have been removed: the new API no longer needs them. This speeds up loading, eases testing and removes complexity for the same features. PUBLIC FEATURES There are several public methods on the Wysiwyg class: * Wysiwyg.prepare (WidgetParent): returns a deferred resolved when the library (xml, lazy, assets...) is loaded. * Wysiwyg.getRange (DOM): returns the range (selection in the dom) * Wysiwyg.setRange (startNode, startOffset, endNode, endOffset): creates a range (selection in the dom) * Wysiwyg.setRangeFromNode (DOM, options) that creates a range from an element (option available to select all, start or end) A jQuery selector was added: :o_editable, which indicates whether the current element is editable. That is, if it is contained in a tag with the attribute 'contentEditable = "true"' or in a tag with the class o_editable. Several methods are also present: * focusIn: makes a focus and places the cursor at the beginning of the element * focusInEnd: makes a focus and places the cursor at the end of the element * selectContent: makes a focus and selects the content HTML FIELD The HTML field can receive different options: * style-inline: {boolean} transforms a class into an inline style when saving and vice versa when reading. * no-attachment: {boolean} prevents the use of attachments (in media dialog) * cssEdit: {xml_id} to use a template containing the css to loaded in an iframe when editing * cssReadonly: {xml_id} to use a template containing the css to load into an iframe when viewing in readonly * snippets: {xml_id} snippets template (can be used with or without cssEdit) * wrapper: {template} qweb static template (containing a tag: id = "wrapper") that will include the content during editing (removed on save) MASS MAILING A widget was created for mass mailing. There are now two fields: body_html and body_arch. body_arch contains the code with the class without conversion into inline style, useful when editing and one with the inline style that is visible in readonly mode and sent by email. Advantage: no spreading errors, able to update css/theme, able to do more changes when converting to inline style so that a maximum of mail clients have an impeccable rendering. Co-authored-by: Antoine Guenet --- addons/mail/static/src/js/discuss.js | 2 +- addons/mass_mailing/__manifest__.py | 3 + addons/mass_mailing/controllers/__init__.py | 1 - addons/mass_mailing/controllers/web_editor.py | 26 - .../mass_mailing/data/mass_mailing_demo.xml | 168 +- addons/mass_mailing/models/mass_mailing.py | 5 +- .../static/src/js/mass_mailing.js | 37 + .../src/js/mass_mailing_field_text_html.js | 29 - .../static/src/js/mass_mailing_snippets.js | 312 +-- .../static/src/js/mass_mailing_widget.js | 505 ++++ .../static/src/scss/mass_mailing.ui.scss | 67 +- .../static/src/scss/themes/theme_default.scss | 10 +- .../mass_mailing/views/editor_field_html.xml | 51 +- .../views/mass_mailing_template.xml | 11 +- .../mass_mailing/views/mass_mailing_views.xml | 9 +- addons/mass_mailing/views/snippets_themes.xml | 1 + .../views/snippets_themes_options.xml | 10 +- .../views/unsubscribe_templates.xml | 1 + addons/note/data/note_demo.xml | 72 +- addons/point_of_sale/controllers/__init__.py | 1 - .../point_of_sale/controllers/web_editor.py | 18 - .../src/css/customer_facing_display.css | 3 - .../static/src/js/field_text_html.js | 20 - .../src/scss/customer_facing_display.scss | 7 - addons/point_of_sale/views/point_of_sale.xml | 21 +- .../point_of_sale/views/pos_config_view.xml | 5 +- addons/point_of_sale/views/pos_templates.xml | 10 +- addons/portal/static/src/scss/portal.scss | 24 +- .../src/js/product_configurator_controller.js | 3 +- .../src/js/product_configurator_modal.js | 4 +- .../static/src/js/timesheet_plan.js | 2 +- addons/survey/static/src/js/survey.js | 3 +- addons/web/static/src/js/core/ajax.js | 58 + addons/web/static/src/js/core/domain.js | 5 + .../web/static/src/js/report/client_action.js | 63 - .../web/static/src/js/report/report.editor.js | 81 - addons/web/static/src/js/report/report.js | 44 +- addons/web/static/src/scss/fields.scss | 2 +- addons/web/static/src/scss/report.editor.scss | 26 - .../web/static/src/scss/report_backend.scss | 2 +- addons/web/static/src/xml/report.xml | 11 +- addons/web/views/report_templates.xml | 34 +- addons/web/views/webclient_templates.xml | 1 - addons/web_editor/__manifest__.py | 1 - addons/web_editor/controllers/main.py | 171 +- .../static/src/js/backend/convert_inline.js | 109 +- .../static/src/js/backend/field_html.js | 712 +++-- addons/web_editor/static/src/js/common/ace.js | 3 +- addons/web_editor/static/src/js/editor/rte.js | 749 ----- .../static/src/js/editor/rte.summernote.js | 1274 --------- .../static/src/js/editor/summernote.js | 2436 ----------------- addons/web_editor/static/src/js/iframe.js | 153 -- addons/web_editor/static/src/js/inline.js | 91 - addons/web_editor/static/src/js/tours/rte.js | 325 --- .../web_editor/static/src/js/wysiwyg/fonts.js | 60 +- .../static/src/js/wysiwyg/options.js | 6 + .../static/src/js/wysiwyg/plugin/abstract.js | 222 ++ .../static/src/js/wysiwyg/plugin/bullet.js | 565 ++++ .../static/src/js/wysiwyg/plugin/buttons.js | 88 + .../static/src/js/wysiwyg/plugin/codeview.js | 60 + .../static/src/js/wysiwyg/plugin/dropzone.js | 210 ++ .../static/src/js/wysiwyg/plugin/editor.js | 189 ++ .../static/src/js/wysiwyg/plugin/font.js | 559 ++++ .../src/js/wysiwyg/plugin/font_buttons.js | 31 + .../src/js/wysiwyg/plugin/help_dialog.js | 27 + .../static/src/js/wysiwyg/plugin/helper.js | 1843 +++++++++++++ .../static/src/js/wysiwyg/plugin/hint.js | 139 + .../static/src/js/wysiwyg/plugin/history.js | 81 + .../static/src/js/wysiwyg/plugin/keyboard.js | 1182 ++++++++ .../static/src/js/wysiwyg/plugin/link.js | 479 ++++ .../static/src/js/wysiwyg/plugin/media.js | 1232 +++++++++ .../static/src/js/wysiwyg/plugin/plugins.js | 78 + .../static/src/js/wysiwyg/plugin/table.js | 244 ++ .../static/src/js/wysiwyg/plugin/text.js | 799 ++++++ .../static/src/js/wysiwyg/plugin/toolbar.js | 57 + .../static/src/js/wysiwyg/plugin/transform.js | 85 + .../src/js/wysiwyg/plugin/unbreakable.js | 360 +++ .../static/src/js/wysiwyg/plugin_registry.js | 43 + .../static/src/js/wysiwyg/translation.js | 161 ++ .../src/js/wysiwyg/widgets/alt_dialog.js | 64 + .../js/wysiwyg/widgets/colorpicker_dialog.js | 390 +-- .../src/js/wysiwyg/widgets/crop_dialog.js | 218 ++ .../static/src/js/wysiwyg/widgets/dialog.js | 58 + .../src/js/wysiwyg/widgets/link_dialog.js | 219 ++ .../static/src/js/wysiwyg/widgets/media.js | 1086 ++------ .../src/js/wysiwyg/widgets/media_dialog.js | 207 ++ .../static/src/js/wysiwyg/widgets/widgets.js | 26 + .../static/src/js/wysiwyg/wysiwyg.js | 821 ++++++ .../static/src/js/wysiwyg/wysiwyg_iframe.js | 261 ++ .../js/wysiwyg_snippets/snippets.editor.js | 281 +- .../js/wysiwyg_snippets/snippets.options.js | 96 +- .../js/wysiwyg_snippets/wysiwyg_snippets.js | 260 ++ .../static/src/scss/web_editor.backend.scss | 90 +- .../static/src/scss/web_editor.common.scss | 46 +- .../static/src/scss/web_editor.ui.scss | 1616 ----------- .../static/src/scss/web_editor.variables.scss | 6 + .../web_editor/static/src/scss/wysiwyg.scss | 522 ++++ .../static/src/scss/wysiwyg_iframe.scss | 24 + .../static/src/scss/wysiwyg_snippets.scss | 872 ++++++ .../static/src/scss/wysiwyg_variables.scss | 42 + addons/web_editor/static/src/xml/backend.xml | 14 - addons/web_editor/static/src/xml/editor.xml | 372 --- addons/web_editor/static/src/xml/snippets.xml | 6 +- addons/web_editor/static/src/xml/wysiwyg.xml | 375 +++ .../static/src/xml/wysiwyg_colorpicker.xml | 139 +- addons/web_editor/views/editor.xml | 166 +- addons/web_editor/views/iframe.xml | 80 - addons/web_editor/views/snippets.xml | 18 +- .../static/src/js/unsplash_image_widget.js | 8 +- .../static/src/xml/unsplash_image_widget.xml | 2 +- .../views/web_unsplash_templates.xml | 4 +- addons/website/controllers/main.py | 4 - addons/website/static/src/js/content/base.js | 28 + .../website/static/src/js/content/context.js | 24 + addons/website/static/src/js/content/ready.js | 7 + .../src/js/content/snippets.animation.js | 5 +- .../static/src/js/content/website_root.js | 10 +- .../static/src/js/editor/editor_menu.js | 255 +- .../src/js/editor/editor_menu_translate.js | 344 +-- .../static/src/js/editor/rte.summernote.js | 59 - .../static/src/js/editor/snippets.options.js | 48 +- .../static/src/js/editor/widget_link.js | 4 +- .../static/src/js/editor/wysiwyg_multizone.js | 582 ++++ .../js/editor/wysiwyg_multizone_translate.js | 289 ++ addons/website/static/src/js/menu/content.js | 11 +- .../website/static/src/js/menu/customize.js | 2 +- addons/website/static/src/js/menu/edit.js | 87 +- addons/website/static/src/js/menu/navbar.js | 15 +- .../website/static/src/js/menu/new_content.js | 2 +- addons/website/static/src/js/menu/seo.js | 2 +- .../website/static/src/js/menu/translate.js | 4 +- addons/website/static/src/js/widgets/theme.js | 10 +- .../static/src/scss/website.editor.ui.scss | 812 ++++++ addons/website/static/src/scss/website.scss | 6 +- addons/website/static/src/xml/translator.xml | 2 +- .../website/static/src/xml/website.editor.xml | 17 + addons/website/views/website_templates.xml | 20 +- .../static/src/js/website_blog.editor.js | 24 +- .../views/website_crm_templates.xml | 2 +- .../static/src/js/website_forum.editor.js | 12 +- .../static/src/js/website_forum.js | 49 +- addons/website_forum/views/website_forum.xml | 6 - .../static/src/js/website_gengo.js | 7 +- .../website_hr_recruitment_templates.xml | 2 +- addons/website_mass_mailing/__manifest__.py | 3 + .../website_mass_mailing/controllers/main.py | 17 +- .../src/js/website_mass_mailing.editor.js | 40 +- .../static/src/xml/website_mass_mailing.xml | 14 + .../views/mass_mailing_view.xml | 6 +- .../views/snippets_templates.xml | 11 +- .../views/website_mass_mailing_templates.xml | 2 +- .../static/src/js/website_sale_options.js | 3 +- addons/website_sale/views/templates.xml | 2 + odoo/addons/base/models/assetsbundle.py | 27 +- odoo/addons/base/models/ir_actions_report.py | 6 +- odoo/addons/base/models/ir_qweb_fields.py | 2 +- 156 files changed, 17258 insertions(+), 10607 deletions(-) delete mode 100644 addons/mass_mailing/controllers/web_editor.py create mode 100644 addons/mass_mailing/static/src/js/mass_mailing.js delete mode 100644 addons/mass_mailing/static/src/js/mass_mailing_field_text_html.js create mode 100644 addons/mass_mailing/static/src/js/mass_mailing_widget.js delete mode 100644 addons/point_of_sale/controllers/web_editor.py delete mode 100644 addons/point_of_sale/static/src/js/field_text_html.js delete mode 100644 addons/web/static/src/js/report/report.editor.js delete mode 100644 addons/web/static/src/scss/report.editor.scss delete mode 100644 addons/web_editor/static/src/js/editor/rte.js delete mode 100644 addons/web_editor/static/src/js/editor/rte.summernote.js delete mode 100644 addons/web_editor/static/src/js/editor/summernote.js delete mode 100644 addons/web_editor/static/src/js/iframe.js delete mode 100644 addons/web_editor/static/src/js/inline.js delete mode 100644 addons/web_editor/static/src/js/tours/rte.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/options.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/abstract.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/buttons.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/codeview.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/dropzone.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/editor.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/font.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/font_buttons.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/help_dialog.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/helper.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/hint.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/history.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/link.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/media.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/plugins.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/table.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/text.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/toolbar.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/transform.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin/unbreakable.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/plugin_registry.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/translation.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/widgets/alt_dialog.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/widgets/crop_dialog.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/widgets/dialog.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/widgets/link_dialog.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/wysiwyg.js create mode 100644 addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js create mode 100644 addons/web_editor/static/src/js/wysiwyg_snippets/wysiwyg_snippets.js delete mode 100644 addons/web_editor/static/src/scss/web_editor.ui.scss create mode 100644 addons/web_editor/static/src/scss/wysiwyg.scss create mode 100644 addons/web_editor/static/src/scss/wysiwyg_iframe.scss create mode 100644 addons/web_editor/static/src/scss/wysiwyg_snippets.scss create mode 100644 addons/web_editor/static/src/scss/wysiwyg_variables.scss create mode 100644 addons/web_editor/static/src/xml/wysiwyg.xml delete mode 100644 addons/web_editor/views/iframe.xml create mode 100644 addons/website/static/src/js/content/base.js create mode 100644 addons/website/static/src/js/content/context.js create mode 100644 addons/website/static/src/js/content/ready.js delete mode 100644 addons/website/static/src/js/editor/rte.summernote.js create mode 100644 addons/website/static/src/js/editor/wysiwyg_multizone.js create mode 100644 addons/website/static/src/js/editor/wysiwyg_multizone_translate.js create mode 100644 addons/website/static/src/scss/website.editor.ui.scss create mode 100644 addons/website_mass_mailing/static/src/xml/website_mass_mailing.xml 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_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 index c7e95198005..fe8093b17d9 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_snippets.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js @@ -1,18 +1,10 @@ -odoo.define('mass_mailing.editor', function (require) { +odoo.define('mass_mailing.snippets.options', 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({ +options.registry.mass_mailing_sizing_x = options.Class.extend({ /** * @override */ @@ -31,7 +23,7 @@ options.registry.sizing_x = options.Class.extend({ this.$overlay.find(".oe_snippet_move, .oe_snippet_clone").addClass('d-none'); } - var $body = $(document.body); + var $body = $(this.ownerDocument.body); this.$overlay.find(".o_handle").on('mousedown', function (event) { event.preventDefault(); var $handle = $(this); @@ -88,7 +80,7 @@ options.registry.sizing_x = options.Class.extend({ }, }); -options.registry.table_item = options.Class.extend({ +options.registry.mass_mailing_table_item = options.Class.extend({ onClone: function (options) { this._super.apply(this, arguments); @@ -134,300 +126,16 @@ options.registry.table_item = options.Class.extend({ }, }); -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({ +options.registry.background = options.registry.background.extend({ start: function () { this._super(); - var $table_target = this.$target.find('table:first'); - if ($table_target) { - this.$target = $table_target; + 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..1e2bf350fec --- /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: this.dataPointID, + changes: _.object([fieldName], [this._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 $('