[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>
This commit is contained in:
committed by
qsm-odoo
co-authored by
qsm-odoo
parent
4946739c17
commit
218e1cad61
@@ -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"/>
|
||||
|
||||
@@ -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>'))]
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 977 B |
@@ -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);
|
||||
|
||||
this.customizePanel = document.createElement('div');
|
||||
this.customizePanel.classList.add('o_we_customize_panel', 'd-none');
|
||||
|
||||
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.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);
|
||||
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;
|
||||
}
|
||||
@@ -1285,6 +1284,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
|
||||
*/
|
||||
@@ -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',
|
||||
@@ -1971,6 +1995,43 @@ var SnippetsMenu = Widget.extend({
|
||||
.toggleClass('fa-eye-slash', !isVisible);
|
||||
return this._activateSnippet(isVisible ? $snippet : false);
|
||||
},
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user