Skip to content

Commit 17d5686

Browse files
[FIX] html_builder, *: parametrize the "mobile" media breakpoint
Prior to this commit, `lg` bootstrap media breakpoint (992px) was hardcoded in the html Builder. However it is not adequate for MassMailing as the content will be displayed in its "mobile" form while the width of the screen is still large enough to be displayed in its "desktop" form. This is particularly notable in the Form view which allows a large amount of space for the chatter. This commit parametrize a `mobileBreakpoint` so that `md` can be provided instead. `lg` will still be used in website and by default, whilst `mass_mailing` will use `md`. This change is reflected in all snippets/templates/plugins/tools used by `mass_mailing`. task-4247642 Co-authored-by: Damien Abeloos <[email protected]> Co-authored-by: Thomas Josse <[email protected]>
1 parent 655d442 commit 17d5686

Some content is hidden

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

41 files changed

+428
-348
lines changed

addons/html_builder/static/src/builder.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@odoo/owl";
1515
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
1616
import { _t } from "@web/core/l10n/translation";
17+
import { SIZES, MEDIAS_BREAKPOINTS } from "@web/core/ui/ui_service";
1718
import { useService } from "@web/core/utils/hooks";
1819
import { addLoadingEffect as addButtonLoadingEffect } from "@web/core/utils/ui";
1920
import { InvisibleElementsPanel } from "@html_builder/sidebar/invisible_elements_panel";
@@ -77,13 +78,23 @@ export class Builder extends Component {
7778
this.editorBus = new EventBus();
7879
this.colorPresetToShow = null;
7980
this.activeTargetEl = null;
81+
const mobileBreakpoint = this.props.config.mobileBreakpoint ?? "lg";
8082

8183
// TODO: maybe do a different config for the translate mode and the
8284
// "regular" mode.
8385
this.editor = new Editor(
8486
{
8587
Plugins: this.props.Plugins,
8688
...this.props.config,
89+
mobileBreakpoint,
90+
isMobileView: (targetEl) => {
91+
const mobileViewThreshold =
92+
MEDIAS_BREAKPOINTS[SIZES[mobileBreakpoint.toUpperCase()]].minWidth;
93+
const clientWidth =
94+
targetEl.ownerDocument.defaultView?.frameElement?.clientWidth ||
95+
targetEl.ownerDocument.documentElement.clientWidth;
96+
return !!clientWidth && clientWidth < mobileViewThreshold;
97+
},
8798
onChange: ({ isPreviewing }) => {
8899
if (!isPreviewing) {
89100
this.state.canUndo = this.editor.shared.history.canUndo();
@@ -322,6 +333,6 @@ export class Builder extends Component {
322333
}
323334

324335
getActiveTarget() {
325-
return this.editor.shared["builderOptions"].getContainers().at(-1)?.element
336+
return this.editor.shared["builderOptions"].getContainers().at(-1)?.element;
326337
}
327338
}

addons/html_builder/static/src/core/builder_overlay/builder_overlay.js

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { renderToElement } from "@web/core/utils/render";
2-
import { isMobileView } from "@html_builder/utils/utils";
32
import {
43
addBackgroundGrid,
54
getGridProperties,
@@ -24,7 +23,18 @@ export const sizingGrid = {
2423
};
2524

2625
export class BuilderOverlay {
27-
constructor(overlayTarget, { iframe, overlayContainer, history, hasOverlayOptions, next }) {
26+
constructor(
27+
overlayTarget,
28+
{
29+
iframe,
30+
overlayContainer,
31+
history,
32+
hasOverlayOptions,
33+
next,
34+
isMobileView,
35+
mobileBreakpoint,
36+
}
37+
) {
2838
this.history = history;
2939
this.next = next;
3040
this.hasOverlayOptions = hasOverlayOptions;
@@ -43,6 +53,8 @@ export class BuilderOverlay {
4353
`.e:not(.o_grid_handle), .w:not(.o_grid_handle)`
4454
);
4555
this.gridHandles = this.handlesWrapperEl.querySelectorAll(".o_grid_handle");
56+
this.isMobileView = isMobileView;
57+
this.mobileBreakpoint = mobileBreakpoint;
4658

4759
this.initHandles();
4860
this.initSizing();
@@ -91,7 +103,7 @@ export class BuilderOverlay {
91103
}
92104

93105
if (this.overlayTarget.parentNode?.classList.contains("row")) {
94-
const isMobile = isMobileView(this.overlayTarget);
106+
const isMobile = this.isMobileView(this.overlayTarget);
95107
const isGridOn = this.overlayTarget.classList.contains("o_grid_item");
96108
const isGrid = !isMobile && isGridOn;
97109
// Hiding/showing the correct resize handles if we are in grid mode
@@ -222,7 +234,7 @@ export class BuilderOverlay {
222234
}
223235

224236
getSizingXConfig() {
225-
const resolutionModifier = this.isMobile ? "" : "lg-";
237+
const resolutionModifier = this.isMobile ? "" : `${this.mobileBreakpoint}-`;
226238
const rowWidth = this.overlayTarget.closest(".row").getBoundingClientRect().width;
227239
const valuesE = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
228240
const valuesW = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
@@ -241,7 +253,7 @@ export class BuilderOverlay {
241253
}
242254

243255
onResizeX(compass, initialClasses, currentIndex) {
244-
const resolutionModifier = this.isMobile ? "" : "lg-";
256+
const resolutionModifier = this.isMobile ? "" : `${this.mobileBreakpoint}-`;
245257
// (?!\S): following char cannot be a non-space character
246258
const offsetRegex = new RegExp(`(?:^|\\s+)offset-${resolutionModifier}(\\d{1,2})(?!\\S)`);
247259
const colRegex = new RegExp(`(?:^|\\s+)col-${resolutionModifier}(\\d{1,2})(?!\\S)`);
@@ -268,16 +280,18 @@ export class BuilderOverlay {
268280

269281
// Add/remove the `offset-lg-0` class when needed.
270282
if (this.isMobile && offset === 0) {
271-
this.overlayTarget.classList.remove("offset-lg-0");
283+
this.overlayTarget.classList.remove(`offset-${this.mobileBreakpoint}-0`);
272284
} else {
273285
const className = this.overlayTarget.className;
274-
const hasDesktopClass = !!className.match(/(^|\s+)offset-lg-\d{1,2}(?!\S)/);
286+
const hasDesktopClass = !!className.match(
287+
new RegExp(`(^|\\s+)offset-${this.mobileBreakpoint}-\\d{1,2}(?!\\S)`)
288+
);
275289
const hasMobileClass = !!className.match(/(^|\s+)offset-\d{1,2}(?!\S)/);
276290
if (
277291
(this.isMobile && offset > 0 && !hasDesktopClass) ||
278292
(!this.isMobile && offset === 0 && hasMobileClass)
279293
) {
280-
this.overlayTarget.classList.add("offset-lg-0");
294+
this.overlayTarget.classList.add(`offset-${this.mobileBreakpoint}-0`);
281295
}
282296
}
283297
} else if (initialOffset > 0) {
@@ -318,12 +332,12 @@ export class BuilderOverlay {
318332
cssProperty: "grid-row-end",
319333
},
320334
w: {
321-
classes: valuesW.map((v) => "g-col-lg-" + (columnEnd - v)),
335+
classes: valuesW.map((v) => `g-col-${this.mobileBreakpoint}-` + (columnEnd - v)),
322336
values: valuesW.map((v) => (gridProp.columnSize + gridProp.columnGap) * (v - 1)),
323337
cssProperty: "grid-column-start",
324338
},
325339
e: {
326-
classes: valuesE.map((v) => "g-col-lg-" + (v - columnStart)),
340+
classes: valuesE.map((v) => `g-col-${this.mobileBreakpoint}-` + (v - columnStart)),
327341
values: valuesE.map((v) => (gridProp.columnSize + gridProp.columnGap) * (v - 1)),
328342
cssProperty: "grid-column-end",
329343
},
@@ -389,7 +403,10 @@ export class BuilderOverlay {
389403

390404
if (compass === "w" || compass === "e") {
391405
const numberColumns = style.gridColumnEnd - style.gridColumnStart;
392-
this.replaceSizingClass(/\s*(g-col-lg-)([0-9-]+)/g, `g-col-lg-${numberColumns}`);
406+
this.replaceSizingClass(
407+
new RegExp(`\\s*(g-col-${this.mobileBreakpoint}-)([0-9-]+)`, "g"),
408+
`g-col-${this.mobileBreakpoint}-${numberColumns}`
409+
);
393410
}
394411
}
395412

@@ -473,7 +490,7 @@ export class BuilderOverlay {
473490

474491
const handleEl = ev.currentTarget;
475492
const isGridHandle = handleEl.classList.contains("o_grid_handle");
476-
this.isMobile = isMobileView(this.overlayTarget);
493+
this.isMobile = this.isMobileView(this.overlayTarget);
477494

478495
// If we are in grid mode, add a background grid and place it in front
479496
// of the other elements.

addons/html_builder/static/src/core/builder_overlay/builder_overlay_plugin.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export class BuilderOverlayPlugin extends Plugin {
9898
history: this.dependencies.history,
9999
hasOverlayOptions: checkElement(option.element, {}) && option.hasOverlayOptions,
100100
next: this.dependencies.operation.next,
101+
isMobileView: this.config.isMobileView,
102+
mobileBreakpoint: this.config.mobileBreakpoint,
101103
});
102104
this.overlays.push(overlay);
103105
this.overlayContainer.append(overlay.overlayElement);

addons/html_builder/static/src/core/grid_layout/grid_layout_plugin.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
toggleGridMode,
1717
hasGridLayoutOption,
1818
} from "@html_builder/utils/grid_layout_utils";
19-
import { isMobileView } from "@html_builder/utils/utils";
2019
import { isElement } from "@html_editor/utils/dom_info";
2120

2221
const gridItemSelector = ".row.o_grid_mode > div.o_grid_item";
@@ -76,7 +75,7 @@ export class GridLayoutPlugin extends Plugin {
7675

7776
const buttons = [];
7877
this.overlayTarget = target;
79-
if (!isMobileView(this.overlayTarget)) {
78+
if (!this.config.isMobileView(this.overlayTarget)) {
8079
buttons.push(
8180
{
8281
class: "o_send_back oi",
@@ -140,7 +139,7 @@ export class GridLayoutPlugin extends Plugin {
140139
*/
141140
adjustGridItem(el) {
142141
const gridItemEl = el.closest(".o_grid_item");
143-
if (gridItemEl && gridItemEl !== el && !isMobileView(gridItemEl)) {
142+
if (gridItemEl && gridItemEl !== el && !this.config.isMobileView(gridItemEl)) {
144143
const rowEl = gridItemEl.parentElement;
145144
const { rowGap, rowSize } = getGridProperties(rowEl);
146145
const { rowStart, rowEnd } = getGridItemProperties(gridItemEl);
@@ -212,7 +211,7 @@ export class GridLayoutPlugin extends Plugin {
212211
// The columns move handles are not visible in mobile view to prevent
213212
// dragging them.
214213
const isColumn = targetEl.parentElement?.classList.contains("row");
215-
if (isColumn && isMobileView(targetEl)) {
214+
if (isColumn && this.config.isMobileView(targetEl)) {
216215
return false;
217216
}
218217
return true;
@@ -242,7 +241,7 @@ export class GridLayoutPlugin extends Plugin {
242241
// Toggle the grid mode if it is not already on.
243242
if (!isRowInGridMode) {
244243
const preserveSelection = this.dependencies.selection.preserveSelection;
245-
toggleGridMode(containerEl, preserveSelection);
244+
toggleGridMode(containerEl, preserveSelection, this.config.mobileBreakpoint);
246245
}
247246
const gridItemProps = getGridItemProperties(columnEl);
248247

@@ -292,7 +291,13 @@ export class GridLayoutPlugin extends Plugin {
292291
// grid item and store its dimensions.
293292
if (!columnEl.classList.contains("o_grid_item")) {
294293
const { columnWidth, columnHeight } = dragState;
295-
const spans = convertColumnToGrid(rowEl, columnEl, columnWidth, columnHeight);
294+
const spans = convertColumnToGrid(
295+
rowEl,
296+
columnEl,
297+
columnWidth,
298+
columnHeight,
299+
this.config.mobileBreakpoint
300+
);
296301
dragState.columnSpan = spans.columnSpan;
297302
dragState.rowSpan = spans.rowSpan;
298303
}
@@ -397,7 +402,7 @@ export class GridLayoutPlugin extends Plugin {
397402
resizeGrid(rowEl);
398403
} else if (columnEl.classList.contains("o_grid_item")) {
399404
// Case when dropping a grid item in a non-grid dropzone.
400-
convertToNormalColumn(columnEl);
405+
convertToNormalColumn(columnEl, this.config.mobileBreakpoint);
401406
}
402407
}
403408

@@ -416,7 +421,13 @@ export class GridLayoutPlugin extends Plugin {
416421
// grid item and store its dimensions.
417422
if (!columnEl.classList.contains("o_grid_item")) {
418423
const { columnWidth, columnHeight } = dragState;
419-
const spans = convertColumnToGrid(rowEl, columnEl, columnWidth, columnHeight);
424+
const spans = convertColumnToGrid(
425+
rowEl,
426+
columnEl,
427+
columnWidth,
428+
columnHeight,
429+
this.config.mobileBreakpoint
430+
);
420431
dragState.columnSpan = spans.columnSpan;
421432
dragState.rowSpan = spans.rowSpan;
422433
}
@@ -434,7 +445,7 @@ export class GridLayoutPlugin extends Plugin {
434445
resizeGrid(rowEl);
435446
} else if (columnEl.classList.contains("o_grid_item")) {
436447
// Case when a grid item is dropped near a non-grid dropzone.
437-
convertToNormalColumn(columnEl);
448+
convertToNormalColumn(columnEl, this.config.mobileBreakpoint);
438449
}
439450
}
440451

addons/html_builder/static/src/core/move_plugin.js

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,12 @@ import {
66
fillRemovedItemGap,
77
removeMobileOrders,
88
} from "@html_builder/utils/column_layout_utils";
9-
import { isElementInViewport, isMobileView } from "@html_builder/utils/utils";
9+
import { isElementInViewport } from "@html_builder/utils/utils";
1010
import { scrollTo } from "@html_builder/utils/scrolling";
1111

12-
export function getVisibleSibling(target, direction) {
13-
const siblingEls = [...target.parentNode.children];
14-
const visibleSiblingEls = siblingEls.filter(
15-
(el) => window.getComputedStyle(el).display !== "none"
16-
);
17-
const targetMobileOrder = target.style.order;
18-
// On mobile, if the target has a mobile order (which is independent
19-
// from desktop), consider these orders instead of the DOM order.
20-
if (targetMobileOrder && isMobileView(target)) {
21-
visibleSiblingEls.sort((a, b) => parseInt(a.style.order) - parseInt(b.style.order));
22-
}
23-
const targetIndex = visibleSiblingEls.indexOf(target);
24-
const siblingIndex = direction === "prev" ? targetIndex - 1 : targetIndex + 1;
25-
if (siblingIndex === -1 || siblingIndex === visibleSiblingEls.length) {
26-
return false;
27-
}
28-
return visibleSiblingEls[siblingIndex];
29-
}
30-
3112
export class MovePlugin extends Plugin {
3213
static id = "move";
14+
static dependencies = ["visibility"];
3315
resources = {
3416
has_overlay_options: { hasOption: (el) => this.isMovable(el) },
3517
get_overlay_buttons: withSequence(0, {
@@ -95,7 +77,7 @@ export class MovePlugin extends Plugin {
9577
const columnEls = [...rowEl.children];
9678
const orderedColumnEls = columnEls.filter((el) => el.style.order);
9779
if (orderedColumnEls.length && orderedColumnEls.length !== columnEls.length) {
98-
removeMobileOrders(orderedColumnEls);
80+
removeMobileOrders(orderedColumnEls, this.config.mobileBreakpoint);
9981
}
10082
}
10183
}
@@ -121,8 +103,14 @@ export class MovePlugin extends Plugin {
121103
const isVertical =
122104
this.overlayTarget.matches(this.verticalMove.selector) &&
123105
!this.overlayTarget.matches(this.verticalMove.exclude);
124-
const previousSiblingEl = getVisibleSibling(this.overlayTarget, "prev");
125-
const nextSiblingEl = getVisibleSibling(this.overlayTarget, "next");
106+
const previousSiblingEl = this.dependencies.visibility.getVisibleSibling(
107+
this.overlayTarget,
108+
"prev"
109+
);
110+
const nextSiblingEl = this.dependencies.visibility.getVisibleSibling(
111+
this.overlayTarget,
112+
"next"
113+
);
126114

127115
if (previousSiblingEl) {
128116
const direction = isVertical ? "up" : "left";
@@ -188,11 +176,11 @@ export class MovePlugin extends Plugin {
188176
}
189177

190178
// Remove all the mobile orders in the new snippet.
191-
removeMobileOrders(parentEl.children);
179+
removeMobileOrders(parentEl.children, this.config.mobileBreakpoint);
192180
}
193181

194182
areArrowsHidden() {
195-
const isMobile = isMobileView(this.overlayTarget);
183+
const isMobile = this.config.isMobileView(this.overlayTarget);
196184
const isGridItem = this.overlayTarget.classList.contains("o_grid_item");
197185
const siblingsEl = [...this.overlayTarget.parentNode.children];
198186
const visibleSiblingEl = siblingsEl.find(
@@ -210,7 +198,7 @@ export class MovePlugin extends Plugin {
210198
* @param {String} direction "prev" or "next"
211199
*/
212200
onMoveClick(direction) {
213-
const isMobile = isMobileView(this.overlayTarget);
201+
const isMobile = this.config.isMobileView(this.overlayTarget);
214202
let hasMobileOrder = !!this.overlayTarget.style.order;
215203
const parentEl = this.overlayTarget.parentNode;
216204
const siblingEls = parentEl.children;
@@ -221,14 +209,17 @@ export class MovePlugin extends Plugin {
221209
// mobile view, the mobile order is reset.
222210
const isColumn = parentEl.classList.contains("row");
223211
if (isMobile && isColumn && !hasMobileOrder) {
224-
addMobileOrders(siblingEls);
212+
addMobileOrders(siblingEls, this.config.mobileBreakpoint);
225213
hasMobileOrder = true;
226214
} else if (!isMobile && hasMobileOrder) {
227-
removeMobileOrders(siblingEls);
215+
removeMobileOrders(siblingEls, this.config.mobileBreakpoint);
228216
hasMobileOrder = false;
229217
}
230218

231-
const siblingEl = getVisibleSibling(this.overlayTarget, direction);
219+
const siblingEl = this.dependencies.visibility.getVisibleSibling(
220+
this.overlayTarget,
221+
direction
222+
);
232223
if (hasMobileOrder) {
233224
// Swap the mobile orders.
234225
const currentOrder = this.overlayTarget.style.order;

addons/html_builder/static/src/core/remove_plugin.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Plugin } from "@html_editor/plugin";
22
import { withSequence } from "@html_editor/utils/resource";
33
import { _t } from "@web/core/l10n/translation";
4-
import { getVisibleSibling } from "./move_plugin";
54
import { unremovableNodePredicates as deletePluginPredicates } from "@html_editor/core/delete_plugin";
65
import { isUnremovableQWebElement as qwebPluginPredicate } from "@html_editor/others/qweb_plugin";
76
import { isEditable } from "@html_builder/utils/utils";
@@ -27,7 +26,7 @@ const layoutElementsSelector = [
2726

2827
export class RemovePlugin extends Plugin {
2928
static id = "remove";
30-
static dependencies = ["builderOptions"];
29+
static dependencies = ["builderOptions", "visibility"];
3130
resources = {
3231
get_overlay_buttons: withSequence(3, {
3332
getButtons: this.getActiveOverlayButtons.bind(this),
@@ -129,8 +128,11 @@ export class RemovePlugin extends Plugin {
129128

130129
// Get the parent and the previous and next visible siblings.
131130
let parentEl = toRemoveEl.parentElement;
132-
const previousSiblingEl = getVisibleSibling(toRemoveEl, "prev");
133-
const nextSiblingEl = getVisibleSibling(toRemoveEl, "next");
131+
const previousSiblingEl = this.dependencies.visibility.getVisibleSibling(
132+
toRemoveEl,
133+
"prev"
134+
);
135+
const nextSiblingEl = this.dependencies.visibility.getVisibleSibling(toRemoveEl, "next");
134136
if (parentEl.matches(".o_editable:not(body)")) {
135137
// If we target the editable, we want to reset the selection to the
136138
// body. If the editable has options, we do not want to show them.

0 commit comments

Comments
 (0)