FEATURE: Use rich user status tooltip everywhere (#21125)
- Inline mentions on posts - Inline mentions on chat messages - The user autocomplete for the composer - The user autocomplete for chat - The chat section of the sidebar
This commit is contained in:
parent
5034eda386
commit
585a2e4e77
|
@ -42,6 +42,11 @@ import { isTesting } from "discourse-common/config/environment";
|
||||||
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||||
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
||||||
import userSearch from "discourse/lib/user-search";
|
import userSearch from "discourse/lib/user-search";
|
||||||
|
import {
|
||||||
|
destroyTippyInstances,
|
||||||
|
initUserStatusHtml,
|
||||||
|
renderUserStatusHtml,
|
||||||
|
} from "discourse/lib/user-status-on-autocomplete";
|
||||||
|
|
||||||
// original string `![image|foo=bar|690x220, 50%|bar=baz](upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title")`
|
// original string `![image|foo=bar|690x220, 50%|bar=baz](upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title")`
|
||||||
// group 1 `image|foo=bar`
|
// group 1 `image|foo=bar`
|
||||||
|
@ -220,18 +225,27 @@ export default Component.extend(
|
||||||
if (this.siteSettings.enable_mentions) {
|
if (this.siteSettings.enable_mentions) {
|
||||||
$input.autocomplete({
|
$input.autocomplete({
|
||||||
template: findRawTemplate("user-selector-autocomplete"),
|
template: findRawTemplate("user-selector-autocomplete"),
|
||||||
dataSource: (term) =>
|
dataSource: (term) => {
|
||||||
userSearch({
|
destroyTippyInstances();
|
||||||
|
return userSearch({
|
||||||
term,
|
term,
|
||||||
topicId: this.topic?.id,
|
topicId: this.topic?.id,
|
||||||
categoryId: this.topic?.category_id || this.composer?.categoryId,
|
categoryId: this.topic?.category_id || this.composer?.categoryId,
|
||||||
includeGroups: true,
|
includeGroups: true,
|
||||||
}),
|
}).then((result) => {
|
||||||
|
initUserStatusHtml(result.users);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onRender: (options) => {
|
||||||
|
renderUserStatusHtml(options);
|
||||||
|
},
|
||||||
key: "@",
|
key: "@",
|
||||||
transformComplete: (v) => v.username || v.name,
|
transformComplete: (v) => v.username || v.name,
|
||||||
afterComplete: this._afterMentionComplete,
|
afterComplete: this._afterMentionComplete,
|
||||||
triggerRule: (textarea) =>
|
triggerRule: (textarea) =>
|
||||||
!inCodeBlock(textarea.value, caretPosition(textarea)),
|
!inCodeBlock(textarea.value, caretPosition(textarea)),
|
||||||
|
onClose: destroyTippyInstances,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default class DiscourseTooltip extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPropagation(instance, event) {
|
stopPropagation(instance, event) {
|
||||||
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{{@content}}
|
{{@content}}
|
||||||
|
{{@contentComponent}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{{#if @badgeText}}
|
{{#if @badgeText}}
|
||||||
|
|
|
@ -47,6 +47,10 @@
|
||||||
@didInsert={{link.didInsert}}
|
@didInsert={{link.didInsert}}
|
||||||
@willDestroy={{link.willDestroy}}
|
@willDestroy={{link.willDestroy}}
|
||||||
@content={{link.text}}
|
@content={{link.text}}
|
||||||
|
@contentComponent={{component
|
||||||
|
link.contentComponent
|
||||||
|
status=link.contentComponentArgs
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</Sidebar::Section>
|
</Sidebar::Section>
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
<span class={{concat-class "user-status-message" @class}}>
|
{{#if @status}}
|
||||||
{{emoji @status.emoji skipTitle=true}}
|
<span class={{concat-class "user-status-message" @class}}>
|
||||||
{{#if @showDescription}}
|
{{emoji @status.emoji skipTitle=true}}
|
||||||
<span class="user-status-message-description">
|
{{#if @showDescription}}
|
||||||
{{@status.description}}
|
<span class="user-status-message-description">
|
||||||
</span>
|
{{@status.description}}
|
||||||
{{/if}}
|
</span>
|
||||||
{{#if this.showTooltip}}
|
{{/if}}
|
||||||
<DTooltip>
|
{{#if this.showTooltip}}
|
||||||
<div class="user-status-message-tooltip">
|
<DTooltip>
|
||||||
{{emoji @status.emoji skipTitle=true}}
|
<div class="user-status-message-tooltip">
|
||||||
<span class="user-status-tooltip-description">
|
{{emoji @status.emoji skipTitle=true}}
|
||||||
{{@status.description}}
|
<span class="user-status-tooltip-description">
|
||||||
</span>
|
{{@status.description}}
|
||||||
{{#if this.until}}
|
</span>
|
||||||
<div class="user-status-tooltip-until">
|
{{#if this.until}}
|
||||||
{{this.until}}
|
<div class="user-status-tooltip-until">
|
||||||
</div>
|
{{this.until}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
{{/if}}
|
||||||
</DTooltip>
|
</div>
|
||||||
{{/if}}
|
</DTooltip>
|
||||||
</span>
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
|
@ -148,6 +148,7 @@ export default function (options) {
|
||||||
|
|
||||||
function closeAutocomplete() {
|
function closeAutocomplete() {
|
||||||
_autoCompletePopper?.destroy();
|
_autoCompletePopper?.destroy();
|
||||||
|
options.onClose && options.onClose();
|
||||||
|
|
||||||
if (div) {
|
if (div) {
|
||||||
div.hide().remove();
|
div.hide().remove();
|
||||||
|
@ -404,6 +405,10 @@ export default function (options) {
|
||||||
scrollElement = div.find(options.scrollElementSelector);
|
scrollElement = div.find(options.scrollElementSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.onRender) {
|
||||||
|
options.onRender(autocompleteOptions);
|
||||||
|
}
|
||||||
|
|
||||||
if (isInput || options.treatAsTextarea) {
|
if (isInput || options.treatAsTextarea) {
|
||||||
_autoCompletePopper && _autoCompletePopper.destroy();
|
_autoCompletePopper && _autoCompletePopper.destroy();
|
||||||
_autoCompletePopper = createPopper(me[0], div[0], {
|
_autoCompletePopper = createPopper(me[0], div[0], {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import tippy from "tippy.js";
|
||||||
|
|
||||||
|
function stopPropagation(instance, event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
function hasTouchCapabilities() {
|
||||||
|
return navigator.maxTouchPoints > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createDTooltip(target, content) {
|
||||||
|
return tippy(target, {
|
||||||
|
interactive: false,
|
||||||
|
content,
|
||||||
|
trigger: hasTouchCapabilities() ? "click" : "mouseenter",
|
||||||
|
theme: "d-tooltip",
|
||||||
|
arrow: false,
|
||||||
|
placement: "bottom-start",
|
||||||
|
onTrigger: stopPropagation,
|
||||||
|
onUntrigger: stopPropagation,
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,36 +1,14 @@
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
import createUserStatusMessage from "discourse/lib/user-status-message";
|
||||||
import { emojiUnescape } from "discourse/lib/text";
|
|
||||||
import { until } from "discourse/lib/formatter";
|
|
||||||
|
|
||||||
export function updateUserStatusOnMention(mention, status, currentUser) {
|
export function updateUserStatusOnMention(mention, status, tippyInstances) {
|
||||||
removeStatus(mention);
|
removeStatus(mention);
|
||||||
if (status) {
|
if (status) {
|
||||||
const html = statusHtml(status, currentUser);
|
const statusHtml = createUserStatusMessage(status, { showTooltip: true });
|
||||||
mention.insertAdjacentHTML("beforeend", html);
|
tippyInstances.push(statusHtml._tippy);
|
||||||
|
mention.appendChild(statusHtml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeStatus(mention) {
|
function removeStatus(mention) {
|
||||||
mention.querySelector("img.user-status")?.remove();
|
mention.querySelector("span.user-status-message")?.remove();
|
||||||
}
|
|
||||||
|
|
||||||
function statusHtml(status, currentUser) {
|
|
||||||
const emoji = escapeExpression(`:${status.emoji}:`);
|
|
||||||
return emojiUnescape(emoji, {
|
|
||||||
class: "user-status",
|
|
||||||
title: statusTitle(status, currentUser),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusTitle(status, currentUser) {
|
|
||||||
if (!status.ends_at) {
|
|
||||||
return status.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timezone = currentUser
|
|
||||||
? currentUser.user_option?.timezone
|
|
||||||
: moment.tz.guess();
|
|
||||||
|
|
||||||
const until_ = until(status.ends_at, timezone, currentUser?.locale);
|
|
||||||
return escapeExpression(`${status.description} ${until_}`);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import createDTooltip from "discourse/lib/d-tooltip";
|
||||||
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
|
import { until } from "discourse/lib/formatter";
|
||||||
|
import User from "discourse/models/user";
|
||||||
|
|
||||||
|
function getUntil(endsAt) {
|
||||||
|
const currentUser = User.current();
|
||||||
|
|
||||||
|
const timezone = currentUser
|
||||||
|
? currentUser.user_option?.timezone
|
||||||
|
: moment.tz.guess();
|
||||||
|
|
||||||
|
return until(endsAt, timezone, currentUser?.locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmoji(emojiName) {
|
||||||
|
const emoji = escapeExpression(`:${emojiName}:`);
|
||||||
|
return emojiUnescape(emoji, {
|
||||||
|
skipTitle: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachTooltip(target, status) {
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.classList.add("user-status-message-tooltip");
|
||||||
|
content.innerHTML = getEmoji(status.emoji);
|
||||||
|
|
||||||
|
const tooltipDescription = document.createElement("span");
|
||||||
|
tooltipDescription.classList.add("user-status-tooltip-description");
|
||||||
|
tooltipDescription.innerText = status.description;
|
||||||
|
content.appendChild(tooltipDescription);
|
||||||
|
|
||||||
|
if (status.ends_at) {
|
||||||
|
const untilElement = document.createElement("div");
|
||||||
|
untilElement.classList.add("user-status-tooltip-until");
|
||||||
|
untilElement.innerText = getUntil(status.ends_at);
|
||||||
|
content.appendChild(untilElement);
|
||||||
|
}
|
||||||
|
createDTooltip(target, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createUserStatusMessage(status, opts) {
|
||||||
|
const userStatusMessage = document.createElement("span");
|
||||||
|
userStatusMessage.classList.add("user-status-message");
|
||||||
|
if (opts?.class) {
|
||||||
|
userStatusMessage.classList.add(opts.class);
|
||||||
|
}
|
||||||
|
userStatusMessage.innerHTML = getEmoji(status.emoji);
|
||||||
|
|
||||||
|
if (opts?.showDescription) {
|
||||||
|
const messageDescription = document.createElement("span");
|
||||||
|
messageDescription.classList.add("user-status-message-description");
|
||||||
|
messageDescription.innerText = status.description;
|
||||||
|
userStatusMessage.appendChild(messageDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts?.showTooltip) {
|
||||||
|
attachTooltip(userStatusMessage, status);
|
||||||
|
}
|
||||||
|
return userStatusMessage;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import createUserStatusMessage from "discourse/lib/user-status-message";
|
||||||
|
|
||||||
|
let tippyInstances = [];
|
||||||
|
|
||||||
|
export function initUserStatusHtml(users) {
|
||||||
|
(users || []).forEach((user, index) => {
|
||||||
|
if (user.status) {
|
||||||
|
user.index = index;
|
||||||
|
user.statusHtml = createUserStatusMessage(user.status, {
|
||||||
|
showTooltip: true,
|
||||||
|
showDescription: true,
|
||||||
|
});
|
||||||
|
tippyInstances.push(user.statusHtml._tippy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderUserStatusHtml(options) {
|
||||||
|
const users = document.querySelectorAll(".autocomplete.ac-user li");
|
||||||
|
users.forEach((user) => {
|
||||||
|
const index = user.dataset.index;
|
||||||
|
const statusHtml = options.find(function (el) {
|
||||||
|
return el.index === parseInt(index, 10);
|
||||||
|
})?.statusHtml;
|
||||||
|
if (statusHtml) {
|
||||||
|
user.querySelector(".user-status").replaceWith(statusHtml);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyTippyInstances() {
|
||||||
|
tippyInstances.forEach((instance) => {
|
||||||
|
instance.destroy();
|
||||||
|
});
|
||||||
|
tippyInstances = [];
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
{{#each options as |item|}}
|
{{#each options as |item|}}
|
||||||
{{#if item.isUser}}
|
{{#if item.isUser}}
|
||||||
<li>
|
<li data-index={{item.index}}>
|
||||||
<a href title="{{item.name}}" class="{{item.cssClasses}}">
|
<a href title="{{item.name}}" class="{{item.cssClasses}}">
|
||||||
{{avatar item imageSize="tiny"}}
|
{{avatar item imageSize="tiny"}}
|
||||||
<span class='username'>{{format-username item.username}}</span>
|
<span class='username'>{{format-username item.username}}</span>
|
||||||
|
@ -10,13 +10,7 @@
|
||||||
<span class='name'>{{item.name}}</span>
|
<span class='name'>{{item.name}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if item.status}}
|
{{#if item.status}}
|
||||||
{{emoji item.status.emoji}}
|
<span class='user-status'></span>
|
||||||
<span class='status-description' title='{{item.status.description}}'>
|
|
||||||
{{item.status.description}}
|
|
||||||
</span>
|
|
||||||
{{#if item.status.ends_at}}
|
|
||||||
{{format-age item.status.ends_at}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -36,6 +36,7 @@ function createDetachedElement(nodeName) {
|
||||||
|
|
||||||
export default class PostCooked {
|
export default class PostCooked {
|
||||||
originalQuoteContents = null;
|
originalQuoteContents = null;
|
||||||
|
tippyInstances = [];
|
||||||
|
|
||||||
constructor(attrs, decoratorHelper, currentUser) {
|
constructor(attrs, decoratorHelper, currentUser) {
|
||||||
this.attrs = attrs;
|
this.attrs = attrs;
|
||||||
|
@ -76,6 +77,7 @@ export default class PostCooked {
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this._stopTrackingMentionedUsersStatus();
|
this._stopTrackingMentionedUsersStatus();
|
||||||
|
this._destroyTippyInstances();
|
||||||
}
|
}
|
||||||
|
|
||||||
_decorateAndAdopt(cooked) {
|
_decorateAndAdopt(cooked) {
|
||||||
|
@ -380,7 +382,14 @@ export default class PostCooked {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_destroyTippyInstances() {
|
||||||
|
this.tippyInstances.forEach((instance) => {
|
||||||
|
instance.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_rerenderUserStatusOnMentions() {
|
_rerenderUserStatusOnMentions() {
|
||||||
|
this._destroyTippyInstances();
|
||||||
this._post()?.mentioned_users?.forEach((user) =>
|
this._post()?.mentioned_users?.forEach((user) =>
|
||||||
this._rerenderUserStatusOnMention(this.cookedDiv, user)
|
this._rerenderUserStatusOnMention(this.cookedDiv, user)
|
||||||
);
|
);
|
||||||
|
@ -391,7 +400,7 @@ export default class PostCooked {
|
||||||
const mentions = postElement.querySelectorAll(`a.mention[href="${href}"]`);
|
const mentions = postElement.querySelectorAll(`a.mention[href="${href}"]`);
|
||||||
|
|
||||||
mentions.forEach((mention) => {
|
mentions.forEach((mention) => {
|
||||||
updateUserStatusOnMention(mention, user.status, this.currentUser);
|
updateUserStatusOnMention(mention, user.status, this.tippyInstances);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,19 +137,16 @@ acceptance("Composer - editor mentions", function (needs) {
|
||||||
await emulateAutocomplete(".d-editor-input", "@u");
|
await emulateAutocomplete(".d-editor-input", "@u");
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(`.autocomplete .emoji[title='${status.emoji}']`),
|
exists(`.autocomplete .emoji[alt='${status.emoji}']`),
|
||||||
"status emoji is shown"
|
"status emoji is shown"
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".autocomplete .status-description").textContent.trim(),
|
query(
|
||||||
|
".autocomplete .user-status-message-description"
|
||||||
|
).textContent.trim(),
|
||||||
status.description,
|
status.description,
|
||||||
"status description is shown"
|
"status description is shown"
|
||||||
);
|
);
|
||||||
assert.equal(
|
|
||||||
query(".autocomplete .relative-date").textContent.trim(),
|
|
||||||
"1h",
|
|
||||||
"status expiration time is shown"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("metadata matches are moved to the end", async function (assert) {
|
test("metadata matches are moved to the end", async function (assert) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
publishToMessageBus,
|
publishToMessageBus,
|
||||||
query,
|
query,
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { visit } from "@ember/test-helpers";
|
import { triggerEvent, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
import topicFixtures from "../fixtures/topic";
|
import topicFixtures from "../fixtures/topic";
|
||||||
|
@ -32,6 +32,14 @@ function topicWithUserStatus(topicId, mentionedUserId, status) {
|
||||||
return topic;
|
return topic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function mouseenter() {
|
||||||
|
await triggerEvent(query(".user-status-message"), "mouseenter");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mouseleave() {
|
||||||
|
await triggerEvent(query(".user-status-message"), "mouseleave");
|
||||||
|
}
|
||||||
|
|
||||||
acceptance("Post inline mentions", function (needs) {
|
acceptance("Post inline mentions", function (needs) {
|
||||||
needs.user();
|
needs.user();
|
||||||
|
|
||||||
|
@ -51,19 +59,28 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"user status is shown"
|
"user status is shown"
|
||||||
);
|
);
|
||||||
const statusElement = query(".topic-post .cooked .mention .user-status");
|
|
||||||
assert.equal(
|
const statusElement = query(
|
||||||
statusElement.title,
|
".topic-post .cooked .mention .user-status-message img"
|
||||||
status.description,
|
|
||||||
"status description is correct"
|
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
statusElement.src.includes(status.emoji),
|
statusElement.src.includes(status.emoji),
|
||||||
"status emoji is correct"
|
"status emoji is correct"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await mouseenter();
|
||||||
|
const statusTooltipDescription = document.querySelector(
|
||||||
|
".user-status-message-tooltip .user-status-tooltip-description"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
statusTooltipDescription.innerText,
|
||||||
|
status.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("inserts user status on message bus message", async function (assert) {
|
test("inserts user status on message bus message", async function (assert) {
|
||||||
|
@ -73,7 +90,7 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
||||||
|
|
||||||
assert.notOk(
|
assert.notOk(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"user status isn't shown"
|
"user status isn't shown"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -85,19 +102,30 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"user status is shown"
|
"user status is shown"
|
||||||
);
|
);
|
||||||
const statusElement = query(".topic-post .cooked .mention .user-status");
|
|
||||||
assert.equal(
|
const statusElement = query(
|
||||||
statusElement.title,
|
".topic-post .cooked .mention .user-status-message img"
|
||||||
status.description,
|
|
||||||
"status description is correct"
|
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
statusElement.src.includes(status.emoji),
|
statusElement.src.includes(status.emoji),
|
||||||
"status emoji is correct"
|
"status emoji is correct"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await mouseenter();
|
||||||
|
const statusTooltipDescription = document.querySelector(
|
||||||
|
".user-status-message-tooltip .user-status-tooltip-description"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
statusTooltipDescription.innerText,
|
||||||
|
status.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Needed to remove the tooltip in between tests
|
||||||
|
await mouseleave();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("updates user status on message bus message", async function (assert) {
|
test("updates user status on message bus message", async function (assert) {
|
||||||
|
@ -107,7 +135,7 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"initial user status is shown"
|
"initial user status is shown"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -122,20 +150,32 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await mouseenter();
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"updated user status is shown"
|
"updated user status is shown"
|
||||||
);
|
);
|
||||||
const statusElement = query(".topic-post .cooked .mention .user-status");
|
|
||||||
assert.equal(
|
const statusElement = query(
|
||||||
statusElement.title,
|
".topic-post .cooked .mention .user-status-message img"
|
||||||
newStatus.description,
|
|
||||||
"updated status description is correct"
|
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
statusElement.src.includes(newStatus.emoji),
|
statusElement.src.includes(newStatus.emoji),
|
||||||
"updated status emoji is correct"
|
"updated status emoji is correct"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const statusTooltipDescription = document.querySelector(
|
||||||
|
".user-status-message-tooltip .user-status-tooltip-description"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
statusTooltipDescription.innerText,
|
||||||
|
newStatus.description,
|
||||||
|
"updated status description is correct"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Needed to remove the tooltip in between tests
|
||||||
|
await mouseleave();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("removes user status on message bus message", async function (assert) {
|
test("removes user status on message bus message", async function (assert) {
|
||||||
|
@ -145,7 +185,7 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"initial user status is shown"
|
"initial user status is shown"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -154,7 +194,7 @@ acceptance("Post inline mentions", function (needs) {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.notOk(
|
assert.notOk(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"updated user has disappeared"
|
"updated user has disappeared"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -177,7 +217,7 @@ acceptance("Post inline mentions as an anonymous user", function () {
|
||||||
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"user status is shown"
|
"user status is shown"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -197,7 +237,7 @@ acceptance("Post inline mentions as an anonymous user", function () {
|
||||||
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
await visit(`/t/lorem-ipsum-dolor-sit-amet/${topicId}`);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".topic-post .cooked .mention .user-status"),
|
exists(".topic-post .cooked .mention .user-status-message"),
|
||||||
"user status is shown"
|
"user status is shown"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -475,10 +475,17 @@ html.composer-open {
|
||||||
color: var(--primary-high);
|
color: var(--primary-high);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-description {
|
.user-status-message {
|
||||||
@include ellipsis;
|
display: flex;
|
||||||
font-size: var(--font-down-2);
|
align-items: center;
|
||||||
color: var(--primary-high);
|
gap: 0.25em;
|
||||||
|
|
||||||
|
.user-status-message-description {
|
||||||
|
@include ellipsis;
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
color: var(--primary-high);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.relative-date {
|
.relative-date {
|
||||||
|
|
|
@ -20,6 +20,11 @@ import { isPresent } from "@ember/utils";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import User from "discourse/models/user";
|
import User from "discourse/models/user";
|
||||||
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
|
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
|
||||||
|
import {
|
||||||
|
destroyTippyInstances,
|
||||||
|
initUserStatusHtml,
|
||||||
|
renderUserStatusHtml,
|
||||||
|
} from "discourse/lib/user-status-on-autocomplete";
|
||||||
|
|
||||||
export default class ChatComposer extends Component {
|
export default class ChatComposer extends Component {
|
||||||
@service capabilities;
|
@service capabilities;
|
||||||
|
@ -409,6 +414,7 @@ export default class ChatComposer extends Component {
|
||||||
return obj.username || obj.name;
|
return obj.username || obj.name;
|
||||||
},
|
},
|
||||||
dataSource: (term) => {
|
dataSource: (term) => {
|
||||||
|
destroyTippyInstances();
|
||||||
return userSearch({ term, includeGroups: true }).then((result) => {
|
return userSearch({ term, includeGroups: true }).then((result) => {
|
||||||
if (result?.users?.length > 0) {
|
if (result?.users?.length > 0) {
|
||||||
const presentUserNames =
|
const presentUserNames =
|
||||||
|
@ -418,16 +424,21 @@ export default class ChatComposer extends Component {
|
||||||
user.cssClasses = "is-online";
|
user.cssClasses = "is-online";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
initUserStatusHtml(result.users);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onRender: (options) => {
|
||||||
|
renderUserStatusHtml(options);
|
||||||
|
},
|
||||||
afterComplete: (text, event) => {
|
afterComplete: (text, event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.composer.textarea.value = text;
|
this.composer.textarea.value = text;
|
||||||
this.composer.focus();
|
this.composer.focus();
|
||||||
this.captureMentions();
|
this.captureMentions();
|
||||||
},
|
},
|
||||||
|
onClose: destroyTippyInstances,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-m
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
let _chatMessageDecorators = [];
|
let _chatMessageDecorators = [];
|
||||||
|
let _tippyInstances = [];
|
||||||
|
|
||||||
export function addChatMessageDecorator(decorator) {
|
export function addChatMessageDecorator(decorator) {
|
||||||
_chatMessageDecorators.push(decorator);
|
_chatMessageDecorators.push(decorator);
|
||||||
|
@ -136,6 +137,13 @@ export default class ChatMessage extends Component {
|
||||||
this.#teardownMentionedUsers();
|
this.#teardownMentionedUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#destroyTippyInstances() {
|
||||||
|
_tippyInstances.forEach((instance) => {
|
||||||
|
instance.destroy();
|
||||||
|
});
|
||||||
|
_tippyInstances = [];
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
refreshStatusOnMentions() {
|
refreshStatusOnMentions() {
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
|
@ -146,7 +154,7 @@ export default class ChatMessage extends Component {
|
||||||
);
|
);
|
||||||
|
|
||||||
mentions.forEach((mention) => {
|
mentions.forEach((mention) => {
|
||||||
updateUserStatusOnMention(mention, user.status, this.currentUser);
|
updateUserStatusOnMention(mention, user.status, _tippyInstances);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -402,5 +410,6 @@ export default class ChatMessage extends Component {
|
||||||
user.stopTrackingStatus();
|
user.stopTrackingStatus();
|
||||||
user.off("status-changed", this, "refreshStatusOnMentions");
|
user.off("status-changed", this, "refreshStatusOnMentions");
|
||||||
});
|
});
|
||||||
|
this.#destroyTippyInstances();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,15 +227,19 @@ export default {
|
||||||
return this.channel.chatable.users.length === 1;
|
return this.channel.chatable.users.length === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get contentComponentArgs() {
|
||||||
|
return this.channel.chatable.users[0].get("status");
|
||||||
|
}
|
||||||
|
|
||||||
|
get contentComponent() {
|
||||||
|
return "user-status-message";
|
||||||
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
const username = this.channel.escapedTitle.replaceAll("@", "");
|
const username = this.channel.escapedTitle.replaceAll("@", "");
|
||||||
if (this.oneOnOneMessage) {
|
if (this.oneOnOneMessage) {
|
||||||
const status = this.channel.chatable.users[0].get("status");
|
|
||||||
const statusHtml = status ? this._userStatusHtml(status) : "";
|
|
||||||
return htmlSafe(
|
return htmlSafe(
|
||||||
`${escapeExpression(
|
`${escapeExpression(username)}${decorateUsername(
|
||||||
username
|
|
||||||
)}${statusHtml} ${decorateUsername(
|
|
||||||
escapeExpression(username)
|
escapeExpression(username)
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
@ -307,14 +311,6 @@ export default {
|
||||||
return I18n.t("chat.direct_messages.leave");
|
return I18n.t("chat.direct_messages.leave");
|
||||||
}
|
}
|
||||||
|
|
||||||
_userStatusHtml(status) {
|
|
||||||
const emoji = escapeExpression(`:${status.emoji}:`);
|
|
||||||
const title = this._userStatusTitle(status);
|
|
||||||
return `<span class="user-status">${emojiUnescape(emoji, {
|
|
||||||
title,
|
|
||||||
})}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_userStatusTitle(status) {
|
_userStatusTitle(status) {
|
||||||
let title = `${escapeExpression(status.description)}`;
|
let title = `${escapeExpression(status.description)}`;
|
||||||
|
|
||||||
|
|
|
@ -181,10 +181,10 @@ RSpec.describe "Chat channel", type: :system do
|
||||||
chat.visit_channel(channel_1)
|
chat.visit_channel(channel_1)
|
||||||
|
|
||||||
expect(page).to have_selector(
|
expect(page).to have_selector(
|
||||||
".mention .user-status[title='#{current_user.user_status.description}']",
|
".mention .user-status-message img[alt='#{current_user.user_status.emoji}']",
|
||||||
)
|
)
|
||||||
expect(page).to have_selector(
|
expect(page).to have_selector(
|
||||||
".mention .user-status[title='#{other_user.user_status.description}']",
|
".mention .user-status-message img[alt='#{other_user.user_status.emoji}']",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -146,7 +146,7 @@ RSpec.describe "Sidebar navigation menu", type: :system do
|
||||||
visit("/")
|
visit("/")
|
||||||
|
|
||||||
expect(sidebar_page.dms_section.find("a.sidebar-section-link:nth-child(1)")).to have_css(
|
expect(sidebar_page.dms_section.find("a.sidebar-section-link:nth-child(1)")).to have_css(
|
||||||
".user-status",
|
".user-status-message",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,8 +20,8 @@ RSpec.describe "User status | sidebar", type: :system do
|
||||||
|
|
||||||
visit("/")
|
visit("/")
|
||||||
|
|
||||||
expect(find(".user-status .emoji")["title"]).to eq("online")
|
expect(find(".user-status-message .emoji")["alt"]).to eq("heart")
|
||||||
expect(find(".user-status .emoji")["src"]).to include("heart")
|
expect(find(".user-status-message .emoji")["src"]).to include("heart")
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when changing status" do
|
context "when changing status" do
|
||||||
|
@ -31,8 +31,8 @@ RSpec.describe "User status | sidebar", type: :system do
|
||||||
visit("/")
|
visit("/")
|
||||||
current_user.set_status!("offline", "tooth")
|
current_user.set_status!("offline", "tooth")
|
||||||
|
|
||||||
expect(page).to have_css('.user-status .emoji[title="offline"]')
|
expect(page).to have_css('.user-status-message .emoji[alt="tooth"]')
|
||||||
expect(find(".user-status .emoji")["src"]).to include("tooth")
|
expect(find(".user-status-message .emoji")["src"]).to include("tooth")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ RSpec.describe "User status | sidebar", type: :system do
|
||||||
visit("/")
|
visit("/")
|
||||||
current_user.clear_status!
|
current_user.clear_status!
|
||||||
|
|
||||||
expect(page).to have_no_css(".user-status")
|
expect(page).to have_no_css(".user-status-message")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -102,6 +102,11 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
statusSelector(mentionedUser2.username),
|
statusSelector(mentionedUser2.username),
|
||||||
mentionedUser2.status
|
mentionedUser2.status
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser2.username),
|
||||||
|
mentionedUser2.status
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
skip("just posted messages | it updates status on mentions", async function (assert) {
|
skip("just posted messages | it updates status on mentions", async function (assert) {
|
||||||
|
@ -115,6 +120,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
const selector = statusSelector(mentionedUser2.username);
|
const selector = statusSelector(mentionedUser2.username);
|
||||||
await waitFor(selector);
|
await waitFor(selector);
|
||||||
assertStatusIsRendered(assert, selector, newStatus);
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
await assertStatusTooltipIsRendered(assert, selector, newStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
skip("just posted messages | it deletes status on mentions", async function (assert) {
|
skip("just posted messages | it deletes status on mentions", async function (assert) {
|
||||||
|
@ -144,6 +150,11 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
statusSelector(mentionedUser3.username),
|
statusSelector(mentionedUser3.username),
|
||||||
mentionedUser3.status
|
mentionedUser3.status
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser3.username),
|
||||||
|
mentionedUser3.status
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
skip("edited messages | it updates status on mentions", async function (assert) {
|
skip("edited messages | it updates status on mentions", async function (assert) {
|
||||||
|
@ -160,6 +171,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
const selector = statusSelector(mentionedUser3.username);
|
const selector = statusSelector(mentionedUser3.username);
|
||||||
await waitFor(selector);
|
await waitFor(selector);
|
||||||
assertStatusIsRendered(assert, selector, newStatus);
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
await assertStatusTooltipIsRendered(assert, selector, newStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
skip("edited messages | it deletes status on mentions", async function (assert) {
|
skip("edited messages | it deletes status on mentions", async function (assert) {
|
||||||
|
@ -190,6 +202,11 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
statusSelector(mentionedUser1.username),
|
statusSelector(mentionedUser1.username),
|
||||||
mentionedUser1.status
|
mentionedUser1.status
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser1.username),
|
||||||
|
mentionedUser1.status
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("deleted messages | it updates status on mentions", async function (assert) {
|
test("deleted messages | it updates status on mentions", async function (assert) {
|
||||||
|
@ -205,6 +222,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
const selector = statusSelector(mentionedUser1.username);
|
const selector = statusSelector(mentionedUser1.username);
|
||||||
await waitFor(selector);
|
await waitFor(selector);
|
||||||
assertStatusIsRendered(assert, selector, newStatus);
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
await assertStatusTooltipIsRendered(assert, selector, newStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("deleted messages | it deletes status on mentions", async function (assert) {
|
test("deleted messages | it deletes status on mentions", async function (assert) {
|
||||||
|
@ -233,6 +251,11 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
statusSelector(mentionedUser1.username),
|
statusSelector(mentionedUser1.username),
|
||||||
mentionedUser1.status
|
mentionedUser1.status
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser1.username),
|
||||||
|
mentionedUser1.status
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("restored messages | it updates status on mentions", async function (assert) {
|
test("restored messages | it updates status on mentions", async function (assert) {
|
||||||
|
@ -248,6 +271,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
const selector = statusSelector(mentionedUser1.username);
|
const selector = statusSelector(mentionedUser1.username);
|
||||||
await waitFor(selector);
|
await waitFor(selector);
|
||||||
assertStatusIsRendered(assert, selector, newStatus);
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
await assertStatusTooltipIsRendered(assert, selector, newStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("restored messages | it deletes status on mentions", async function (assert) {
|
test("restored messages | it deletes status on mentions", async function (assert) {
|
||||||
|
@ -269,11 +293,6 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
assert
|
assert
|
||||||
.dom(selector)
|
.dom(selector)
|
||||||
.exists("status is rendered")
|
.exists("status is rendered")
|
||||||
.hasAttribute(
|
|
||||||
"title",
|
|
||||||
status.description,
|
|
||||||
"status description is updated"
|
|
||||||
)
|
|
||||||
.hasAttribute(
|
.hasAttribute(
|
||||||
"src",
|
"src",
|
||||||
new RegExp(`${status.emoji}.png`),
|
new RegExp(`${status.emoji}.png`),
|
||||||
|
@ -281,6 +300,27 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function assertStatusTooltipIsRendered(assert, selector, status) {
|
||||||
|
await triggerEvent(selector, "mouseenter");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
document
|
||||||
|
.querySelector(".user-status-tooltip-description")
|
||||||
|
.textContent.trim(),
|
||||||
|
status.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
document.querySelector(
|
||||||
|
`.user-status-message-tooltip img[alt='${status.emoji}']`
|
||||||
|
),
|
||||||
|
"status emoji is correct"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerEvent(selector, "mouseleave");
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteMessage(messageSelector) {
|
async function deleteMessage(messageSelector) {
|
||||||
await triggerEvent(query(messageSelector), "mouseenter");
|
await triggerEvent(query(messageSelector), "mouseenter");
|
||||||
await click(".more-buttons .select-kit-header-wrapper");
|
await click(".more-buttons .select-kit-header-wrapper");
|
||||||
|
@ -340,6 +380,6 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusSelector(username) {
|
function statusSelector(username) {
|
||||||
return `.mention[href='/u/${username}'] .user-status`;
|
return `.mention[href='/u/${username}'] .user-status-message img`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import hbs from "htmlbars-inline-precompile";
|
import hbs from "htmlbars-inline-precompile";
|
||||||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
import { render, waitFor } from "@ember/test-helpers";
|
import { render, triggerEvent, waitFor } from "@ember/test-helpers";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import pretender, { OK } from "discourse/tests/helpers/create-pretender";
|
import pretender, { OK } from "discourse/tests/helpers/create-pretender";
|
||||||
import { publishToMessageBus } from "discourse/tests/helpers/qunit-helpers";
|
import { publishToMessageBus } from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
@ -76,6 +76,11 @@ module(
|
||||||
statusSelector(mentionedUser.username),
|
statusSelector(mentionedUser.username),
|
||||||
mentionedUser.status
|
mentionedUser.status
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser.username),
|
||||||
|
mentionedUser.status
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("it updates status on mentions", async function (assert) {
|
test("it updates status on mentions", async function (assert) {
|
||||||
|
@ -97,6 +102,11 @@ module(
|
||||||
statusSelector(mentionedUser.username),
|
statusSelector(mentionedUser.username),
|
||||||
newStatus
|
newStatus
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser.username),
|
||||||
|
newStatus
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("it deletes status on mentions", async function (assert) {
|
test("it deletes status on mentions", async function (assert) {
|
||||||
|
@ -121,6 +131,11 @@ module(
|
||||||
statusSelector(mentionedUser2.username),
|
statusSelector(mentionedUser2.username),
|
||||||
mentionedUser2.status
|
mentionedUser2.status
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser2.username),
|
||||||
|
mentionedUser2.status
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("it updates status on mentions on messages that came from Message Bus", async function (assert) {
|
test("it updates status on mentions on messages that came from Message Bus", async function (assert) {
|
||||||
|
@ -142,6 +157,11 @@ module(
|
||||||
statusSelector(mentionedUser2.username),
|
statusSelector(mentionedUser2.username),
|
||||||
newStatus
|
newStatus
|
||||||
);
|
);
|
||||||
|
await assertStatusTooltipIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser2.username),
|
||||||
|
newStatus
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("it deletes status on mentions on messages that came from Message Bus", async function (assert) {
|
test("it deletes status on mentions on messages that came from Message Bus", async function (assert) {
|
||||||
|
@ -161,11 +181,6 @@ module(
|
||||||
assert
|
assert
|
||||||
.dom(selector)
|
.dom(selector)
|
||||||
.exists("status is rendered")
|
.exists("status is rendered")
|
||||||
.hasAttribute(
|
|
||||||
"title",
|
|
||||||
status.description,
|
|
||||||
"status description is updated"
|
|
||||||
)
|
|
||||||
.hasAttribute(
|
.hasAttribute(
|
||||||
"src",
|
"src",
|
||||||
new RegExp(`${status.emoji}.png`),
|
new RegExp(`${status.emoji}.png`),
|
||||||
|
@ -173,6 +188,27 @@ module(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function assertStatusTooltipIsRendered(assert, selector, status) {
|
||||||
|
await triggerEvent(selector, "mouseenter");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
document
|
||||||
|
.querySelector(".user-status-tooltip-description")
|
||||||
|
.textContent.trim(),
|
||||||
|
status.description,
|
||||||
|
"status description is correct"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
document.querySelector(
|
||||||
|
`.user-status-message-tooltip img[alt='${status.emoji}']`
|
||||||
|
),
|
||||||
|
"status emoji is correct"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerEvent(selector, "mouseleave");
|
||||||
|
}
|
||||||
|
|
||||||
async function receiveChatMessageViaMessageBus() {
|
async function receiveChatMessageViaMessageBus() {
|
||||||
await publishToMessageBus(`/chat/${channelId}`, {
|
await publishToMessageBus(`/chat/${channelId}`, {
|
||||||
chat_message: {
|
chat_message: {
|
||||||
|
@ -193,7 +229,7 @@ module(
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusSelector(username) {
|
function statusSelector(username) {
|
||||||
return `.mention[href='/u/${username}'] .user-status`;
|
return `.mention[href='/u/${username}'] .user-status-message img`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue