diff --git a/addons/mail/views/mail_template_views.xml b/addons/mail/views/mail_template_views.xml index 027b095ae9e..4412e327d0c 100644 --- a/addons/mail/views/mail_template_views.xml +++ b/addons/mail/views/mail_template_views.xml @@ -54,7 +54,7 @@ diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js index 21f86b1a657..939bd30b99c 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js @@ -13,6 +13,7 @@ import { HtmlField } from "@web_editor/js/backend/html_field"; import { getWysiwygClass } from 'web_editor.loader'; import { device } from 'web.config'; import { MassMailingMobilePreviewDialog } from "./mass_mailing_mobile_preview"; +import { getRangePosition } from '@web_editor/js/editor/odoo-editor/src/utils/utils'; const { useEffect, @@ -79,6 +80,29 @@ export class MassMailingHtmlField extends HtmlField { }; } + /** + * @param {HTMLElement} popover + * @param {Object} position + * @override + */ + positionDynamicPlaceholder(popover, position) { + const editable = this.wysiwyg.$iframe ? this.wysiwyg.$iframe[0] : this.wysiwyg.$editable[0]; + const relativeParentPosition = editable.getBoundingClientRect(); + + let topPosition = relativeParentPosition.top; + let leftPosition = relativeParentPosition.left; + + const rangePosition = getRangePosition(popover, this.wysiwyg.options.document); + topPosition += rangePosition.top; + // Offset the popover to ensure the arrow is pointing at + // the precise range location. + leftPosition += rangePosition.left - 14; + + // Apply the position back to the element. + popover.style.top = topPosition + 'px'; + popover.style.left = leftPosition + 'px'; + } + async commitChanges() { if (this.props.readonly || !this.isRendered) { return super.commitChanges(); diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector.js b/addons/web/static/src/core/model_field_selector/model_field_selector.js index 43714898f40..a6434056d3e 100644 --- a/addons/web/static/src/core/model_field_selector/model_field_selector.js +++ b/addons/web/static/src/core/model_field_selector/model_field_selector.js @@ -1,28 +1,11 @@ /** @odoo-module **/ -import { usePopover } from "@web/core/popover/popover_hook"; import { useModelField } from "./model_field_hook"; +import { useUniquePopover } from "./unique_popover_hook"; import { ModelFieldSelectorPopover } from "./model_field_selector_popover"; const { Component, onWillStart, onWillUpdateProps } = owl; -function useUniquePopover() { - const popover = usePopover(); - let remove = null; - return Object.assign(Object.create(popover), { - add(target, component, props, options) { - if (remove) { - remove(); - } - remove = popover.add(target, component, props, options); - return () => { - remove(); - remove = null; - }; - }, - }); -} - export class ModelFieldSelector extends Component { setup() { this.popover = useUniquePopover(); diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js index d55ef218fa4..332c9a9c3b3 100644 --- a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js +++ b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js @@ -12,12 +12,17 @@ export class ModelFieldSelectorPopover extends Component { setup() { this.chain = Array.from(this.props.chain); this.modelField = useModelField(); + this.unfilteredFields = {}; this.fields = {}; this.fieldKeys = []; + this.currentActiveFieldId = 0; this.searchValue = ""; + this.defaultValue = ""; + this.isDefaultValueVisible = false; this.fullFieldName = this.fieldNameChain.join("."); if (!this.env.isSmall) { useAutofocus(); + useAutofocus({ refName: 'autofocusDefaultValue', selectAll: true }); } onWillStart(async () => { @@ -25,6 +30,10 @@ export class ModelFieldSelectorPopover extends Component { }); } + get currentActiveField() { + return this.fieldKeys[this.currentActiveFieldId]; + } + get currentNode() { return this.chain[this.chain.length - 1]; } @@ -38,7 +47,15 @@ export class ModelFieldSelectorPopover extends Component { } async loadFields() { - this.fields = await this.modelField.loadModelFields(this.currentNode.resModel); + this.unfilteredFields = await this.modelField.loadModelFields(this.currentNode.resModel); + this.fields = {...this.unfilteredFields}; + this.fieldKeys = this.sortedKeys(this.fields); + for (let key of this.fieldKeys) { + const field = this.fields[key]; + if (!field.searchable || !this.props.filter(field)) { + delete this.fields[key]; + } + } this.fieldKeys = this.sortedKeys(this.fields); } sortedKeys(obj) { @@ -53,6 +70,47 @@ export class ModelFieldSelectorPopover extends Component { this.render(); } + async onInputKeydown(ev) { + switch (ev.key) { + case "ArrowUp": + ev.preventDefault(); + ev.stopPropagation(); + if (this.currentActiveFieldId> 0) { + this.currentActiveFieldId--; + await this.render(); + } + break; + case "ArrowDown": + ev.preventDefault(); + ev.stopPropagation(); + if (this.currentActiveFieldId < this.fieldKeys.length-1) { + this.currentActiveFieldId++; + await this.render(); + } + break; + case "ArrowLeft": + ev.preventDefault(); + ev.stopPropagation(); + this.onPreviousBtnClick(); + break; + case "Escape": + ev.preventDefault(); + ev.stopPropagation(); + this.props.close(); + break; + case "Enter": + case "ArrowRight": + ev.preventDefault(); + ev.stopPropagation(); + if (this.isDefaultValueVisible) { + this.selectDefaultValue(true); + } else { + const field = { ...this.fields[this.currentActiveField], name: this.currentActiveField } + this.onFieldSelected(field); + } + break; + } + } onSearch(ev) { this.searchValue = ev.target.value; let fieldKeys = this.sortedKeys(this.fields); @@ -62,6 +120,10 @@ export class ModelFieldSelectorPopover extends Component { this.fieldKeys = fieldKeys; this.render(); } + onDefaultValue(ev) { + this.defaultValue = ev.target.value; + this.render(); + } onPreviousBtnClick() { this.searchValue = ""; if (this.currentNode.field === null) { @@ -72,16 +134,31 @@ export class ModelFieldSelectorPopover extends Component { } onFieldSelected(field) { this.searchValue = ""; + this.currentActiveFieldId = 0; this.currentNode.field = field; if (field.relation && this.props.followRelations) { this.chain.push({ resModel: field.relation, field: null, }); + this.update(); + } else if(this.props.needDefaultValue) { + this.isDefaultValueVisible = true; + this.render(); + this.update(); } else { + this.update(); this.props.close(); + this.props.validate(this.fieldNameChain, this.defaultValue); } + } + selectDefaultValue (acceptDefaultValue) { + if (!acceptDefaultValue) { + this.defaultValue = ""; + } + this.props.close(); this.update(); + this.props.validate(this.fieldNameChain, this.defaultValue); } async onFieldNameChange(ev) { this.fullFieldName = ev.target.value.replace(/\s+/g, ""); @@ -98,19 +175,24 @@ export class ModelFieldSelectorPopover extends Component { } } -Object.assign(ModelFieldSelectorPopover, { - template: "web.ModelFieldSelectorPopover", - props: { - chain: Array, - update: Function, - showSearchInput: Boolean, - isDebugMode: Boolean, - loadChain: Function, - filter: Function, - close: Function, - followRelations: { type: Boolean, optional: true }, - }, - defaultProps: { - followRelations: true, - }, -}); +ModelFieldSelectorPopover.defaultProps = { + validate: () => {}, + needDefaultValue: false, + isDebugMode: false, + followRelations: true, +}; + +ModelFieldSelectorPopover.props = { + chain: Array, + update: Function, + showSearchInput: Boolean, + isDebugMode: { type: Boolean, optional: true}, + loadChain: Function, + filter: Function, + close: Function, + followRelations: { type: Boolean, optional: true }, + needDefaultValue: { type: Boolean, optional: true}, + validate: { type: Function, optional: true}, +}; + +ModelFieldSelectorPopover.template = "web.ModelFieldSelectorPopover"; diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml index 8426068b7d3..f2c6241c4a3 100644 --- a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml +++ b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml @@ -2,7 +2,8 @@ - + - + + Default value + + + + - - + - + + + + + + - + - - + + + () + + + + Default text is used when no values are set + + + As a default text when no value are set + + +