Skip to content

Commit 1dff079

Browse files
sobo-odooqsm-odoo
authored andcommitted
[IMP] web_editor, *: improve toggle and d&d in grid mode for the images
*: website In grid mode, the images take the whole space in their column so they always have the same size as them. However, if text was added in the column (before or after toggle), it always looked like it was overflowing and the overlay would not cover it even after resizing. Also, since these images have the value of the `object-fit` property set to `cover`, the images are cut if they are too big for the column. This is problematic if the image has a shape because the shape is cut too, ruining the visual effect. This commit solves these issues by distinguishing the images that are alone in their column from those that are not. An image is considered alone if it is a direct child of the column and if there is no text in it (the line breaks and empty lines are excluded). Now, only this kind of images, if they have no shape, take the whole space in the column and have `object-fit` set to `cover`; the other ones behave like the columns in normal mode. This distinction also allows to block the edition of the columns containing only an image. Indeed, adding text or dropping inner content in them had the same overflowing effect. task-2973198 X-original-commit: e9c7e02 Part-of: odoo#102525
1 parent 339f963 commit 1dff079

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

addons/web_editor/static/src/js/common/grid_layout_utils.js

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,26 +141,36 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
141141
const allBackgroundColor = [...columnEls].every(columnEl => columnEl.classList.contains('o_cc'));
142142

143143
for (const columnEl of columnEls) {
144+
// Finding out if the images are alone in their column.
145+
let isImageColumn = _checkIfImageColumn(columnEl);
146+
const imageEl = columnEl.querySelector('img');
147+
148+
// Placing the column.
144149
const style = window.getComputedStyle(columnEl);
145150
// Horizontal placement.
146-
const columnLeft = columnEl.offsetLeft;
151+
const columnLeft = isImageColumn ? imageEl.offsetLeft : columnEl.offsetLeft;
147152
// Getting the width of the column.
148153
const paddingLeft = parseFloat(style.paddingLeft);
149-
const width = parseFloat(columnEl.scrollWidth) - (allBackgroundColor ? 0 : 2 * paddingLeft);
150-
const columnSpan = Math.round((width + columnGap) / (columnSize + columnGap));
154+
const width = isImageColumn ? parseFloat(imageEl.scrollWidth)
155+
: parseFloat(columnEl.scrollWidth) - (allBackgroundColor ? 0 : 2 * paddingLeft);
156+
let columnSpan = Math.round((width + columnGap) / (columnSize + columnGap));
157+
if (columnSpan < 1) {
158+
columnSpan = 1;
159+
}
151160
const columnStart = Math.round(columnLeft / (columnSize + columnGap)) + 1;
152161
const columnEnd = columnStart + columnSpan;
153162

154163
// Vertical placement.
155-
const columnTop = columnEl.offsetTop;
164+
const columnTop = isImageColumn ? imageEl.offsetTop : columnEl.offsetTop;
156165
// Getting the top and bottom paddings and computing the row offset.
157166
const paddingTop = parseFloat(style.paddingTop);
158167
const paddingBottom = parseFloat(style.paddingBottom);
159168
const rowOffsetTop = Math.floor((paddingTop + rowGap) / (rowSize + rowGap));
160169
// Getting the height of the column.
161-
const height = parseFloat(columnEl.scrollHeight) - (allBackgroundColor ? 0 : paddingTop + paddingBottom);
170+
const height = isImageColumn ? parseFloat(imageEl.scrollHeight)
171+
: parseFloat(columnEl.scrollHeight) - (allBackgroundColor ? 0 : paddingTop + paddingBottom);
162172
const rowSpan = Math.ceil((height + rowGap) / (rowSize + rowGap));
163-
const rowStart = Math.round(columnTop / (rowSize + rowGap)) + 1 + (allBackgroundColor ? 0 : rowOffsetTop);
173+
const rowStart = Math.round(columnTop / (rowSize + rowGap)) + 1 + (allBackgroundColor || isImageColumn ? 0 : rowOffsetTop);
164174
const rowEnd = rowStart + rowSpan;
165175

166176
columnEl.style.gridArea = `${rowStart} / ${columnStart} / ${rowEnd} / ${columnEnd}`;
@@ -183,6 +193,7 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
183193

184194
maxRowEnd = Math.max(rowEnd, maxRowEnd);
185195
columnSpans.push(columnSpan);
196+
imageColumns.push(isImageColumn);
186197
}
187198

188199
// If all the columns have a background color, set their padding to the
@@ -196,14 +207,19 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
196207
rowEl.style.setProperty('--grid-item-padding-x', paddingX);
197208
}
198209

199-
// Removing padding and offset classes.
200210
for (const [i, columnEl] of [...columnEls].entries()) {
211+
// Removing padding and offset classes.
201212
const regex = /^(pt|pb|col-|offset-)/;
202213
const toRemove = [...columnEl.classList].filter(c => {
203214
return regex.test(c);
204215
});
205216
columnEl.classList.remove(...toRemove);
206217
columnEl.classList.add('col-lg-' + columnSpans[i]);
218+
219+
// If the column only has an image, convert it.
220+
if (imageColumns[i]) {
221+
_convertImageColumn(columnEl);
222+
}
207223
}
208224

209225
return maxRowEnd;
@@ -223,3 +239,42 @@ export function _reloadLazyImages(columnEl) {
223239
imageEl.src = src;
224240
}
225241
}
242+
/**
243+
* Checks whether the column only contains an image or not. An image is
244+
* considered alone if the column only contains empty textnodes and line breaks
245+
* in addition to the image.
246+
*
247+
* @private
248+
* @param {Element} columnEl
249+
* @returns {Boolean}
250+
*/
251+
export function _checkIfImageColumn(columnEl) {
252+
let isImageColumn = false;
253+
const imageEls = columnEl.querySelectorAll(':scope > img');
254+
const columnChildrenEls = [...columnEl.children].filter(el => el.nodeName !== 'BR');
255+
if (imageEls.length === 1 && columnChildrenEls.length === 1) {
256+
// If there is only one image and if this image is the only "real"
257+
// child of the column, we need to check if there is text in it.
258+
const textNodeEls = [...columnEl.childNodes].filter(el => el.nodeType === Node.TEXT_NODE);
259+
const areTextNodesEmpty = [...textNodeEls].every(textNodeEl => textNodeEl.nodeValue.trim() === '');
260+
isImageColumn = areTextNodesEmpty;
261+
}
262+
return isImageColumn;
263+
}
264+
/**
265+
* Removes the line breaks and textnodes of the column, adds the grid class and
266+
* sets the image width to default so it can be displayed as expected. Also
267+
* blocks the edition of the column.
268+
*
269+
* @private
270+
* @param {Element} columnEl a column containing only an image.
271+
*/
272+
function _convertImageColumn(columnEl) {
273+
columnEl.querySelectorAll('br').forEach(el => el.remove());
274+
const textNodeEls = [...columnEl.childNodes].filter(el => el.nodeType === Node.TEXT_NODE);
275+
textNodeEls.forEach(el => el.remove());
276+
const imageEl = columnEl.querySelector('img');
277+
columnEl.classList.add('o_grid_item_image');
278+
columnEl.contentEditable = false;
279+
imageEl.style.removeProperty('width');
280+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ var SnippetEditor = Widget.extend({
263263
// a flickering when not needed.
264264
this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', postAnimationCover);
265265

266+
// Set the `contenteditable` attribute to false for all the columns
267+
// having the class `o_grid_item_image` (as it is removed when leaving
268+
// the edit mode).
269+
const imageColumnEls = this.$target[0].querySelectorAll('.o_grid_item_image');
270+
imageColumnEls.forEach(imageColumnEl => imageColumnEl.contentEditable = false);
271+
266272
return Promise.all(defs).then(() => {
267273
this.__isStartedResolveFunc(this);
268274
});
@@ -1177,6 +1183,10 @@ var SnippetEditor = Widget.extend({
11771183
// Case when dropping a grid item in a non-grid dropzone.
11781184
this.$target[0].classList.remove('o_grid_item');
11791185
this.$target[0].style.removeProperty('grid-area');
1186+
if (this.$target[0].classList.contains('o_grid_item_image')) {
1187+
this.$target[0].classList.remove('o_grid_item_image');
1188+
this.$target[0].removeAttribute('contentEditable');
1189+
}
11801190
}
11811191

11821192
// TODO lot of this is duplicated code of the d&d feature of snippets
@@ -1202,6 +1212,10 @@ var SnippetEditor = Widget.extend({
12021212
// Case when a column is dropped near a non-grid dropzone.
12031213
this.$target[0].classList.remove('o_grid_item');
12041214
this.$target[0].style.removeProperty('z-index');
1215+
if (this.$target[0].classList.contains('o_grid_item_image')) {
1216+
this.$target[0].classList.remove('o_grid_item_image');
1217+
this.$target[0].removeAttribute('contentEditable');
1218+
}
12051219
}
12061220
}
12071221

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4965,7 +4965,8 @@ registry.layout_column = SnippetOptionWidget.extend({
49654965

49664966
if (elementType === 'image') {
49674967
// Set the columns properties.
4968-
newColumnEl.classList.add('col-lg-6', 'g-col-lg-6', 'g-height-6');
4968+
newColumnEl.classList.add('col-lg-6', 'g-col-lg-6', 'g-height-6', 'o_grid_item_image');
4969+
newColumnEl.contentEditable = false;
49694970
numberColumns = 6;
49704971
numberRows = 6;
49714972

@@ -5109,6 +5110,10 @@ registry.layout_column = SnippetOptionWidget.extend({
51095110
columnEl.classList.remove('o_grid_item');
51105111
columnEl.style.removeProperty('grid-area');
51115112
columnEl.style.removeProperty('z-index');
5113+
if (columnEl.classList.contains('o_grid_item_image')) {
5114+
columnEl.classList.remove('o_grid_item_image');
5115+
columnEl.removeAttribute('contentEditable');
5116+
}
51125117
}
51135118
// Removing the grid properties.
51145119
delete rowEl.dataset.rowCount;

addons/web_editor/static/src/scss/web_editor.frontend.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@
7171
.o_grid_item {
7272
word-break: break-word;
7373

74-
> img {
75-
width: 100%;
76-
height: 100%;
74+
&.o_grid_item_image > img:not([data-shape]) {
75+
width: 100% !important;
76+
height: 100% !important;
7777
object-fit: cover !important;
7878
}
7979
}

addons/website/views/snippets/snippets.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@
623623
<t t-set="so_content_addition_selector" t-translation="off">blockquote, .s_card:not(.s_timeline_card), .s_alert, .o_facebook_page, .s_share, .s_social_media, .s_rating, .s_hr, .s_google_map, .s_map, .s_countdown, .s_chart, .s_text_highlight, .s_progress_bar, .s_badge, .s_embed_code, .s_donation, .s_add_to_cart</t>
624624
<div id="so_content_addition"
625625
t-att-data-selector="so_content_addition_selector"
626-
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div > img, #{so_content_addition_selector}"
626+
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div:not(.o_grid_item_image) > img, #{so_content_addition_selector}"
627627
data-drop-in=".content, nav"/>
628628

629629
<div data-js="SnippetSave"

0 commit comments

Comments
 (0)