diff options
| author | fja-odoo <fja@odoo.com> | 2019-11-15 16:13:17 +0000 |
|---|---|---|
| committer | qsm-odoo <qsm@odoo.com> | 2020-01-16 14:29:04 +0000 |
| commit | 218e1cad6152999a0f41f073b5efa3c9428da48d (patch) | |
| tree | 76ebfa75fcb86195fd4b4c14af7b87b8ad3920c8 /addons | |
| parent | 4946739c17488d32d3e61b1258ab48b5aec701a4 (diff) | |
[IMP] web_editor, *: allow saving of snippets
* website, mass_mailing, website_event, website_form,
website_mass_mailing, website_sale
Now the user can save snippets to use them on other pages.
task-2120409
closes odoo/odoo#40408
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Co-authored-by: qsm-odoo <qsm@odoo.com>
Diffstat (limited to 'addons')
17 files changed, 333 insertions, 58 deletions
diff --git a/addons/mass_mailing/views/snippets_themes.xml b/addons/mass_mailing/views/snippets_themes.xml index 9633db6cfea..bb6ebc943d0 100644 --- a/addons/mass_mailing/views/snippets_themes.xml +++ b/addons/mass_mailing/views/snippets_themes.xml @@ -5,9 +5,9 @@ <xpath expr="//div[@id='snippets_menu']" position="inside"> <button type="button" disabled="disabled">Select a template</button> </xpath> - <xpath expr="//div[@id='o_scroll']" position="replace"> - <t t-set="company_id" t-value="res_company"/> - <div id="o_scroll"> + <xpath expr="//t[@id='default_snippets']" position="replace"> + <t id="default_snippets"> + <t t-set="company_id" t-value="res_company"/> <div id="email_designer_themes"> <div data-name="basic" data-nowrap="1" @@ -70,7 +70,7 @@ <t t-snippet="mass_mailing.s_mail_block_footer_social_left" t-thumbnail="/mass_mailing/static/src/img/blocks/block_footer_social_left.png"/> </div> </div> - </div> + </t> </xpath> <xpath expr="//div[@id='snippet_options']" position="inside"> <t t-call="mass_mailing.snippet_options"/> @@ -797,12 +797,21 @@ data-exclude=".o_mail_no_options" data-drop-near=".col_mv, td, th"/> + <t t-set="mailing_content_selector">.note-editable > div:not(.o_layout), .note-editable .oe_structure > div, .oe_snippet_body</t> <div data-js="content" - data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, .oe_snippet_body" + t-att-data-selector="mailing_content_selector" data-exclude=".o_mail_no_options" data-drop-near="[data-oe-field='body_html']:not(:has(.o_layout)) > *, .oe_structure > *" data-drop-in="[data-oe-field='body_html']:not(:has(.o_layout)), .oe_structure"/> + <div data-js="SnippetSave" + t-att-data-selector="mailing_content_selector"> + <we-button class="fa fa-fw fa-save" + title="Save the block to use it elsewhere" + data-save-snippet="" + data-no-preview="true"/> + </div> + <div data-js="sizing_y" data-selector=".note-editable > div:not(.o_layout), .note-editable .oe_structure > div, td, th" data-exclude=".o_mail_no_resize, .o_mail_no_options"/> diff --git a/addons/web_editor/models/ir_qweb.py b/addons/web_editor/models/ir_qweb.py index 78ebe9b2178..0cf3ff932c9 100644 --- a/addons/web_editor/models/ir_qweb.py +++ b/addons/web_editor/models/ir_qweb.py @@ -50,9 +50,10 @@ class QWeb(models.AbstractModel): view_id = View.get_view_id(el.attrib.get('t-call')) name = View.browse(view_id).name thumbnail = el.attrib.pop('t-thumbnail', "oe-thumbnail") - div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s">' % ( + div = u'<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s" data-oe-snippet-id="%s">' % ( escape(pycompat.to_text(name)), - escape(pycompat.to_text(thumbnail)) + escape(pycompat.to_text(thumbnail)), + escape(pycompat.to_text(view_id)), ) return [self._append(ast.Str(div))] + self._compile_node(el, options) + [self._append(ast.Str(u'</div>'))] diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py index 0aefd108432..19470d40c3c 100644 --- a/addons/web_editor/models/ir_ui_view.py +++ b/addons/web_editor/models/ir_ui_view.py @@ -3,10 +3,11 @@ import copy import logging +import uuid from lxml import etree, html from odoo.exceptions import AccessError -from odoo import api, fields, models +from odoo import api, models _logger = logging.getLogger(__name__) @@ -271,3 +272,81 @@ class IrUiView(models.Model): View = self.with_context(active_test=False, lang=None) views = View._views_get(key, bundles=bundles) return views.filtered(lambda v: not v.groups_id or len(user_groups.intersection(v.groups_id))) + + # -------------------------------------------------------------------------- + # Snippet saving + # -------------------------------------------------------------------------- + + @api.model + def _get_default_snippet_thumbnail(self, snippet_class=None): + return '/web_editor/static/src/img/snippets_thumbs/s_custom_snippet.png' + + @api.model + def _get_snippet_addition_view_key(self, template_key, key): + return '%s.%s' % (template_key, key) + + @api.model + def _snippet_save_view_values_hook(self): + return {} + + @api.model + def save_snippet(self, name, arch, template_key, snippet_class=None, thumbnail_url=None): + """ + Saves a new snippet arch so that it appears with the given name when + using the given snippets template. + + :param name: the name of the snippet to save + :param arch: the html structure of the snippet to save + :param template_key: the key of the view regrouping all snippets in + which the snippet to save is meant to appear + :param snippet_class: a className which is supposed to uniquely-identify + the snippet from which the snippet to save originates + :param thumbnail_url: the url of the thumbnail to use when displaying + the snippet to save (default: see '_get_default_snippet_thumbnail') + """ + if not thumbnail_url: + thumbnail_url = self._get_default_snippet_thumbnail(snippet_class) + + app_name = template_key.split('.')[0] + snippet_class = snippet_class or 's_custom_snippet' + key = '%s_%s' % (snippet_class, uuid.uuid4().hex) + snippet_key = '%s.%s' % (app_name, key) + + # html to xml to add '/' at the end of self closing tags like br, ... + xml_arch = etree.tostring(html.fromstring(arch)) + new_snippet_view_values = { + 'name': name, + 'key': snippet_key, + 'type': 'qweb', + 'arch': xml_arch, + } + new_snippet_view_values.update(self._snippet_save_view_values_hook()) + self.create(new_snippet_view_values) + + custom_section = self.search([('key', '=', template_key)]) + snippet_addition_view_values = { + 'name': name + ' Block', + 'key': self._get_snippet_addition_view_key(template_key, key), + 'inherit_id': custom_section.id, + 'type': 'qweb', + 'arch': """ + <data inherit_id="%s"> + <xpath expr="//div[@id='snippet_custom']" position="attributes"> + <attribute name="class" remove="d-none" separator=" "/> + </xpath> + <xpath expr="//div[@id='snippet_custom_body']" position="inside"> + <t t-snippet="%s" t-thumbnail="%s"/> + </xpath> + </data> + """ % (template_key, snippet_key, thumbnail_url), + } + snippet_addition_view_values.update(self._snippet_save_view_values_hook()) + self.create(snippet_addition_view_values) + + @api.model + def delete_snippet(self, view_id, template_key): + snippet_view = self.browse(view_id) + key = snippet_view.key.split('.')[1] + custom_key = self._get_snippet_addition_view_key(template_key, key) + snippet_addition_view = self.search([('key', '=', custom_key)]) + (snippet_addition_view | snippet_view).unlink() diff --git a/addons/web_editor/static/src/img/snippets_thumbs/s_custom_snippet.png b/addons/web_editor/static/src/img/snippets_thumbs/s_custom_snippet.png Binary files differnew file mode 100644 index 00000000000..76ffb0905fb --- /dev/null +++ b/addons/web_editor/static/src/img/snippets_thumbs/s_custom_snippet.png diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 1e510ba9414..57ab6c37b73 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -780,6 +780,7 @@ var SnippetsMenu = Widget.extend({ events: { 'click .o_install_btn': '_onInstallBtnClick', 'click .o_we_invisible_entry': '_onInvisibleEntryClick', + 'click #snippet_custom .o_delete_btn': '_onDeleteBtnClick', }, custom_events: { 'activate_insertion_zones': '_onActivateInsertionZones', @@ -801,6 +802,7 @@ var SnippetsMenu = Widget.extend({ 'block_preview_overlays': '_onBlockPreviewOverlays', 'unblock_preview_overlays': '_onUnblockPreviewOverlays', 'user_value_widget_opening': '_onUserValueWidgetOpening', + 'reload_snippet_template': '_onReloadSnippetTemplate', }, /** @@ -857,26 +859,22 @@ var SnippetsMenu = Widget.extend({ this.window = this.ownerDocument.defaultView; this.$window = $(this.window); - // Fetch snippet templates and compute it + this.customizePanel = document.createElement('div'); + this.customizePanel.classList.add('o_we_customize_panel', 'd-none'); - defs.push(this.loadSnippets().then(html => { - return this._computeSnippetTemplates(html); - }).then(() => { - this.customizePanel = document.createElement('div'); - this.customizePanel.classList.add('o_we_customize_panel', 'd-none'); - this.$el.append(this.customizePanel); - - this.invisibleDOMPanelEl = document.createElement('div'); - this.invisibleDOMPanelEl.classList.add('o_we_invisible_el_panel'); - this.invisibleDOMPanelEl.appendChild( - $('<div/>', { - text: _t('Invisible Elements'), - class: 'o_panel_header', - }).prepend( - $('<i/>', {class: 'fa fa-eye-slash'}) - )[0] - ); - this.$el.append(this.invisibleDOMPanelEl); + this.invisibleDOMPanelEl = document.createElement('div'); + this.invisibleDOMPanelEl.classList.add('o_we_invisible_el_panel'); + this.invisibleDOMPanelEl.appendChild( + $('<div/>', { + text: _t('Invisible Elements'), + class: 'o_panel_header', + }).prepend( + $('<i/>', {class: 'fa fa-eye-slash'}) + )[0] + ); + + // Fetch snippet templates and compute it + defs.push(this._loadSnippetsTemplates().then(() => { return this._updateInvisibleDOM(); })); @@ -998,9 +996,10 @@ var SnippetsMenu = Widget.extend({ }, /** * Load snippets. + * @param {boolean} invalidateCache */ - loadSnippets: function () { - if (this.cacheSnippetTemplate[this.options.snippets]) { + loadSnippets: function (invalidateCache) { + if (!invalidateCache && this.cacheSnippetTemplate[this.options.snippets]) { this._defLoadSnippets = this.cacheSnippetTemplate[this.options.snippets]; return this._defLoadSnippets; } @@ -1287,6 +1286,17 @@ var SnippetsMenu = Widget.extend({ }, /** * @private + * @param {boolean} invalidateCache + */ + _loadSnippetsTemplates: async function (invalidateCache) { + return this._mutex.exec(async () => { + await this._destroyEditors(); + const html = await this.loadSnippets(invalidateCache); + await this._computeSnippetTemplates(html); + }); + }, + /** + * @private */ _destroyEditors: async function () { const proms = _.map(this.snippetEditors, async function (snippetEditor) { @@ -1502,6 +1512,7 @@ var SnippetsMenu = Widget.extend({ var $snippet = $(this); var name = $snippet.attr('name'); var $sbody = $snippet.children(':not(.oe_snippet_thumbnail)').addClass('oe_snippet_body'); + const isCustomSnippet = !!$snippet.parents('#snippet_custom').length; // Associate in-page snippets to their name if ($sbody.length) { @@ -1525,6 +1536,13 @@ var SnippetsMenu = Widget.extend({ $snippet.find('[data-oe-thumbnail]').data('oeThumbnail'), name )); + if (isCustomSnippet) { + const btn = document.createElement('we-button'); + btn.dataset.snippetId = $snippet.data('oeSnippetId'); + btn.classList.add('o_delete_btn', 'fa', 'fa-trash'); + $thumbnail.prepend(btn); + $thumbnail.prepend($('<div class="o_image_ribbon"/>')); + } $snippet.prepend($thumbnail); // Create the install button (t-install feature) if necessary @@ -1563,6 +1581,8 @@ var SnippetsMenu = Widget.extend({ // Add the computed template and make elements draggable this.$el.html($html); + this.$el.append(this.customizePanel); + this.$el.append(this.invisibleDOMPanelEl); this._makeSnippetDraggable(this.$snippets); this._disableUndroppableSnippets(); @@ -1677,7 +1697,11 @@ var SnippetsMenu = Widget.extend({ $snippets.draggable({ greedy: true, - helper: 'clone', + helper: function () { + const dragSnip = this.cloneNode(true); + dragSnip.querySelectorAll('.o_delete_btn, .o_image_ribbon').forEach(el => el.remove()); + return dragSnip; + }, appendTo: this.$body, cursor: 'move', handle: '.oe_snippet_thumbnail', @@ -1974,6 +1998,43 @@ var SnippetsMenu = Widget.extend({ /** * @private */ + _onDeleteBtnClick: function (ev) { + const $snippet = $(ev.target).closest('.oe_snippet'); + new Dialog(this, { + size: 'medium', + title: _t('Confirmation'), + $content: $('<div><p>' + _t(`Are you sure you want to delete the snippet: ${$snippet.attr('name')} ?`) + '</p></div>'), + buttons: [{ + text: _t("Yes"), + close: true, + classes: 'btn-primary', + click: async () => { + await this._rpc({ + model: 'ir.ui.view', + method: 'delete_snippet', + kwargs: { + 'view_id': parseInt(ev.currentTarget.dataset.snippetId), + 'template_key': this.options.snippets, + }, + }); + await this._loadSnippetsTemplates(true); + }, + }, { + text: _t("No"), + close: true, + }], + }).open(); + }, + /** + * @private + */ + _onReloadSnippetTemplate: async function (ev) { + await this._activateSnippet(false); + await this._loadSnippetsTemplates(true); + }, + /** + * @private + */ _onBlockPreviewOverlays: function (ev) { this._blockPreviewOverlays = true; }, diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 86a3f70ac2b..53a36008a56 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -3,6 +3,7 @@ odoo.define('web_editor.snippets.options', function (require) { var core = require('web.core'); const ColorpickerDialog = require('web.ColorpickerDialog'); +const Dialog = require('web.Dialog'); const time = require('web.time'); var Widget = require('web.Widget'); var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget; @@ -2973,6 +2974,60 @@ registry.many2one = SnippetOptionWidget.extend({ } }); +/** + * Handle the save of a snippet as a template that can be reused later + */ +registry.SnippetSave = SnippetOptionWidget.extend({ + xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], + isTopOption: true, + + //-------------------------------------------------------------------------- + // Options + //-------------------------------------------------------------------------- + + /** + * @see this.selectClass for parameters + */ + saveSnippet: function (previewMode, widgetValue, params) { + return new Promise(resolve => { + const dialog = new Dialog(this, { + title: _t("Save Your Block"), + size: 'small', + $content: $(qweb.render('web_editor.dialog.save_snippet', { + currentSnippetName: _.str.sprintf(_t("Custom %s"), this.data.snippetName), + })), + buttons: [{ + text: _t("Save"), + classes: 'btn-primary', + close: true, + click: async () => { + const snippetName = dialog.el.querySelector('.o_we_snippet_name_input').value; + const targetCopyEl = this.$target[0].cloneNode(true); + delete targetCopyEl.dataset.name; + await this._rpc({ + model: 'ir.ui.view', + method: 'save_snippet', + kwargs: { + 'name': snippetName, + 'arch': targetCopyEl.outerHTML, + 'template_key': this.options.snippets, + 'snippet_class': [...this.$target[0].classList].filter(x => /\bs_./g.test(x))[0], + }, + }); + this.trigger_up('reload_snippet_template'); + resolve(); + }, + }, { + text: _t("Discard"), + close: true, + click: () => resolve(), + }], + }).open(); + }); + }, +}); + + return { SnippetOptionWidget: SnippetOptionWidget, snippetOptionRegistry: registry, diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index 3dcb6e872e9..b8e87ff69f5 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -171,6 +171,33 @@ body.editor_enable.editor_has_snippets { } } } + #snippet_custom .oe_snippet .oe_snippet_thumbnail { + .o_delete_btn { + @include o-position-absolute(0, 0); + z-index: 1; + display: none; + width: 25px; + height: 25px; + background-color: rgba(255, 0, 0, 0.75); + justify-content: center; + align-items: center; + + &:hover { + cursor: pointer; + } + } + &:hover .o_delete_btn { + display: flex; + } + .o_image_ribbon { + position: absolute; + z-index: 1; + width: 0; + height: 0; + border-right: 20px solid transparent; + border-top: 20px solid $o-enterprise-color; + } + } } > .o_we_customize_panel { diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml index b1d7128e2c1..bb4abe15f60 100644 --- a/addons/web_editor/static/src/xml/editor.xml +++ b/addons/web_editor/static/src/xml/editor.xml @@ -25,4 +25,18 @@ </div> </div> </t> + <!-- Save Snippet Name option dialog --> + <t t-name="web_editor.dialog.save_snippet"> + <div> + <div class="form-group form-row"> + <label class="col-form-label col-md-3" for="snippetName">Name</label> + <div class="col-md-9"> + <input type="text" + class="form-control o_we_snippet_name_input" + id="snippetName" + t-att-value="currentSnippetName"/> + </div> + </div> + </div> + </t> </templates> diff --git a/addons/web_editor/views/snippets.xml b/addons/web_editor/views/snippets.xml index 8f44a8f05a4..ab920aa65fb 100644 --- a/addons/web_editor/views/snippets.xml +++ b/addons/web_editor/views/snippets.xml @@ -14,14 +14,22 @@ </div> <div id="o_scroll"> - <div id="snippet_structure" class="o_panel"> + <div id="snippet_custom" class="o_panel d-none"> <div class="o_panel_header"> - <i class="fa fa-th-large"/> First Panel - </div> - <div class="o_panel_body"> - <t t-snippet="web_editor.s_hr" t-thumbnail="/website/static/src/img/snippets_thumbs/s_separator.png"/> + <i class="fa fa-fw fa-save"/> Custom </div> + <div id="snippet_custom_body" class="o_panel_body"/> </div> + <t id="default_snippets"> + <div id="snippet_structure" class="o_panel"> + <div class="o_panel_header"> + <i class="fa fa-th-large"/> First Panel + </div> + <div class="o_panel_body"> + <t t-snippet="web_editor.s_hr" t-thumbnail="/website/static/src/img/snippets_thumbs/s_separator.png"/> + </div> + </div> + </t> </div> <div id="snippet_options" class="d-none"> diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 3618c0a141c..881b8355a3e 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -2,11 +2,13 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging -import werkzeug +import os import uuid +import werkzeug from odoo import api, fields, models from odoo import tools +from odoo.addons import website from odoo.addons.http_routing.models.ir_http import url_for from odoo.osv import expression from odoo.http import request @@ -462,3 +464,23 @@ class View(models.Model): if website_specific_view: self = website_specific_view super(View, self).save(value, xpath=xpath) + + # -------------------------------------------------------------------------- + # Snippet saving + # -------------------------------------------------------------------------- + + @api.model + def _get_default_snippet_thumbnail(self, snippet_class=None): + if snippet_class: + for path in website.__path__: + if os.path.isfile(path + '/static/src/img/snippets_thumbs/%s.png' % snippet_class): + return '/website/static/src/img/snippets_thumbs/%s.png' % snippet_class + return super()._get_default_snippet_thumbnail() + + @api.model + def _snippet_save_view_values_hook(self): + res = super()._snippet_save_view_values_hook() + website_id = self.env.context.get('website_id') + if website_id: + res['website_id'] = website_id + return res diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index 9024cf7b8e0..5468ab9f321 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -3,7 +3,6 @@ odoo.define('website.editor.snippets.options', function (require) { var core = require('web.core'); var Dialog = require('web.Dialog'); -var weWidgets = require('wysiwyg.widgets'); const wUtils = require('website.utils'); var options = require('web_editor.snippets.options'); diff --git a/addons/website/views/snippets/s_title.xml b/addons/website/views/snippets/s_title.xml index 5fdbb9975af..db2791be3c6 100644 --- a/addons/website/views/snippets/s_title.xml +++ b/addons/website/views/snippets/s_title.xml @@ -2,7 +2,7 @@ <odoo> <template id="s_title" name="Title"> - <section class="pt32 pb32"> + <section class="s_title pt32 pb32"> <div class="container"> <div class="row s_nb_column_fixed"> <div class="col-lg-12 s_title pt16 pb16" style="text-align: center;"> diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml index 699f9fa6474..4594574c88f 100644 --- a/addons/website/views/snippets/snippets.xml +++ b/addons/website/views/snippets/snippets.xml @@ -3,8 +3,8 @@ <!-- Snippets menu --> <template id="snippets" inherit_id="web_editor.snippets" primary="True"> - <xpath expr="//div[@id='o_scroll']" position="replace"> - <div id="o_scroll"> + <xpath expr="//t[@id='default_snippets']" position="replace"> + <t id="default_snippets"> <div id="snippet_mega_menu" class="o_panel d-none"> <div class="o_panel_header"> <i class="fa fa-puzzle-piece"/> Mega Menu @@ -77,7 +77,7 @@ <t t-snippet="website.s_chart" t-thumbnail="/website/static/src/img/snippets_thumbs/s_chart.png"/> </div> </div> - </div> + </t> </xpath> <xpath expr="//div[@id='snippet_options']/t" position="attributes"> @@ -308,16 +308,25 @@ data-drop-near=".row > div" data-exclude=".s_col_no_resize.row > div"/> + <t t-set="so_snippet_addition_selector">section, .parallax, .s_popup</t> <div id="so_snippet_addition" - data-selector="section, .parallax, .s_popup" + t-att-data-selector="so_snippet_addition_selector" data-drop-in=":not(p).oe_structure:not(.oe_structure_solo), :not(p)[data-oe-type=html], :not(p).oe_structure.oe_structure_solo:not(:has(> section, > div))"/> - <!-- Main content drop --> + <t t-set="so_content_addition_selector">blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr, .s_countdown, .s_chart</t> <div id="so_content_addition" - data-selector="blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr, .s_countdown, .s_chart" - data-drop-near="p, h1, h2, h3, blockquote, .s_btn, .s_card, .s_alert, .o_facebook_page, .s_share, .s_rating, .s_hr, .s_chart" + t-att-data-selector="so_content_addition_selector" + t-attf-data-drop-near="p, h1, h2, h3, #{so_content_addition_selector}" data-drop-in=".content, nav"/> + <div data-js="SnippetSave" + t-attf-data-selector="#{so_snippet_addition_selector}, #{so_content_addition_selector}"> + <we-button class="fa fa-fw fa-save" + title="Save the block to use it elsewhere" + data-save-snippet="" + data-no-preview="true"/> + </div> + <div data-js="ul" data-selector=":not(li) > ul:has(ul,ol), :not(li) > ol:has(ul,ol)"> <we-checkbox string="Folded list" data-select-class="o_ul_folded"/> diff --git a/addons/website_event/views/event_snippets.xml b/addons/website_event/views/event_snippets.xml index 438a19fbd15..fb890988102 100644 --- a/addons/website_event/views/event_snippets.xml +++ b/addons/website_event/views/event_snippets.xml @@ -59,9 +59,7 @@ </template> <template id="snippet_options" inherit_id="website.snippet_options"> - <xpath expr="//div[@id='so_content_addition']" position="attributes"> - <attribute name="data-selector" add=".oe_country_events, .s_speaker_bio" separator=","/> - </xpath> + <xpath expr="//*[@t-set='so_content_addition_selector']" position="inside">, .oe_country_events, .s_speaker_bio</xpath> </template> <!-- Snippet - Speaker Bio --> diff --git a/addons/website_form/views/snippets.xml b/addons/website_form/views/snippets.xml index 0b4ef73b623..4f96ac66f4e 100644 --- a/addons/website_form/views/snippets.xml +++ b/addons/website_form/views/snippets.xml @@ -24,9 +24,7 @@ </template> <template id="snippet_options" inherit_id="website.snippet_options"> - <xpath expr="//*[@id='so_snippet_addition']" position="attributes"> - <attribute name="data-selector" add=".s_website_form" separator=","/> - </xpath> + <xpath expr="//*[@t-set='so_snippet_addition_selector']" position="inside">, .s_website_form</xpath> <xpath expr="//div" position="after"> <!-- Form --> <div data-js="website_form_editor" data-selector=".s_website_form"> diff --git a/addons/website_mass_mailing/views/snippets_templates.xml b/addons/website_mass_mailing/views/snippets_templates.xml index 6200662243f..1411f42e89e 100644 --- a/addons/website_mass_mailing/views/snippets_templates.xml +++ b/addons/website_mass_mailing/views/snippets_templates.xml @@ -55,9 +55,7 @@ </template> <template id="newsletter_subscribe_options" name="Newsletter Subscribe Options" inherit_id="website.snippet_options"> - <xpath expr="//*[@id='so_snippet_addition']" position="attributes"> - <attribute name="data-selector" add=".o_newsletter_popup" separator=","/> - </xpath> + <xpath expr="//*[@t-set='so_snippet_addition_selector']" position="inside">, .o_newsletter_popup</xpath> <xpath expr="//*[@id='row_valign_snippet_option']" position="attributes"> <attribute name="data-selector" separator="," add=".s_newsletter_block"/> </xpath> diff --git a/addons/website_sale/views/snippets.xml b/addons/website_sale/views/snippets.xml index dd349a6b8ea..430cdf4e5e6 100644 --- a/addons/website_sale/views/snippets.xml +++ b/addons/website_sale/views/snippets.xml @@ -106,10 +106,7 @@ <we-button data-no-preview="true" data-open-searchbar-settings="">Products Search Bar</we-button> </div> </xpath> - <xpath expr="//div[@id='so_content_addition']" position="attributes"> - <attribute name="data-selector" add=".s_wsale_products_searchbar_input" separator=","/> - <attribute name="data-drop-near" add=".s_wsale_products_searchbar_input" separator=","/> - </xpath> + <xpath expr="//*[@t-set='so_content_addition_selector']" position="inside">, .s_wsale_products_searchbar_input</xpath> </template> </odoo> |
