diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index 2bbb0b657a5..ce912d8d49d 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -193,6 +193,7 @@ For more specific needs, you may also assign custom-defined actions
'mail/static/src/components/*/*.js',
'mail/static/src/components/*/*.scss',
'mail/static/src/components/*/*.xml',
+ 'mail/static/src/views/*/*.xml',
('include', 'mail.assets_messaging'),
'mail/static/src/services/*.js',
'mail/static/src/views/**/*.js',
diff --git a/addons/mail/static/src/views/form/form_compiler.js b/addons/mail/static/src/views/form/form_compiler.js
index 74feeed4915..d6503c8741d 100644
--- a/addons/mail/static/src/views/form/form_compiler.js
+++ b/addons/mail/static/src/views/form/form_compiler.js
@@ -5,9 +5,9 @@ import { registry } from "@web/core/registry";
import { SIZES } from "@web/core/ui/ui_service";
import { patch } from "@web/core/utils/patch";
import { append, createElement, setAttributes } from "@web/core/utils/xml";
+import { ViewCompiler, getModifier } from "@web/views/view_compiler";
import { FormCompiler } from "@web/views/form/form_compiler";
-
function compileChatter(node, params) {
let hasActivities = false;
let hasFollowers = false;
@@ -44,9 +44,9 @@ function compileChatter(node, params) {
"hasParentReloadOnFollowersUpdate": hasParentReloadOnFollowersUpdate,
"hasParentReloadOnMessagePosted": hasParentReloadOnMessagePosted,
"isAttachmentBoxVisibleInitially": isAttachmentBoxVisibleInitially,
- "threadId": "props.record.resId or undefined",
- "threadModel": "props.record.resModel",
- "webRecord": "props.record",
+ "threadId": params.threadId,
+ "threadModel": params.threadModel,
+ "webRecord": params.webRecord,
});
const chatterContainerHookXml = createElement("div");
chatterContainerHookXml.classList.add("o_FormRenderer_chatterContainer");
@@ -59,27 +59,94 @@ function compileAttachmentPreview(node, params) {
webClientViewAttachmentViewContainerHookXml.classList.add('o_attachment_preview');
const webClientViewAttachmentViewContainerXml = createElement("WebClientViewAttachmentViewContainer");
setAttributes(webClientViewAttachmentViewContainerXml, {
- "threadId": "props.record.resId or undefined",
- "threadModel": "props.record.resModel",
+ "threadId": params.threadId,
+ "threadModel": params.threadModel,
});
append(webClientViewAttachmentViewContainerHookXml, webClientViewAttachmentViewContainerXml);
return webClientViewAttachmentViewContainerHookXml;
}
+export class MailFormCompiler extends ViewCompiler {
+ setup() {
+ this.compilers.push({ selector: "t", fn: this.compileT });
+ this.compilers.push({ selector: "div.oe_chatter", fn: this.compileChatter });
+ this.compilers.push({
+ selector: "div.o_attachment_preview",
+ fn: this.compileAttachmentPreview,
+ });
+ }
+
+ compile(node, params) {
+ const res = super.compile(node, params).children[0];
+ const chatterContainerHookXml = res.querySelector(".o_FormRenderer_chatterContainer");
+ if (chatterContainerHookXml) {
+ setAttributes(chatterContainerHookXml, {
+ "t-if": `!hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`,
+ "t-attf-class": `{{ uiService.size >= ${SIZES.XXL} ? "o-aside" : "" }}`,
+ });
+ }
+ const attachmentViewHookXml = res.querySelector(".o_attachment_preview");
+ if (attachmentViewHookXml) {
+ setAttributes(attachmentViewHookXml, {
+ "t-if": `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`,
+ });
+ }
+ return res;
+ }
+
+ compileT(node, params) {
+ const compiledRoot = createElement("t");
+ for (const child of node.childNodes) {
+ const invisible = getModifier(child, "invisible");
+ let compiledChild = this.compileNode(child, params, false);
+ compiledChild = this.applyInvisible(invisible, compiledChild, {
+ ...params,
+ recordExpr: "model.root",
+ });
+ append(compiledRoot, compiledChild);
+ }
+ return compiledRoot;
+ }
+
+ compileChatter(node) {
+ return compileChatter(node, {
+ threadId: "model.root.resId or undefined",
+ threadModel: "model.root.resModel",
+ webRecord: "model.root",
+ });
+ }
+
+ compileAttachmentPreview(node) {
+ return compileAttachmentPreview(node, {
+ threadId: "model.root.resId or undefined",
+ threadModel: "model.root.resModel",
+ });
+ }
+}
+
registry.category("form_compilers").add("chatter_compiler", {
selector: "div.oe_chatter",
- fn: compileChatter,
+ fn: (node) =>
+ compileChatter(node, {
+ threadId: "props.record.resId or undefined",
+ threadModel: "props.record.resModel",
+ webRecord: "props.record",
+ }),
});
registry.category("form_compilers").add("attachment_preview_compiler", {
selector: "div.o_attachment_preview",
- fn: compileAttachmentPreview,
+ fn: (node) =>
+ compileAttachmentPreview(node, {
+ threadId: "props.record.resId or undefined",
+ threadModel: "props.record.resModel",
+ }),
});
patch(FormCompiler.prototype, 'mail', {
- compile() {
+ compile(node, params) {
// TODO no chatter if in dialog?
- const res = this._super(...arguments);
+ const res = this._super(node, params);
const chatterContainerHookXml = res.querySelector('.o_FormRenderer_chatterContainer');
if (!chatterContainerHookXml) {
return res; // no chatter, keep the result as it is
@@ -97,17 +164,12 @@ patch(FormCompiler.prototype, 'mail', {
if (!parentXml) {
return res; // miss-config: a sheet-bg is required for the rest
}
- const webClientViewAttachmentViewHookXml = res.querySelector('.o_attachment_preview');
- // TODO hasAttachmentViewer should also depend on the groups= and/or invisible modifier on o_attachment_preview (see invoice form)
- if (webClientViewAttachmentViewHookXml) {
+ if (params.hasAttachmentViewer) {
// in sheet bg (attachment viewer present)
- setAttributes(webClientViewAttachmentViewHookXml, {
- 't-if': `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`,
- });
- const sheetBgChatterContainerHookXml = chatterContainerHookXml.cloneNode(true);
+ const sheetBgChatterContainerHookXml = chatterContainerHookXml;
sheetBgChatterContainerHookXml.classList.add('o-isInFormSheetBg');
setAttributes(sheetBgChatterContainerHookXml, {
- 't-if': `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`,
+ 't-if': `uiService.size < ${SIZES.XXL} or hasAttachmentViewer()`,
});
append(formSheetBgXml, sheetBgChatterContainerHookXml);
const sheetBgChatterContainerXml = sheetBgChatterContainerHookXml.querySelector('ChatterContainer');
@@ -116,29 +178,19 @@ patch(FormCompiler.prototype, 'mail', {
"hasExternalBorder": "true",
"hasMessageListScrollAdjust": "false",
});
- }
- // after sheet bg (standard position, either aside or below)
- if (webClientViewAttachmentViewHookXml) {
- setAttributes(chatterContainerHookXml, {
- 't-if': `!(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL})`,
- 't-attf-class': `{{ uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}) ? "o-aside" : "" }}`,
- });
- setAttributes(chatterContainerXml, {
- "isInFormSheetBg": "hasAttachmentViewer()",
- "hasExternalBorder": `!(uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}))`,
- "hasMessageListScrollAdjust": `uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL})`,
- });
} else {
+ // after sheet bg (standard position, either aside or below)
setAttributes(chatterContainerXml, {
"isInFormSheetBg": "false",
"hasExternalBorder": `uiService.size < ${SIZES.XXL}`,
"hasMessageListScrollAdjust": `uiService.size >= ${SIZES.XXL}`,
});
setAttributes(chatterContainerHookXml, {
+ 't-if': `uiService.size < ${SIZES.XXL}`,
't-attf-class': `{{ uiService.size >= ${SIZES.XXL} ? "o-aside" : "" }}`,
});
+ append(parentXml, chatterContainerHookXml);
}
- append(parentXml, chatterContainerHookXml);
return res;
},
});
diff --git a/addons/mail/static/src/views/form/form_controller.js b/addons/mail/static/src/views/form/form_controller.js
new file mode 100644
index 00000000000..0920be0f0af
--- /dev/null
+++ b/addons/mail/static/src/views/form/form_controller.js
@@ -0,0 +1,79 @@
+/** @odoo-module */
+
+import { useModels } from "@mail/component_hooks/use_models";
+import { ChatterContainer } from "@mail/components/chatter_container/chatter_container";
+import { WebClientViewAttachmentViewContainer } from "@mail/components/web_client_view_attachment_view_container/web_client_view_attachment_view_container";
+
+import { browser } from "@web/core/browser/browser";
+import { useService } from "@web/core/utils/hooks";
+import { patch } from "@web/core/utils/patch";
+import { useDebounced } from "@web/core/utils/timing";
+import { FormController } from "@web/views/form/form_controller";
+import { useViewCompiler } from "@web/views/view_compiler";
+import { evalDomain } from "@web/views/utils";
+
+import { MailFormCompiler } from "./form_compiler";
+
+const { onMounted, onWillUnmount } = owl;
+
+patch(FormController.prototype, "mail", {
+ setup() {
+ this._super();
+ this.uiService = useService("ui");
+ this.hasAttachmentViewerInArch = false;
+
+ if (this.env.services.messaging) {
+ useModels();
+ }
+
+ const { archInfo } = this.props;
+ const { arch, xmlDoc } = archInfo;
+
+ const template = document.createElement("t");
+ const xmlDocAttachmentPreview = xmlDoc.querySelector("div.o_attachment_preview");
+ if (xmlDocAttachmentPreview && xmlDocAttachmentPreview.parentNode.nodeName === "form") {
+ // TODO hasAttachmentViewer should also depend on the groups= and/or invisible modifier on o_attachment_preview (see invoice form)
+ template.appendChild(xmlDocAttachmentPreview);
+ this.hasAttachmentViewerInArch = true;
+ }
+
+ const xmlDocChatter = xmlDoc.querySelector("div.oe_chatter");
+ if (xmlDocChatter && xmlDocChatter.parentNode.nodeName === "form") {
+ template.appendChild(xmlDocChatter.cloneNode(true));
+ }
+
+ const mailTemplates = useViewCompiler(MailFormCompiler, arch, { Mail: template }, {});
+ this.mailTemplate = mailTemplates.Mail;
+
+ this.onResize = useDebounced(this.render, 200);
+ onMounted(() => browser.addEventListener("resize", this.onResize));
+ onWillUnmount(() => browser.removeEventListener("resize", this.onResize));
+ },
+ /**
+ * @returns {Messaging|undefined}
+ */
+ getMessaging() {
+ return this.env.services.messaging && this.env.services.messaging.modelManager.messaging;
+ },
+ /**
+ * @returns {boolean}
+ */
+ hasAttachmentViewer() {
+ if (!this.getMessaging() || !this.model.root.resId) {
+ return false;
+ }
+ const thread = this.getMessaging().models['Thread'].insert({
+ id: this.model.root.resId,
+ model: this.model.root.resModel,
+ });
+ return thread.attachmentsInWebClientView.length > 0;
+ },
+ evalDomainFromRecord(record, expr) {
+ return evalDomain(expr, record.evalContext);
+ },
+});
+
+Object.assign(FormController.components, {
+ ChatterContainer,
+ WebClientViewAttachmentViewContainer,
+});
diff --git a/addons/mail/static/src/views/form/form_controller.xml b/addons/mail/static/src/views/form/form_controller.xml
new file mode 100644
index 00000000000..b15f4d7e4fd
--- /dev/null
+++ b/addons/mail/static/src/views/form/form_controller.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ hasAttachmentViewerInArch
+
+
+
+
diff --git a/addons/mail/static/src/views/form/form_renderer.js b/addons/mail/static/src/views/form/form_renderer.js
index 40237e82aa6..82d84e74721 100644
--- a/addons/mail/static/src/views/form/form_renderer.js
+++ b/addons/mail/static/src/views/form/form_renderer.js
@@ -15,6 +15,13 @@ patch(FormRenderer.prototype, 'mail', {
}
},
+ get compileParams() {
+ return {
+ ...this._super(),
+ hasAttachmentViewer: this.props.hasAttachmentViewer,
+ };
+ },
+
//--------------------------------------------------------------------------
// Mail Methods
//--------------------------------------------------------------------------
diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer.scss b/addons/mail/static/src/widgets/form_renderer/form_renderer.scss
index 7ff19adfab6..aef5bcd2c61 100644
--- a/addons/mail/static/src/widgets/form_renderer/form_renderer.scss
+++ b/addons/mail/static/src/widgets/form_renderer/form_renderer.scss
@@ -34,7 +34,6 @@
.o_FormRenderer_chatterContainer {
display: flex;
- flex: 1 1 auto;
margin: 0 auto;
max-width: $o-form-view-sheet-max-width;
padding: map-get($spacers, 3) map-get($spacers, 3) map-get($spacers, 5);
diff --git a/addons/web/static/src/views/form/form_controller.scss b/addons/web/static/src/views/form/form_controller.scss
index d075bb3e586..6a44a81253e 100644
--- a/addons/web/static/src/views/form/form_controller.scss
+++ b/addons/web/static/src/views/form/form_controller.scss
@@ -224,6 +224,13 @@
}
}
+ .o_form_view_container {
+ display: flex;
+ flex-flow: column nowrap;
+ height: 100%;
+ flex: 1 1 auto;
+ }
+
// No sheet
&.o_form_nosheet, .o_form_nosheet {
@include o-webclient-padding($top: $o-sheet-vpadding, $bottom: $o-sheet-vpadding);
@@ -1150,6 +1157,7 @@
}
.o_form_view.o_xxl_form_view {
+ flex-flow: row nowrap !important;
.o_form_sheet_bg {
flex: 1 1 auto; // Side chatter is disabled if this has to shrink but this was added for safety
diff --git a/addons/web/static/src/views/form/form_controller.xml b/addons/web/static/src/views/form/form_controller.xml
index 20a3e726a42..89541836651 100644
--- a/addons/web/static/src/views/form/form_controller.xml
+++ b/addons/web/static/src/views/form/form_controller.xml
@@ -3,43 +3,58 @@
diff --git a/addons/web/static/src/views/form/form_renderer.js b/addons/web/static/src/views/form/form_renderer.js
index 9034eac12dc..8725b330733 100644
--- a/addons/web/static/src/views/form/form_renderer.js
+++ b/addons/web/static/src/views/form/form_renderer.js
@@ -65,6 +65,10 @@ export class FormRenderer extends Component {
evalDomainFromRecord(record, expr) {
return evalDomain(expr, record.evalContext);
}
+
+ get compileParams() {
+ return {};
+ }
}
FormRenderer.template = xml``;
diff --git a/addons/web/static/src/views/view_compiler.js b/addons/web/static/src/views/view_compiler.js
index c34815ac0d8..0cc5e426e39 100644
--- a/addons/web/static/src/views/view_compiler.js
+++ b/addons/web/static/src/views/view_compiler.js
@@ -254,7 +254,8 @@ export class ViewCompiler {
return;
}
if (!params.enableInvisible) {
- let isVisileExpr = `!evalDomainFromRecord(props.record,${JSON.stringify(invisible)})`;
+ const recordExpr = params.recordExpr || "props.record";
+ let isVisileExpr = `!evalDomainFromRecord(${recordExpr},${JSON.stringify(invisible)})`;
if (compiled.hasAttribute("t-if")) {
const formerTif = compiled.getAttribute("t-if");
isVisileExpr = `( ${formerTif} ) and ${isVisileExpr}`;
diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js b/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js
index 30552a9d0e6..9a7c4f19ed5 100644
--- a/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js
+++ b/addons/web/static/src/webclient/settings_form_view/settings_form_renderer.js
@@ -20,13 +20,17 @@ export class SettingsFormRenderer extends FormRenderer {
if (!labels[this.props.archInfo.arch]) {
labels[this.props.archInfo.arch] = [];
}
- this.compileParams = {
+ super.setup();
+ this.searchState = useState(this.env.searchState);
+ }
+
+ get compileParams() {
+ return {
+ ...super.compileParams,
labels: labels[this.props.archInfo.arch],
getFieldExpr: this.getFieldExpr,
record: this.props.record,
};
- super.setup();
- this.searchState = useState(this.env.searchState);
}
getFieldExpr(fieldName, fieldWidget) {
diff --git a/addons/web/static/src/webclient/webclient_layout.scss b/addons/web/static/src/webclient/webclient_layout.scss
index 4d35d4f298e..c225285ff2f 100644
--- a/addons/web/static/src/webclient/webclient_layout.scss
+++ b/addons/web/static/src/webclient/webclient_layout.scss
@@ -34,7 +34,7 @@ html {
> .o_control_panel {
flex: 0 0 auto;
}
- > .o_content {
+ .o_content {
flex: 1 1 auto;
position: relative; // Allow to redistribute the 100% height to its child
overflow: auto;
@@ -47,7 +47,7 @@ html {
&:not(.o_action_delegate_scroll) {
overflow: auto;
- > .o_content {
+ .o_content {
overflow: initial;
}
}