[REF] mail: make thread actions more generic
This will remove the necessity to patch component, and remove duplciation between discuss and chat window. Part of task-3265211 closes odoo/odoo#123454 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
import { ChatWindow } from "@mail/chat_window/chat_window";
|
||||
import { FeedbackPanel } from "../feedback_panel/feedback_panel";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { SESSION_STATE } from "../core/livechat_service";
|
||||
@@ -33,22 +32,4 @@ patch(ChatWindow.prototype, "im_livechat", {
|
||||
this.livechatService.leaveSession();
|
||||
this.chatbotService.stop();
|
||||
},
|
||||
|
||||
get actions() {
|
||||
if (!this.chatbotService.canRestart) {
|
||||
return this._super();
|
||||
}
|
||||
return this._super().concat([
|
||||
{
|
||||
id: "restart",
|
||||
name: _t("Restart Conversation"),
|
||||
icon: "fa fa-fw fa-refresh",
|
||||
onSelect: () => {
|
||||
this.chatbotService.restart();
|
||||
this.chatWindowService.show(this.props.chatWindow);
|
||||
},
|
||||
sequence: 99,
|
||||
},
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("mail.thread/actions").add("restart", {
|
||||
condition(component) {
|
||||
return component.chatbotService.canRestart;
|
||||
},
|
||||
icon: "fa fa-fw fa-refresh",
|
||||
name: _t("Restart Conversation"),
|
||||
open(component) {
|
||||
component.chatbotService.restart();
|
||||
component.chatWindowService.show(component.props.chatWindow);
|
||||
},
|
||||
sequence: 99,
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { Call } from "@mail/rtc/call";
|
||||
import { Thread } from "@mail/core_ui/thread";
|
||||
import { Composer } from "@mail/composer/composer";
|
||||
import { useStore } from "@mail/core/messaging_hook";
|
||||
import { useThreadActions } from "@mail/core/thread_actions";
|
||||
import { useRtc } from "@mail/rtc/rtc_hook";
|
||||
import { useMessageEdition, useMessageHighlight, useMessageToReplyTo } from "@mail/utils/hooks";
|
||||
import { Component, useChildSubEnv, useRef, useState } from "@odoo/owl";
|
||||
@@ -16,10 +17,6 @@ import { _t } from "@web/core/l10n/translation";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
|
||||
export const MODES = {
|
||||
NONE: "",
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {import("@mail/chat_window/chat_window_model").ChatWindow} chatWindow
|
||||
@@ -40,7 +37,6 @@ export class ChatWindow extends Component {
|
||||
static template = "mail.ChatWindow";
|
||||
|
||||
setup() {
|
||||
this.MODES = MODES;
|
||||
this.store = useStore();
|
||||
/** @type {import("@mail/chat_window/chat_window_service").ChatWindowService} */
|
||||
this.chatWindowService = useState(useService("mail.chat_window"));
|
||||
@@ -50,19 +46,10 @@ export class ChatWindow extends Component {
|
||||
this.messageEdition = useMessageEdition();
|
||||
this.messageHighlight = useMessageHighlight();
|
||||
this.messageToReplyTo = useMessageToReplyTo();
|
||||
this.state = useState({
|
||||
moreActionsExpanded: false,
|
||||
/**
|
||||
* activeMode:
|
||||
* "member-list": channel member list is displayed
|
||||
* "in-settings": settings is displayed
|
||||
* "add-users": add users is displayed (small device)
|
||||
* "": no action pannel
|
||||
*/
|
||||
activeMode: MODES.NONE,
|
||||
});
|
||||
this.state = useState({ moreActionsExpanded: false });
|
||||
this.ui = useState(useService("ui"));
|
||||
this.contentRef = useRef("content");
|
||||
this.threadActions = useThreadActions();
|
||||
useChildSubEnv({
|
||||
inChatWindow: true,
|
||||
messageHighlight: this.messageHighlight,
|
||||
@@ -125,35 +112,6 @@ export class ChatWindow extends Component {
|
||||
this.chatWindowService.notifyState(this.props.chatWindow);
|
||||
}
|
||||
|
||||
get orderedActions() {
|
||||
return this.actions.sort((act1, act2) => act1.sequence - act2.sequence);
|
||||
}
|
||||
|
||||
get actions() {
|
||||
const acts = [];
|
||||
if (
|
||||
this.thread?.allowCalls &&
|
||||
this.thread !== this.rtc.state.channel &&
|
||||
!this.props.chatWindow.hidden
|
||||
) {
|
||||
acts.push({
|
||||
id: "call",
|
||||
name: _t("Start a Call"),
|
||||
icon: "fa fa-fw fa-phone",
|
||||
onSelect: () => this.rtc.toggleCall(this.props.chatWindow.thread),
|
||||
sequence: 10,
|
||||
});
|
||||
}
|
||||
acts.push({
|
||||
id: "close",
|
||||
name: _t("Close Chat Window"),
|
||||
icon: "fa fa-fw fa-close",
|
||||
onSelect: () => this.close(),
|
||||
sequence: 100,
|
||||
});
|
||||
return acts;
|
||||
}
|
||||
|
||||
get moreMenuText() {
|
||||
return _t("More actions");
|
||||
}
|
||||
|
||||
@@ -32,33 +32,34 @@
|
||||
(<t t-out="thread.message_unread_counter"/>)
|
||||
</div>
|
||||
<div class="flex-grow-1"/>
|
||||
<t t-if="actions.length > 1">
|
||||
<t t-set="firstAction" t-value="orderedActions[0]"/>
|
||||
<button class="o-mail-ChatWindow-command btn d-flex p-2 opacity-75 opacity-100-hover" t-att-title="firstAction.name" t-on-click.stop="firstAction.onSelect"><i t-att-class="firstAction.icon"/></button>
|
||||
<t t-if="threadActions.actions.length > 1">
|
||||
<t t-set="firstAction" t-value="threadActions.actions[0]"/>
|
||||
<button class="o-mail-ChatWindow-command btn d-flex p-2 opacity-75 opacity-100-hover" t-att-title="firstAction.name" t-on-click.stop="() => firstAction.onSelect()"><i t-att-class="firstAction.icon"/></button>
|
||||
</t>
|
||||
<t t-if="actions.length === 3">
|
||||
<t t-set="secondAction" t-value="orderedActions[1]"/>
|
||||
<button class="o-mail-ChatWindow-command btn d-flex p-2 opacity-75 opacity-100-hover" t-att-title="secondAction.name" t-on-click.stop="secondAction.onSelect"><i t-att-class="secondAction.icon"/></button>
|
||||
<t t-if="threadActions.actions.length === 3">
|
||||
<t t-set="secondAction" t-value="threadActions.actions[1]"/>
|
||||
<button class="o-mail-ChatWindow-command btn d-flex p-2 opacity-75 opacity-100-hover" t-att-title="secondAction.name" t-on-click.stop="() => secondAction.onSelect()"><i t-att-class="secondAction.icon"/></button>
|
||||
</t>
|
||||
<t t-elif="actions.length > 3">
|
||||
<t t-elif="threadActions.actions.length > 3">
|
||||
<Dropdown position="'bottom-end'" onStateChanged="state => this.onMoreActionsStateChanged(state)" togglerClass="'o-mail-ChatWindow-command d-flex btn p-0 p-2'" menuClass="'d-flex flex-column py-0'" class="'d-flex'" title="moreMenuText">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="fa fa-fw fa-ellipsis-h" tabindex="1"/>
|
||||
</t>
|
||||
<t t-set-slot="default">
|
||||
<DropdownItem t-foreach="orderedActions.slice(1, -1)" t-as="action" t-key="action.id" class="'o-mail-ChatWindow-command btn rounded-0 d-flex align-items-center px-2 py-2 m-0 opacity-75 opacity-100-hover'" title="action.name" onSelected="action.onSelect"><i t-att-class="action.icon"/><span class="mx-2" t-out="action.name"/></DropdownItem>
|
||||
<DropdownItem t-foreach="threadActions.actions.slice(1, -1)" t-as="action" t-key="action.id" class="'o-mail-ChatWindow-command btn rounded-0 d-flex align-items-center px-2 py-2 m-0 opacity-75 opacity-100-hover'" title="action.name" onSelected="() => action.onSelect()"><i t-att-class="action.icon"/><span class="mx-2" t-out="action.name"/></DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</t>
|
||||
<t t-if="actions.length > 0">
|
||||
<t t-set="lastAction" t-value="orderedActions.at(-1)"/>
|
||||
<button class="o-mail-ChatWindow-command btn d-flex p-2 me-1 opacity-75 opacity-100-hover" t-att-title="lastAction.name" t-on-click.stop="lastAction.onSelect"><i t-att-class="lastAction.icon"/></button>
|
||||
<t t-if="threadActions.actions.length > 0">
|
||||
<t t-set="lastAction" t-value="threadActions.actions.at(-1)"/>
|
||||
<button class="o-mail-ChatWindow-command btn d-flex p-2 me-1 opacity-75 opacity-100-hover" t-att-title="lastAction.name" t-on-click.stop="() => lastAction.onSelect()"><i t-att-class="lastAction.icon"/></button>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="!props.chatWindow.folded" class="o-mail-ChatWindow-content bg-view d-flex flex-column h-100 overflow-auto position-relative" t-ref="content">
|
||||
<button t-if="state.activeMode" class="o-mail-ChatWindow-closePanel btn position-absolute top-0 end-0 btn opacity-75 opacity-100-hover" title="Close panel" t-on-click.stop="() => state.activeMode = ''"><i class="fa fa-close"/></button>
|
||||
<button t-if="threadActions.activeAction" class="o-mail-ChatWindow-closePanel btn position-absolute top-0 end-0 btn opacity-75 opacity-100-hover" title="Close panel" t-on-click.stop="() => threadActions.activeAction.close()"><i class="fa fa-close"/></button>
|
||||
<t t-if="thread" name="thread content">
|
||||
<t t-if="state.activeMode === MODES.NONE">
|
||||
<t t-if="threadActions.activeAction?.componentCondition" t-component="threadActions.activeAction.component" thread="thread" t-props="threadActions.activeAction.componentProps"/>
|
||||
<t t-else="">
|
||||
<Call t-if="Object.keys(thread.rtcSessions).length > 0" thread="thread" compact="true"/>
|
||||
<Thread isInChatWindow="true" thread="thread" t-key="thread.localId" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo"/>
|
||||
<Composer composer="thread.composer" autofocus="props.chatWindow.autofocus" mode="'compact'" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo" dropzoneRef="contentRef"/>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { useComponent, useState } from "@odoo/owl";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export const threadActionsRegistry = registry.category("mail.thread/actions");
|
||||
|
||||
threadActionsRegistry.add("close", {
|
||||
condition(component) {
|
||||
return component.props.chatWindow;
|
||||
},
|
||||
icon: "fa fa-fw fa-close",
|
||||
name: _t("Close Chat Window"),
|
||||
open(component) {
|
||||
component.close();
|
||||
},
|
||||
sequence: 100,
|
||||
});
|
||||
|
||||
function transformAction(component, id, action) {
|
||||
return {
|
||||
/** Closes this action. */
|
||||
close() {
|
||||
if (this.toggle) {
|
||||
component.threadActions.activeAction = null;
|
||||
}
|
||||
action.close?.(component, this);
|
||||
},
|
||||
/** Optional component that should be displayed in the view when this action is active. */
|
||||
component: action.component,
|
||||
/** Condition to display the component of this action. */
|
||||
get componentCondition() {
|
||||
return this.isActive && this.component && this.condition && !this.popover;
|
||||
},
|
||||
/** Props to pass to the component of this action. */
|
||||
componentProps: action.componentProps,
|
||||
/** Condition to display this action. */
|
||||
get condition() {
|
||||
return action.condition(component);
|
||||
},
|
||||
/** Condition to disable the button of this action (but still display it). */
|
||||
get disabledCondition() {
|
||||
return action.disabledCondition?.(component);
|
||||
},
|
||||
/** Icon for the button this action. */
|
||||
icon: action.icon,
|
||||
/** Large icon for the button this action. */
|
||||
iconLarge: action.iconLarge ?? action.icon,
|
||||
/** Unique id of this action. */
|
||||
id,
|
||||
/** States whether this action is currently active. */
|
||||
get isActive() {
|
||||
return id === component.threadActions.activeAction?.id;
|
||||
},
|
||||
/** Name of this action, displayed to the user. */
|
||||
get name() {
|
||||
return this.isActive && action.nameActive ? action.nameActive : action.name;
|
||||
},
|
||||
/** Action to execute when this action is selected (on or off). */
|
||||
onSelect() {
|
||||
if (this.toggle && this.isActive) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
},
|
||||
/** Opens this action. */
|
||||
open() {
|
||||
if (this.toggle) {
|
||||
component.threadActions.activeAction = this;
|
||||
}
|
||||
action.open?.(component, this);
|
||||
},
|
||||
/** Determines whether this is a popover linked to this action. */
|
||||
popover: null,
|
||||
/** Determines the order of this action (smaller first). */
|
||||
get sequence() {
|
||||
return typeof action.sequence === "function"
|
||||
? action.sequence(component)
|
||||
: action.sequence;
|
||||
},
|
||||
/** Component setup to execute when this action is registered. */
|
||||
setup: action.setup,
|
||||
/** Text for the button of this action */
|
||||
text: action.text,
|
||||
/** Determines whether this action is a one time effect or can be toggled (on or off). */
|
||||
toggle: action.toggle,
|
||||
};
|
||||
}
|
||||
|
||||
export function useThreadActions() {
|
||||
const component = useComponent();
|
||||
const transformedActions = threadActionsRegistry
|
||||
.getEntries()
|
||||
.map(([id, action]) => transformAction(component, id, action));
|
||||
for (const action of transformedActions) {
|
||||
if (action.setup) {
|
||||
action.setup(action);
|
||||
}
|
||||
}
|
||||
const state = useState({
|
||||
get actions() {
|
||||
return transformedActions
|
||||
.filter((action) => action.condition)
|
||||
.sort((a1, a2) => a1.sequence - a2.sequence);
|
||||
},
|
||||
activeAction: null,
|
||||
});
|
||||
return state;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ $o-mail-LinkPreviewCard-height: 80px !default;
|
||||
$o-mail-Message-sidebarWidth: 48px !default;
|
||||
$o-mail-NavigableList-zIndex: 11;
|
||||
$o-mail-Chatter-minWidth: 530px !default;
|
||||
$o-mail-Discuss-inspector: 250px !default;
|
||||
$o-mail-Discuss-inspector: 300px !default;
|
||||
$o-mail-Discuss-headerHeight: 48px !default;
|
||||
|
||||
@mixin o-FileViewer-arrow {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { threadActionsRegistry } from "@mail/core/thread_actions";
|
||||
import { CallSettings } from "@mail/discuss/core/call_settings";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
threadActionsRegistry
|
||||
.add("call", {
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread?.allowCalls &&
|
||||
component.thread !== component.rtc.state.channel &&
|
||||
!component.props.chatWindow?.hidden
|
||||
);
|
||||
},
|
||||
icon: "fa fa-fw fa-phone",
|
||||
iconLarge: "fa fa-fw fa-lg fa-phone",
|
||||
name: _t("Start a Call"),
|
||||
open(component) {
|
||||
component.rtc.toggleCall(component.thread);
|
||||
},
|
||||
sequence: 10,
|
||||
})
|
||||
.add("settings", {
|
||||
component: CallSettings,
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread?.allowCalls &&
|
||||
(!component.props.chatWindow || component.props.chatWindow.isOpen)
|
||||
);
|
||||
},
|
||||
icon: "fa fa-fw fa-gear",
|
||||
iconLarge: "fa fa-fw fa-lg fa-gear",
|
||||
name: _t("Show Call Settings"),
|
||||
nameActive: _t("Hide Call Settings"),
|
||||
sequence(component) {
|
||||
return component.props.chatWindow && component.thread === component.rtc.state.channel
|
||||
? 6
|
||||
: 60;
|
||||
},
|
||||
toggle: true,
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="discuss.CallSettings" owl="1">
|
||||
<div class="o-discuss-CallSettings py-2 user-select-none bg-view" t-attf-class="{{ props.className }}" t-ref="root">
|
||||
<div class="o-discuss-CallSettings flex-grow-1 py-2 user-select-none bg-view" t-attf-class="{{ props.className }}" t-ref="root">
|
||||
<div class="d-flex flex-column px-3 overflow-auto">
|
||||
<div class="py-2 fw-bolder text-700 text-truncate text-uppercase">Voice Settings</div>
|
||||
<div class="mb-3 d-flex align-items-center flex-wrap">
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useService } from "@web/core/utils/hooks";
|
||||
export class ChannelInvitation extends Component {
|
||||
static components = { ImStatus };
|
||||
static defaultProps = { hasSizeConstraints: false };
|
||||
static props = ["hasSizeConstraints?", "thread", "close?", "chatState?"];
|
||||
static props = ["hasSizeConstraints?", "thread", "close", "className?"];
|
||||
static template = "discuss.ChannelInvitation";
|
||||
|
||||
setup() {
|
||||
@@ -114,11 +114,7 @@ export class ChannelInvitation extends Component {
|
||||
}
|
||||
);
|
||||
}
|
||||
if (this.ui.isSmall) {
|
||||
this.props.chatState.activeMode = "";
|
||||
} else {
|
||||
this.props.close();
|
||||
}
|
||||
this.props.close();
|
||||
}
|
||||
|
||||
get invitationButtonText() {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.o-discuss-ChannelInvitation {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.o-discuss-ChannelInvitation-has-size-constraints {
|
||||
width: Min(95vw, 400px);
|
||||
max-height: Min(calc(100vh - 140px), 530px);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="discuss.ChannelInvitation" owl="1">
|
||||
<div class="o-discuss-ChannelInvitation bg-view d-flex flex-column" t-att-class="{ 'o-discuss-ChannelInvitation-has-size-constraints': props.hasSizeConstraints }">
|
||||
<div class="o-discuss-ChannelInvitation bg-view d-flex flex-column" t-att-class="{ 'o-discuss-ChannelInvitation-has-size-constraints': props.hasSizeConstraints }" t-attf-class="{{ props.className }}">
|
||||
<t t-if="store.user">
|
||||
<h3 class="mx-3 mt-3 mb-2">Invite people</h3>
|
||||
<div class="mx-3 my-2">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ChannelMemberList extends Component {
|
||||
static components = { ImStatus };
|
||||
static props = ["thread", "className"];
|
||||
static props = ["thread", "className?"];
|
||||
static template = "discuss.ChannelMemberList";
|
||||
|
||||
setup() {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { CallSettings } from "@mail/discuss/core/call_settings";
|
||||
import { ChannelInvitation } from "@mail/discuss/core/channel_invitation";
|
||||
import { ChannelMemberList } from "@mail/discuss/core/channel_member_list";
|
||||
import { Discuss } from "@mail/discuss_app/discuss";
|
||||
import { useRef } from "@odoo/owl";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Discuss, "discuss", {
|
||||
components: { ...Discuss.components, CallSettings, ChannelMemberList },
|
||||
});
|
||||
|
||||
patch(Discuss.prototype, "discuss", {
|
||||
setup() {
|
||||
this._super();
|
||||
this.addUsersRef = useRef("addUsers");
|
||||
this.popover = usePopover(ChannelInvitation, {
|
||||
onClose: () => (this.state.isAddingUsers = false),
|
||||
});
|
||||
this.state.isAddingUsers = false;
|
||||
},
|
||||
toggleInviteForm() {
|
||||
if (this.popover.isOpen) {
|
||||
this.popover.close();
|
||||
} else {
|
||||
this.state.isAddingUsers = true;
|
||||
this.popover.open(this.addUsersRef.el, {
|
||||
hasSizeConstraints: true,
|
||||
thread: this.thread,
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleMemberList() {
|
||||
this.state.activeMode =
|
||||
this.state.activeMode === this.MODES.MEMBER_LIST
|
||||
? this.MODES.NONE
|
||||
: this.MODES.MEMBER_LIST;
|
||||
},
|
||||
toggleSettings() {
|
||||
this.state.activeMode =
|
||||
this.state.activeMode === this.MODES.SETTINGS ? this.MODES.NONE : this.MODES.SETTINGS;
|
||||
},
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
.o-mail-Discuss-callSettings {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.o-mail-Discuss-channelMemberList {
|
||||
width: $o-mail-Discuss-inspector;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.Discuss" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@t-ref='startCall']" position="after">
|
||||
<button t-if="thread.model === 'discuss.channel'" class="btn rounded px-2 shadow-none" t-att-class="{ 'o-isActive': state.isAddingUsers }" title="Add Users" t-on-click.stop="toggleInviteForm" t-ref="addUsers">
|
||||
<i class="fa fa-lg fa-user-plus text-700"/>
|
||||
</button>
|
||||
<button t-if="thread.hasMemberList" class="btn rounded px-2 shadow-none" t-att-class="{ 'o-isActive': state.activeMode === MODES.MEMBER_LIST }" t-att-title="state.activeMode === MODES.MEMBER_LIST ? 'Hide Member List' : 'Show Member List'" t-on-click.stop="toggleMemberList">
|
||||
<i class="fa fa-lg fa-users text-700"/>
|
||||
</button>
|
||||
<button t-if="thread.allowCalls" class="btn rounded px-2 shadow-none" t-att-class="{ 'o-isActive': state.activeMode === MODES.SETTINGS }" t-att-title="state.activeMode === MODES.SETTINGS ? 'Hide Call Settings' : 'Show Call Settings'" t-on-click="toggleSettings">
|
||||
<i class="fa fa-lg fa-gear text-700"/>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//*[@t-ref='core']" position="inside">
|
||||
<ChannelMemberList t-if="thread.hasMemberList and state.activeMode === MODES.MEMBER_LIST" thread="thread" className="'o-mail-Discuss-channelMemberList flex-shrink-0 border-start'"/>
|
||||
<CallSettings t-if="thread.allowCalls and state.activeMode === MODES.SETTINGS" thread="thread" className="'o-mail-Discuss-callSettings border-start'"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,60 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { threadActionsRegistry } from "@mail/core/thread_actions";
|
||||
import { ChannelInvitation } from "@mail/discuss/core/channel_invitation";
|
||||
import { ChannelMemberList } from "@mail/discuss/core/channel_member_list";
|
||||
import { useComponent } from "@odoo/owl";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
|
||||
threadActionsRegistry
|
||||
.add("add-users", {
|
||||
close(component, action) {
|
||||
action.popover?.close();
|
||||
},
|
||||
component: ChannelInvitation,
|
||||
componentProps(action) {
|
||||
return { close: () => action.close() };
|
||||
},
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread?.model === "discuss.channel" &&
|
||||
(!component.props.chatWindow || component.props.chatWindow.isOpen)
|
||||
);
|
||||
},
|
||||
icon: "fa fa-fw fa-user-plus",
|
||||
iconLarge: "fa fa-fw fa-lg fa-user-plus",
|
||||
name: _t("Add Users"),
|
||||
nameActive: _t("Stop Adding Users"),
|
||||
open(component, action) {
|
||||
action.popover?.open(component.root.el.querySelector(`[name="${action.id}"]`), {
|
||||
hasSizeConstraints: true,
|
||||
thread: component.thread,
|
||||
});
|
||||
},
|
||||
sequence: 30,
|
||||
setup(action) {
|
||||
const component = useComponent();
|
||||
if (!component.props.chatWindow) {
|
||||
action.popover = usePopover(ChannelInvitation, {
|
||||
onClose: () => action.close(),
|
||||
});
|
||||
}
|
||||
},
|
||||
toggle: true,
|
||||
})
|
||||
.add("member-list", {
|
||||
component: ChannelMemberList,
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread?.hasMemberList &&
|
||||
(!component.props.chatWindow || component.props.chatWindow.isOpen)
|
||||
);
|
||||
},
|
||||
icon: "fa fa-fw fa-users",
|
||||
iconLarge: "fa fa-fw fa-lg fa-users",
|
||||
name: _t("Show Member List"),
|
||||
nameActive: _t("Hide Member List"),
|
||||
sequence: 40,
|
||||
toggle: true,
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { PinnedMessagesPanel } from "@mail/discuss/message_pin/pinned_messages_panel";
|
||||
import { Discuss, MODES } from "@mail/discuss_app/discuss";
|
||||
import { useChildSubEnv } from "@odoo/owl";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
MODES.PINNED_MESSAGES = "pinned-messages";
|
||||
|
||||
patch(Discuss, "discuss/message_pin", {
|
||||
components: { ...Discuss.components, PinnedMessagesPanel },
|
||||
});
|
||||
|
||||
patch(Discuss.prototype, "discuss/message_pin", {
|
||||
setup() {
|
||||
this._super();
|
||||
useChildSubEnv({
|
||||
pinMenu: {
|
||||
open: () => (this.state.activeMode = "pinned-messages"),
|
||||
close: () => {
|
||||
if (this.state.activeMode === "pinned-messages") {
|
||||
this.state.activeMode = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
togglePinMenu() {
|
||||
this.state.activeMode =
|
||||
this.state.activeMode === MODES.PINNED_MESSAGES ? MODES.NONE : MODES.PINNED_MESSAGES;
|
||||
},
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.Discuss" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@t-ref='startCall']" position="before">
|
||||
<button t-if="thread.model === 'discuss.channel'" class="btn rounded px-2 shadow-none" t-att-class="{ 'o-isActive': state.activeMode === MODES.PINNED_MESSAGES }" title="Pinned messages" t-on-click.stop="togglePinMenu">
|
||||
<i class="fa fa-lg fa-thumb-tack text-700"/>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//*[@t-ref='core']" position="inside">
|
||||
<PinnedMessagesPanel t-if="state.activeMode === MODES.PINNED_MESSAGES and thread.model === 'discuss.channel'" thread="thread"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -11,7 +11,7 @@ export class PinnedMessagesPanel extends Component {
|
||||
static components = {
|
||||
Message,
|
||||
};
|
||||
static props = ["thread"];
|
||||
static props = ["thread", "className?"];
|
||||
static template = "discuss.PinnedMessagesPanel";
|
||||
|
||||
setup() {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
<t t-name="discuss.PinnedMessagesPanel" owl="1">
|
||||
<div class="o-discuss-PinnedMessagesPanel overflow-auto d-flex flex-column flex-shrink-0 bg-view position-relative" t-att-class="{
|
||||
'flex-grow-1': env.inChatWindow,
|
||||
'border-start': !env.inChatWindow,
|
||||
}">
|
||||
}" t-attf-class="{{ props.className }}">
|
||||
<p class="o-discuss-PinnedMessagesPanel-title position-sticky top-0 py-3 mb-0 fw-bolder text-center text-uppercase text-700">
|
||||
Pinned messages
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { threadActionsRegistry } from "@mail/core/thread_actions";
|
||||
import { PinnedMessagesPanel } from "@mail/discuss/message_pin/pinned_messages_panel";
|
||||
import { useChildSubEnv } from "@odoo/owl";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
threadActionsRegistry.add("pinned-messages", {
|
||||
component: PinnedMessagesPanel,
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread?.model === "discuss.channel" &&
|
||||
(!component.props.chatWindow || component.props.chatWindow.isOpen)
|
||||
);
|
||||
},
|
||||
icon: "fa fa-fw fa-thumb-tack",
|
||||
iconLarge: "fa fa-fw fa-lg fa-thumb-tack",
|
||||
name: _t("Pinned Messages"),
|
||||
nameActive: _t("Hide Pinned Messages"),
|
||||
sequence: 20,
|
||||
setup(action) {
|
||||
useChildSubEnv({
|
||||
pinMenu: {
|
||||
open: () => action.open(),
|
||||
close: () => {
|
||||
if (action.isActive) {
|
||||
action.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
toggle: true,
|
||||
});
|
||||
@@ -1,15 +1,9 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { Thread } from "@mail/core_ui/thread";
|
||||
import { useState } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Thread.prototype, "discuss/message_pin", {
|
||||
setup() {
|
||||
this._super();
|
||||
this.ui = useState(useService("ui"));
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {MouseEvent} ev
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { PinnedMessagesPanel } from "@mail/discuss/message_pin/pinned_messages_panel";
|
||||
import { ChatWindow, MODES } from "@mail/chat_window/chat_window";
|
||||
import { useChildSubEnv } from "@odoo/owl";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
MODES.PINNED_MESSAGES = "pinned-messages";
|
||||
|
||||
patch(ChatWindow, "discuss/message_pin", {
|
||||
components: { ...ChatWindow.components, PinnedMessagesPanel },
|
||||
});
|
||||
|
||||
patch(ChatWindow.prototype, "discuss/message_pin", {
|
||||
setup() {
|
||||
this._super();
|
||||
useChildSubEnv({
|
||||
pinMenu: {
|
||||
open: () => (this.state.activeMode = MODES.PINNED_MESSAGES),
|
||||
close: () => {
|
||||
if (this.state.activeMode === MODES.PINNED_MESSAGES) {
|
||||
this.state.activeMode = MODES.NONE;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
togglePinMenu() {
|
||||
this.state.activeMode =
|
||||
this.state.activeMode === MODES.PINNED_MESSAGES ? MODES.NONE : MODES.PINNED_MESSAGES;
|
||||
},
|
||||
get actions() {
|
||||
const acts = this._super();
|
||||
if (this.thread?.model === "discuss.channel" && this.props.chatWindow.isOpen) {
|
||||
acts.push({
|
||||
id: "pinned",
|
||||
name: _t("Pinned Messages"),
|
||||
icon: "fa fa-fw fa-thumb-tack",
|
||||
onSelect: () => this.togglePinMenu(),
|
||||
sequence: 20,
|
||||
});
|
||||
}
|
||||
return acts;
|
||||
},
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.ChatWindow" t-inherit-mode="extension">
|
||||
<xpath expr="//t[@name='thread content']" position="inside">
|
||||
<PinnedMessagesPanel t-if="state.activeMode === MODES.PINNED_MESSAGES" thread="thread"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,64 +0,0 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { CallSettings } from "@mail/discuss/core/call_settings";
|
||||
import { ChannelInvitation } from "@mail/discuss/core/channel_invitation";
|
||||
import { ChannelMemberList } from "@mail/discuss/core/channel_member_list";
|
||||
import { ChatWindow } from "@mail/chat_window/chat_window";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
patch(ChatWindow, "discuss", {
|
||||
components: { ...ChatWindow.components, CallSettings, ChannelInvitation, ChannelMemberList },
|
||||
});
|
||||
|
||||
patch(ChatWindow.prototype, "discuss", {
|
||||
toggleAddUsers() {
|
||||
this.state.activeMode = this.state.activeMode === "add-users" ? "" : "add-users";
|
||||
},
|
||||
toggleMemberList() {
|
||||
this.state.activeMode = this.state.activeMode === "member-list" ? "" : "member-list";
|
||||
},
|
||||
toggleSettings() {
|
||||
this.state.activeMode = this.state.activeMode === "in-settings" ? "" : "in-settings";
|
||||
},
|
||||
get actions() {
|
||||
const acts = this._super();
|
||||
if (this.props.chatWindow.isOpen && this.thread?.model === "discuss.channel") {
|
||||
acts.push({
|
||||
id: "invite",
|
||||
name:
|
||||
this.state.activeMode === "add-users"
|
||||
? _t("Stop Adding Users")
|
||||
: _t("Add Users"),
|
||||
icon: "fa fa-fw fa-user-plus",
|
||||
onSelect: () => this.toggleAddUsers(),
|
||||
sequence: 30,
|
||||
});
|
||||
}
|
||||
if (this.thread?.hasMemberList && this.props.chatWindow.isOpen) {
|
||||
acts.push({
|
||||
id: "member",
|
||||
name:
|
||||
this.state.activeMode === "member-list"
|
||||
? _t("Hide Member List")
|
||||
: _t("Show Member List"),
|
||||
icon: "fa fa-fw fa-users",
|
||||
onSelect: () => this.toggleMemberList(),
|
||||
sequence: 40,
|
||||
});
|
||||
}
|
||||
if (this.thread?.allowCalls && this.props.chatWindow.isOpen) {
|
||||
acts.push({
|
||||
id: "settings",
|
||||
name:
|
||||
this.state.activeMode === "in-settings"
|
||||
? _t("Hide Call Settings")
|
||||
: _t("Show Call Settings"),
|
||||
icon: "fa fa-fw fa-gear",
|
||||
onSelect: () => this.toggleSettings(),
|
||||
sequence: this.thread === this.rtc.state.channel ? 6 : 60,
|
||||
});
|
||||
}
|
||||
return acts;
|
||||
},
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.ChatWindow" t-inherit-mode="extension">
|
||||
<xpath expr="//t[@name='thread content']" position="inside">
|
||||
<CallSettings t-if="state.activeMode === 'in-settings'" thread="thread" className="'flex-grow-1'"/>
|
||||
<ChannelMemberList t-if="state.activeMode === 'member-list'" thread="thread" className="'flex-shrink-0 w-100 h-100'"/>
|
||||
<ChannelInvitation t-if="state.activeMode === 'add-users'" thread="thread" chatState="state"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,29 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { threadActionsRegistry } from "@mail/core/thread_actions";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
threadActionsRegistry.add("expand-discuss", {
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread &&
|
||||
component.props.chatWindow?.isOpen &&
|
||||
component.thread.model === "discuss.channel" &&
|
||||
!component.ui.isSmall
|
||||
);
|
||||
},
|
||||
icon: "fa fa-fw fa-expand",
|
||||
name: _t("Open in Discuss"),
|
||||
open(component) {
|
||||
component.threadService.setDiscussThread(component.thread);
|
||||
component.actionService.doAction(
|
||||
{
|
||||
type: "ir.actions.client",
|
||||
tag: "mail.action_discuss",
|
||||
name: _t("Discuss"),
|
||||
},
|
||||
{ clearBreadcrumbs: true }
|
||||
);
|
||||
},
|
||||
sequence: 51,
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { ImStatus } from "./im_status";
|
||||
import { AutoresizeInput } from "./autoresize_input";
|
||||
import { useThreadActions } from "@mail/core/thread_actions";
|
||||
import { Thread } from "../core_ui/thread";
|
||||
import { ThreadIcon } from "./thread_icon";
|
||||
import { useMessaging, useStore } from "../core/messaging_hook";
|
||||
@@ -24,12 +25,6 @@ import { useService } from "@web/core/utils/hooks";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { url } from "@web/core/utils/urls";
|
||||
|
||||
export const MODES = {
|
||||
MEMBER_LIST: "member-list",
|
||||
SETTINGS: "settings",
|
||||
NONE: "",
|
||||
};
|
||||
|
||||
export class Discuss extends Component {
|
||||
static components = {
|
||||
AutoresizeInput,
|
||||
@@ -44,7 +39,6 @@ export class Discuss extends Component {
|
||||
static template = "mail.Discuss";
|
||||
|
||||
setup() {
|
||||
this.MODES = MODES;
|
||||
this.messaging = useMessaging();
|
||||
this.store = useStore();
|
||||
this.threadService = useState(useService("mail.thread"));
|
||||
@@ -55,7 +49,7 @@ export class Discuss extends Component {
|
||||
this.messageEdition = useMessageEdition();
|
||||
this.messageToReplyTo = useMessageToReplyTo();
|
||||
this.contentRef = useRef("content");
|
||||
this.state = useState({ activeMode: MODES.NONE });
|
||||
this.root = useRef("root");
|
||||
this.orm = useService("orm");
|
||||
this.effect = useService("effect");
|
||||
this.ui = useState(useService("ui"));
|
||||
@@ -65,6 +59,7 @@ export class Discuss extends Component {
|
||||
messageHighlight: this.messageHighlight,
|
||||
});
|
||||
this.notification = useService("notification");
|
||||
this.threadActions = useThreadActions();
|
||||
useEffect(
|
||||
() => {
|
||||
if (
|
||||
@@ -87,10 +82,6 @@ export class Discuss extends Component {
|
||||
onWillUnmount(() => (this.store.discuss.isActive = false));
|
||||
}
|
||||
|
||||
markAllAsRead() {
|
||||
this.orm.silent.call("mail.message", "mark_all_as_read");
|
||||
}
|
||||
|
||||
get thread() {
|
||||
return this.store.threads[this.store.discuss.threadLocalId];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o-mail-Discuss-inspector {
|
||||
width: $o-mail-Discuss-inspector;
|
||||
}
|
||||
|
||||
.o-mail-Discuss-selfAvatar {
|
||||
height: $o-mail-Avatar-sizeSmall;
|
||||
width: $o-mail-Avatar-sizeSmall;
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
</t>
|
||||
</div>
|
||||
<div class="o-mail-Discuss-header flex-shrink-0 d-flex align-items-center ms-1">
|
||||
<button t-if="thread.id === 'inbox'" class="btn btn-link" t-att-disabled="thread.isEmpty" t-on-click="markAllAsRead">Mark all read</button>
|
||||
<button t-if="thread.id === 'starred'" class="btn btn-link" t-att-disabled="thread.isEmpty" t-on-click="messageService.unstarAll">Unstar all</button>
|
||||
<button t-if="thread.allowCalls and thread.type !== 'mailbox' and thread !== rtc.state.channel" class="btn rounded px-2 shadow-none" title="Start a Call" t-on-click="() => this.rtc.toggleCall(this.thread)" t-ref="startCall">
|
||||
<i class="fa fa-lg fa-phone text-700"/>
|
||||
</button>
|
||||
<t t-foreach="threadActions.actions" t-as="action" t-key="action.id">
|
||||
<button class="btn px-2" t-attf-class="{{ action.isActive ? 'o-isActive' : '' }}" t-att-disabled="action.disabledCondition" t-att-title="action.name" t-att-name="action.id" t-on-click="() => action.onSelect()">
|
||||
<i t-if="action.iconLarge" t-att-class="action.iconLarge"/> <span t-if="action.text" t-esc="action.text"/>
|
||||
</button>
|
||||
</t>
|
||||
<div t-if="store.inPublicPage and !ui.isSmall" class="d-flex align-items-center">
|
||||
<img class="o-mail-Discuss-selfAvatar mx-1 rounded-circle o_object_fit_cover flex-shrink-0" alt="Avatar" t-att-src="threadService.avatarUrl(store.self, thread)"/>
|
||||
<div class="lead fw-bold flex-shrink-1 text-dark">
|
||||
@@ -66,6 +66,7 @@
|
||||
<Thread thread="thread" t-key="thread.localId" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo"/>
|
||||
<Composer t-if="thread.type !== 'mailbox' or messageToReplyTo.thread === thread" t-key="thread.localId" composer="thread.composer" autofocus="true" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo" onDiscardCallback="() => messageToReplyTo.cancel()" dropzoneRef="contentRef"/>
|
||||
</div>
|
||||
<t t-if="threadActions.activeAction?.componentCondition" t-component="threadActions.activeAction.component" thread="thread" className="'o-mail-Discuss-inspector border-start'" t-props="threadActions.activeAction.componentProps"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="!thread and (!ui.isSmall or store.discuss.activeTab === 'mailbox')" class="d-flex flex-grow-1 align-items-center justify-content-center w-100 bg-view">
|
||||
|
||||
@@ -2,7 +2,7 @@ $o-mail-Avatar-size: 42px !default;
|
||||
$o-mail-ChatWindow-width: 360px !default; // same value as CHAT_WINDOW_WIDTH
|
||||
$o-mail-Chatter-mobile-gap: 2% !default;
|
||||
$o-mail-sidebar-icon-opacity: 0.7 !default;
|
||||
$o-mail-Discuss-inspector: 250px !default;
|
||||
$o-mail-Discuss-inspector: 300px !default;
|
||||
$o-mail-Avatar-sizeSmall: 24px !default;
|
||||
$o-mail-partner-status-icon-size: 1.2em !default;
|
||||
// Needed because $border-radius variations are all set to 0 in enterprise.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { ChatWindow } from "@mail/chat_window/chat_window";
|
||||
import { ChannelSelector } from "@mail/discuss_app/channel_selector";
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
@@ -14,43 +13,4 @@ patch(ChatWindow.prototype, "mail/chat_window", {
|
||||
this._super(...arguments);
|
||||
this.actionService = useService("action");
|
||||
},
|
||||
|
||||
expand() {
|
||||
if (this.thread.type === "chatter") {
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
res_id: this.thread.id,
|
||||
res_model: this.thread.model,
|
||||
views: [[false, "form"]],
|
||||
});
|
||||
this.chatWindowService.close(this.props.chatWindow);
|
||||
return;
|
||||
}
|
||||
this.threadService.setDiscussThread(this.thread);
|
||||
this.actionService.doAction(
|
||||
{
|
||||
type: "ir.actions.client",
|
||||
tag: "mail.action_discuss",
|
||||
name: _t("Discuss"),
|
||||
},
|
||||
{ clearBreadcrumbs: true }
|
||||
);
|
||||
},
|
||||
|
||||
get actions() {
|
||||
const acts = this._super();
|
||||
if (this.thread && this.props.chatWindow.isOpen) {
|
||||
acts.splice(acts.length - 1, 0, {
|
||||
id: "expand",
|
||||
name:
|
||||
this.thread.model === "discuss.channel"
|
||||
? _t("Open in Discuss")
|
||||
: _t("Open Form View"),
|
||||
icon: "fa fa-fw fa-expand",
|
||||
onSelect: () => this.expand(),
|
||||
sequence: 50,
|
||||
});
|
||||
}
|
||||
return acts;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { threadActionsRegistry } from "@mail/core/thread_actions";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
threadActionsRegistry
|
||||
.add("mark-all-read", {
|
||||
condition(component) {
|
||||
return component.thread?.id === "inbox";
|
||||
},
|
||||
disabledCondition(component) {
|
||||
return component.thread.isEmpty;
|
||||
},
|
||||
open(component) {
|
||||
component.orm.silent.call("mail.message", "mark_all_as_read");
|
||||
},
|
||||
sequence: 1,
|
||||
text: _t("Mark all read"),
|
||||
})
|
||||
.add("unstar-all", {
|
||||
condition(component) {
|
||||
return component.thread?.id === "starred";
|
||||
},
|
||||
disabledCondition(component) {
|
||||
return component.thread.isEmpty;
|
||||
},
|
||||
open(component) {
|
||||
component.messageService.unstarAll();
|
||||
},
|
||||
sequence: 2,
|
||||
text: _t("Unstar all"),
|
||||
})
|
||||
.add("expand-form", {
|
||||
condition(component) {
|
||||
return component.thread?.type === "chatter" && component.props.chatWindow?.isOpen;
|
||||
},
|
||||
icon: "fa fa-fw fa-expand",
|
||||
name: _t("Open Form View"),
|
||||
open(component) {
|
||||
component.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
res_id: component.thread.id,
|
||||
res_model: component.thread.model,
|
||||
views: [[false, "form"]],
|
||||
});
|
||||
component.chatWindowService.close(component.props.chatWindow);
|
||||
},
|
||||
sequence: 50,
|
||||
});
|
||||
@@ -17,6 +17,7 @@ QUnit.test("Add member to channel", async (assert) => {
|
||||
await click("[title='Add Users']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable:contains(Harry)");
|
||||
await click("[title='Invite to Channel']");
|
||||
await click("[title='Show Member List']");
|
||||
assert.containsOnce($, ".o-discuss-ChannelMember:contains(Harry)");
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ QUnit.test("Pin message", async (assert) => {
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Discuss-header button[title='Pinned messages']");
|
||||
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
|
||||
assert.containsOnce(
|
||||
$,
|
||||
".o-discuss-PinnedMessagesPanel:contains(This channel doesn't have any pinned messages.)"
|
||||
@@ -37,7 +37,7 @@ QUnit.test("Unpin message", async (assert) => {
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Discuss-header button[title='Pinned messages']");
|
||||
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
|
||||
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item:contains(Unpin)");
|
||||
@@ -57,7 +57,7 @@ QUnit.test("Deleted messages are not pinned", async (assert) => {
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Discuss-header button[title='Pinned messages']");
|
||||
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
|
||||
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item:contains(Delete)");
|
||||
@@ -108,7 +108,7 @@ QUnit.test("Jump to message", async (assert) => {
|
||||
}
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Discuss-header button[title='Pinned messages']");
|
||||
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
|
||||
await click(".o-discuss-PinnedMessagesPanel button:contains(Jump)");
|
||||
await nextTick();
|
||||
assert.isVisible($(".o-mail-Message:contains(Hello world!)"));
|
||||
|
||||
Reference in New Issue
Block a user