diff --git a/addons/web_editor/static/src/js/common/grid_layout_utils.js b/addons/web_editor/static/src/js/common/grid_layout_utils.js
index 8e071690ba2..b3435e09155 100644
--- a/addons/web_editor/static/src/js/common/grid_layout_utils.js
+++ b/addons/web_editor/static/src/js/common/grid_layout_utils.js
@@ -141,26 +141,36 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
const allBackgroundColor = [...columnEls].every(columnEl => columnEl.classList.contains('o_cc'));
for (const columnEl of columnEls) {
+ // Finding out if the images are alone in their column.
+ let isImageColumn = _checkIfImageColumn(columnEl);
+ const imageEl = columnEl.querySelector('img');
+
+ // Placing the column.
const style = window.getComputedStyle(columnEl);
// Horizontal placement.
- const columnLeft = columnEl.offsetLeft;
+ const columnLeft = isImageColumn ? imageEl.offsetLeft : columnEl.offsetLeft;
// Getting the width of the column.
const paddingLeft = parseFloat(style.paddingLeft);
- const width = parseFloat(columnEl.scrollWidth) - (allBackgroundColor ? 0 : 2 * paddingLeft);
- const columnSpan = Math.round((width + columnGap) / (columnSize + columnGap));
+ const width = isImageColumn ? parseFloat(imageEl.scrollWidth)
+ : parseFloat(columnEl.scrollWidth) - (allBackgroundColor ? 0 : 2 * paddingLeft);
+ let columnSpan = Math.round((width + columnGap) / (columnSize + columnGap));
+ if (columnSpan < 1) {
+ columnSpan = 1;
+ }
const columnStart = Math.round(columnLeft / (columnSize + columnGap)) + 1;
const columnEnd = columnStart + columnSpan;
// Vertical placement.
- const columnTop = columnEl.offsetTop;
+ const columnTop = isImageColumn ? imageEl.offsetTop : columnEl.offsetTop;
// Getting the top and bottom paddings and computing the row offset.
const paddingTop = parseFloat(style.paddingTop);
const paddingBottom = parseFloat(style.paddingBottom);
const rowOffsetTop = Math.floor((paddingTop + rowGap) / (rowSize + rowGap));
// Getting the height of the column.
- const height = parseFloat(columnEl.scrollHeight) - (allBackgroundColor ? 0 : paddingTop + paddingBottom);
+ const height = isImageColumn ? parseFloat(imageEl.scrollHeight)
+ : parseFloat(columnEl.scrollHeight) - (allBackgroundColor ? 0 : paddingTop + paddingBottom);
const rowSpan = Math.ceil((height + rowGap) / (rowSize + rowGap));
- const rowStart = Math.round(columnTop / (rowSize + rowGap)) + 1 + (allBackgroundColor ? 0 : rowOffsetTop);
+ const rowStart = Math.round(columnTop / (rowSize + rowGap)) + 1 + (allBackgroundColor || isImageColumn ? 0 : rowOffsetTop);
const rowEnd = rowStart + rowSpan;
columnEl.style.gridArea = `${rowStart} / ${columnStart} / ${rowEnd} / ${columnEnd}`;
@@ -183,6 +193,7 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
maxRowEnd = Math.max(rowEnd, maxRowEnd);
columnSpans.push(columnSpan);
+ imageColumns.push(isImageColumn);
}
// If all the columns have a background color, set their padding to the
@@ -196,14 +207,19 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
rowEl.style.setProperty('--grid-item-padding-x', paddingX);
}
- // Removing padding and offset classes.
for (const [i, columnEl] of [...columnEls].entries()) {
+ // Removing padding and offset classes.
const regex = /^(pt|pb|col-|offset-)/;
const toRemove = [...columnEl.classList].filter(c => {
return regex.test(c);
});
columnEl.classList.remove(...toRemove);
columnEl.classList.add('col-lg-' + columnSpans[i]);
+
+ // If the column only has an image, convert it.
+ if (imageColumns[i]) {
+ _convertImageColumn(columnEl);
+ }
}
return maxRowEnd;
@@ -223,3 +239,42 @@ export function _reloadLazyImages(columnEl) {
imageEl.src = src;
}
}
+/**
+ * Checks whether the column only contains an image or not. An image is
+ * considered alone if the column only contains empty textnodes and line breaks
+ * in addition to the image.
+ *
+ * @private
+ * @param {Element} columnEl
+ * @returns {Boolean}
+ */
+export function _checkIfImageColumn(columnEl) {
+ let isImageColumn = false;
+ const imageEls = columnEl.querySelectorAll(':scope > img');
+ const columnChildrenEls = [...columnEl.children].filter(el => el.nodeName !== 'BR');
+ if (imageEls.length === 1 && columnChildrenEls.length === 1) {
+ // If there is only one image and if this image is the only "real"
+ // child of the column, we need to check if there is text in it.
+ const textNodeEls = [...columnEl.childNodes].filter(el => el.nodeType === Node.TEXT_NODE);
+ const areTextNodesEmpty = [...textNodeEls].every(textNodeEl => textNodeEl.nodeValue.trim() === '');
+ isImageColumn = areTextNodesEmpty;
+ }
+ return isImageColumn;
+}
+/**
+ * Removes the line breaks and textnodes of the column, adds the grid class and
+ * sets the image width to default so it can be displayed as expected. Also
+ * blocks the edition of the column.
+ *
+ * @private
+ * @param {Element} columnEl a column containing only an image.
+ */
+function _convertImageColumn(columnEl) {
+ columnEl.querySelectorAll('br').forEach(el => el.remove());
+ const textNodeEls = [...columnEl.childNodes].filter(el => el.nodeType === Node.TEXT_NODE);
+ textNodeEls.forEach(el => el.remove());
+ const imageEl = columnEl.querySelector('img');
+ columnEl.classList.add('o_grid_item_image');
+ columnEl.contentEditable = false;
+ imageEl.style.removeProperty('width');
+}
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 551244f2ae9..ce7a6dd14d0 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -263,6 +263,12 @@ var SnippetEditor = Widget.extend({
// a flickering when not needed.
this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', postAnimationCover);
+ // Set the `contenteditable` attribute to false for all the columns
+ // having the class `o_grid_item_image` (as it is removed when leaving
+ // the edit mode).
+ const imageColumnEls = this.$target[0].querySelectorAll('.o_grid_item_image');
+ imageColumnEls.forEach(imageColumnEl => imageColumnEl.contentEditable = false);
+
return Promise.all(defs).then(() => {
this.__isStartedResolveFunc(this);
});
@@ -1177,6 +1183,10 @@ var SnippetEditor = Widget.extend({
// Case when dropping a grid item in a non-grid dropzone.
this.$target[0].classList.remove('o_grid_item');
this.$target[0].style.removeProperty('grid-area');
+ if (this.$target[0].classList.contains('o_grid_item_image')) {
+ this.$target[0].classList.remove('o_grid_item_image');
+ this.$target[0].removeAttribute('contentEditable');
+ }
}
// TODO lot of this is duplicated code of the d&d feature of snippets
@@ -1202,6 +1212,10 @@ var SnippetEditor = Widget.extend({
// Case when a column is dropped near a non-grid dropzone.
this.$target[0].classList.remove('o_grid_item');
this.$target[0].style.removeProperty('z-index');
+ if (this.$target[0].classList.contains('o_grid_item_image')) {
+ this.$target[0].classList.remove('o_grid_item_image');
+ this.$target[0].removeAttribute('contentEditable');
+ }
}
}
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 3ba4414db77..4a1d7e7d94b 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -4965,7 +4965,8 @@ registry.layout_column = SnippetOptionWidget.extend({
if (elementType === 'image') {
// Set the columns properties.
- newColumnEl.classList.add('col-lg-6', 'g-col-lg-6', 'g-height-6');
+ newColumnEl.classList.add('col-lg-6', 'g-col-lg-6', 'g-height-6', 'o_grid_item_image');
+ newColumnEl.contentEditable = false;
numberColumns = 6;
numberRows = 6;
@@ -5109,6 +5110,10 @@ registry.layout_column = SnippetOptionWidget.extend({
columnEl.classList.remove('o_grid_item');
columnEl.style.removeProperty('grid-area');
columnEl.style.removeProperty('z-index');
+ if (columnEl.classList.contains('o_grid_item_image')) {
+ columnEl.classList.remove('o_grid_item_image');
+ columnEl.removeAttribute('contentEditable');
+ }
}
// Removing the grid properties.
delete rowEl.dataset.rowCount;
diff --git a/addons/web_editor/static/src/scss/web_editor.frontend.scss b/addons/web_editor/static/src/scss/web_editor.frontend.scss
index 9c32a7ddac5..47a3d8ca914 100644
--- a/addons/web_editor/static/src/scss/web_editor.frontend.scss
+++ b/addons/web_editor/static/src/scss/web_editor.frontend.scss
@@ -71,9 +71,9 @@
.o_grid_item {
word-break: break-word;
- > img {
- width: 100%;
- height: 100%;
+ &.o_grid_item_image > img:not([data-shape]) {
+ width: 100% !important;
+ height: 100% !important;
object-fit: cover !important;
}
}
diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml
index 2f045081bbe..769a6270ace 100644
--- a/addons/website/views/snippets/snippets.xml
+++ b/addons/website/views/snippets/snippets.xml
@@ -623,7 +623,7 @@