Skip to content

Master mysterious egg loco 1 #4489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master-mysterious-egg
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 52 additions & 15 deletions addons/html_builder/static/src/core/save_plugin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Plugin } from "@html_editor/plugin";
import { rpc } from "@web/core/network/rpc";

const oeStructureSelector = "#wrapwrap .oe_structure[data-oe-xpath][data-oe-id]";
const oeFieldSelector = "#wrapwrap [data-oe-field]:not([data-oe-sanitize-prevent-edition])";
Expand Down Expand Up @@ -32,22 +33,29 @@ export class SavePlugin extends Plugin {
};

async save() {
// TODO: implement the "group by" feature for save
const proms = [];
for (const fn of this.getResource("before_save_handlers")) {
proms.push(fn());
}
await Promise.all(proms);
const saveProms = [...this.editable.querySelectorAll(".o_dirty")].map(async (dirtyEl) => {
dirtyEl.classList.remove("o_dirty");
const cleanedEl = dirtyEl.cloneNode(true);
this.dispatchTo("clean_for_save_handlers", { root: cleanedEl });

if (this.config.isTranslation) {
await this.saveTranslationElement(cleanedEl);
} else {
await this.saveView(cleanedEl);
const dirtyEls = [];
for (const getDirtyElsNotInDom of this.getResource("get_dirty_els_not_in_dom")) {
dirtyEls.push(...getDirtyElsNotInDom());
}
const saveProms = [...dirtyEls, ...this.editable.querySelectorAll(".o_dirty")].map(
async (dirtyEl) => {
dirtyEl.classList.remove("o_dirty");
const cleanedEl = dirtyEl.cloneNode(true);
this.dispatchTo("clean_for_save_handlers", { root: cleanedEl });

if (this.config.isTranslation) {
await this.saveTranslationElement(cleanedEl);
} else {
await this.saveView(cleanedEl);
}
}
});
);
// used to track dirty out of the editable scope, like header, footer or wrapwrap
const willSaves = this.getResource("save_handlers").map((c) => c());
await Promise.all(saveProms.concat(willSaves));
Expand Down Expand Up @@ -125,18 +133,47 @@ export class SavePlugin extends Plugin {
if (el.dataset["oeTranslationSourceSha"]) {
const translations = {};
translations[this.services.website.currentWebsite.metadata.lang] = {
[el.dataset["oeTranslationSourceSha"]]: el.innerHTML,
[el.dataset["oeTranslationSourceSha"]]: this.getEscapedElement(el).innerHTML,
};
return this.services.orm.call(el.dataset["oeModel"], "web_update_field_translations", [
[Number(el.dataset["oeId"])],
el.dataset["oeField"],
return rpc("/web_editor/field/translation/update", {
model: el.dataset["oeModel"],
record_id: [Number(el.dataset["oeId"])],
field_name: el.dataset["oeField"],
translations,
]);
});
}
// TODO: check what we want to modify in translate mode
return this.saveView(el);
}

getEscapedElement(el) {
const escapedEl = el.cloneNode(true);
const allElements = [escapedEl, ...escapedEl.querySelectorAll("*")];
const exclusion = [];
for (const element of allElements) {
if (
element.matches(
"object,iframe,script,style,[data-oe-model]:not([data-oe-model='ir.ui.view'])"
)
) {
exclusion.push(element);
exclusion.push(...element.querySelectorAll("*"));
}
}
const exclusionSet = new Set(exclusion);
const toEscapeEls = allElements.filter((el) => !exclusionSet.has(el));
for (const toEscapeEl of toEscapeEls) {
for (const child of Array.from(toEscapeEl.childNodes)) {
if (child.nodeType === 3) {
const divEl = document.createElement("div");
divEl.textContent = child.nodeValue;
child.nodeValue = divEl.innerHTML;
}
}
}
return escapedEl;
}

/**
* Handles the flag of the closest savable element to the mutation as dirty
*
Expand Down
17 changes: 13 additions & 4 deletions addons/html_builder/static/src/core/setup_editor_plugin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Plugin } from "@html_editor/plugin";
import { _t } from "@web/core/l10n/translation";
import { getTranslationEditableEls } from "@html_builder/website_builder/plugins/translation_plugin";

export class SetupEditorPlugin extends Plugin {
static id = "setup_editor_plugin";
Expand All @@ -10,9 +11,20 @@ export class SetupEditorPlugin extends Plugin {
};

setup() {
this.websiteService = this.services.website;
this.editable.setAttribute("contenteditable", false);

// Add the `o_editable` class on the editable elements
if (this.config.isTranslation) {
const translationSavableEls = getTranslationEditableEls(
this.websiteService.pageDocument
);
for (const translationSavableEl of translationSavableEls) {
if (!translationSavableEl.hasAttribute("data-oe-readonly")) {
translationSavableEl.classList.add("o_editable");
}
}
return;
}
let editableEls = this.getEditableElements("[data-oe-model]")
.filter((el) => !el.matches("link, script"))
.filter((el) => !el.hasAttribute("data-oe-readonly"))
Expand All @@ -37,9 +49,6 @@ export class SetupEditorPlugin extends Plugin {
el.setAttribute("data-editor-message", _t("DRAG BUILDING BLOCKS HERE"));
}
});

// Set the `contenteditable` attribute on the editables.
this.setContenteditable();
Copy link
Author

@loco-odoo loco-odoo Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done at the normalize so useless at the end of the setup

}

getEditableElements(selector) {
Expand Down
72 changes: 72 additions & 0 deletions addons/html_builder/static/src/website_builder/dialog_temp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Dialog } from "@web/core/dialog/dialog";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just copy pasted from website

import { _t } from "@web/core/l10n/translation";
import { useState, Component } from "@odoo/owl";

const NO_OP = () => {};

export class WebsiteDialog extends Component {
static template = "website_builder.WebsiteDialog";
static components = { Dialog };
static props = {
...Dialog.props,
primaryTitle: { type: String, optional: true },
primaryClick: { type: Function, optional: true },
secondaryTitle: { type: String, optional: true },
secondaryClick: { type: Function, optional: true },
showSecondaryButton: { type: Boolean, optional: true },
close: { type: Function, optional: true },
closeOnClick: { type: Boolean, optional: true },
body: { type: String, optional: true },
slots: { type: Object, optional: true },
showFooter: { type: Boolean, optional: true },
};
static defaultProps = {
...Dialog.defaultProps,
title: _t("Confirmation"),
showFooter: true,
primaryTitle: _t("Ok"),
secondaryTitle: _t("Cancel"),
showSecondaryButton: true,
size: "md",
closeOnClick: true,
close: NO_OP,
};

setup() {
this.state = useState({
disabled: false,
});
}
/**
* Disables the buttons of the dialog when a click is made.
* If a handler is provided, await for its call.
* If the prop closeOnClick is true, close the dialog.
* Otherwise, restore the button.
*
* @param handler {function|void} The handler to protect.
* @returns {function(): Promise} handler called when a click is made.
*/
protectedClick(handler) {
return async () => {
if (this.state.disabled) {
return;
}
this.state.disabled = true;
if (handler) {
await handler();
}
if (this.props.closeOnClick) {
return this.props.close();
}
this.state.disabled = false;
};
}

get contentClasses() {
const websiteDialogClass = "o_website_dialog";
if (this.props.contentClass) {
return `${websiteDialogClass} ${this.props.contentClass}`;
}
return websiteDialogClass;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.o_website_dialog {
label {
font-weight: $font-weight-bold;
}
}
26 changes: 26 additions & 0 deletions addons/html_builder/static/src/website_builder/dialog_temp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="website_builder.WebsiteDialog">
<Dialog contentClass="contentClasses"
footer="props.showFooter ? undefined : false"
size="props.size"
title="props.title">
<t t-set-slot="default">
<t t-if="props.slots and props.slots.default" t-slot="default"/>
<t t-else="" t-esc="props.body"/>
</t>
<t t-if="props.showFooter" t-set-slot="footer">
<t t-if="props.slots and props.slots.footer" t-slot="footer"/>
<t t-else="">
<button class="btn btn-primary" t-on-click="protectedClick(props.primaryClick)" t-att-disabled="state.disabled">
<t t-esc="props.primaryTitle"/>
</button>
<button t-if="props.showSecondaryButton" class="btn btn-secondary" t-on-click="protectedClick(props.secondaryClick)" t-att-disabled="state.disabled">
<t t-esc="props.secondaryTitle"/>
</button>
</t>
</t>
</Dialog>
</t>

</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
}

.s_website_form {
input, textarea, select, .s_website_form_label {
&:not(.o_translatable_attribute) {
pointer-events: none;
}
}
// Hidden field is only partially hidden in editor
.s_website_form_field_hidden {
display: block !important;
Expand Down
Loading