[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: e9c7e020daf88022d6e02de0a5620074e8417b5a
Part-of: odoo/odoo#102525
This commit is contained in:
Soukéina Bojabza
2022-10-06 23:14:40 +02:00
committed by qsm-odoo
parent 339f9636eb
commit 1dff079f09
5 changed files with 86 additions and 12 deletions
@@ -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');
}
@@ -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');
}
}
}
@@ -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;
@@ -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;
}
}
+1 -1
View File
@@ -623,7 +623,7 @@
<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>
<div id="so_content_addition"
t-att-data-selector="so_content_addition_selector"
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div > img, #{so_content_addition_selector}"
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div:not(.o_grid_item_image) > img, #{so_content_addition_selector}"
data-drop-in=".content, nav"/>
<div data-js="SnippetSave"