summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/img/curved_arrow.svg14
-rw-r--r--addons/web_editor/static/src/js/common/utils.js13
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.editor.js7
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js121
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/media.js195
-rw-r--r--addons/web_editor/static/src/scss/web_editor.common.scss25
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg.scss9
-rw-r--r--addons/web_editor/static/src/xml/wysiwyg.xml39
8 files changed, 398 insertions, 25 deletions
diff --git a/addons/web_editor/static/src/img/curved_arrow.svg b/addons/web_editor/static/src/img/curved_arrow.svg
new file mode 100644
index 00000000000..a8ed9acd242
--- /dev/null
+++ b/addons/web_editor/static/src/img/curved_arrow.svg
@@ -0,0 +1,14 @@
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1144 1280">
+<g transform="translate(0,1280) scale(0.1,-0.1)" fill="#000000" stroke="none">
+<path d="M7166 11004 c-603 -901 -1094 -1639 -1092 -1641 1 -2 369 42 817 97
+448 55 825 101 838 103 30 3 26 14 86 -278 131 -634 218 -1317 255 -2010 13
+-246 13 -898 0 -1135 -71 -1262 -367 -2292 -883 -3065 -162 -242 -308 -418
+-541 -650 -661 -658 -1465 -1094 -2581 -1400 -1069 -293 -2283 -471 -3660
+-536 -132 -6 -242 -13 -244 -15 -6 -5 16 -190 30 -255 9 -43 13 -47 47 -53
+108 -17 1148 -2 1627 24 2760 152 4778 866 6094 2155 506 496 887 1018 1216
+1670 444 877 715 1860 819 2970 35 365 41 510 41 1000 0 581 -17 880 -76 1355
+-19 158 -65 452 -74 477 -5 14 -54 7 730 104 363 44 662 83 665 85 2 3 0 8 -6
+12 -6 4 -682 594 -1503 1312 -822 718 -1497 1306 -1501 1308 -4 2 -501 -734
+-1104 -1634z"/>
+</g>
+</svg>
diff --git a/addons/web_editor/static/src/js/common/utils.js b/addons/web_editor/static/src/js/common/utils.js
index c66bb75a47c..6e92bd6955b 100644
--- a/addons/web_editor/static/src/js/common/utils.js
+++ b/addons/web_editor/static/src/js/common/utils.js
@@ -32,6 +32,18 @@ const CSS_UNITS_CONVERSION = {
'rem-px': () => _computePxByRem(),
'px-rem': () => _computePxByRem(true),
};
+/**
+ * Colors of the default palette, used for substitution in shapes/illustrations.
+ * key: number of the color in the palette (ie, o-color-<1-5>)
+ * value: color hex code
+ */
+const DEFAULT_PALETTE = {
+ '1': '#3AADAA',
+ '2': '#7C6576',
+ '3': '#F6F6F6',
+ '4': '#FFFFFF',
+ '5': '#383E45',
+};
/**
* Computes the number of "px" needed to make a "rem" unit. Subsequent calls
@@ -218,6 +230,7 @@ function _getBgImageURL(el) {
return {
CSS_SHORTHANDS: CSS_SHORTHANDS,
CSS_UNITS_CONVERSION: CSS_UNITS_CONVERSION,
+ DEFAULT_PALETTE: DEFAULT_PALETTE,
computePxByRem: _computePxByRem,
convertValueToUnit: _convertValueToUnit,
convertNumericToUnit: _convertNumericToUnit,
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 4254afe270f..37eda9910d1 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -10,6 +10,7 @@ var options = require('web_editor.snippets.options');
var Wysiwyg = require('web_editor.wysiwyg');
const {ColorPaletteWidget} = require('web_editor.ColorPalette');
const SmoothScrollOnDrag = require('web/static/src/js/core/smooth_scroll_on_drag.js');
+const {getCSSVariableValue} = require('web_editor.utils');
var _t = core._t;
@@ -1853,6 +1854,12 @@ var SnippetsMenu = Widget.extend({
}
$toInsert = $baseBody.clone();
+ // Color-customize dynamic SVGs in dropped snippets with current theme colors.
+ [...$toInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => {
+ const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin);
+ colorCustomizedURL.searchParams.set('c1', getCSSVariableValue('o-color-1'));
+ dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search;
+ });
if (!$selectorSiblings.length && !$selectorChildren.length) {
console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in");
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 8e236bb93d1..1a0e8d34935 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -3578,6 +3578,30 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
this.$target.trigger('background_changed', [previewMode]);
}
},
+ /**
+ * Changes the main color of dynamic SVGs.
+ *
+ * @see this.selectClass for parameters
+ */
+ async dynamicColor(previewMode, widgetValue, params) {
+ const currentSrc = getBgImageURL(this.$target[0]);
+ switch (previewMode) {
+ case true:
+ this.previousSrc = currentSrc;
+ break;
+ case 'reset':
+ this.$target.css('background-image', `url('${this.previousSrc}')`);
+ return;
+ }
+ const newURL = new URL(currentSrc, window.location.origin);
+ newURL.searchParams.set('c1', normalizeColor(widgetValue));
+ const src = newURL.pathname + newURL.search;
+ await loadImage(src);
+ this.$target.css('background-image', `url('${src}')`);
+ if (!previewMode) {
+ this.previousSrc = src;
+ }
+ },
//--------------------------------------------------------------------------
// Public
@@ -3608,8 +3632,21 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
* @override
*/
_computeWidgetState: function (methodName) {
- if (methodName === 'background') {
- return getBgImageURL(this.$target[0]);
+ switch (methodName) {
+ case 'background':
+ return getBgImageURL(this.$target[0]);
+ case 'dynamicColor':
+ return new URL(getBgImageURL(this.$target[0]), window.location.origin).searchParams.get('c1');
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _computeWidgetVisibility(widgetName, params) {
+ if (widgetName === 'dynamic_color_opt') {
+ const src = new URL(getBgImageURL(this.$target[0]), window.location.origin);
+ return src.origin === window.location.origin && src.pathname.startsWith('/web_editor/shape/');
}
return this._super(...arguments);
},
@@ -4505,6 +4542,86 @@ registry.SnippetSave = SnippetOptionWidget.extend({
},
});
+/**
+ * Handles the dynamic colors for dynamic SVGs.
+ */
+registry.DynamicSvg = SnippetOptionWidget.extend({
+ /**
+ * @override
+ */
+ start() {
+ this.$target.on('image_changed.DynamicSvg', this._onImageChanged.bind(this));
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ destroy() {
+ this.$target.off('.DynamicSvg');
+ return this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Sets the dynamic SVG's dynamic color.
+ *
+ * @see this.selectClass for params
+ */
+ async color(previewMode, widgetValue, params) {
+ const target = this.$target[0];
+ switch (previewMode) {
+ case true:
+ this.previousSrc = target.getAttribute('src');
+ break;
+ case 'reset':
+ target.src = this.previousSrc;
+ return;
+ }
+ const newURL = new URL(target.src, window.location.origin);
+ newURL.searchParams.set('c1', normalizeColor(widgetValue));
+ const src = newURL.pathname + newURL.search;
+ await loadImage(src);
+ target.src = src;
+ if (!previewMode) {
+ this.previousSrc = src;
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _computeWidgetState(methodName, params) {
+ switch (methodName) {
+ case 'color':
+ return new URL(this.$target[0].src, window.location.origin).searchParams.get('c1');
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _computeVisibility(methodName, params) {
+ return this.$target.is("img[src^='/web_editor/shape/']");
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _onImageChanged(methodName, params) {
+ return this.updateUI();
+ },
+});
return {
SnippetOptionWidget: SnippetOptionWidget,
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
index d405d647ce7..3134f55b373 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
@@ -10,6 +10,7 @@ var utils = require('web.utils');
var Widget = require('web.Widget');
var session = require('web.session');
const {removeOnImageChangeAttrs} = require('web_editor.image_processing');
+const {getCSSVariableValue, DEFAULT_PALETTE} = require('web_editor.utils');
var QWeb = core.qweb;
var _t = core._t;
@@ -106,6 +107,7 @@ var SearchableMediaWidget = MediaWidget.extend({
* @private
*/
_onSearchInput: function (ev) {
+ this.attachments = [];
this.search($(ev.currentTarget).val() || '').then(() => this._renderThumbnails());
this.hasSearched = true;
},
@@ -128,6 +130,7 @@ var FileWidget = SearchableMediaWidget.extend({
IMAGE_MIMETYPES: ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png', 'image/svg+xml'],
NUMBER_OF_ATTACHMENTS_TO_DISPLAY: 30,
+ MAX_DB_ATTACHMENTS: 5,
/**
* @constructor
@@ -144,6 +147,8 @@ var FileWidget = SearchableMediaWidget.extend({
this.attachments = [];
this.selectedAttachments = [];
+ this.libraryMedia = [];
+ this.selectedMedia = [];
this._onUploadURLButtonClick = dom.makeAsyncHandler(this._onUploadURLButtonClick);
},
@@ -204,7 +209,6 @@ var FileWidget = SearchableMediaWidget.extend({
* @override
*/
search: function (needle) {
- this.attachments = [];
this.needle = needle;
return this.fetchAttachments(this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY, 0);
},
@@ -231,6 +235,12 @@ var FileWidget = SearchableMediaWidget.extend({
Array.prototype.splice.apply(this.attachments, [offset, attachments.length].concat(attachments));
});
},
+ /**
+ * Computes whether there is content to display in the template.
+ */
+ hasContent() {
+ return this.attachments.length;
+ },
//--------------------------------------------------------------------------
// Private
@@ -292,7 +302,7 @@ var FileWidget = SearchableMediaWidget.extend({
domain.push(['name', 'ilike', needle]);
}
domain.push('!', ['name', '=like', '%.crop']);
- domain.push('|', ['type', '=', 'binary'], ['url', '!=', false]);
+ domain.push('|', ['type', '=', 'binary'], '!', ['url', '=like', '/%/static/%']);
return domain;
},
/**
@@ -357,6 +367,7 @@ var FileWidget = SearchableMediaWidget.extend({
this._highlightSelected();
// adapt load more
+ this.$('.o_we_load_more').toggleClass('d-none', !this.hasContent());
var noLoadMoreButton = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY >= this.attachments.length;
var noMoreImgToLoad = this.numberOfAttachmentsToDisplay >= this.attachments.length;
this.$('.o_load_done_msg').toggleClass('d-none', noLoadMoreButton || !noMoreImgToLoad);
@@ -367,11 +378,36 @@ var FileWidget = SearchableMediaWidget.extend({
* @returns {Promise}
*/
_save: async function () {
+ // Create all media-library attachments.
+ const toSave = Object.fromEntries(this.selectedMedia.map(media => [
+ media.id, {
+ query: media.query || '',
+ is_dynamic_svg: !!media.isDynamicSVG,
+ }
+ ]));
+ let mediaAttachments = [];
+ if (Object.keys(toSave).length !== 0) {
+ mediaAttachments = await this._rpc({
+ route: '/web_editor/save_library_media',
+ params: {
+ media: toSave,
+ },
+ });
+ }
+ const selected = this.selectedAttachments.concat(mediaAttachments).map(attachment => {
+ // Color-customize dynamic SVGs with the primary theme color
+ if (attachment.image_src.startsWith('/web_editor/shape/')) {
+ const colorCustomizedURL = new URL(attachment.image_src, window.location.origin);
+ colorCustomizedURL.searchParams.set('c1', getCSSVariableValue('o-color-1'));
+ attachment.image_src = colorCustomizedURL.pathname + colorCustomizedURL.search;
+ }
+ return attachment;
+ });
if (this.options.multiImages) {
- return this.selectedAttachments;
+ return selected;
}
- var img = this.selectedAttachments[0];
+ const img = selected[0];
if (!img || !img.id) {
return this.media;
}
@@ -435,22 +471,30 @@ var FileWidget = SearchableMediaWidget.extend({
* and to close the media dialog
* @private
*/
- _selectAttachement: function (attachment, save) {
+ _selectAttachement: function (attachment, save, {type = 'attachment'} = {}) {
+ const possibleProps = {
+ 'attachment': 'selectedAttachments',
+ 'media': 'selectedMedia'
+ };
+ const prop = possibleProps[type];
if (this.options.multiImages) {
// if the clicked attachment is already selected then unselect it
// unless it was a save request (then keep the current selection)
- var index = this.selectedAttachments.indexOf(attachment);
+ const index = this[prop].indexOf(attachment);
if (index !== -1) {
if (!save) {
- this.selectedAttachments.splice(index, 1);
+ this[prop].splice(index, 1);
}
} else {
// if the clicked attachment is not selected, add it to selected
- this.selectedAttachments.push(attachment);
+ this[prop].push(attachment);
}
} else {
+ Object.values(possibleProps).forEach(prop => {
+ this[prop] = [];
+ });
// select the clicked attachment
- this.selectedAttachments = [attachment];
+ this[prop] = [attachment];
}
this._highlightSelected();
if (save) {
@@ -481,9 +525,15 @@ var FileWidget = SearchableMediaWidget.extend({
* @private
*/
_onAttachmentClick: function (ev) {
- var $attachment = $(ev.currentTarget);
- var attachment = _.find(this.attachments, {id: $attachment.data('id')});
- this._selectAttachement(attachment, !this.options.multiImages);
+ const attachment = ev.currentTarget;
+ const {id: attachmentID, mediaId} = attachment.dataset;
+ if (attachmentID) {
+ const attachment = this.attachments.find(attachment => attachment.id === parseInt(attachmentID));
+ this._selectAttachement(attachment, !this.options.multiImages);
+ } else if (mediaId) {
+ const media = this.libraryMedia.find(media => media.id === parseInt(mediaId));
+ this._selectAttachement(media, !this.options.multiImages, {type: 'media'});
+ }
},
/**
* Handles change of the file input: create attachments with the new files
@@ -639,6 +689,7 @@ var FileWidget = SearchableMediaWidget.extend({
* @override
*/
_onSearchInput: function () {
+ this.attachments = [];
this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;
this._super.apply(this, arguments);
},
@@ -652,6 +703,7 @@ var ImageWidget = FileWidget.extend({
existingAttachmentsTemplate: 'wysiwyg.widgets.image.existing.attachments',
events: Object.assign({}, FileWidget.prototype.events, {
'change input.o_we_show_optimized': '_onShowOptimizedChange',
+ 'change .o_we_search_select': '_onSearchSelect',
}),
MIN_ROW_HEIGHT: 128,
@@ -659,6 +711,7 @@ var ImageWidget = FileWidget.extend({
* @constructor
*/
init: function (parent, media, options) {
+ this.searchService = 'all';
options = _.extend({
accept: 'image/*',
mimetypeDomain: [['mimetype', 'in', this.IMAGE_MIMETYPES]],
@@ -681,6 +734,60 @@ var ImageWidget = FileWidget.extend({
this.el.removeEventListener('load', this._onAttachmentImageLoad, true);
return this._super(...arguments);
},
+ /**
+ * @override
+ */
+ async fetchAttachments(number, offset) {
+ if (this.needle && this.searchService !== 'database') {
+ number = this.MAX_DB_ATTACHMENTS;
+ offset = 0;
+ }
+ const result = await this._super(number, offset);
+ // Color-substitution for dynamic SVG attachment
+ const primaryColor = getCSSVariableValue('o-color-1');
+ this.attachments.forEach(attachment => {
+ if (attachment.image_src.startsWith('/')) {
+ const newURL = new URL(attachment.image_src, window.location.origin);
+ // Set the main color of dynamic SVGs to o-color-1
+ if (attachment.image_src.startsWith('/web_editor/shape/')) {
+ newURL.searchParams.set('c1', primaryColor);
+ } else {
+ // Set height so that db images load faster
+ newURL.searchParams.set('height', 2 * this.MIN_ROW_HEIGHT);
+ }
+ attachment.thumbnail_src = newURL.pathname + newURL.search;
+ }
+ });
+ if (this.needle) {
+ try {
+ const response = await this._rpc({
+ route: '/web_editor/media_library_search',
+ params: {
+ 'query': this.needle,
+ 'offset': this.libraryMedia.length,
+ },
+ });
+ const newMedia = response.media;
+ this.nbMediaResults = response.results;
+ this.libraryMedia.push(...newMedia);
+ } catch (e) {
+ // Either API endpoint doesn't exist or is misconfigured.
+ console.error(`Couldn't reach API endpoint.`);
+ }
+ }
+ return result;
+ },
+ /**
+ * @override
+ */
+ hasContent() {
+ if (this.searchService === 'all') {
+ return this._super(...arguments) || this.libraryMedia.length;
+ } else if (this.searchService === 'media-library') {
+ return !!this.libraryMedia.length;
+ }
+ return this._super(...arguments);
+ },
//--------------------------------------------------------------------------
// Private
@@ -708,7 +815,7 @@ var ImageWidget = FileWidget.extend({
this.$('.o_existing_attachment_cell').addClass('d-none');
// Replace images that had been previously loaded if any to prevent scroll resetting to top
alreadyLoaded.each((index, el) => {
- const toReplace = this.$(`.o_existing_attachment_cell[data-id="${el.dataset.id}"]`);
+ const toReplace = this.$(`.o_existing_attachment_cell[data-id="${el.dataset.id}"], .o_existing_attachment_cell[data-media-id="${el.dataset.mediaId}"]`);
if (toReplace.length) {
toReplace.replaceWith(el);
}
@@ -720,13 +827,23 @@ var ImageWidget = FileWidget.extend({
flexGrow: placeholderWidth,
flexBasis: placeholderWidth,
});
+ if (this.needle && ['media-library', 'all'].includes(this.searchService)) {
+ const noMoreImgToLoad = this.libraryMedia.length === this.nbMediaResults;
+ const noLoadMoreButton = noMoreImgToLoad && this.libraryMedia.length <= 15;
+ this.$('.o_load_done_msg').toggleClass('d-none', noLoadMoreButton || !noMoreImgToLoad);
+ this.$('.o_load_more').toggleClass('d-none', noMoreImgToLoad);
+ }
},
/**
* @override
*/
_renderExisting: function (attachments) {
+ if (this.needle && this.searchService !== 'database') {
+ attachments = attachments.slice(0, this.MAX_DB_ATTACHMENTS);
+ }
return QWeb.render(this.existingAttachmentsTemplate, {
attachments: attachments,
+ libraryMedia: this.libraryMedia,
widget: this,
});
},
@@ -738,6 +855,16 @@ var ImageWidget = FileWidget.extend({
_toggleOptimized: function (value) {
this.$('.o_we_attachment_optimized').each((i, cell) => cell.style.setProperty('display', value ? null : 'none', 'important'));
},
+ /**
+ * @override
+ */
+ _highlightSelected: function () {
+ this._super(...arguments);
+ this.selectedMedia.forEach(media => {
+ this.$(`.o_existing_attachment_cell[data-media-id=${media.id}]`)
+ .addClass("o_we_attachment_selected");
+ });
+ },
//--------------------------------------------------------------------------
// Handlers
@@ -746,12 +873,37 @@ var ImageWidget = FileWidget.extend({
/**
* @override
*/
- _onAttachmentImageLoad: function (ev) {
+ _onAttachmentImageLoad: async function (ev) {
const img = ev.target;
const cell = img.closest('.o_existing_attachment_cell');
if (!cell) {
return;
}
+ if (cell.dataset.mediaId && !img.src.startsWith('blob')) {
+ const mediaUrl = img.src;
+ try {
+ const response = await fetch(mediaUrl);
+ if (response.headers.get('content-type') === 'image/svg+xml') {
+ const svg = await response.text();
+ const colorRegex = new RegExp(DEFAULT_PALETTE['1'], 'gi');
+ if (colorRegex.test(svg)) {
+ const fileName = mediaUrl.split('/').pop();
+ const file = new File([svg.replace(colorRegex, getCSSVariableValue('o-color-1'))], fileName, {
+ type: "image/svg+xml",
+ });
+ img.src = URL.createObjectURL(file);
+ const media = this.libraryMedia.find(media => media.id === parseInt(cell.dataset.mediaId));
+ if (media) {
+ media.isDynamicSVG = true;
+ }
+ // We changed the src: wait for the next load event to do the styling
+ return;
+ }
+ }
+ } catch (e) {
+ console.error('CORS is misconfigured on the API server, image will be treated as non-dynamic.');
+ }
+ }
const aspectRatio = img.naturalWidth / img.naturalHeight;
const width = aspectRatio * this.MIN_ROW_HEIGHT;
cell.style.flexGrow = width;
@@ -766,6 +918,21 @@ var ImageWidget = FileWidget.extend({
_onShowOptimizedChange: function (ev) {
this._toggleOptimized(ev.target.checked);
},
+ /**
+ * @override
+ */
+ _onSearchSelect: function (ev) {
+ const {value} = ev.target;
+ this.searchService = value;
+ this.$('.o_we_search').trigger('input');
+ },
+ /**
+ * @private
+ */
+ _onSearchInput: function (ev) {
+ this.libraryMedia = [];
+ this._super(...arguments);
+ },
});
diff --git a/addons/web_editor/static/src/scss/web_editor.common.scss b/addons/web_editor/static/src/scss/web_editor.common.scss
index 0cd22143290..ea7aee164e6 100644
--- a/addons/web_editor/static/src/scss/web_editor.common.scss
+++ b/addons/web_editor/static/src/scss/web_editor.common.scss
@@ -469,6 +469,31 @@ a.o_underline {
}
}
+.o_we_search_prompt {
+ position: relative;
+ min-height: 250px;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ & > h2 {
+ max-width: 500px;
+ text-align: center;
+ }
+
+ &::before {
+ content: "";
+ @include o-position-absolute($top: 0, $right: 50px);
+ width: 100px;
+ height: 150px;
+ opacity: .5;
+ background-image: url('/web_editor/static/src/img/curved_arrow.svg');
+ background-size: 100%;
+ background-repeat: no-repeat;
+ }
+}
+
@include media-breakpoint-down(sm) {
odoo-wysiwyg-container {
.panel-heading.note-toolbar {
diff --git a/addons/web_editor/static/src/scss/wysiwyg.scss b/addons/web_editor/static/src/scss/wysiwyg.scss
index 6eaa65c6e56..39fa326c737 100644
--- a/addons/web_editor/static/src/scss/wysiwyg.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg.scss
@@ -394,6 +394,15 @@ img.o_we_selected_image {
img::selection {
background: transparent;
}
+.o_we_media_author {
+ font-size: 11px;
+ @include o-position-absolute($bottom: 0, $left: 0, $right: 0);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: center;
+ background-color: rgba(255, 255, 255, .7);
+}
@include media-breakpoint-down(md) {
#web_editor-top-edit {
diff --git a/addons/web_editor/static/src/xml/wysiwyg.xml b/addons/web_editor/static/src/xml/wysiwyg.xml
index 714d0913e5d..96823fde263 100644
--- a/addons/web_editor/static/src/xml/wysiwyg.xml
+++ b/addons/web_editor/static/src/xml/wysiwyg.xml
@@ -125,7 +125,7 @@
<t t-call="wysiwyg.widgets.files.submenu"/>
<div class="form-text o_we_error_text"/>
<div class="o_we_existing_attachments"/>
- <div class="mt-4 text-center mx-auto">
+ <div class="mt-4 text-center mx-auto o_we_load_more">
<button class="btn btn-primary o_load_more d-none" type="button">Load more...</button>
<div class="mt-4 o_load_done_msg d-none">
<span><i>All images have been loaded</i></span>
@@ -170,12 +170,17 @@
<t t-set="searchPlaceholder">Search an image</t>
<t t-set="urlWarningTitle">The URL does not contain any image. The file will be added in the document section.</t>
<div class="d-flex justify-content-end flex-grow-1 pr-3">
- <div t-attf-class="custom-control custom-switch #{__debug__ or 'd-none'}">
+ <div t-attf-class="custom-control custom-switch #{__debug__ and 'd-flex' or 'd-none'} align-items-center mr-2">
<input class="o_we_show_optimized ml-2 custom-control-input" type="checkbox" id="o_we_show_optimized_switch"/>
<label class="custom-control-label" for="o_we_show_optimized_switch">
Show optimized images
</label>
</div>
+ <select class="custom-select o_we_search_select">
+ <option value="all">All</option>
+ <option value="database">My Images</option>
+ <option value="media-library">Illustrations</option>
+ </select>
</div>
</t>
</t>
@@ -288,14 +293,22 @@
<t t-name="wysiwyg.widgets.image.existing.attachments">
<div class="o_we_existing_attachments o_we_images d-flex flex-wrap w-100 justify-content-between align-items-stretch my-0">
- <div t-if="!attachments.length" class="o_nocontent_help">
- <p class="o_empty_folder_image">No images found.</p>
- <p class="o_empty_folder_subtitle">You can upload images with the button located in the top left of the screen.</p>
- </div>
+ <t t-if="!widget.hasContent()">
+ <div t-if="widget.needle" class="o_nocontent_help">
+ <p class="o_empty_folder_image">No images found.</p>
+ <p class="o_empty_folder_subtitle">You can upload images with the button located in the top left of the screen.</p>
+ </div>
+ <div t-else="" class="o_we_search_prompt">
+ <h2>Get the perfect image by searching in our library of copyright free photos and illustrations.</h2>
+ </div>
+ </t>
<t t-else="">
- <t t-foreach="attachments" t-as="attachment">
+ <t t-if="['all', 'database'].includes(widget.searchService)" t-foreach="attachments" t-as="attachment">
<t t-call="wysiwyg.widgets.image.existing.attachment"/>
</t>
+ <t t-if="['all', 'media-library'].includes(widget.searchService)" t-foreach="libraryMedia" t-as="media">
+ <t t-call="wysiwyg.widgets.image.library_media"/>
+ </t>
<!-- 20 placeholders is just enough for a 5K screen, change this if ImageWidget.MIN_ROW_HEIGHT changes -->
<t t-foreach="20">
<div class="o_we_attachment_placeholder"/>
@@ -309,13 +322,21 @@
<div t-attf-class="o_existing_attachment_cell position-relative bg-light #{isOptimized and 'o_we_attachment_optimized d-none' or ''} align-items-center justify-content-center" t-att-data-id="attachment.id">
<t t-call="wysiwyg.widgets.file.existing.remove"/>
<div class="o_we_media_dialog_img_wrapper">
- <!-- 256 is 2 * ImageWidget.MIN_ROW_HEIGHT, hopefully the highest display width of any image -->
- <img class="img img-fluid o_we_attachment_highlight" t-attf-src="#{attachment.image_src}?height=256" t-att-alt="attachment.name" t-att-title="attachment.name"/>
+ <img class="img img-fluid o_we_attachment_highlight" t-attf-src="#{attachment.thumbnail_src or attachment.image_src}" t-att-alt="attachment.name" t-att-title="attachment.name"/>
</div>
<span t-if="isOptimized" class="badge badge-success">Optimized</span>
</div>
</t>
+ <t t-name="wysiwyg.widgets.image.library_media">
+ <div t-attf-class="o_existing_attachment_cell position-relative bg-light align-items-center justify-content-center o_library_media_cell" t-att-data-media-id="media.id">
+ <div class="o_we_media_dialog_img_wrapper">
+ <img class="img img-fluid o_we_attachment_highlight" t-attf-src="#{media.thumbnail_url}" t-att-title="media.tooltip or ''" crossorigin="anonymous"/>
+ <a t-if="media.author" class="o_we_media_author" t-att-href="media.author_link" target="_blank" t-esc="media.author"/>
+ </div>
+ </div>
+ </t>
+
<t t-name="wysiwyg.widgets.document.existing.attachments">
<div class="o_we_existing_attachments o_we_documents">
<div t-if="!attachments.length" class="o_nocontent_help">