diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index a8d293f5475..f5d58f6243d 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -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',
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 1f08f6504a7..369b6803b21 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -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,
}
})
diff --git a/addons/mail/static/src/core/message_model.js b/addons/mail/static/src/core/message_model.js
index ffe2e92cb2d..ed7c66c50df 100644
--- a/addons/mail/static/src/core/message_model.js
+++ b/addons/mail/static/src/core/message_model.js
@@ -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")
diff --git a/addons/mail/static/src/core/message_service.js b/addons/mail/static/src/core/message_service.js
index 640b6a70f79..d9570eddb9c 100644
--- a/addons/mail/static/src/core/message_service.js
+++ b/addons/mail/static/src/core/message_service.js
@@ -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) {
diff --git a/addons/mail/static/src/core/messaging_service.js b/addons/mail/static/src/core/messaging_service.js
index 6339d71b536..2996585db96 100644
--- a/addons/mail/static/src/core/messaging_service.js
+++ b/addons/mail/static/src/core/messaging_service.js
@@ -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) {
diff --git a/addons/mail/static/src/core/thread_model.js b/addons/mail/static/src/core/thread_model.js
index f9c482ae90b..8901f28e83d 100644
--- a/addons/mail/static/src/core/thread_model.js
+++ b/addons/mail/static/src/core/thread_model.js
@@ -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} */
diff --git a/addons/mail/static/src/core/thread_service.js b/addons/mail/static/src/core/thread_service.js
index 4f9c6e8c5cb..52496db17ec 100644
--- a/addons/mail/static/src/core/thread_service.js
+++ b/addons/mail/static/src/core/thread_service.js
@@ -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";
diff --git a/addons/mail/static/src/core_ui/message.js b/addons/mail/static/src/core_ui/message.js
index 9b1ab2a4971..8f571ba10b4 100644
--- a/addons/mail/static/src/core_ui/message.js
+++ b/addons/mail/static/src/core_ui/message.js
@@ -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) {
diff --git a/addons/mail/static/src/core_ui/message.scss b/addons/mail/static/src/core_ui/message.scss
index 27e92b3af22..f3f228cc0ff 100644
--- a/addons/mail/static/src/core_ui/message.scss
+++ b/addons/mail/static/src/core_ui/message.scss
@@ -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 {
diff --git a/addons/mail/static/src/core_ui/message.xml b/addons/mail/static/src/core_ui/message.xml
index a640c1c56c6..0e054fc1914 100644
--- a/addons/mail/static/src/core_ui/message.xml
+++ b/addons/mail/static/src/core_ui/message.xml
@@ -4,18 +4,7 @@
View Reactions
Mark as Read
Unfollow
-
Mark as Unread
Edit
Delete
diff --git a/addons/mail/static/src/core_ui/thread.js b/addons/mail/static/src/core_ui/thread.js
index f3c760e569b..9813399197b 100644
--- a/addons/mail/static/src/core_ui/thread.js
+++ b/addons/mail/static/src/core_ui/thread.js
@@ -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();
}
}
diff --git a/addons/mail/static/src/core_ui/thread.scss b/addons/mail/static/src/core_ui/thread.scss
index c170d2c2875..27c4fe66d75 100644
--- a/addons/mail/static/src/core_ui/thread.scss
+++ b/addons/mail/static/src/core_ui/thread.scss
@@ -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;
diff --git a/addons/mail/static/src/core_ui/thread.xml b/addons/mail/static/src/core_ui/thread.xml
index 55dff09a31a..addaf2f9662 100644
--- a/addons/mail/static/src/core_ui/thread.xml
+++ b/addons/mail/static/src/core_ui/thread.xml
@@ -115,9 +115,8 @@
+ }" t-ref="root">
diff --git a/addons/mail/static/src/discuss/discuss_patch.xml b/addons/mail/static/src/discuss/discuss_patch.xml
index ca044ae1c1f..b109875dd94 100644
--- a/addons/mail/static/src/discuss/discuss_patch.xml
+++ b/addons/mail/static/src/discuss/discuss_patch.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/addons/mail/static/src/discuss/message_pin/discuss_patch.js b/addons/mail/static/src/discuss/message_pin/discuss_patch.js
new file mode 100644
index 00000000000..6a1e6e445e3
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/discuss_patch.js
@@ -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;
+ },
+});
diff --git a/addons/mail/static/src/discuss/message_pin/discuss_patch.xml b/addons/mail/static/src/discuss/message_pin/discuss_patch.xml
new file mode 100644
index 00000000000..9a963fe9a97
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/discuss_patch.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/static/src/discuss/message_pin/message_patch.js b/addons/mail/static/src/discuss/message_pin/message_patch.js
new file mode 100644
index 00000000000..84d9a7cb770
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/message_patch.js
@@ -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();
+ },
+});
diff --git a/addons/mail/static/src/discuss/message_pin/message_patch.xml b/addons/mail/static/src/discuss/message_pin/message_patch.xml
new file mode 100644
index 00000000000..f6d2b25111d
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/message_patch.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/static/src/discuss/message_pin/message_pin_service.js b/addons/mail/static/src/discuss/message_pin/message_pin_service.js
new file mode 100644
index 00000000000..ccfbf7158ca
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/message_pin_service.js
@@ -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} */
+ loadStateByChannelId = new Map();
+ /** @type {Map>} */
+ messageIdsByChannelId = new Map();
+ /** @type {Map} */
+ channelIdByMessageId = new Map();
+ /** @type {Map} */
+ 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"));
+}
diff --git a/addons/mail/static/src/discuss_app/pinned_messages_panel.js b/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.js
similarity index 84%
rename from addons/mail/static/src/discuss_app/pinned_messages_panel.js
rename to addons/mail/static/src/discuss/message_pin/pinned_messages_panel.js
index c98c09ad547..86ddec993c3 100644
--- a/addons/mail/static/src/discuss_app/pinned_messages_panel.js
+++ b/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.js
@@ -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),
});
}
diff --git a/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.scss b/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.scss
new file mode 100644
index 00000000000..6deb2fb7ef1
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.scss
@@ -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;
+}
diff --git a/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.xml b/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.xml
new file mode 100644
index 00000000000..0e354a9b2a2
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/pinned_messages_panel.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Pinned messages
+
+
+
+
+
diff --git a/addons/mail/static/src/discuss/message_pin/thread_patch.js b/addons/mail/static/src/discuss/message_pin/thread_patch.js
new file mode 100644
index 00000000000..ff88052d070
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/thread_patch.js
@@ -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();
+ }
+ },
+});
diff --git a/addons/mail/static/src/discuss/message_pin/thread_patch.scss b/addons/mail/static/src/discuss/message_pin/thread_patch.scss
new file mode 100644
index 00000000000..f860fcc0d30
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/thread_patch.scss
@@ -0,0 +1,3 @@
+.o-mail-Thread-noPinnedPanel [data-oe-type="pin-menu"] {
+ display: none;
+}
diff --git a/addons/mail/static/src/discuss/message_pin/thread_patch.xml b/addons/mail/static/src/discuss/message_pin/thread_patch.xml
new file mode 100644
index 00000000000..1f8ccc5c1d4
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/thread_patch.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ {{ env.inChatWindow and !ui.isSmall ? 'o-mail-Thread-noPinnedPanel' : '' }}>
+
+
+
diff --git a/addons/mail/static/src/discuss/message_pin/web/chat_window_patch.js b/addons/mail/static/src/discuss/message_pin/web/chat_window_patch.js
new file mode 100644
index 00000000000..8efd73d9198
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/web/chat_window_patch.js
@@ -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;
+ },
+});
diff --git a/addons/mail/static/src/discuss/message_pin/web/chat_window_patch.xml b/addons/mail/static/src/discuss/message_pin/web/chat_window_patch.xml
new file mode 100644
index 00000000000..06fd1b000c9
--- /dev/null
+++ b/addons/mail/static/src/discuss/message_pin/web/chat_window_patch.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/static/src/discuss/web/chat_window_patch.xml b/addons/mail/static/src/discuss/web/chat_window_patch.xml
index a772a391aba..553fd45776f 100644
--- a/addons/mail/static/src/discuss/web/chat_window_patch.xml
+++ b/addons/mail/static/src/discuss/web/chat_window_patch.xml
@@ -14,10 +14,10 @@
-
-
-
-
+
+
+
+
diff --git a/addons/mail/static/src/discuss_app/discuss.js b/addons/mail/static/src/discuss_app/discuss.js
index 30a4d92376f..b0689a76d09 100644
--- a/addons/mail/static/src/discuss_app/discuss.js
+++ b/addons/mail/static/src/discuss_app/discuss.js
@@ -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" });
diff --git a/addons/mail/static/src/discuss_app/discuss.xml b/addons/mail/static/src/discuss_app/discuss.xml
index c07658a1c6f..e5c1fffbf46 100644
--- a/addons/mail/static/src/discuss_app/discuss.xml
+++ b/addons/mail/static/src/discuss_app/discuss.xml
@@ -42,9 +42,6 @@
-
diff --git a/addons/mail/static/src/discuss_app/pinned_messages_panel.scss b/addons/mail/static/src/discuss_app/pinned_messages_panel.scss
deleted file mode 100644
index a424f21f4de..00000000000
--- a/addons/mail/static/src/discuss_app/pinned_messages_panel.scss
+++ /dev/null
@@ -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;
-}
diff --git a/addons/mail/static/src/discuss_app/pinned_messages_panel.xml b/addons/mail/static/src/discuss_app/pinned_messages_panel.xml
deleted file mode 100644
index 2d7d18a47c7..00000000000
--- a/addons/mail/static/src/discuss_app/pinned_messages_panel.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
- Pinned messages
-
-
-
-
-
diff --git a/addons/mail/static/src/web/chat_window/chat_window.js b/addons/mail/static/src/web/chat_window/chat_window.js
index 34af53d7caf..7d90cf0c9cd 100644
--- a/addons/mail/static/src/web/chat_window/chat_window.js
+++ b/addons/mail/static/src/web/chat_window/chat_window.js
@@ -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({
diff --git a/addons/mail/static/src/web/chat_window/chat_window.xml b/addons/mail/static/src/web/chat_window/chat_window.xml
index 6cef10aac89..3a0cd757dd6 100644
--- a/addons/mail/static/src/web/chat_window/chat_window.xml
+++ b/addons/mail/static/src/web/chat_window/chat_window.xml
@@ -32,9 +32,6 @@
(
)
-
-
-
@@ -48,9 +45,8 @@
-
-
-
+
+
diff --git a/addons/mail/static/tests/discuss_app/pinned_messages_tests.js b/addons/mail/static/tests/discuss/message_pin/pinned_messages_tests.js
similarity index 87%
rename from addons/mail/static/tests/discuss_app/pinned_messages_tests.js
rename to addons/mail/static/tests/discuss/message_pin/pinned_messages_tests.js
index 7d5dae9382b..7e173376289 100644
--- a/addons/mail/static/tests/discuss_app/pinned_messages_tests.js
+++ b/addons/mail/static/tests/discuss/message_pin/pinned_messages_tests.js
@@ -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!)"));
});
diff --git a/addons/mail/static/tests/helpers/discuss/message_pin/setup_manager_patch.js b/addons/mail/static/tests/helpers/discuss/message_pin/setup_manager_patch.js
new file mode 100644
index 00000000000..eada1b2478e
--- /dev/null
+++ b/addons/mail/static/tests/helpers/discuss/message_pin/setup_manager_patch.js
@@ -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,
+ };
+ },
+});