[IMP] mail: display email addresses when sending a message from chatter

Task-3358505

closes odoo/odoo#125580

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Didier (did)
2023-06-29 18:20:19 +02:00
committed by Alexandre Kühn
parent 2ccce7ff1e
commit 3d2342cde8
9 changed files with 115 additions and 38 deletions
@@ -36,4 +36,8 @@ export class Persona {
get nameOrDisplayName() {
return this.name || this.displayName;
}
get emailWithoutDomain() {
return this.email.substring(0, this.email.lastIndexOf("@"));
}
}
+28 -14
View File
@@ -27,10 +27,12 @@ import {
import { Dropdown } from "@web/core/dropdown/dropdown";
import { _t } from "@web/core/l10n/translation";
import { usePopover } from "@web/core/popover/popover_hook";
import { useService } from "@web/core/utils/hooks";
import { escape, sprintf } from "@web/core/utils/strings";
import { escape } from "@web/core/utils/strings";
import { useThrottleForAnimation } from "@web/core/utils/timing";
import { FileUploader } from "@web/views/fields/file_handler";
import { RecipientList } from "./recipient_list";
/**
* @typedef {Object} Props
@@ -105,6 +107,7 @@ export class Chatter extends Component {
this.scrollPosition = useScrollPosition("root", undefined, "top");
this.rootRef = useRef("root");
this.onScrollDebounced = useThrottleForAnimation(this.onScroll);
this.recipientsPopover = usePopover(RecipientList);
useChildSubEnv({ inChatter: true });
useDropzone(
this.rootRef,
@@ -197,19 +200,23 @@ export class Chatter extends Component {
/**
* @returns {string}
*/
get toFollowersText() {
const threadName = this.state.thread.displayName || this.state.thread.name;
const toFollowersText = threadName
? sprintf(
_t(
'<span class="fw-bold">To:</span> <span class="fst-italic">Followers of</span> <span class="fw-bold">"%(thread name)s"</span>.'
),
{ "thread name": escape(threadName) }
)
: _t(
'<span class="fw-bold">To:</span> <span class="fst-italic">Followers of</span> this document.'
);
return markup(toFollowersText);
get toRecipientsText() {
const followers = this.state.thread.followers.slice(0, 5).map(({ partner }) => {
if (partner === this.store.self) {
return `<span class="text-muted" title="${escape(partner.email)}">me</span>`;
}
return `<span class="text-muted" title="${escape(partner.email)}">${escape(
partner.emailWithoutDomain
)}</span>`;
});
const formatter = new Intl.ListFormat(
this.store.env.services["user"].lang?.replace("_", "-"),
{ type: "unit" }
);
if (this.state.thread.followers.length > 5) {
followers.push("…");
}
return markup(formatter.format(followers));
}
/**
@@ -409,4 +416,11 @@ export class Chatter extends Component {
onScroll() {
this.state.isTopStickyPinned = this.rootRef.el.scrollTop !== 0;
}
onClickRecipientList(ev) {
if (this.recipientsPopover.isOpen) {
return this.recipientsPopover.close();
}
this.recipientsPopover.open(ev.target, { thread: this.state.thread });
}
}
@@ -19,6 +19,10 @@
}
}
.o-mail-Chatter-recipientListButton:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.o-mail-Followers-button:focus {
background-color: $gray-200;
}
+7 -2
View File
@@ -92,8 +92,13 @@
</button>
</div>
<t t-if="state.thread.composer.type">
<t t-if="state.thread.composer.type === 'message'">
<div class="flex-shrink-0 px-3 pt-3 text-truncate small" style="margin-left:48px" t-out="toFollowersText"/>
<t t-if="state.thread.composer.type === 'message' and state.thread.followers.length > 0">
<div class="flex-shrink-0 px-3 pt-3 text-truncate small mb-2" style="margin-left:48px">
<span class="fw-bold">To:</span> <t t-out="toRecipientsText"/>
<button class="o-mail-Chatter-recipientListButton btn btn-link badge rounded-pill border-0 p-1 ms-1" title="Show all recipients" t-on-click="onClickRecipientList">
<i class="fa fa-caret-down"/>
</button>
</div>
</t>
<t t-if="state.thread.composer.type === 'message'" t-set="placeholder">Send a message to followers...</t>
<t t-else="" t-set="placeholder">Log an internal note...</t>
@@ -0,0 +1,14 @@
/* @odoo-module */
import { Component } from "@odoo/owl";
/**
* @typedef {Object} Props
* @property {import('@mail/core/common/thread_model').Thread} thread
* @property {function} [close]
* @extends {Component<Props, Env>}
*/
export class RecipientList extends Component {
static template = "mail.RecipientList";
static props = ["thread", "close?"];
}
@@ -0,0 +1,3 @@
.o-mail-RecipientList {
max-height: 410px;
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="mail.RecipientList" owl="1">
<div class="o-mail-RecipientList p-2 overflow-auto">
<ul class="list-unstyled mb-0">
<li t-foreach="props.thread.followers" t-as="follower" t-key="follower.id">
<t t-out="follower.partner.email"/>
</li>
</ul>
</div>
</t>
</templates>
@@ -827,25 +827,52 @@ QUnit.test("remove an uploading attachment", async (assert) => {
assert.containsNone($, ".o-mail-Composer .o-mail-AttachmentCard");
});
QUnit.test(
"Show a default status in the recipient status text when the thread doesn't have a name.",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
await click("button:contains(Send message)");
assert.containsOnce($, ".o-mail-Chatter:contains(To: Followers of this document)");
}
);
QUnit.test("Show a thread name in the recipient status text.", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "test name" });
const partnerId = pyEnv["res.partner"].create({ name: "test name", email: "test@odoo.com" });
pyEnv["mail.followers"].create({
is_active: true,
partner_id: partnerId,
res_id: partnerId,
res_model: "res.partner",
});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
await click("button:contains(Send message)");
assert.containsOnce($, '.o-mail-Chatter:contains(To: Followers of "test name")');
assert.containsOnce($, ".o-mail-Chatter:contains(To: test)");
assert.containsOnce($, 'span[title="test@odoo.com"]');
});
QUnit.test("Show follower list when there is more than 5 followers.", async (assert) => {
const pyEnv = await startServer();
const partnerIds = pyEnv["res.partner"].create([
{ name: "test name 1", email: "test1@odoo.com" },
{ name: "test name 2", email: "test2@odoo.com" },
{ name: "test name 3", email: "test3@odoo.com" },
{ name: "test name 4", email: "test4@odoo.com" },
{ name: "test name 5", email: "test5@odoo.com" },
{ name: "test name 6", email: "test6@odoo.com" },
]);
for (const partner of partnerIds) {
pyEnv["mail.followers"].create({
is_active: true,
partner_id: partner,
res_id: partnerIds[0],
res_model: "res.partner",
});
}
const { openFormView } = await start();
await openFormView("res.partner", partnerIds[0]);
await click("button:contains(Send message)");
assert.containsOnce($, "button[title='Show all recipients']");
await click("button[title='Show all recipients']");
assert.containsOnce($, "li:contains('test1@odoo.com')");
assert.containsOnce($, "li:contains('test2@odoo.com')");
assert.containsOnce($, "li:contains('test3@odoo.com')");
assert.containsOnce($, "li:contains('test4@odoo.com')");
assert.containsOnce($, "li:contains('test5@odoo.com')");
assert.containsOnce($, "li:contains('test6@odoo.com')");
assert.containsOnce($, ".o-mail-Chatter:contains('test1, test2, test3, test4, test5, …')");
});
QUnit.test(
@@ -40,14 +40,6 @@ QUnit.test("simple chatter on a record", async (assert) => {
]);
});
QUnit.test("displayname is used when sending a message", async (assert) => {
const { openFormView, pyEnv } = await start();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
await openFormView("res.partner", partnerId);
await click("button:contains(Send message)");
assert.containsOnce($, '.o-mail-Chatter:contains(To: Followers of "John Doe")');
});
QUnit.test("can post a message on a record thread", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });