Skip to content

Commit 507b80a

Browse files
sleepy-monaxbvr-odoo
authored andcommitted
[IMP] web_editor, website: add button/video/image as building block
This commit introduces 3 new inner snippets: button, image and video. What sets these snippets apart is that, once dropped onto the page, they transform into basic HTML code, just like if they were created using the editor (no data-snippet etc). task-3248881 closes odoo#126717 Signed-off-by: Quentin Smetz (qsm) <[email protected]> Co-authored-by: bvr-odoo <[email protected]>
1 parent a98926d commit 507b80a

File tree

12 files changed

+360
-32
lines changed

12 files changed

+360
-32
lines changed

addons/web_editor/static/src/js/editor/snippets.editor.js

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ var SnippetEditor = Widget.extend({
120120
}
121121
} else {
122122
this.$('.o_overlay_move_options').addClass('d-none');
123-
$customize.find('.oe_snippet_clone').addClass('d-none');
123+
const cloneButtonEl = $customize[0].querySelector(".oe_snippet_clone");
124+
cloneButtonEl.classList.toggle("d-none", !this.forceDuplicateButton);
124125
}
125126

126127
if (!this.isTargetRemovable) {
@@ -196,10 +197,14 @@ var SnippetEditor = Widget.extend({
196197
/**
197198
* Notifies all the associated snippet options that the snippet has just
198199
* been dropped in the page.
200+
*
201+
* @param {HTMLElement} targetEl the snippet dropped in the page
199202
*/
200-
buildSnippet: async function () {
203+
async buildSnippet(targetEl) {
201204
for (var i in this.styles) {
202-
await this.styles[i].onBuilt();
205+
await this.styles[i].onBuilt({
206+
isCurrent: targetEl === this.$target[0],
207+
});
203208
}
204209
await this.toggleTargetVisibility(true);
205210
},
@@ -339,6 +344,9 @@ var SnippetEditor = Widget.extend({
339344
if (this.$target.is('#wrapwrap > main')) {
340345
return _t("Page Options");
341346
}
347+
if (this.$target[0].matches(".btn")) {
348+
return _t("Button");
349+
}
342350
return _t("Block");
343351
},
344352
/**
@@ -774,6 +782,10 @@ var SnippetEditor = Widget.extend({
774782
this.displayOverlayOptions = true;
775783
}
776784

785+
if (option.forceDuplicateButton) {
786+
this.forceDuplicateButton = true;
787+
}
788+
777789
return option.appendTo(document.createDocumentFragment());
778790
});
779791

@@ -1063,6 +1075,7 @@ var SnippetEditor = Widget.extend({
10631075
}
10641076

10651077
const openModalEl = this.$target[0].closest('.modal');
1078+
const toInsertInline = window.getComputedStyle(this.$target[0]).display.includes('inline');
10661079

10671080
this.dropped = false;
10681081
this._dropSiblings = {
@@ -1148,6 +1161,7 @@ var SnippetEditor = Widget.extend({
11481161
$selectorSiblings: $selectorSiblings,
11491162
$selectorChildren: $selectorChildren,
11501163
canBeSanitizedUnless: canBeSanitizedUnless,
1164+
toInsertInline: toInsertInline,
11511165
selectorGrids: selectorGrids,
11521166
});
11531167

@@ -2217,7 +2231,7 @@ var SnippetsMenu = Widget.extend({
22172231
// First call the onBuilt of all options of each item in the snippet
22182232
// (and so build their editor instance first).
22192233
await this._callForEachChildSnippet($target, function (editor, $snippet) {
2220-
return editor.buildSnippet();
2234+
return editor.buildSnippet($target[0]);
22212235
});
22222236
// The snippet is now fully built, notify the editor for changed
22232237
// content.
@@ -2270,11 +2284,13 @@ var SnippetsMenu = Widget.extend({
22702284
* true: always allows,
22712285
* false: always forbid,
22722286
* string: specific type of forbidden sanitization
2287+
* @param {Boolean} [toInsertInline=false]
2288+
* elements which are inline as the "s_badge" snippet for example
22732289
* @param {Object} [selectorGrids = []]
22742290
* elements which are in grid mode and for which a grid dropzone
22752291
* needs to be inserted
22762292
*/
2277-
_activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, selectorGrids = []) {
2293+
_activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, toInsertInline, selectorGrids = []) {
22782294
var self = this;
22792295

22802296
// If a modal or a dropdown is open, the drop zones must be created
@@ -2290,20 +2306,27 @@ var SnippetsMenu = Widget.extend({
22902306
}
22912307

22922308
// Check if the drop zone should be horizontal or vertical
2293-
function setDropZoneDirection($elem, $parent, $sibling) {
2294-
var vertical = false;
2295-
var style = {};
2309+
function setDropZoneDirection($elem, $parent, toInsertInline, $sibling) {
2310+
let vertical = false;
2311+
let style = {};
22962312
$sibling = $sibling || $elem;
2297-
var css = window.getComputedStyle($elem[0]);
2298-
var parentCss = window.getComputedStyle($parent[0]);
2299-
var float = css.float || css.cssFloat;
2300-
var display = parentCss.display;
2301-
var flex = parentCss.flexDirection;
2302-
if (float === 'left' || float === 'right' || (display === 'flex' && flex === 'row')) {
2303-
style['float'] = float;
2304-
if ($sibling.parent().width() !== $sibling.outerWidth(true)) {
2313+
const css = window.getComputedStyle($elem[0]);
2314+
const parentCss = window.getComputedStyle($parent[0]);
2315+
const float = css.float || css.cssFloat;
2316+
const display = parentCss.display;
2317+
const flex = parentCss.flexDirection;
2318+
if (toInsertInline || float === 'left' || float === 'right' || (display === 'flex' && flex === 'row')) {
2319+
if (!toInsertInline) {
2320+
style['float'] = float;
2321+
}
2322+
if ((parseInt($sibling.parent().width()) !== parseInt($sibling.outerWidth(true)))) {
23052323
vertical = true;
23062324
style['height'] = Math.max($sibling.outerHeight(), 30) + 'px';
2325+
if (toInsertInline) {
2326+
style["display"] = "inline-block";
2327+
style["verticalAlign"] = "middle";
2328+
style["float"] = "none";
2329+
}
23072330
}
23082331
}
23092332
return {
@@ -2337,7 +2360,7 @@ var SnippetsMenu = Widget.extend({
23372360
}
23382361
var data;
23392362
if ($neighbor.length) {
2340-
data = setDropZoneDirection($neighbor, $neighbor.parent());
2363+
data = setDropZoneDirection($neighbor, $neighbor.parent(), toInsertInline);
23412364
} else {
23422365
data = {
23432366
vertical: false,
@@ -2355,13 +2378,13 @@ var SnippetsMenu = Widget.extend({
23552378

23562379
if (!$zone.children().last().is('.oe_drop_zone')) {
23572380
data = testPreviousSibling($zone[0].lastChild, $zone)
2358-
|| setDropZoneDirection($zone, $zone, $children.last());
2381+
|| setDropZoneDirection($zone, $zone, toInsertInline, $children.last());
23592382
self._insertDropzone($('<we-hook/>').appendTo($zone), data.vertical, data.style, canBeSanitizedUnless);
23602383
}
23612384

23622385
if (!$zone.children().first().is('.oe_drop_clone')) {
23632386
data = testPreviousSibling($zone[0].firstChild, $zone)
2364-
|| setDropZoneDirection($zone, $zone, $children.first());
2387+
|| setDropZoneDirection($zone, $zone, toInsertInline, $children.first());
23652388
self._insertDropzone($('<we-hook/>').prependTo($zone), data.vertical, data.style, canBeSanitizedUnless);
23662389
}
23672390
});
@@ -2381,7 +2404,7 @@ var SnippetsMenu = Widget.extend({
23812404
$zoneToCheck = $zoneToCheck.prev();
23822405
}
23832406
if (!$zoneToCheck.prev('.oe_drop_zone:visible, .oe_drop_clone').length) {
2384-
data = setDropZoneDirection($zone, $zone.parent());
2407+
data = setDropZoneDirection($zone, $zone.parent(), toInsertInline);
23852408
self._insertDropzone($('<we-hook/>').insertBefore($zone), data.vertical, data.style, canBeSanitizedUnless);
23862409
}
23872410

@@ -2390,7 +2413,7 @@ var SnippetsMenu = Widget.extend({
23902413
$zoneToCheck = $zoneToCheck.next();
23912414
}
23922415
if (!$zoneToCheck.next('.oe_drop_zone:visible, .oe_drop_clone').length) {
2393-
data = setDropZoneDirection($zone, $zone.parent());
2416+
data = setDropZoneDirection($zone, $zone.parent(), toInsertInline);
23942417
self._insertDropzone($('<we-hook/>').insertAfter($zone), data.vertical, data.style, canBeSanitizedUnless);
23952418
}
23962419
});
@@ -3319,7 +3342,11 @@ var SnippetsMenu = Widget.extend({
33193342

33203343
const forbidSanitize = $snippet.data('oeForbidSanitize');
33213344
const canBeSanitizedUnless = forbidSanitize === 'form' ? 'form' : !forbidSanitize;
3322-
self._activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless);
3345+
// Specific case for inline snippet (e.g. "s_badge")
3346+
$baseBody[0].classList.remove("oe_snippet_body");
3347+
const toInsertInline = window.getComputedStyle($baseBody[0]).display.includes('inline');
3348+
$baseBody[0].classList.add("oe_snippet_body");
3349+
self._activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, toInsertInline);
33233350
$dropZones = self.getEditableArea().find('.oe_drop_zone');
33243351
if (forbidSanitize === 'form') {
33253352
$dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize]:not([data-oe-sanitize="allow_form"]) .oe_drop_zone'));
@@ -3414,6 +3441,15 @@ var SnippetsMenu = Widget.extend({
34143441

34153442
var $target = $toInsert;
34163443

3444+
if ($target[0].classList.contains("o_snippet_drop_in_only")) {
3445+
// If it's a "drop in only" snippet, after dropping
3446+
// it, we modify it so that it's no longer a
3447+
// draggable snippet but rather simple HTML code, as
3448+
// if the element had been created with the editor.
3449+
$target[0].classList.remove("o_snippet_drop_in_only");
3450+
delete $target[0].dataset.snippet;
3451+
delete $target[0].dataset.name;
3452+
}
34173453

34183454
self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
34193455
await self._scrollToSnippet($target, self.$scrollable);
@@ -3683,7 +3719,7 @@ var SnippetsMenu = Widget.extend({
36833719
* @param {OdooEvent} ev
36843720
*/
36853721
_onActivateInsertionZones: function (ev) {
3686-
this._activateInsertionZones(ev.data.$selectorSiblings, ev.data.$selectorChildren, ev.data.canBeSanitizedUnless, ev.data.selectorGrids);
3722+
this._activateInsertionZones(ev.data.$selectorSiblings, ev.data.$selectorChildren, ev.data.canBeSanitizedUnless, ev.data.toInsertInline, ev.data.selectorGrids);
36873723
},
36883724
/**
36893725
* Called when a child editor asks to deactivate the current snippet

addons/web_editor/static/src/js/editor/snippets.options.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3188,6 +3188,12 @@ const SnippetOptionWidget = Widget.extend({
31883188
* @type {boolean}
31893189
*/
31903190
displayOverlayOptions: false,
3191+
/**
3192+
* Forces the target to be duplicable.
3193+
*
3194+
* @type {boolean}
3195+
*/
3196+
forceDuplicateButton: false,
31913197

31923198
/**
31933199
* The option `$el` is supposed to be the associated DOM UI element.
@@ -3245,9 +3251,13 @@ const SnippetOptionWidget = Widget.extend({
32453251
* menu. Note: this is called after the start and onFocus methods.
32463252
*
32473253
* @abstract
3254+
* @param {Object} options
3255+
* @param {boolean} options.isCurrent
3256+
* true if the main element has been built (so not when a child of
3257+
* the main element has been built).
32483258
* @returns {Promise|undefined}
32493259
*/
3250-
async onBuilt() {},
3260+
async onBuilt(options) {},
32513261
/**
32523262
* Called when the parent edition overlay is removed from the associated
32533263
* snippet (another snippet enters edition for example).
@@ -8589,7 +8599,8 @@ registry.SnippetSave = SnippetOptionWidget.extend({
85898599
classes: 'btn-primary',
85908600
close: true,
85918601
click: () => {
8592-
const snippetKey = this.$target[0].dataset.snippet;
8602+
const isButton = this.$target[0].matches("a.btn");
8603+
const snippetKey = !isButton ? this.$target[0].dataset.snippet : "s_button";
85938604
let thumbnailURL;
85948605
this.trigger_up('snippet_thumbnail_url_request', {
85958606
key: snippetKey,
@@ -8603,9 +8614,15 @@ registry.SnippetSave = SnippetOptionWidget.extend({
86038614
reloadEditor: true,
86048615
invalidateSnippetCache: true,
86058616
onSuccess: async () => {
8606-
const defaultSnippetName = _t("Custom %s", this.data.snippetName);
8617+
const defaultSnippetName = !isButton
8618+
? _t("Custom %s", this.data.snippetName)
8619+
: _t("Custom Button");
86078620
const targetCopyEl = this.$target[0].cloneNode(true);
86088621
delete targetCopyEl.dataset.name;
8622+
if (isButton) {
8623+
targetCopyEl.classList.remove("mb-2");
8624+
targetCopyEl.classList.add("o_snippet_drop_in_only", "s_custom_button");
8625+
}
86098626
// By the time onSuccess is called after request_save, the
86108627
// current widget has been destroyed and is orphaned, so this._rpc
86118628
// will not work as it can't trigger_up. For this reason, we need

addons/website/__manifest__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@
9595
'views/snippets/s_embed_code.xml',
9696
'views/snippets/s_website_form.xml',
9797
'views/snippets/s_searchbar.xml',
98+
'views/snippets/s_button.xml',
99+
'views/snippets/s_image.xml',
100+
'views/snippets/s_video.xml',
98101
'views/new_page_template_templates.xml',
99102
'views/website_views.xml',
100103
'views/website_pages_views.xml',
Lines changed: 31 additions & 0 deletions
Loading
Lines changed: 44 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)