[REF] mail: move message pin to discuss folder
Part of task-3265211 closes odoo/odoo#121418 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -184,13 +184,13 @@ For more specific needs, you may also assign custom-defined actions
|
||||
('remove', 'mail/static/src/xml/**/*'),
|
||||
('remove', 'mail/static/src/worklets/**/*'),
|
||||
('remove', 'mail/static/src/views/**/*'),
|
||||
('remove', 'mail/static/src/web/**/*'),
|
||||
('remove', 'mail/static/src/**/web/**/*'),
|
||||
('remove', 'mail/static/src/emoji_picker/emoji_data.js'),
|
||||
('remove', 'mail/static/src/**/*.dark.scss'),
|
||||
# discuss (loaded last to fix dependencies)
|
||||
('remove', 'mail/static/src/discuss/**/*'),
|
||||
'mail/static/src/discuss/**/*',
|
||||
('remove', 'mail/static/src/discuss/web/**/*'),
|
||||
('remove', 'mail/static/src/discuss/**/web/**/*'),
|
||||
]
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -4088,6 +4088,7 @@ class MailThread(models.AbstractModel):
|
||||
'id': message.id,
|
||||
'body': message.body,
|
||||
'attachment_ids': message.attachment_ids._attachment_format(),
|
||||
'pinned_at': message.pinned_at,
|
||||
'write_date': message.write_date,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -66,8 +66,6 @@ export class Message {
|
||||
/** @type {string|undefined} */
|
||||
notificationType;
|
||||
/** @type {string} */
|
||||
pinned_at;
|
||||
/** @type {string} */
|
||||
create_date;
|
||||
/** @type {string} */
|
||||
write_date;
|
||||
@@ -237,13 +235,6 @@ export class Message {
|
||||
return this.notifications.filter((notification) => notification.isFailure);
|
||||
}
|
||||
|
||||
get pinnedAt() {
|
||||
if (!this.pinned_at) {
|
||||
return null;
|
||||
}
|
||||
return luxon.DateTime.fromISO(new Date(this.pinned_at).toISOString());
|
||||
}
|
||||
|
||||
get editDatetimeHuge() {
|
||||
return deserializeDateTime(this.editDate).toLocaleString(
|
||||
omit(DateTime.DATETIME_HUGE, "timeZoneName")
|
||||
|
||||
@@ -136,13 +136,6 @@ export class MessageService {
|
||||
await this.orm.silent.call("mail.message", "set_message_done", [[message.id]]);
|
||||
}
|
||||
|
||||
setPin(message, pinned) {
|
||||
return this.orm.call("discuss.channel", "set_message_pin", [message.originThread.id], {
|
||||
message_id: message.id,
|
||||
pinned,
|
||||
});
|
||||
}
|
||||
|
||||
async unfollow(message) {
|
||||
if (message.isNeedaction) {
|
||||
await this.setDone(message);
|
||||
@@ -232,12 +225,6 @@ export class MessageService {
|
||||
* @param {Object} data
|
||||
*/
|
||||
update(message, data) {
|
||||
if (message.pinned_at && data.pinned_at === false) {
|
||||
removeFromArrayWithPredicate(
|
||||
message.originThread.pinnedMessages,
|
||||
({ id }) => id === message.id
|
||||
);
|
||||
}
|
||||
const {
|
||||
attachment_ids: attachments = message.attachments,
|
||||
default_subject: defaultSubject = message.defaultSubject,
|
||||
@@ -333,6 +320,7 @@ export class MessageService {
|
||||
message.notificationType =
|
||||
htmlBody.querySelector(".o_mail_notification")?.dataset.oeType;
|
||||
}
|
||||
this.env.bus.trigger("mail.message/onUpdate", { message, data });
|
||||
}
|
||||
|
||||
_updateReactions(message, reactionGroups) {
|
||||
|
||||
@@ -310,10 +310,6 @@ export class Messaging {
|
||||
({ id }) => id === message.id
|
||||
);
|
||||
}
|
||||
removeFromArrayWithPredicate(
|
||||
message.originThread.pinnedMessages,
|
||||
({ id }) => id === message.id
|
||||
);
|
||||
if (message.id > message.originThread.seen_message_id) {
|
||||
message.originThread.message_unread_counter--;
|
||||
}
|
||||
@@ -587,22 +583,9 @@ export class Messaging {
|
||||
...messageData,
|
||||
body: messageData.body ? markup(messageData.body) : messageData.body,
|
||||
});
|
||||
if (
|
||||
message.pinned_at &&
|
||||
!message.originThread?.pinnedMessages.some(({ id }) => id === message.id)
|
||||
) {
|
||||
message.originThread.pinnedMessages.unshift(message);
|
||||
}
|
||||
if (isStarred && message.isEmpty) {
|
||||
this.messageService.updateStarred(message, false);
|
||||
}
|
||||
if (message.pinned_at && message.isEmpty) {
|
||||
message.pinned_at = false;
|
||||
removeFromArrayWithPredicate(
|
||||
message.originThread.pinnedMessages,
|
||||
({ id }) => id === message.id
|
||||
);
|
||||
}
|
||||
}
|
||||
const { "res.users.settings": settings } = notif.payload;
|
||||
if (settings) {
|
||||
|
||||
@@ -59,8 +59,6 @@ export class Thread {
|
||||
isLoadingAttachments = false;
|
||||
isLoadedDeferred = new Deferred();
|
||||
isLoaded = false;
|
||||
/** @type {"loading"|"loaded"} */
|
||||
pinLoadState;
|
||||
/** @type {import("@mail/attachments/attachment_model").Attachment} */
|
||||
mainAttachment;
|
||||
memberCount = 0;
|
||||
@@ -98,8 +96,6 @@ export class Thread {
|
||||
* @type {import("@mail/core/message_model").Message[]}
|
||||
*/
|
||||
needactionMessages = [];
|
||||
/** @type {import("@mail/core/message_model").Message[]} */
|
||||
pinnedMessages = [];
|
||||
/** @type {string} */
|
||||
name;
|
||||
/** @type {number|false} */
|
||||
|
||||
@@ -160,31 +160,6 @@ export class ThreadService {
|
||||
await this.orm.silent.call("discuss.channel", "channel_fetched", [[thread.id]]);
|
||||
}
|
||||
|
||||
async fetchPinnedMessages(thread) {
|
||||
if (
|
||||
thread.model !== "discuss.channel" ||
|
||||
["loaded", "loading"].includes(thread.pinLoadState)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
thread.pinLoadState = "loading";
|
||||
try {
|
||||
const messages = await this.rpc("/discuss/channel/pinned_messages", {
|
||||
channel_id: thread.id,
|
||||
});
|
||||
const pinnedMessages = messages.map((message) => {
|
||||
if (message.parentMessage) {
|
||||
message.parentMessage.body = markup(message.parentMessage.body);
|
||||
}
|
||||
message.body = markup(message.body);
|
||||
return this.messageService.insert(message);
|
||||
});
|
||||
thread.pinnedMessages = pinnedMessages;
|
||||
} finally {
|
||||
thread.pinLoadState = "loaded";
|
||||
}
|
||||
}
|
||||
|
||||
getFetchRoute(thread) {
|
||||
if (thread.model === "discuss.channel") {
|
||||
return "/discuss/channel/messages";
|
||||
|
||||
@@ -145,6 +145,26 @@ export class Message extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
get attClass() {
|
||||
return {
|
||||
"o-highlighted-from-mention": this.message.isHighlightedFromMention,
|
||||
"o-highlighted bg-view shadow-lg": this.props.highlighted,
|
||||
"o-selfAuthored": this.message.isSelfAuthored,
|
||||
"o-selected": this.props.messageToReplyTo?.isSelected(
|
||||
this.props.thread,
|
||||
this.props.message
|
||||
),
|
||||
"o-squashed pt-1": this.props.squashed,
|
||||
"mt-1": !this.props.squashed && this.props.thread,
|
||||
"px-3": !this.props.isInChatWindow,
|
||||
"px-1": this.props.isInChatWindow,
|
||||
"opacity-50": this.props.messageToReplyTo?.isNotSelected(
|
||||
this.props.thread,
|
||||
this.props.message
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
get authorAvatarUrl() {
|
||||
if (
|
||||
this.message.type === "email" &&
|
||||
@@ -260,9 +280,7 @@ export class Message extends Component {
|
||||
|
||||
get isAlignedRight() {
|
||||
return Boolean(
|
||||
!this.env.pinnedPanel &&
|
||||
this.env.inChatWindow &&
|
||||
this.user.partnerId === this.props.message.author?.id
|
||||
this.env.inChatWindow && this.user.partnerId === this.props.message.author?.id
|
||||
);
|
||||
}
|
||||
|
||||
@@ -293,7 +311,7 @@ export class Message extends Component {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get shouldDisplayAuthorName() {
|
||||
if (!this.env.inChatWindow || this.env.pinnedPanel) {
|
||||
if (!this.env.inChatWindow) {
|
||||
return true;
|
||||
}
|
||||
if (this.message.isSelfAuthored) {
|
||||
@@ -314,20 +332,6 @@ export class Message extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
onClickPin() {
|
||||
const prompt = this.message.pinned_at
|
||||
? _t("Are you sure you want to remove this pinned message?")
|
||||
: _t(
|
||||
"The following message will be pinned to the channel. Are you sure you want to continue?"
|
||||
);
|
||||
this.env.services.dialog.add(MessageConfirmDialog, {
|
||||
message: this.message,
|
||||
messageComponent: Message,
|
||||
prompt,
|
||||
onConfirm: () => this.messageService.setPin(this.message, !this.message.pinned_at),
|
||||
});
|
||||
}
|
||||
|
||||
onClickReplyTo(ev) {
|
||||
this.props.messageToReplyTo.toggle(this.props.thread, this.props.message);
|
||||
}
|
||||
@@ -359,10 +363,6 @@ export class Message extends Component {
|
||||
return this.hasOpenChatFeature ? _t("Open chat") : "";
|
||||
}
|
||||
|
||||
get pinOptionText() {
|
||||
return this.message.pinned_at ? _t("Unpin") : _t("Pin");
|
||||
}
|
||||
|
||||
openChatAvatar(ev) {
|
||||
markEventHandled(ev, "Message.ClickAuthor");
|
||||
if (!this.hasOpenChatFeature) {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o-mail-ChatWindow .o-mail-Message:not(.o-pinnedPanel).o-selfAuthored {
|
||||
.o-mail-ChatWindow .o-mail-Message:not(.o-cancelSelfAuthored).o-selfAuthored {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.o-mail-Message-core, .o-mail-Message-content {
|
||||
|
||||
@@ -4,18 +4,7 @@
|
||||
<t t-name="mail.Message" owl="1">
|
||||
<ActionSwiper onRightSwipe="hasTouch() and isInInbox ? { action: () => this.messageService.setDone(this.message), bgColor: 'bg-success', icon: 'fa-check-circle' } : undefined">
|
||||
<div class="o-mail-Message position-relative py-1"
|
||||
t-att-class="{
|
||||
'o-pinnedPanel': env.pinnedPanel,
|
||||
'o-highlighted-from-mention': message.isHighlightedFromMention,
|
||||
'o-highlighted bg-view shadow-lg': props.highlighted,
|
||||
'o-selfAuthored': message.isSelfAuthored,
|
||||
'o-selected': props.messageToReplyTo?.isSelected(props.thread, props.message),
|
||||
'o-squashed pt-1': props.squashed,
|
||||
'mt-1': !props.squashed and props.thread and !env.pinnedPanel,
|
||||
'px-3': !props.isInChatWindow and !env.pinnedPanel,
|
||||
'px-1': props.isInChatWindow,
|
||||
'opacity-50': props.messageToReplyTo?.isNotSelected(props.thread, props.message),
|
||||
}"
|
||||
t-att-class="attClass"
|
||||
role="group"
|
||||
t-att-aria-label="messageTypeText"
|
||||
t-on-click="onClick"
|
||||
@@ -145,7 +134,6 @@
|
||||
<DropdownItem t-if="message.reactions.length" class="'px-2 d-flex align-items-center rounded-0'" title="'Reactions'" onSelected="() => this.openReactionMenu()"><i class="fa fa-lg fa-smile-o pe-2"/>View Reactions</DropdownItem>
|
||||
<DropdownItem t-if="isInInbox" class="'px-2 d-flex align-items-center rounded-0'" title="'Mark as Read'" onSelected="() => messageService.setDone(props.message)"><i class="fa fa-lg fa-check pe-2"/>Mark as Read</DropdownItem>
|
||||
<DropdownItem t-if="canUnfollow" class="'px-2 d-flex align-items-center rounded-0'" title="'Unfollow'" onSelected="() => messageService.unfollow(props.message)"><i class="fa fa-lg fa-user-times pe-2"/>Unfollow</DropdownItem>
|
||||
<DropdownItem t-if="store.user and props.thread.model === 'discuss.channel'" class="'px-2 d-flex align-items-center rounded-0'" title="pinOptionText" onSelected="() => this.onClickPin()"><i class="fa fa-lg fa-thumb-tack pe-2"/><t t-esc="pinOptionText"/></DropdownItem>
|
||||
<DropdownItem t-if="props.thread.model === 'discuss.channel'" class="'px-2 d-flex align-items-center rounded-0'" title="'Mark as Unread'" onSelected="() => this.onClickMarkAsUnread()"><i class="fa fa-lg fa-eye-slash pe-2"/>Mark as Unread</DropdownItem>
|
||||
<DropdownItem t-if="editable" class="'px-2 d-flex align-items-center rounded-0'" title="'Edit'" onSelected="() => this.onClickEdit()"><i class="fa fa-lg fa-pencil pe-2"/>Edit</DropdownItem>
|
||||
<DropdownItem t-if="deletable" class="'px-2 d-flex align-items-center rounded-0'" title="'Delete'" onSelected="() => this.onClickDelete()"><i class="fa fa-lg fa-trash pe-2"/>Delete</DropdownItem>
|
||||
|
||||
@@ -52,7 +52,6 @@ export class Thread extends Component {
|
||||
this.state = useState({ isReplyingTo: false, showJumpPresent: false });
|
||||
/** @type {import("@mail/core/thread_service").ThreadService} */
|
||||
this.threadService = useState(useService("mail.thread"));
|
||||
this.ui = useState(useService("ui"));
|
||||
if (!this.env.inChatter || !this.props.hasScrollAdjust) {
|
||||
useAutoScroll("messages", () => {
|
||||
if (this.env.messageHighlight?.highlightedMessageId) {
|
||||
@@ -174,8 +173,6 @@ export class Thread extends Component {
|
||||
const { oeType, oeId } = ev.target.dataset;
|
||||
if (oeType === "highlight") {
|
||||
await this.env.messageHighlight?.highlightMessage(Number(oeId), this.props.thread);
|
||||
} else if (oeType === "pin-menu") {
|
||||
this.env.pinMenu?.open();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.o-mail-Thread-noPinnedPanel [data-oe-type="pin-menu"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o-mail-Thread-jumpPresent {
|
||||
background-color: mix(map-get($theme-colors, 'info'), $o-webclient-background-color, 5%);
|
||||
z-index: $o-mail-NavigableList-zIndex - 2;
|
||||
|
||||
@@ -115,9 +115,8 @@
|
||||
|
||||
<t t-name="mail.NotificationMessage" owl="1">
|
||||
<div class="o-mail-NotificationMessage mx-auto text-500 small px-3 text-center" t-on-click="onClickNotification" t-att-class="{
|
||||
'o-mail-Thread-noPinnedPanel': env.inChatWindow and !ui.isSmall,
|
||||
'mt-2': prevMsg and !prevMsg.isNotification,
|
||||
}">
|
||||
}" t-ref="root">
|
||||
<i t-if="msg.notificationIcon" t-attf-class="{{ msg.notificationIcon }} me-1"/>
|
||||
<span t-if="!msg.body.includes(escape(msg.author.name))" class="d-inline mx-1" t-esc="msg.author.name"/> <t t-out="msg.body"/>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<i class="fa fa-lg fa-gear text-700"/>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//PinnedMessagesPanel" position="after">
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* @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;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,55 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { Message } from "@mail/core_ui/message";
|
||||
import { MessageConfirmDialog } from "@mail/core_ui/message_confirm_dialog";
|
||||
import { useMessagePinService } from "@mail/discuss/message_pin/message_pin_service";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Message, "discuss/message_pin", {
|
||||
components: { ...Message.components },
|
||||
});
|
||||
|
||||
patch(Message.prototype, "discuss/message_pin", {
|
||||
setup() {
|
||||
this._super();
|
||||
this.messagePinService = useMessagePinService();
|
||||
},
|
||||
onClickPin() {
|
||||
const prompt = this.messagePinService.getPinnedAt(this.message.id)
|
||||
? _t("Are you sure you want to remove this pinned message?")
|
||||
: _t(
|
||||
"The following message will be pinned to the channel. Are you sure you want to continue?"
|
||||
);
|
||||
this.env.services.dialog.add(MessageConfirmDialog, {
|
||||
message: this.message,
|
||||
messageComponent: Message,
|
||||
prompt,
|
||||
onConfirm: () =>
|
||||
this.messagePinService.setPin(
|
||||
this.message,
|
||||
!this.messagePinService.getPinnedAt(this.message.id)
|
||||
),
|
||||
});
|
||||
},
|
||||
get attClass() {
|
||||
const res = this._super();
|
||||
return Object.assign(res, {
|
||||
"o-cancelSelfAuthored": this.env.pinnedPanel,
|
||||
"mt-1": res["mt-1"] && !this.env.pinnedPanel,
|
||||
"px-3": res["px-3"] && !this.env.pinnedPanel,
|
||||
});
|
||||
},
|
||||
get isAlignedRight() {
|
||||
return !this.env.pinnedPanel && this._super();
|
||||
},
|
||||
get pinOptionText() {
|
||||
return this.messagePinService.getPinnedAt(this.message.id) ? _t("Unpin") : _t("Pin");
|
||||
},
|
||||
get shouldDisplayAuthorName() {
|
||||
if (this.env.pinnedPanel) {
|
||||
return true;
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.Message" t-inherit-mode="extension">
|
||||
<xpath expr="//DropdownItem[@t-if='canUnfollow']" position="after">
|
||||
<DropdownItem
|
||||
t-if="store.user and props.thread.model === 'discuss.channel'"
|
||||
class="'px-2 d-flex align-items-center rounded-0'"
|
||||
title="pinOptionText"
|
||||
onSelected="() => this.onClickPin()"
|
||||
>
|
||||
<span><i class="fa fa-lg fa-thumb-tack pe-2"/><t t-esc="pinOptionText"/></span>
|
||||
</DropdownItem>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,198 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { markup, reactive, useState } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export const OTHER_LONG_TYPING = 60000;
|
||||
|
||||
export class MessagePin {
|
||||
busService;
|
||||
/** @type {import("@mail/core/message_service").MessageService} */
|
||||
messageService;
|
||||
/** @type {Map<number, string>} */
|
||||
loadStateByChannelId = new Map();
|
||||
/** @type {Map<number, Set<number>>} */
|
||||
messageIdsByChannelId = new Map();
|
||||
/** @type {Map<number, number>} */
|
||||
channelIdByMessageId = new Map();
|
||||
/** @type {Map<number, string>} */
|
||||
pinnedAtByMessageId = new Map();
|
||||
/** @type {import("@mail/core/store_service").Store} */
|
||||
storeService;
|
||||
|
||||
constructor(
|
||||
env,
|
||||
{
|
||||
bus_service: busService,
|
||||
"mail.message": messageService,
|
||||
"mail.store": storeService,
|
||||
orm: ormService,
|
||||
rpc: rpcService,
|
||||
}
|
||||
) {
|
||||
Object.assign(this, {
|
||||
busService,
|
||||
env,
|
||||
messageService,
|
||||
ormService,
|
||||
rpcService,
|
||||
storeService,
|
||||
});
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.env.bus.addEventListener("mail.message/onUpdate", ({ detail: { message, data } }) => {
|
||||
this.onMessageUpdate(message, data);
|
||||
});
|
||||
this.busService.subscribe("mail.message/delete", ({ message_ids }) => {
|
||||
for (const messageId of message_ids) {
|
||||
this.removePinnedMessage(messageId);
|
||||
}
|
||||
});
|
||||
this.busService.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} channelId
|
||||
* @param {number} messageId
|
||||
* @param {string} pinnedAt
|
||||
*/
|
||||
addPinnedMessage(channelId, messageId, pinnedAt) {
|
||||
if (!this.messageIdsByChannelId.has(channelId)) {
|
||||
this.messageIdsByChannelId.set(channelId, new Set());
|
||||
}
|
||||
const messageIds = this.messageIdsByChannelId.get(channelId);
|
||||
messageIds.add(messageId);
|
||||
this.channelIdByMessageId.set(messageId, channelId);
|
||||
this.pinnedAtByMessageId.set(messageId, pinnedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@mail/core/thread_model").Thread} channel
|
||||
*/
|
||||
async fetchPinnedMessages(channel) {
|
||||
if (
|
||||
channel.model !== "discuss.channel" ||
|
||||
["loaded", "loading"].includes(this.loadStateByChannelId.get(channel.id))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.loadStateByChannelId.set(channel.id, "loading");
|
||||
try {
|
||||
const messagesData = await this.rpcService("/discuss/channel/pinned_messages", {
|
||||
channel_id: channel.id,
|
||||
});
|
||||
messagesData.forEach((messageData) => {
|
||||
if (messageData.parentMessage) {
|
||||
messageData.parentMessage.body = markup(messageData.parentMessage.body);
|
||||
}
|
||||
messageData.body = markup(messageData.body);
|
||||
this.messageService.insert(messageData);
|
||||
});
|
||||
this.loadStateByChannelId.set(channel.id, "loaded");
|
||||
} catch (e) {
|
||||
this.loadStateByChannelId.set(channel.id, "error");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} messageId
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getPinnedAt(messageId) {
|
||||
const pinnedAt = this.pinnedAtByMessageId.get(messageId);
|
||||
return pinnedAt ? luxon.DateTime.fromISO(new Date(pinnedAt).toISOString()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@mail/core/thread_model").Thread} channel
|
||||
* @returns {import("@mail/core/message_model").Message[]}
|
||||
*/
|
||||
getPinnedMessages(channel) {
|
||||
return [...(this.messageIdsByChannelId.get(channel.id) ?? new Set())]
|
||||
.map((id) => this.messageService.insert({ id }))
|
||||
.sort((a, b) => {
|
||||
const aPinnedAt = this.pinnedAtByMessageId.get(a.id);
|
||||
const bPinnedAt = this.pinnedAtByMessageId.get(b.id);
|
||||
if (aPinnedAt === bPinnedAt) {
|
||||
return b.id - a.id;
|
||||
}
|
||||
return aPinnedAt < bPinnedAt ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@mail/core/thread_model").Thread} channel
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasPinnedMessages(channel) {
|
||||
return this.getPinnedMessages(channel).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@mail/core/message_model").Message}
|
||||
* @param {Object} data
|
||||
*/
|
||||
onMessageUpdate(message, { pinned_at: pinnedAt }) {
|
||||
if (
|
||||
message.originThread?.model === "discuss.channel" &&
|
||||
(pinnedAt !== undefined || message.isEmpty)
|
||||
) {
|
||||
if (pinnedAt && !message.isEmpty) {
|
||||
this.addPinnedMessage(message.originThread.id, message.id, pinnedAt);
|
||||
} else {
|
||||
this.removePinnedMessage(message.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} messageId
|
||||
*/
|
||||
removePinnedMessage(messageId) {
|
||||
const channelId = this.channelIdByMessageId.get(messageId);
|
||||
if (!channelId) {
|
||||
return;
|
||||
}
|
||||
const messageIds = this.messageIdsByChannelId.get(channelId);
|
||||
if (messageIds) {
|
||||
messageIds.delete(messageId);
|
||||
if (messageIds.size === 0) {
|
||||
this.messageIdsByChannelId.delete(channelId);
|
||||
}
|
||||
}
|
||||
this.channelIdByMessageId.delete(messageId);
|
||||
this.pinnedAtByMessageId.delete(messageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@mail/core/message_model").Message}
|
||||
* @param {boolean} pinned
|
||||
*/
|
||||
setPin(message, pinned) {
|
||||
this.ormService.call("discuss.channel", "set_message_pin", [message.originThread.id], {
|
||||
message_id: message.id,
|
||||
pinned,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const messagePinService = {
|
||||
dependencies: ["bus_service", "mail.message", "mail.store", "orm", "rpc"],
|
||||
start(env, services) {
|
||||
const messagePin = reactive(new MessagePin(env, services));
|
||||
messagePin.setup();
|
||||
return messagePin;
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("discuss.message.pin", messagePinService);
|
||||
|
||||
/**
|
||||
* @returns {MessagePin}
|
||||
* */
|
||||
export function useMessagePinService() {
|
||||
return useState(useService("discuss.message.pin"));
|
||||
}
|
||||
+8
-7
@@ -1,17 +1,18 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { Component, onWillStart, onWillUpdateProps, useState, useSubEnv } from "@odoo/owl";
|
||||
import { Message } from "@mail/core_ui/message";
|
||||
import { MessageConfirmDialog } from "@mail/core_ui/message_confirm_dialog";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useMessagePinService } from "@mail/discuss/message_pin/message_pin_service";
|
||||
import { Component, onWillStart, onWillUpdateProps, useState, useSubEnv } from "@odoo/owl";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class PinnedMessagesPanel extends Component {
|
||||
static components = {
|
||||
Message,
|
||||
};
|
||||
static props = ["thread"];
|
||||
static template = "mail.PinnedMessagesPanel";
|
||||
static template = "discuss.PinnedMessagesPanel";
|
||||
|
||||
setup() {
|
||||
this.threadService = useService("mail.thread");
|
||||
@@ -19,13 +20,13 @@ export class PinnedMessagesPanel extends Component {
|
||||
this.store = useService("mail.store");
|
||||
this.rpc = useService("rpc");
|
||||
this.ui = useState(useService("ui"));
|
||||
|
||||
this.messagePinService = useMessagePinService();
|
||||
onWillStart(() => {
|
||||
this.threadService.fetchPinnedMessages(this.props.thread);
|
||||
this.messagePinService.fetchPinnedMessages(this.props.thread);
|
||||
});
|
||||
onWillUpdateProps(async (nextProps) => {
|
||||
if (nextProps.thread.id !== this.props.thread.id) {
|
||||
this.threadService.fetchPinnedMessages(nextProps.thread);
|
||||
this.messagePinService.fetchPinnedMessages(nextProps.thread);
|
||||
}
|
||||
});
|
||||
useSubEnv({
|
||||
@@ -60,7 +61,7 @@ export class PinnedMessagesPanel extends Component {
|
||||
message,
|
||||
messageComponent: Message,
|
||||
prompt: _t("Are you sure you want to remove this pinned message?"),
|
||||
onConfirm: () => this.messageService.setPin(message, false),
|
||||
onConfirm: () => this.messagePinService.setPin(message, false),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
.o-discuss-PinnedMessagesPanel {
|
||||
flex-basis: 425px;
|
||||
}
|
||||
|
||||
.o-discuss-PinnedMessagesPanel-title {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.o-discuss-PinnedMessagesPanel-jump {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-body:hover .o-discuss-PinnedMessagesPanel-jump {
|
||||
opacity: 100 !important;
|
||||
}
|
||||
|
||||
.o_touch_device .o-discuss-PinnedMessagesPanel-jump.btn {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<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 border-start" t-att-class="{
|
||||
'flex-grow-1': env.inChatWindow,
|
||||
}">
|
||||
<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>
|
||||
<div class="d-flex flex-column" t-att-class="{ 'justify-content-center flex-grow-1': !messagePinService.hasPinnedMessages(props.thread) }" t-ref="message-list">
|
||||
<div class="card m-3 mt-0" t-foreach="messagePinService.getPinnedMessages(props.thread)" t-as="message" t-key="message.id">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button class="o-discuss-PinnedMessagesPanel-jump btn rounded bg-400 badge opacity-0 flex-shrink-0" t-att-class="{ 'opacity-100 py-1 px-2': ui.isSmall }" t-on-click="() => this.onClickJump(message)">Jump</button>
|
||||
<button class="btn ms-2 p-0 text-700 shadow-none" t-att-class="{ 'fs-5 align-self-end': ui.isSmall }" title="Unpin" t-on-click="() => this.onClickUnpin(message)">
|
||||
<i class="d-block fa fa-times"/>
|
||||
</button>
|
||||
</div>
|
||||
<Message hasActions="false" message="message" thread="props.thread"/>
|
||||
</div>
|
||||
</div>
|
||||
<p t-if="!messagePinService.hasPinnedMessages(props.thread)" t-esc="emptyMessage" class="text-center fst-italic text-500 fs-6"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,23 @@
|
||||
/* @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
|
||||
*/
|
||||
async onClickNotification(ev) {
|
||||
const { oeType } = ev.target.dataset;
|
||||
if (oeType === "pin-menu") {
|
||||
this.env.pinMenu?.open();
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
.o-mail-Thread-noPinnedPanel [data-oe-type="pin-menu"] {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.NotificationMessage" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@t-ref='root']" position="attributes">
|
||||
<attribute name="t-attf-class">{{ env.inChatWindow and !ui.isSmall ? 'o-mail-Thread-noPinnedPanel' : '' }}</>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,32 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { PinnedMessagesPanel } from "@mail/discuss/message_pin/pinned_messages_panel";
|
||||
import { ChatWindow, MODES } from "@mail/web/chat_window/chat_window";
|
||||
import { useChildSubEnv } from "@odoo/owl";
|
||||
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;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.ChatWindow" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@t-ref='startCall']" position="before">
|
||||
<div t-if="(thread?.model === 'discuss.channel' and ui.isSmall) or state.activeMode === MODES.PINNED_MESSAGES" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Pinned messages" t-on-click.stop="togglePinMenu">
|
||||
<i class="fa fa-lg fa-thumb-tack"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//t[@name='thread content']" position="inside">
|
||||
<PinnedMessagesPanel t-if="state.activeMode === MODES.PINNED_MESSAGES" thread="thread"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -14,10 +14,10 @@
|
||||
<i class="fa fa-gear"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//PinnedMessagesPanel" position="after">
|
||||
<CallSettings t-elif="state.activeMode === 'in-settings'" thread="thread" className="'flex-grow-1'"/>
|
||||
<ChannelMemberList t-elif="state.activeMode === 'member-list'" thread="thread" className="'flex-shrink-0 border-start w-100 h-100'"/>
|
||||
<ChannelInvitation t-elif="state.activeMode === 'add-users'" thread="thread" chatState="state"/>
|
||||
<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 border-start w-100 h-100'"/>
|
||||
<ChannelInvitation t-if="state.activeMode === 'add-users'" thread="thread" chatState="state"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -23,7 +23,12 @@ import {
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { url } from "@web/core/utils/urls";
|
||||
import { PinnedMessagesPanel } from "./pinned_messages_panel";
|
||||
|
||||
export const MODES = {
|
||||
MEMBER_LIST: "member-list",
|
||||
SETTINGS: "settings",
|
||||
NONE: "",
|
||||
};
|
||||
|
||||
export class Discuss extends Component {
|
||||
static components = {
|
||||
@@ -32,7 +37,6 @@ export class Discuss extends Component {
|
||||
ThreadIcon,
|
||||
Composer,
|
||||
Call,
|
||||
PinnedMessagesPanel,
|
||||
FileUploader,
|
||||
ImStatus,
|
||||
};
|
||||
@@ -41,14 +45,8 @@ export class Discuss extends Component {
|
||||
};
|
||||
static template = "mail.Discuss";
|
||||
|
||||
MODES = Object.freeze({
|
||||
MEMBER_LIST: "member-list",
|
||||
PINNED_MESSAGES: "pinned-messages",
|
||||
SETTINGS: "settings",
|
||||
NONE: "",
|
||||
});
|
||||
|
||||
setup() {
|
||||
this.MODES = MODES;
|
||||
this.messaging = useMessaging();
|
||||
this.store = useStore();
|
||||
this.threadService = useState(useService("mail.thread"));
|
||||
@@ -59,7 +57,7 @@ export class Discuss extends Component {
|
||||
this.messageEdition = useMessageEdition();
|
||||
this.messageToReplyTo = useMessageToReplyTo();
|
||||
this.contentRef = useRef("content");
|
||||
this.state = useState({ activeMode: this.MODES.NONE });
|
||||
this.state = useState({ activeMode: MODES.NONE });
|
||||
this.orm = useService("orm");
|
||||
this.effect = useService("effect");
|
||||
this.ui = useState(useService("ui"));
|
||||
@@ -67,14 +65,6 @@ export class Discuss extends Component {
|
||||
useChildSubEnv({
|
||||
inDiscussApp: true,
|
||||
messageHighlight: this.messageHighlight,
|
||||
pinMenu: {
|
||||
open: () => (this.state.activeMode = this.MODES.PINNED_MESSAGES),
|
||||
close: () => {
|
||||
if (this.state.activeMode === this.MODES.PINNED_MESSAGES) {
|
||||
this.state.activeMode = this.MODES.NONE;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
this.notification = useService("notification");
|
||||
useEffect(
|
||||
@@ -115,13 +105,6 @@ export class Discuss extends Component {
|
||||
: this.thread.imgUrl;
|
||||
}
|
||||
|
||||
togglePinMenu() {
|
||||
this.state.activeMode =
|
||||
this.state.activeMode === this.MODES.PINNED_MESSAGES
|
||||
? this.MODES.NONE
|
||||
: this.MODES.PINNED_MESSAGES;
|
||||
}
|
||||
|
||||
async onFileUploaded(file) {
|
||||
await this.threadService.notifyThreadAvatarToServer(this.thread.id, file.data);
|
||||
this.notification.add(_t("The avatar has been updated!"), { type: "success" });
|
||||
|
||||
@@ -42,9 +42,6 @@
|
||||
<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.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>
|
||||
<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>
|
||||
@@ -63,13 +60,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto bg-view d-flex flex-grow-1">
|
||||
<div class="overflow-auto bg-view d-flex flex-grow-1" t-ref="core">
|
||||
<div class="d-flex flex-column flex-grow-1">
|
||||
<Call t-if="Object.keys(thread.rtcSessions).length > 0" thread="thread"/>
|
||||
<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>
|
||||
<PinnedMessagesPanel t-if="state.activeMode === MODES.PINNED_MESSAGES and thread.model === 'discuss.channel'" thread="thread"/>
|
||||
</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">
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
.o-mail-PinnedMessagesPanel {
|
||||
flex-basis: 425px;
|
||||
}
|
||||
|
||||
.o-mail-PinnedMessagesPanel-title {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.o-mail-PinnedMessagesPanel-jump {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-body:hover .o-mail-PinnedMessagesPanel-jump {
|
||||
opacity: 100 !important;
|
||||
}
|
||||
|
||||
.o_touch_device .o-mail-PinnedMessagesPanel-jump.btn {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.PinnedMessagesPanel" owl="1">
|
||||
<div class="o-mail-PinnedMessagesPanel overflow-auto d-flex flex-column flex-shrink-0 bg-view position-relative border-start" t-att-class="{
|
||||
'flex-grow-1': env.inChatWindow,
|
||||
}">
|
||||
<p class="o-mail-PinnedMessagesPanel-title position-sticky top-0 py-3 mb-0 fw-bolder text-center text-uppercase text-700">
|
||||
Pinned messages
|
||||
</p>
|
||||
<div class="d-flex flex-column" t-att-class="{ 'justify-content-center flex-grow-1': props.thread.pinnedMessages.length === 0 }" t-ref="message-list">
|
||||
<div class="card m-3 mt-0" t-foreach="props.thread.pinnedMessages" t-as="message" t-key="message.id">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button class="o-mail-PinnedMessagesPanel-jump btn rounded bg-400 badge opacity-0 flex-shrink-0" t-att-class="{ 'opacity-100 py-1 px-2': ui.isSmall }" t-on-click="() => this.onClickJump(message)">Jump</button>
|
||||
<button class="btn ms-2 p-0 text-700 shadow-none" t-att-class="{ 'fs-5 align-self-end': ui.isSmall }" title="Unpin"
|
||||
t-on-click="() => this.onClickUnpin(message)">
|
||||
<i class="d-block fa fa-times"/>
|
||||
</button>
|
||||
</div>
|
||||
<Message hasActions="false" message="message" thread="props.thread"/>
|
||||
</div>
|
||||
</div>
|
||||
<p t-if="props.thread.pinnedMessages.length === 0" t-esc="emptyMessage" class="text-center fst-italic text-500 fs-6"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -13,9 +13,12 @@ import { ThreadIcon } from "@mail/discuss_app/thread_icon";
|
||||
import { ImStatus } from "@mail/discuss_app/im_status";
|
||||
import { isEventHandled } from "@mail/utils/misc";
|
||||
import { ChannelSelector } from "@mail/discuss_app/channel_selector";
|
||||
import { PinnedMessagesPanel } from "@mail/discuss_app/pinned_messages_panel";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
export const MODES = {
|
||||
NONE: "",
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {import("@mail/web/chat_window/chat_window_model").ChatWindow} chatWindow
|
||||
@@ -30,12 +33,12 @@ export class ChatWindow extends Component {
|
||||
Composer,
|
||||
ThreadIcon,
|
||||
ImStatus,
|
||||
PinnedMessagesPanel,
|
||||
};
|
||||
static props = ["chatWindow", "right?"];
|
||||
static template = "mail.ChatWindow";
|
||||
|
||||
setup() {
|
||||
this.MODES = MODES;
|
||||
this.store = useStore();
|
||||
/** @type {import("@mail/web/chat_window/chat_window_service").ChatWindowService} */
|
||||
this.chatWindowService = useState(useService("mail.chat_window"));
|
||||
@@ -48,13 +51,12 @@ export class ChatWindow extends Component {
|
||||
this.state = useState({
|
||||
/**
|
||||
* activeMode:
|
||||
* "pinned-messages": pin menu is displayed
|
||||
* "member-list": channel member list is displayed
|
||||
* "in-settings": settings is displayed
|
||||
* "add-users": add users is displayed (small device)
|
||||
* "": no action pannel
|
||||
*/
|
||||
activeMode: "",
|
||||
activeMode: MODES.NONE,
|
||||
});
|
||||
this.action = useService("action");
|
||||
this.ui = useState(useService("ui"));
|
||||
@@ -62,14 +64,6 @@ export class ChatWindow extends Component {
|
||||
useChildSubEnv({
|
||||
inChatWindow: true,
|
||||
messageHighlight: this.messageHighlight,
|
||||
pinMenu: {
|
||||
open: () => (this.state.activeMode = "pinned-messages"),
|
||||
close: () => {
|
||||
if (this.state.activeMode === "pinned-messages") {
|
||||
this.state.activeMode = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -124,11 +118,6 @@ export class ChatWindow extends Component {
|
||||
this.chatWindowService.notifyState(this.props.chatWindow);
|
||||
}
|
||||
|
||||
togglePinMenu() {
|
||||
this.state.activeMode =
|
||||
this.state.activeMode === "pinned-messages" ? "" : "pinned-messages";
|
||||
}
|
||||
|
||||
expand() {
|
||||
if (this.thread.type === "chatter") {
|
||||
this.action.doAction({
|
||||
|
||||
@@ -32,9 +32,6 @@
|
||||
(<t t-out="thread.message_unread_counter"/>)
|
||||
</div>
|
||||
<div class="flex-grow-1"/>
|
||||
<div t-if="thread?.model === 'discuss.channel' and ui.isSmall" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Pinned messages" t-on-click.stop="togglePinMenu">
|
||||
<i class="fa fa-lg fa-thumb-tack"/>
|
||||
</div>
|
||||
<div t-if="thread and thread.allowCalls and thread !== rtc.state.channel and !props.chatWindow.hidden" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Start a Call" t-on-click.stop="() => this.rtc.toggleCall(this.props.chatWindow.thread)" t-ref="startCall">
|
||||
<i class="fa fa-phone"/>
|
||||
</div>
|
||||
@@ -48,9 +45,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="!props.chatWindow.folded" class="o-mail-ChatWindow-content bg-view d-flex flex-column h-100 overflow-auto border-start border-end" t-ref="content">
|
||||
<t t-if="thread">
|
||||
<PinnedMessagesPanel t-if="state.activeMode === 'pinned-messages'" thread="thread"/>
|
||||
<t t-else="">
|
||||
<t t-if="thread" name="thread content">
|
||||
<t t-if="state.activeMode === MODES.NONE">
|
||||
<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"/>
|
||||
|
||||
+9
-9
@@ -18,12 +18,12 @@ QUnit.test("Pin message", async (assert) => {
|
||||
await click(".o-mail-Discuss-header button[title='Pinned messages']");
|
||||
assert.containsOnce(
|
||||
$,
|
||||
".o-mail-PinnedMessagesPanel:contains(This channel doesn't have any pinned messages.)"
|
||||
".o-discuss-PinnedMessagesPanel:contains(This channel doesn't have any pinned messages.)"
|
||||
);
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item:contains(Pin)");
|
||||
await click(".modal-footer button:contains(Confirm)");
|
||||
assert.containsOnce($, ".o-mail-PinnedMessagesPanel .o-mail-Message:contains(Hello world)");
|
||||
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message:contains(Hello world)");
|
||||
});
|
||||
|
||||
QUnit.test("Unpin message", async (assert) => {
|
||||
@@ -38,11 +38,11 @@ QUnit.test("Unpin message", async (assert) => {
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Discuss-header button[title='Pinned messages']");
|
||||
assert.containsOnce($, ".o-mail-PinnedMessagesPanel .o-mail-Message");
|
||||
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item:contains(Unpin)");
|
||||
await click(".modal-footer button:contains(Confirm)");
|
||||
assert.containsNone($, ".o-mail-PinnedMessagesPanel .o-mail-Message");
|
||||
assert.containsNone($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
||||
QUnit.test("Deleted messages are not pinned", async (assert) => {
|
||||
@@ -58,11 +58,11 @@ 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']");
|
||||
assert.containsOnce($, ".o-mail-PinnedMessagesPanel .o-mail-Message");
|
||||
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item:contains(Delete)");
|
||||
await click("button:contains(Confirm)");
|
||||
assert.containsNone($, ".o-mail-PinnedMessagesPanel .o-mail-Message");
|
||||
assert.containsNone($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
||||
QUnit.test("Open pinned panel from notification", async (assert) => {
|
||||
@@ -78,9 +78,9 @@ QUnit.test("Open pinned panel from notification", async (assert) => {
|
||||
await click(".o-mail-Message:eq(0) [title='Expand']");
|
||||
await click(".dropdown-item:contains(Pin)");
|
||||
await click(".modal-footer button:contains(Confirm)");
|
||||
assert.containsNone($, ".o-mail-PinnedMessagesPanel");
|
||||
assert.containsNone($, ".o-discuss-PinnedMessagesPanel");
|
||||
await click(".o_mail_notification a:contains(See all pinned messages)");
|
||||
assert.containsOnce($, ".o-mail-PinnedMessagesPanel");
|
||||
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel");
|
||||
});
|
||||
|
||||
QUnit.test("Jump to message", async (assert) => {
|
||||
@@ -109,7 +109,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-PinnedMessagesPanel button:contains(Jump)");
|
||||
await click(".o-discuss-PinnedMessagesPanel button:contains(Jump)");
|
||||
await nextTick();
|
||||
assert.isVisible($(".o-mail-Message:contains(Hello world!)"));
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { messagePinService } from "@mail/discuss/message_pin/message_pin_service";
|
||||
import { setupManager } from "@mail/../tests/helpers/webclient_setup";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(setupManager, "discuss/message_pin", {
|
||||
setupServices() {
|
||||
return {
|
||||
...this._super(...arguments),
|
||||
"discuss.message.pin": messagePinService,
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user