Skip to content

Commit edf81c1

Browse files
bvr-odoorobinlej
authored andcommitted
[IMP] website, web_editor, *: allows selection of a snippet from a modal
test_website, website_* Enhance user snippet discovery by displaying actual previews instead of simplified thumbnails. With this commit, "meta-block" are added in the snippet menu. When a "meta-block" is dropped into the page, a modal appears prompting all the snippets available for that category. "Meta-blocks" can also be clicked directly instead of dragged onto the page. The snippet chosen in the "snippets dialog" will then be placed below the visible snippet on the page. Note that clicked "meta-blocks" do not get placed inside another snippet; for this, they must be dragged. For example, a "Text" snippet within a "Table Of Content" snippet. There is an exception for the "Popup" snippet, where if it is open, a snippet added by clicking on a "meta-block" will be placed correctly. Once a snippet is dropped it can be replaced by another one by clicking a new overlay "replace" button. Clicking it opens the same dialog as for adding new snippets. The link between a snippet and its meta-block (or snippet group) is made using an XML attribute in the "snippets" template. The "meta-block" has a "snippet-group" attribute with the group's name, and the linked snippets have a "group" attribute with the same group name. Note that snippets with a group attribute no longer need thumbnails. Snippets like the "countdown" or "form", which can be dropped both as sections and as inner content, are now defined twice in the "snippets" template. These snippets appear both in the inner content section of the sidebar and in the "snippets dialog". This commit also adds two ways to display certain dynamic snippets in the "snippets dialog": - An image can be added, which will be placed inside the snippet's section when displayed in the "snippets dialog". This image should be added with the new "t-image-preview" attribute in the "snippets" template (e.g., the "embed_code" snippet). - It is also possible to include elements in a snippet's template that should be present when the snippet is displayed in the "snippets dialog". These elements have the class "s_dialog_preview". When a snippet is added to the page from the "snippets dialog", elements with the class "s_dialog_preview" are removed from the snippet. task-3919405 closes odoo#166967 Related: odoo/enterprise#66150 Related: odoo/design-themes#817 Signed-off-by: Robin Lejeune (role) <[email protected]>
1 parent 38505d0 commit edf81c1

File tree

139 files changed

+1874
-2387
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+1874
-2387
lines changed

addons/test_website/static/tests/tours/custom_snippets.js

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import wTourUtils from "@website/js/tours/tour_utils";
1010
* -> customize banner (set text)
1111
* -> save banner as custom snippet
1212
* -> confirm save
13-
* -> ensure custom snippet is available
14-
* -> drag custom snippet
13+
* -> ensure custom snippet is available in the "add snippet" dialog
14+
* -> add custom snippet into the page
1515
* -> ensure block appears as banner
1616
* -> ensure block appears as custom banner
1717
* -> rename custom banner
@@ -29,80 +29,77 @@ wTourUtils.registerWebsitePreviewTour('test_custom_snippet', {
2929
...wTourUtils.dragNDrop({
3030
id: 's_banner',
3131
name: 'Banner',
32+
groupName: "Intro",
3233
}),
3334
{
34-
content: "customize snippet",
35+
content: "Customize snippet",
3536
trigger: ":iframe #wrapwrap .s_banner h1",
3637
run: "editor Test",
3738
},
3839
{
39-
content: "save custom snippet",
40+
content: "Save custom snippet",
4041
trigger: ".snippet-option-SnippetSave we-button",
4142
run: "click",
4243
},
4344
{
44-
content: "confirm reload",
45+
content: "Confirm reload",
4546
trigger: ".modal-dialog button:contains('Save and Reload')",
4647
run: "click",
4748
},
4849
{
49-
content: "ensure custom snippet appeared",
50-
trigger: "#oe_snippets.o_loaded .oe_snippet[name='Custom Banner']",
51-
run: function () {
52-
document.querySelector(
53-
"#oe_snippets .oe_snippet[name='Custom Banner'] .o_rename_btn"
54-
).style.display = "block";
55-
// hover is needed for rename button to appear
56-
},
50+
content: "Click on the Custom category block",
51+
trigger: "#oe_snippets .oe_snippet[name='Custom'].o_we_draggable .oe_snippet_thumbnail",
52+
run: "click",
5753
},
5854
{
59-
trigger: ".oe_snippet[name='Custom Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
55+
content: "Ensure custom snippet preview appeared in the dialog",
56+
trigger: ":iframe .o_snippet_preview_wrap[data-snippet-id='s_banner'] section[data-name='Custom Banner']",
6057
},
6158
{
62-
content: "rename custom snippet",
63-
trigger: ".oe_snippet[name='Custom Banner'] we-button.o_rename_btn",
59+
content: "Rename custom snippet",
60+
trigger: ":iframe .o_custom_snippet_wrap > .o_custom_snippet_edit > button",
6461
run: "click",
6562
},
6663
{
67-
content: "set name",
68-
trigger: ".oe_snippet[name='Custom Banner'] input",
64+
content: "Set name",
65+
trigger: ".o_rename_custom_snippet_dialog input[id='customSnippetName']",
6966
run: "edit Bruce Banner",
7067
},
7168
{
72-
content: "confirm rename",
73-
trigger: ".oe_snippet[name='Custom Banner'] we-button.o_we_confirm_btn",
69+
content: "Confirm rename",
70+
trigger: ".o_rename_custom_snippet_dialog footer .btn-primary",
7471
run: "click",
7572
},
76-
...wTourUtils.dragNDrop({ name: "Bruce Banner" }),
7773
{
78-
content: "ensure banner section exists",
74+
content: "Click on the 'Bruce Banner' snippet",
75+
trigger: ":iframe .o_snippet_preview_wrap[data-snippet-id='s_banner']:has(section[data-name='Bruce Banner'])",
76+
run: "click",
77+
},
78+
{
79+
content: "Ensure banner section exists",
7980
trigger: ":iframe #wrap section[data-name='Banner']",
8081
},
8182
{
82-
content: "ensure custom banner section exists",
83+
content: "Ensure custom banner section exists",
8384
trigger: ":iframe #wrap section[data-name='Bruce Banner']",
84-
run: function () {
85-
document.querySelector(
86-
"#oe_snippets .oe_snippet[name='Bruce Banner'] .o_delete_btn"
87-
).style.display = "block";
88-
// hover is needed for delete button to appear
89-
},
9085
},
9186
{
92-
trigger: ".oe_snippet[name='Bruce Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
87+
content: "Click on the Custom category block",
88+
trigger: "#oe_snippets .oe_snippet[name='Custom'].o_we_draggable .oe_snippet_thumbnail",
89+
run: "click",
9390
},
9491
{
95-
content: "delete custom snippet",
96-
trigger: ".oe_snippet[name='Bruce Banner'] we-button.o_delete_btn",
92+
content: "Delete custom snippet",
93+
trigger: ":iframe .o_custom_snippet_wrap > .o_custom_snippet_edit > button + button",
9794
run: "click",
9895
},
9996
{
100-
content: "confirm delete",
97+
content: "Confirm delete",
10198
trigger: ".modal-dialog button:contains('Yes')",
10299
run: "click",
103100
},
104101
{
105-
content: "ensure custom snippet disappeared",
106-
trigger: "#oe_snippets:not(:has(.oe_snippet[name='Bruce Banner']))",
102+
content: "Ensure custom snippet disappeared",
103+
trigger: ":iframe .o_add_snippets_preview:not(:has(section[data-name='Bruce Banner']))",
107104
},
108105
]);

addons/test_website/static/tests/tours/image_link.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ wTourUtils.registerWebsitePreviewTour('test_image_link', {
2525
...wTourUtils.dragNDrop({
2626
id: 's_text_image',
2727
name: 'Text - Image',
28+
groupName: "Content",
2829
}),
2930
...selectImageSteps,
3031
{

addons/test_website/static/tests/tours/image_upload_progress.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,27 @@ const patchMediaDialog = () => patch(FileSelectorControlPanel.prototype, {
3838

3939
let unpatchMediaDialog = null;
4040

41-
const setupSteps = [{
42-
content: "reload to load patch",
43-
trigger: ".o_website_preview",
44-
run: () => {
45-
unpatchMediaDialog = patchMediaDialog();
46-
},
47-
}, {
48-
content: "drop a snippet",
49-
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
50-
run: "drag_and_drop :iframe #wrap",
51-
},
52-
{
53-
trigger: "body.editor_has_snippets",
54-
},
55-
{
56-
content: "drop a snippet",
57-
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
58-
run: "drag_and_drop :iframe #wrap",
59-
}];
41+
const setupSteps = function () {
42+
return [
43+
{
44+
content: "reload to load patch",
45+
trigger: ".o_website_preview",
46+
run: () => {
47+
unpatchMediaDialog = patchMediaDialog();
48+
},
49+
},
50+
...wTourUtils.dragNDrop({
51+
id: "s_text_image",
52+
name: "Text - Image",
53+
groupName: "Content",
54+
}),
55+
...wTourUtils.dragNDrop({
56+
id: "s_image_gallery",
57+
name: "Image Gallery",
58+
groupName: "Images",
59+
})
60+
];
61+
};
6062

6163
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg, .webp";
6264

@@ -65,7 +67,7 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
6567
test: true,
6668
edition: true,
6769
}, () => [
68-
...setupSteps,
70+
...setupSteps(),
6971
// 1. Check multi image upload
7072
{
7173
content: "click on dropped snippet",
@@ -207,7 +209,7 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress_unsplash', {
207209
test: true,
208210
edition: true,
209211
}, () => [
210-
...setupSteps,
212+
...setupSteps(),
211213
// 1. Check multi image upload
212214
{
213215
content: "click on dropped snippet",

addons/test_website/static/tests/tours/replace_media.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ wTourUtils.registerWebsitePreviewTour('test_replace_media', {
3535
},
3636
...wTourUtils.dragNDrop({
3737
name: 'Title - Image',
38-
id: 's_picture'
38+
id: 's_picture',
39+
groupName: "Images",
3940
}),
4041
{
4142
content: "select image",

addons/test_website/static/tests/tours/reset_views.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ wTourUtils.registerWebsitePreviewTour(
2121
},
2222
() => [
2323
{
24-
content: "drop a snippet",
25-
trigger: ".oe_snippet .oe_snippet_thumbnail[data-snippet=s_cover]",
24+
content: "Drag the Intro snippet group and drop it in #oe_structure_test_website_page.",
25+
trigger: '#oe_snippets .oe_snippet[name="Intro"] .oe_snippet_thumbnail:not(.o_we_already_dragging)',
2626
// id starting by 'oe_structure..' will actually create an inherited view
2727
run: "drag_and_drop :iframe #oe_structure_test_website_page",
2828
},
29+
{
30+
content: "Click on the s_cover snippet.",
31+
trigger: ':iframe .o_snippet_preview_wrap[data-snippet-id="s_cover"]',
32+
run: "click",
33+
},
2934
...wTourUtils.clickOnSave(),
3035
// 2. Edit that COW'd view in the HTML editor to break it.
3136
{

addons/test_website/static/tests/tours/restricted_editor.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ wTourUtils.registerWebsitePreviewTour('test_restricted_editor_only', {
5757
...wTourUtils.clickOnEditAndWaitEditMode(),
5858
{
5959
content: "Check icons cannot be dragged",
60-
trigger: "#oe_snippets .oe_snippet[name='Banner'].o_disabled",
60+
trigger: "#oe_snippets .oe_snippet[name='Intro'].o_disabled",
6161
},
6262
...wTourUtils.clickOnSave(),
6363
...switchTo('fr'),
@@ -70,7 +70,7 @@ wTourUtils.registerWebsitePreviewTour('test_restricted_editor_only', {
7070
...wTourUtils.clickOnEditAndWaitEditMode(),
7171
{
7272
content: "Check icons cannot be dragged",
73-
trigger: "#oe_snippets .oe_snippet[name='Banner'].o_disabled",
73+
trigger: "#oe_snippets .oe_snippet[name='Intro'].o_disabled",
7474
},
7575
...switchTo('fr'),
7676
...translate,
@@ -86,7 +86,7 @@ wTourUtils.registerWebsitePreviewTour('test_restricted_editor_test_admin', {
8686
...wTourUtils.clickOnEditAndWaitEditMode(),
8787
{
8888
content: "Check icons cannot be dragged",
89-
trigger: "#oe_snippets .oe_snippet[name='Banner'].o_disabled",
89+
trigger: "#oe_snippets .oe_snippet[name='Intro'].o_disabled",
9090
},
9191
...wTourUtils.clickOnSave(),
9292
...switchTo('fr'),
@@ -99,13 +99,18 @@ wTourUtils.registerWebsitePreviewTour('test_restricted_editor_test_admin', {
9999
...wTourUtils.clickOnEditAndWaitEditMode(),
100100
{
101101
content: "Check icons can be dragged",
102-
trigger: "#oe_snippets .oe_snippet[name='Banner']:not(.o_disabled)",
102+
trigger: "#oe_snippets .oe_snippet[name='Intro']:not(.o_disabled)",
103103
},
104104
{
105-
content: "Drag the banner block",
106-
trigger: `#oe_snippets .oe_snippet[name="Banner"].o_we_draggable .oe_snippet_thumbnail:not(.o_we_already_dragging)`,
105+
content: "Drag the Intro snippet group",
106+
trigger: '#oe_snippets .oe_snippet[name="Intro"] .oe_snippet_thumbnail:not(.o_we_already_dragging)',
107107
run: "drag_and_drop :iframe [data-oe-expression='record.website_description']",
108108
},
109+
{
110+
content: "Click on the s_banner snippet in the dialog",
111+
trigger: ':iframe .o_snippet_preview_wrap[data-snippet-id="s_banner"]',
112+
run: "click",
113+
},
109114
{
110115
content: "Change name",
111116
trigger: ":iframe [data-oe-expression='record.name']",

addons/web_editor/__manifest__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,14 @@
193193
# widgets & plugins
194194
'web_editor/static/src/js/wysiwyg/widgets/**/*',
195195
'web_editor/static/src/js/editor/toolbar.js',
196+
'web_editor/static/src/js/editor/add_snippet_dialog.js',
196197

197198
# Launcher
198199
'web_editor/static/src/js/wysiwyg/wysiwyg_jquery_extention.js',
199200
'web_editor/static/src/js/wysiwyg/wysiwyg.js',
200201
'web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js',
201202

203+
'web_editor/static/src/xml/add_snippet_dialog.xml',
202204
'web_editor/static/src/xml/editor.xml',
203205
'web_editor/static/src/xml/grid_layout.xml',
204206
'web_editor/static/src/xml/snippets.xml',

addons/web_editor/models/ir_qweb_fields.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,22 @@ def _compile_directive_snippet(self, el, compile_context, indent):
7979
view = self.env['ir.ui.view']._get(key).sudo()
8080
name = el.attrib.pop('string', view.name)
8181
thumbnail = el.attrib.pop('t-thumbnail', "oe-thumbnail")
82+
image_preview = el.attrib.pop('t-image-preview', None)
8283
# Forbid sanitize contains the specific reason:
8384
# - "true": always forbid
8485
# - "form": forbid if forms are sanitized
8586
forbid_sanitize = el.attrib.pop('t-forbid-sanitize', None)
86-
div = '<div name="%s" data-oe-type="snippet" data-oe-thumbnail="%s" data-oe-snippet-id="%s" data-oe-keywords="%s" %s>' % (
87+
snippet_group = el.attrib.pop('snippet-group', None)
88+
group = el.attrib.pop('group', None)
89+
div = '<div name="%s" data-oe-type="snippet" data-o-image-preview="%s" data-oe-thumbnail="%s" data-oe-snippet-id="%s" data-oe-keywords="%s" %s %s %s>' % (
8790
escape(pycompat.to_text(name)),
91+
escape(pycompat.to_text(image_preview)),
8892
escape(pycompat.to_text(thumbnail)),
8993
escape(pycompat.to_text(view.id)),
9094
escape(pycompat.to_text(el.findtext('keywords'))),
9195
f'data-oe-forbid-sanitize="{forbid_sanitize}"' if forbid_sanitize else '',
96+
f'data-o-snippet-group="{snippet_group}"' if snippet_group else '',
97+
f'data-o-group="{group}"' if group else '',
9298
)
9399
self._append_text(div, compile_context)
94100
code = self._compile_node(el, compile_context, indent)
@@ -104,15 +110,19 @@ def _compile_directive_snippet_call(self, el, compile_context, indent):
104110
def _compile_directive_install(self, el, compile_context, indent):
105111
key = el.attrib.pop('t-install')
106112
thumbnail = el.attrib.pop('t-thumbnail', 'oe-thumbnail')
113+
image_preview = el.attrib.pop('t-image-preview', None)
114+
group = el.attrib.pop('group', None)
107115
if self.env.user.has_group('base.group_system'):
108116
module = self.env['ir.module.module'].search([('name', '=', key)])
109117
if not module or module.state == 'installed':
110118
return []
111119
name = el.attrib.get('string') or 'Snippet'
112-
div = '<div name="%s" data-oe-type="snippet" data-module-id="%s" data-oe-thumbnail="%s"><section/></div>' % (
120+
div = '<div name="%s" data-oe-type="snippet" data-module-id="%s" data-o-image-preview="%s" data-oe-thumbnail="%s" %s><section/></div>' % (
113121
escape(pycompat.to_text(name)),
114122
module.id,
115-
escape(pycompat.to_text(thumbnail))
123+
escape(pycompat.to_text(image_preview)),
124+
escape(pycompat.to_text(thumbnail)),
125+
f'data-o-group="{group}"' if group else '',
116126
)
117127
self._append_text(div, compile_context)
118128
return []

0 commit comments

Comments
 (0)