[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:
committed by
Alexandre Kühn
parent
2ccce7ff1e
commit
3d2342cde8
@@ -36,4 +36,8 @@ export class Persona {
|
||||
get nameOrDisplayName() {
|
||||
return this.name || this.displayName;
|
||||
}
|
||||
|
||||
get emailWithoutDomain() {
|
||||
return this.email.substring(0, this.email.lastIndexOf("@"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" });
|
||||
|
||||
Reference in New Issue
Block a user