REFACTOR: <ChatMessage> component (#22172)
- Moves `<ChatMessageInfo />` to `<Chat::Message::Info />` - Moves `<ChatMessageAvatar />` to `<Chat::Message::Avatar />` - Moves `<ChatMessageLeftGutter />` to `<Chat::Message::LeftGutter />`, adds tests - Creates `<Chat::Message::Error />` - Creates `<Chat::Message::MentionWarning />`, adds tests and a styleguide - Creates a model for ChatMessageMentionWarning, adds fabricator for it - Keeps the enter/leave viewport logic inside the `<ChatMessage />` component instead of bubbling it to the channel and thread components - Adds a scale animation when clicking a reaction - Creates `chat/later-fn` modifier which accepts a function and a delay. It allows to call a function Xms after a component has been inserted, it's useful for animations. - Moves css code out of chat-message into relevant files - Deletes unused code <!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
This commit is contained in:
parent
626eda4c91
commit
cbb9396353
|
@ -42,8 +42,6 @@
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
@message={{message}}
|
@message={{message}}
|
||||||
@resendStagedMessage={{this.resendStagedMessage}}
|
@resendStagedMessage={{this.resendStagedMessage}}
|
||||||
@messageDidEnterViewport={{this.messageDidEnterViewport}}
|
|
||||||
@messageDidLeaveViewport={{this.messageDidLeaveViewport}}
|
|
||||||
@context="channel"
|
@context="channel"
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -491,16 +491,6 @@ export default class ChatLivePane extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
messageDidEnterViewport(message) {
|
|
||||||
message.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
messageDidLeaveViewport(message) {
|
|
||||||
message.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@debounce(READ_INTERVAL_MS)
|
@debounce(READ_INTERVAL_MS)
|
||||||
updateLastReadMessage() {
|
updateLastReadMessage() {
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
<div
|
|
||||||
class="chat-message-info"
|
|
||||||
{{did-insert this.trackStatus}}
|
|
||||||
{{will-destroy this.stopTrackingStatus}}
|
|
||||||
>
|
|
||||||
{{#if @message.chatWebhookEvent}}
|
|
||||||
{{#if @message.chatWebhookEvent.username}}
|
|
||||||
<span
|
|
||||||
class={{concat-class
|
|
||||||
"chat-message-info__username"
|
|
||||||
this.usernameClasses
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{{@message.chatWebhookEvent.username}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<span class="chat-message-info__bot-indicator">
|
|
||||||
{{i18n "chat.bot"}}
|
|
||||||
</span>
|
|
||||||
{{else}}
|
|
||||||
<span
|
|
||||||
role="button"
|
|
||||||
class={{concat-class
|
|
||||||
"chat-message-info__username"
|
|
||||||
this.usernameClasses
|
|
||||||
"clickable"
|
|
||||||
}}
|
|
||||||
data-user-card={{@message.user.username}}
|
|
||||||
>
|
|
||||||
<span class="chat-message-info__username__name">{{this.name}}</span>
|
|
||||||
{{#if this.showStatus}}
|
|
||||||
<div class="chat-message-info__status">
|
|
||||||
<UserStatusMessage @status={{@message.user.status}} />
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<span class="chat-message-info__date">
|
|
||||||
{{format-chat-date @message}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{{#if @message.bookmark}}
|
|
||||||
<span class="chat-message-info__bookmark">
|
|
||||||
<BookmarkIcon @bookmark={{@message.bookmark}} />
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.isFlagged}}
|
|
||||||
<span class="chat-message-info__flag">
|
|
||||||
{{#if @message.reviewableId}}
|
|
||||||
<LinkTo @route="review.show" @model={{@message.reviewableId}}>
|
|
||||||
{{d-icon "flag" title="chat.flagged"}}
|
|
||||||
</LinkTo>
|
|
||||||
{{else}}
|
|
||||||
{{d-icon "flag" title="chat.you_flagged"}}
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
|
@ -37,10 +37,8 @@
|
||||||
this.onLongPressCancel
|
this.onLongPressCancel
|
||||||
}}
|
}}
|
||||||
{{chat/track-message
|
{{chat/track-message
|
||||||
(hash
|
(fn (mut @message.visible) true)
|
||||||
didEnterViewport=(fn @messageDidEnterViewport @message)
|
(fn (mut @message.visible) false)
|
||||||
didLeaveViewport=(fn @messageDidLeaveViewport @message)
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{{#if this.show}}
|
{{#if this.show}}
|
||||||
|
@ -54,7 +52,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.deletedAndCollapsed}}
|
{{#if this.deletedAndCollapsed}}
|
||||||
<div class="chat-message-text chat-message-deleted">
|
<div class="chat-message-text -deleted">
|
||||||
<DButton
|
<DButton
|
||||||
@class="btn-flat chat-message-expand"
|
@class="btn-flat chat-message-expand"
|
||||||
@action={{this.expand}}
|
@action={{this.expand}}
|
||||||
|
@ -62,7 +60,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{else if this.hiddenAndCollapsed}}
|
{{else if this.hiddenAndCollapsed}}
|
||||||
<div class="chat-message-hidden">
|
<div class="chat-message-text -hidden">
|
||||||
<DButton
|
<DButton
|
||||||
@class="btn-flat chat-message-expand"
|
@class="btn-flat chat-message-expand"
|
||||||
@action={{this.expand}}
|
@action={{this.expand}}
|
||||||
|
@ -76,15 +74,16 @@
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if this.hideUserInfo}}
|
{{#if this.hideUserInfo}}
|
||||||
<ChatMessageLeftGutter @message={{@message}} />
|
<Chat::Message::LeftGutter @message={{@message}} />
|
||||||
{{else}}
|
{{else}}
|
||||||
<ChatMessageAvatar @message={{@message}} />
|
<Chat::Message::Avatar @message={{@message}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="chat-message-content">
|
<div class="chat-message-content">
|
||||||
{{#unless this.hideUserInfo}}
|
<Chat::Message::Info
|
||||||
<ChatMessageInfo @message={{@message}} />
|
@message={{@message}}
|
||||||
{{/unless}}
|
@show={{not this.hideUserInfo}}
|
||||||
|
/>
|
||||||
|
|
||||||
<ChatMessageText
|
<ChatMessageText
|
||||||
@cooked={{@message.cooked}}
|
@cooked={{@message.cooked}}
|
||||||
|
@ -93,12 +92,6 @@
|
||||||
>
|
>
|
||||||
{{#if @message.reactions.length}}
|
{{#if @message.reactions.length}}
|
||||||
<div class="chat-message-reaction-list">
|
<div class="chat-message-reaction-list">
|
||||||
{{#if this.reactionLabel}}
|
|
||||||
<div class="reaction-users-list">
|
|
||||||
{{replace-emoji this.reactionLabel}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#each @message.reactions as |reaction|}}
|
{{#each @message.reactions as |reaction|}}
|
||||||
<ChatMessageReaction
|
<ChatMessageReaction
|
||||||
@reaction={{reaction}}
|
@reaction={{reaction}}
|
||||||
|
@ -108,92 +101,24 @@
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{#if this.chat.userCanInteractWithChat}}
|
{{#if this.shouldRenderOpenEmojiPickerButton}}
|
||||||
{{#unless this.site.mobileView}}
|
<DButton
|
||||||
<DButton
|
@class="chat-message-react-btn"
|
||||||
@class="chat-message-react-btn"
|
@action={{this.messageInteractor.openEmojiPicker}}
|
||||||
@action={{this.messageInteractor.openEmojiPicker}}
|
@icon="discourse-emojis"
|
||||||
@icon="discourse-emojis"
|
@title="chat.react"
|
||||||
@title="chat.react"
|
@forwardEvent={{true}}
|
||||||
@forwardEvent={{true}}
|
/>
|
||||||
/>
|
|
||||||
{{/unless}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</ChatMessageText>
|
</ChatMessageText>
|
||||||
|
|
||||||
{{#if @message.error}}
|
<Chat::Message::Error
|
||||||
<div class="chat-send-error">
|
@message={{@message}}
|
||||||
{{#if (eq @message.error "network_error")}}
|
@onRetry={{@resendStagedMessage}}
|
||||||
<DButton
|
/>
|
||||||
class="retry-staged-message-btn"
|
<Chat::Message::MentionWarning @message={{@message}} />
|
||||||
@action={{fn @resendStagedMessage @message}}
|
|
||||||
@icon="exclamation-circle"
|
|
||||||
>
|
|
||||||
<span class="retry-staged-message-btn__title">
|
|
||||||
{{i18n "chat.retry_staged_message.title"}}
|
|
||||||
</span>
|
|
||||||
<span class="retry-staged-message-btn__action">
|
|
||||||
{{i18n "chat.retry_staged_message.action"}}
|
|
||||||
</span>
|
|
||||||
</DButton>
|
|
||||||
{{else}}
|
|
||||||
{{@message.error}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.mentionWarning}}
|
|
||||||
<div class="alert alert-info chat-message-mention-warning">
|
|
||||||
{{#if this.mentionWarning.invitation_sent}}
|
|
||||||
{{d-icon "check"}}
|
|
||||||
<span>
|
|
||||||
{{i18n
|
|
||||||
"chat.mention_warning.invitations_sent"
|
|
||||||
count=this.mentionWarning.without_membership.length
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
{{else}}
|
|
||||||
<FlatButton
|
|
||||||
@class="dismiss-mention-warning"
|
|
||||||
@title="chat.mention_warning.dismiss"
|
|
||||||
@action={{this.dismissMentionWarning}}
|
|
||||||
@icon="times"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{{#if this.mentionWarning.cannot_see}}
|
|
||||||
<p class="warning-item cannot-see">
|
|
||||||
{{this.mentionedCannotSeeText}}
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.mentionWarning.without_membership}}
|
|
||||||
<p class="warning-item without-membership">
|
|
||||||
<span>{{this.mentionedWithoutMembershipText}}</span>
|
|
||||||
<a
|
|
||||||
class="invite-link"
|
|
||||||
href
|
|
||||||
onclick={{this.inviteMentioned}}
|
|
||||||
>
|
|
||||||
{{i18n "chat.mention_warning.invite"}}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
{{#if this.mentionWarning.group_mentions_disabled}}
|
|
||||||
<p class="warning-item">
|
|
||||||
{{this.groupsWithDisabledMentions}}
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.mentionWarning.groups_with_too_many_members}}
|
|
||||||
<p class="warning-item">
|
|
||||||
{{this.groupsWithTooManyMembers}}
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.showThreadIndicator}}
|
{{#if this.showThreadIndicator}}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { action } from "@ember/object";
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import optionalService from "discourse/lib/optional-service";
|
import optionalService from "discourse/lib/optional-service";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import { cancel, schedule } from "@ember/runloop";
|
import { cancel, schedule } from "@ember/runloop";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import discourseLater from "discourse-common/lib/later";
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
@ -101,6 +100,10 @@ export default class ChatMessage extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get shouldRenderOpenEmojiPickerButton() {
|
||||||
|
return this.chat.userCanInteractWithChat && this.site.desktopView;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
expand() {
|
expand() {
|
||||||
const recursiveExpand = (message) => {
|
const recursiveExpand = (message) => {
|
||||||
|
@ -386,82 +389,6 @@ export default class ChatMessage extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get mentionWarning() {
|
|
||||||
return this.args.message.mentionWarning;
|
|
||||||
}
|
|
||||||
|
|
||||||
get mentionedCannotSeeText() {
|
|
||||||
return this._findTranslatedWarning(
|
|
||||||
"chat.mention_warning.cannot_see",
|
|
||||||
"chat.mention_warning.cannot_see_multiple",
|
|
||||||
{
|
|
||||||
username: this.mentionWarning?.cannot_see?.[0]?.username,
|
|
||||||
count: this.mentionWarning?.cannot_see?.length,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get mentionedWithoutMembershipText() {
|
|
||||||
return this._findTranslatedWarning(
|
|
||||||
"chat.mention_warning.without_membership",
|
|
||||||
"chat.mention_warning.without_membership_multiple",
|
|
||||||
{
|
|
||||||
username: this.mentionWarning?.without_membership?.[0]?.username,
|
|
||||||
count: this.mentionWarning?.without_membership?.length,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get groupsWithDisabledMentions() {
|
|
||||||
return this._findTranslatedWarning(
|
|
||||||
"chat.mention_warning.group_mentions_disabled",
|
|
||||||
"chat.mention_warning.group_mentions_disabled_multiple",
|
|
||||||
{
|
|
||||||
group_name: this.mentionWarning?.group_mentions_disabled?.[0],
|
|
||||||
count: this.mentionWarning?.group_mentions_disabled?.length,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get groupsWithTooManyMembers() {
|
|
||||||
return this._findTranslatedWarning(
|
|
||||||
"chat.mention_warning.too_many_members",
|
|
||||||
"chat.mention_warning.too_many_members_multiple",
|
|
||||||
{
|
|
||||||
group_name: this.mentionWarning.groups_with_too_many_members?.[0],
|
|
||||||
count: this.mentionWarning.groups_with_too_many_members?.length,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_findTranslatedWarning(oneKey, multipleKey, args) {
|
|
||||||
const translationKey = args.count === 1 ? oneKey : multipleKey;
|
|
||||||
args.count--;
|
|
||||||
return I18n.t(translationKey, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
inviteMentioned() {
|
|
||||||
const userIds = this.mentionWarning.without_membership.mapBy("id");
|
|
||||||
|
|
||||||
ajax(`/chat/${this.args.message.channel.id}/invite`, {
|
|
||||||
method: "PUT",
|
|
||||||
data: { user_ids: userIds, chat_message_id: this.args.message.id },
|
|
||||||
}).then(() => {
|
|
||||||
this.args.message.mentionWarning.set("invitationSent", true);
|
|
||||||
this._invitationSentTimer = discourseLater(() => {
|
|
||||||
this.dismissMentionWarning();
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
dismissMentionWarning() {
|
|
||||||
this.args.message.mentionWarning = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#teardownMentionedUsers() {
|
#teardownMentionedUsers() {
|
||||||
this.args.message.mentionedUsers.forEach((user) => {
|
this.args.message.mentionedUsers.forEach((user) => {
|
||||||
user.stopTrackingStatus();
|
user.stopTrackingStatus();
|
||||||
|
|
|
@ -27,8 +27,6 @@
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
@message={{message}}
|
@message={{message}}
|
||||||
@resendStagedMessage={{this.resendStagedMessage}}
|
@resendStagedMessage={{this.resendStagedMessage}}
|
||||||
@messageDidEnterViewport={{this.messageDidEnterViewport}}
|
|
||||||
@messageDidLeaveViewport={{this.messageDidLeaveViewport}}
|
|
||||||
@context="thread"
|
@context="thread"
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -348,16 +348,6 @@ export default class ChatThreadPanel extends Component {
|
||||||
@action
|
@action
|
||||||
resendStagedMessage() {}
|
resendStagedMessage() {}
|
||||||
|
|
||||||
@action
|
|
||||||
messageDidEnterViewport(message) {
|
|
||||||
message.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
messageDidLeaveViewport(message) {
|
|
||||||
message.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#handleErrors(error) {
|
#handleErrors(error) {
|
||||||
switch (error?.jqXHR?.status) {
|
switch (error?.jqXHR?.status) {
|
||||||
case 429:
|
case 429:
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{{#if @message.error}}
|
||||||
|
<div class="chat-message-error">
|
||||||
|
{{#if (eq @message.error "network_error")}}
|
||||||
|
<DButton
|
||||||
|
class="chat-message-error__retry-btn"
|
||||||
|
@action={{fn @onRetry @message}}
|
||||||
|
@icon="exclamation-circle"
|
||||||
|
>
|
||||||
|
<span class="chat-message-error__retry-btn-title">
|
||||||
|
{{i18n "chat.retry_staged_message.title"}}
|
||||||
|
</span>
|
||||||
|
<span class="chat-message-error__retry-btn-action">
|
||||||
|
{{i18n "chat.retry_staged_message.action"}}
|
||||||
|
</span>
|
||||||
|
</DButton>
|
||||||
|
{{else}}
|
||||||
|
{{@message.error}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,63 @@
|
||||||
|
{{#if @show}}
|
||||||
|
<div
|
||||||
|
class="chat-message-info"
|
||||||
|
{{did-insert this.trackStatus}}
|
||||||
|
{{will-destroy this.stopTrackingStatus}}
|
||||||
|
>
|
||||||
|
{{#if @message.chatWebhookEvent}}
|
||||||
|
{{#if @message.chatWebhookEvent.username}}
|
||||||
|
<span
|
||||||
|
class={{concat-class
|
||||||
|
"chat-message-info__username"
|
||||||
|
this.usernameClasses
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{{@message.chatWebhookEvent.username}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<span class="chat-message-info__bot-indicator">
|
||||||
|
{{i18n "chat.bot"}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<span
|
||||||
|
role="button"
|
||||||
|
class={{concat-class
|
||||||
|
"chat-message-info__username"
|
||||||
|
this.usernameClasses
|
||||||
|
"clickable"
|
||||||
|
}}
|
||||||
|
data-user-card={{@message.user.username}}
|
||||||
|
>
|
||||||
|
<span class="chat-message-info__username__name">{{this.name}}</span>
|
||||||
|
{{#if this.showStatus}}
|
||||||
|
<div class="chat-message-info__status">
|
||||||
|
<UserStatusMessage @status={{@message.user.status}} />
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<span class="chat-message-info__date">
|
||||||
|
{{format-chat-date @message}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{#if @message.bookmark}}
|
||||||
|
<span class="chat-message-info__bookmark">
|
||||||
|
<BookmarkIcon @bookmark={{@message.bookmark}} />
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.isFlagged}}
|
||||||
|
<span class="chat-message-info__flag">
|
||||||
|
{{#if @message.reviewableId}}
|
||||||
|
<LinkTo @route="review.show" @model={{@message.reviewableId}}>
|
||||||
|
{{d-icon "flag" title="chat.flagged"}}
|
||||||
|
</LinkTo>
|
||||||
|
{{else}}
|
||||||
|
{{d-icon "flag" title="chat.you_flagged"}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,56 @@
|
||||||
|
{{#if this.shouldRender}}
|
||||||
|
<div class="chat-message-mention-warning alert alert-info">
|
||||||
|
{{#if this.mentionWarning.invitationSent}}
|
||||||
|
<span
|
||||||
|
class="chat-message-mention-warning__invitation-sent"
|
||||||
|
{{chat/later-fn this.onDismissInvitationSent 3000}}
|
||||||
|
>
|
||||||
|
{{d-icon "check"}}
|
||||||
|
<span>
|
||||||
|
{{i18n
|
||||||
|
"chat.mention_warning.invitations_sent"
|
||||||
|
count=this.mentionWarning.withoutMembership.length
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<DButton
|
||||||
|
class="chat-message-mention-warning__dismiss-btn btn-flat"
|
||||||
|
title={{i18n "chat.mention_warning.dismiss"}}
|
||||||
|
@action={{this.onDismissMentionWarning}}
|
||||||
|
@icon="times"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#if this.mentionWarning.cannotSee}}
|
||||||
|
<p class="chat-message-mention-warning__text -cannot-see">
|
||||||
|
{{this.mentionedCannotSeeText}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.mentionWarning.withoutMembership}}
|
||||||
|
<p class="chat-message-mention-warning__text -without-membership">
|
||||||
|
<span>{{this.mentionedWithoutMembershipText}}</span>
|
||||||
|
<a href {{on "click" this.onSendInvite bubbles=false}}>
|
||||||
|
{{i18n "chat.mention_warning.invite"}}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.mentionWarning.groupWithMentionsDisabled}}
|
||||||
|
<p
|
||||||
|
class="chat-message-mention-warning__text -groups-with-mentions-disabled"
|
||||||
|
>
|
||||||
|
{{this.groupsWithDisabledMentions}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.mentionWarning.groupsWithTooManyMembers}}
|
||||||
|
<p
|
||||||
|
class="chat-message-mention-warning__text -groups-with-too-many-members"
|
||||||
|
>
|
||||||
|
{{this.groupsWithTooManyMembers}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,98 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default class ChatMessageMentionWarning extends Component {
|
||||||
|
@service("chat-api") api;
|
||||||
|
|
||||||
|
@action
|
||||||
|
async onSendInvite() {
|
||||||
|
const userIds = this.mentionWarning.withoutMembership.mapBy("id");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.invite(this.args.message.channel.id, userIds, {
|
||||||
|
messageId: this.args.message.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.mentionWarning.invitationSent = true;
|
||||||
|
} catch (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onDismissInvitationSent() {
|
||||||
|
this.mentionWarning.invitationSent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onDismissMentionWarning() {
|
||||||
|
this.args.message.mentionWarning = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shouldRender() {
|
||||||
|
return (
|
||||||
|
this.mentionWarning &&
|
||||||
|
(this.mentionWarning.groupWithMentionsDisabled?.length ||
|
||||||
|
this.mentionWarning.cannotSee?.length ||
|
||||||
|
this.mentionWarning.withoutMembership?.length ||
|
||||||
|
this.mentionWarning.groupsWithTooManyMembers?.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mentionWarning() {
|
||||||
|
return this.args.message.mentionWarning;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mentionedCannotSeeText() {
|
||||||
|
return this.#findTranslatedWarning(
|
||||||
|
"chat.mention_warning.cannot_see",
|
||||||
|
"chat.mention_warning.cannot_see_multiple",
|
||||||
|
{
|
||||||
|
username: this.mentionWarning?.cannotSee?.[0]?.username,
|
||||||
|
count: this.mentionWarning?.cannotSee?.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mentionedWithoutMembershipText() {
|
||||||
|
return this.#findTranslatedWarning(
|
||||||
|
"chat.mention_warning.without_membership",
|
||||||
|
"chat.mention_warning.without_membership_multiple",
|
||||||
|
{
|
||||||
|
username: this.mentionWarning?.withoutMembership?.[0]?.username,
|
||||||
|
count: this.mentionWarning?.withoutMembership?.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get groupsWithDisabledMentions() {
|
||||||
|
return this.#findTranslatedWarning(
|
||||||
|
"chat.mention_warning.group_mentions_disabled",
|
||||||
|
"chat.mention_warning.group_mentions_disabled_multiple",
|
||||||
|
{
|
||||||
|
group_name: this.mentionWarning?.groupWithMentionsDisabled?.[0],
|
||||||
|
count: this.mentionWarning?.groupWithMentionsDisabled?.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get groupsWithTooManyMembers() {
|
||||||
|
return this.#findTranslatedWarning(
|
||||||
|
"chat.mention_warning.too_many_members",
|
||||||
|
"chat.mention_warning.too_many_members_multiple",
|
||||||
|
{
|
||||||
|
group_name: this.mentionWarning.groupsWithTooManyMembers?.[0],
|
||||||
|
count: this.mentionWarning.groupsWithTooManyMembers?.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#findTranslatedWarning(oneKey, multipleKey, args) {
|
||||||
|
const translationKey = args.count === 1 ? oneKey : multipleKey;
|
||||||
|
args.count--;
|
||||||
|
return I18n.t(translationKey, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<StyleguideExample @title="<Chat::Message::MentionWarning>">
|
||||||
|
<Styleguide::Component>
|
||||||
|
<Chat::Message::MentionWarning @message={{this.message}} />
|
||||||
|
</Styleguide::Component>
|
||||||
|
<Styleguide::Controls::Row @name="Cannot see">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{gt this.message.mentionWarning.cannotSee.length 0}}
|
||||||
|
{{on "click" this.toggleCannotSee}}
|
||||||
|
/>
|
||||||
|
</Styleguide::Controls::Row>
|
||||||
|
<Styleguide::Controls::Row @name="Group with mentions disabled">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{gt
|
||||||
|
this.message.mentionWarning.groupWithMentionsDisabled.length
|
||||||
|
0
|
||||||
|
}}
|
||||||
|
{{on "click" this.toggleGroupWithMentionsDisabled}}
|
||||||
|
/>
|
||||||
|
</Styleguide::Controls::Row>
|
||||||
|
<Styleguide::Controls::Row @name="Group with too many members">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{gt
|
||||||
|
this.message.mentionWarning.groupsWithTooManyMembers.length
|
||||||
|
0
|
||||||
|
}}
|
||||||
|
{{on "click" this.toggleGroupsWithTooManyMembers}}
|
||||||
|
/>
|
||||||
|
</Styleguide::Controls::Row>
|
||||||
|
<Styleguide::Controls::Row @name="Without membership">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{gt this.message.mentionWarning.withoutMembership.length 0}}
|
||||||
|
{{on "click" this.toggleWithoutMembership}}
|
||||||
|
/>
|
||||||
|
</Styleguide::Controls::Row>
|
||||||
|
</StyleguideExample>
|
|
@ -0,0 +1,75 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
|
export default class ChatMessageMentionWarning extends Component {
|
||||||
|
@service currentUser;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.message = fabricators.message({ user: this.currentUser });
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleCannotSee() {
|
||||||
|
if (this.message.mentionWarning?.cannotSee) {
|
||||||
|
this.message.mentionWarning = null;
|
||||||
|
} else {
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
cannot_see: [fabricators.user({ username: "bob" })].map((u) => {
|
||||||
|
return { username: u.username, id: u.id };
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleGroupWithMentionsDisabled() {
|
||||||
|
if (this.message.mentionWarning?.groupWithMentionsDisabled) {
|
||||||
|
this.message.mentionWarning = null;
|
||||||
|
} else {
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
group_mentions_disabled: [fabricators.group()].mapBy("name"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleGroupsWithTooManyMembers() {
|
||||||
|
if (this.message.mentionWarning?.groupsWithTooManyMembers) {
|
||||||
|
this.message.mentionWarning = null;
|
||||||
|
} else {
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
groups_with_too_many_members: [
|
||||||
|
fabricators.group(),
|
||||||
|
fabricators.group({ name: "Moderators" }),
|
||||||
|
].mapBy("name"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@action
|
||||||
|
toggleWithoutMembership() {
|
||||||
|
if (this.message.mentionWarning?.withoutMembership) {
|
||||||
|
this.message.mentionWarning = null;
|
||||||
|
} else {
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
without_membership: [fabricators.user()].map((u) => {
|
||||||
|
return { username: u.username, id: u.id };
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,6 @@
|
||||||
<StyleguideExample @title="<ChatMessage>">
|
<StyleguideExample @title="<ChatMessage>">
|
||||||
<Styleguide::Component>
|
<Styleguide::Component>
|
||||||
<ChatMessage
|
<ChatMessage @message={{this.message}} @context="channel" />
|
||||||
@message={{this.message}}
|
|
||||||
@context="channel"
|
|
||||||
@messageDidEnterViewport={{(noop)}}
|
|
||||||
@messageDidLeaveViewport={{(noop)}}
|
|
||||||
/>
|
|
||||||
</Styleguide::Component>
|
</Styleguide::Component>
|
||||||
|
|
||||||
<Styleguide::Controls>
|
<Styleguide::Controls>
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
<Styleguide::ChatComposer />
|
<Styleguide::ChatComposer />
|
||||||
<Styleguide::ChatThreadListItem />
|
<Styleguide::ChatThreadListItem />
|
||||||
<Styleguide::ChatComposerMessageDetails />
|
<Styleguide::ChatComposerMessageDetails />
|
||||||
<Styleguide::ChatHeaderIcon />
|
<Styleguide::ChatHeaderIcon />
|
||||||
|
<Styleguide::ChatMessageMentionWarning />
|
|
@ -13,10 +13,12 @@ import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||||
import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
|
import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
|
||||||
import ChatThreadPreview from "discourse/plugins/chat/discourse/models/chat-thread-preview";
|
import ChatThreadPreview from "discourse/plugins/chat/discourse/models/chat-thread-preview";
|
||||||
import ChatDirectMessage from "discourse/plugins/chat/discourse/models/chat-direct-message";
|
import ChatDirectMessage from "discourse/plugins/chat/discourse/models/chat-direct-message";
|
||||||
|
import ChatMessageMentionWarning from "discourse/plugins/chat/discourse/models/chat-message-mention-warning";
|
||||||
import ChatMessageReaction from "discourse/plugins/chat/discourse/models/chat-message-reaction";
|
import ChatMessageReaction from "discourse/plugins/chat/discourse/models/chat-message-reaction";
|
||||||
import User from "discourse/models/user";
|
import User from "discourse/models/user";
|
||||||
import Bookmark from "discourse/models/bookmark";
|
import Bookmark from "discourse/models/bookmark";
|
||||||
import Category from "discourse/models/category";
|
import Category from "discourse/models/category";
|
||||||
|
import Group from "discourse/models/group";
|
||||||
|
|
||||||
let sequence = 0;
|
let sequence = 0;
|
||||||
|
|
||||||
|
@ -145,6 +147,16 @@ function reactionFabricator(args = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupFabricator(args = {}) {
|
||||||
|
return Group.create({
|
||||||
|
name: args.name || "Engineers",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function messageMentionWarningFabricator(message, args = {}) {
|
||||||
|
return ChatMessageMentionWarning.create(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
function uploadFabricator() {
|
function uploadFabricator() {
|
||||||
return {
|
return {
|
||||||
extension: "jpeg",
|
extension: "jpeg",
|
||||||
|
@ -175,4 +187,6 @@ export default {
|
||||||
upload: uploadFabricator,
|
upload: uploadFabricator,
|
||||||
category: categoryFabricator,
|
category: categoryFabricator,
|
||||||
directMessage: directMessageFabricator,
|
directMessage: directMessageFabricator,
|
||||||
|
messageMentionWarning: messageMentionWarningFabricator,
|
||||||
|
group: groupFabricator,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class ChatMessageMentionWarning {
|
||||||
|
static create(message, args = {}) {
|
||||||
|
return new ChatMessageMentionWarning(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@tracked invitationSent = false;
|
||||||
|
@tracked cannotSee;
|
||||||
|
@tracked withoutMembership;
|
||||||
|
@tracked groupsWithTooManyMembers;
|
||||||
|
@tracked groupWithMentionsDisabled;
|
||||||
|
|
||||||
|
constructor(message, args = {}) {
|
||||||
|
this.message = args.message;
|
||||||
|
this.cannotSee = args.cannot_see;
|
||||||
|
this.withoutMembership = args.without_membership;
|
||||||
|
this.groupsWithTooManyMembers = args.groups_with_too_many_members;
|
||||||
|
this.groupWithMentionsDisabled = args.group_mentions_disabled;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import Modifier from "ember-modifier";
|
||||||
|
import { registerDestructor } from "@ember/destroyable";
|
||||||
|
import { cancel } from "@ember/runloop";
|
||||||
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
|
||||||
|
export default class ChatLaterFn extends Modifier {
|
||||||
|
constructor(owner, args) {
|
||||||
|
super(owner, args);
|
||||||
|
registerDestructor(this, (instance) => instance.cleanup());
|
||||||
|
}
|
||||||
|
|
||||||
|
modify(element, [fn, delay]) {
|
||||||
|
this.handler = discourseLater(() => {
|
||||||
|
fn?.(element);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
cancel(this.handler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,9 @@ export default class ChatTrackMessage extends Modifier {
|
||||||
registerDestructor(this, (instance) => instance.cleanup());
|
registerDestructor(this, (instance) => instance.cleanup());
|
||||||
}
|
}
|
||||||
|
|
||||||
modify(element, [callbacks = {}]) {
|
modify(element, [didEnterViewport, didLeaveViewport]) {
|
||||||
this.didEnterViewport = callbacks.didEnterViewport;
|
this.didEnterViewport = didEnterViewport;
|
||||||
this.didLeaveViewport = callbacks.didLeaveViewport;
|
this.didLeaveViewport = didLeaveViewport;
|
||||||
|
|
||||||
this.intersectionObserver = new IntersectionObserver(
|
this.intersectionObserver = new IntersectionObserver(
|
||||||
this._intersectionObserverCallback,
|
this._intersectionObserverCallback,
|
||||||
|
|
|
@ -460,6 +460,20 @@ export default class ChatApi extends Service {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite users to a channel.
|
||||||
|
*
|
||||||
|
* @param {number} channelId - The ID of the channel.
|
||||||
|
* @param {Array<number>} userIds - The IDs of the users to invite.
|
||||||
|
* @param {Array<number>} [messageId] - The ID of a message to highlight when opening the notification.
|
||||||
|
*/
|
||||||
|
invite(channelId, userIds, options = {}) {
|
||||||
|
return ajax(`/chat/${channelId}/invite`, {
|
||||||
|
type: "put",
|
||||||
|
data: { user_ids: userIds, chat_message_id: options.messageId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get #basePath() {
|
get #basePath() {
|
||||||
return "/chat/api";
|
return "/chat/api";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Service, { inject as service } from "@ember/service";
|
import Service, { inject as service } from "@ember/service";
|
||||||
import EmberObject from "@ember/object";
|
|
||||||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||||
|
import ChatMessageMentionWarning from "discourse/plugins/chat/discourse/models/chat-message-mention-warning";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
|
||||||
handleMentionWarning(data) {
|
handleMentionWarning(data) {
|
||||||
const message = this.messagesManager.findMessage(data.chat_message_id);
|
const message = this.messagesManager.findMessage(data.chat_message_id);
|
||||||
if (message) {
|
if (message) {
|
||||||
message.mentionWarning = EmberObject.create(data);
|
message.mentionWarning = ChatMessageMentionWarning.create(message, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
.chat-message-error {
|
||||||
|
color: var(--danger-medium);
|
||||||
|
|
||||||
|
&__retry-btn {
|
||||||
|
padding: 0.5em 0;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
.-active & {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus .retry-staged-message-btn__action {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon,
|
||||||
|
&-title,
|
||||||
|
&:hover .d-icon {
|
||||||
|
color: var(--danger) !important;
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon {
|
||||||
|
margin-right: 0.25em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
color: var(--tertiary);
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
margin-left: 0.25em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--tertiary-high);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
.chat-message-mention-warning {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
|
||||||
|
&__dismiss-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__invite-sent {
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
.chat-message-deleted,
|
.chat-message-text.-deleted,
|
||||||
.chat-message-hidden {
|
.chat-message-text.-hidden {
|
||||||
margin-left: calc(var(--message-left-width) + 0.75em);
|
margin-left: calc(var(--message-left-width) + 0.75em);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
@ -18,6 +18,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-message-reaction {
|
||||||
|
> * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-message {
|
.chat-message {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 0.25em 0.5em 0.25em 0.75em;
|
padding: 0.25em 0.5em 0.25em 0.75em;
|
||||||
|
@ -26,10 +32,11 @@
|
||||||
|
|
||||||
.chat-message-reaction {
|
.chat-message-reaction {
|
||||||
@include chat-reaction;
|
@include chat-reaction;
|
||||||
}
|
will-change: scale;
|
||||||
|
|
||||||
.not-mobile-device &.deleted:hover {
|
&:active {
|
||||||
background-color: var(--danger-hover);
|
transform: scale(0.93);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-content {
|
.chat-message-content {
|
||||||
|
@ -87,23 +94,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.reaction-users-list {
|
|
||||||
position: absolute;
|
|
||||||
top: -2px;
|
|
||||||
transform: translateY(-100%);
|
|
||||||
border: 1px solid var(--primary-low);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0.5em;
|
|
||||||
background: var(--primary-very-low);
|
|
||||||
max-width: 300px;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-react-btn {
|
.chat-message-react-btn {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
padding: 0em 0.25em;
|
padding: 0em 0.25em;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
will-change: scale;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.93);
|
||||||
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -121,32 +121,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-send-error {
|
|
||||||
color: var(--danger-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-mention-warning {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 0.25em;
|
|
||||||
font-size: var(--font-down-1);
|
|
||||||
|
|
||||||
.dismiss-mention-warning {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-item {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invite-link {
|
|
||||||
color: var(--tertiary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-avatar .chat-user-avatar .chat-user-avatar-container .avatar,
|
.chat-message-avatar .chat-user-avatar .chat-user-avatar-container .avatar,
|
||||||
.chat-emoji-avatar .chat-emoji-avatar-container {
|
.chat-emoji-avatar .chat-emoji-avatar-container {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
@ -256,94 +230,3 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-full-page-chat .chat-message .onebox:not(img),
|
|
||||||
.chat-drawer-container .chat-message .onebox {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
border-width: 2px;
|
|
||||||
|
|
||||||
header {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 a,
|
|
||||||
h4 a {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: flex;
|
|
||||||
max-height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-drawer-container .chat-message .onebox {
|
|
||||||
width: 85%;
|
|
||||||
border: 2px solid var(--primary-low);
|
|
||||||
|
|
||||||
header {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onebox-body {
|
|
||||||
grid-template-rows: auto auto auto;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
@include line-clamp(2);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: var(--font-down-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-reaction {
|
|
||||||
> * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-staged-message-btn {
|
|
||||||
padding: 0.5em 0;
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
.-active & {
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus .retry-staged-message-btn__action {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.d-icon,
|
|
||||||
&__title,
|
|
||||||
&:hover .d-icon {
|
|
||||||
color: var(--danger) !important;
|
|
||||||
font-size: var(--font-down-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.d-icon {
|
|
||||||
margin-right: 0.25em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__action {
|
|
||||||
color: var(--tertiary);
|
|
||||||
font-size: var(--font-down-1);
|
|
||||||
margin-left: 0.25em;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--tertiary-high);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,8 +28,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-transcript {
|
.chat-drawer-container .chat-message .onebox {
|
||||||
.chat-transcript-user-avatar .avatar {
|
width: 85%;
|
||||||
aspect-ratio: 20 / 20;
|
border: 2px solid var(--primary-low);
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onebox-body {
|
||||||
|
grid-template-rows: auto auto auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@include line-clamp(2);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-full-page-chat .chat-message .onebox:not(img),
|
||||||
|
.chat-drawer-container .chat-message .onebox {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
border-width: 2px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 a,
|
||||||
|
h4 a {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: flex;
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-transcript-user-avatar .avatar {
|
||||||
|
aspect-ratio: 20 / 20;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-transcript-user {
|
.chat-transcript-user {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap-reverse;
|
flex-wrap: wrap-reverse;
|
||||||
|
|
|
@ -58,3 +58,5 @@
|
||||||
@import "chat-thread-unread-indicator";
|
@import "chat-thread-unread-indicator";
|
||||||
@import "chat-thread-participants";
|
@import "chat-thread-participants";
|
||||||
@import "channel-summary-modal";
|
@import "channel-summary-modal";
|
||||||
|
@import "chat-message-mention-warning";
|
||||||
|
@import "chat-message-error";
|
||||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe "Deleted message", type: :system do
|
||||||
last_message = find(".chat-message-container:last-child")
|
last_message = find(".chat-message-container:last-child")
|
||||||
channel_page.delete_message(OpenStruct.new(id: last_message["data-id"]))
|
channel_page.delete_message(OpenStruct.new(id: last_message["data-id"]))
|
||||||
|
|
||||||
expect(channel_page).to have_deleted_message(
|
expect(channel_page.messages).to have_deleted_message(
|
||||||
OpenStruct.new(id: last_message["data-id"]),
|
OpenStruct.new(id: last_message["data-id"]),
|
||||||
count: 1,
|
count: 1,
|
||||||
)
|
)
|
||||||
|
@ -40,12 +40,12 @@ RSpec.describe "Deleted message", type: :system do
|
||||||
.update!(last_read_message_id: message.id)
|
.update!(last_read_message_id: message.id)
|
||||||
chat_page.visit_channel(channel_1)
|
chat_page.visit_channel(channel_1)
|
||||||
channel_page.delete_message(message)
|
channel_page.delete_message(message)
|
||||||
expect(channel_page).to have_deleted_message(message, count: 1)
|
expect(channel_page.messages).to have_deleted_message(message, count: 1)
|
||||||
sidebar_component.click_link(channel_2.name)
|
sidebar_component.click_link(channel_2.name)
|
||||||
expect(channel_page).to have_no_loading_skeleton
|
expect(channel_page).to have_no_loading_skeleton
|
||||||
|
|
||||||
sidebar_component.click_link(channel_1.name)
|
sidebar_component.click_link(channel_1.name)
|
||||||
expect(channel_page).to have_deleted_message(message, count: 1)
|
expect(channel_page.messages).to have_deleted_message(message, count: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the current user is not admin" do
|
context "when the current user is not admin" do
|
||||||
|
@ -72,7 +72,7 @@ RSpec.describe "Deleted message", type: :system do
|
||||||
end
|
end
|
||||||
|
|
||||||
sidebar_component.click_link(channel_1.name)
|
sidebar_component.click_link(channel_1.name)
|
||||||
expect(channel_page).to have_no_message(id: message.id)
|
expect(channel_page.messages).to have_no_message(id: message.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -93,10 +93,9 @@ RSpec.describe "Deleted message", type: :system do
|
||||||
channel_page.delete_message(message_4)
|
channel_page.delete_message(message_4)
|
||||||
channel_page.delete_message(message_6)
|
channel_page.delete_message(message_6)
|
||||||
|
|
||||||
expect(channel_page).to have_deleted_message(message_1)
|
expect(channel_page.messages).to have_deleted_messages(message_1, message_6)
|
||||||
expect(channel_page).to have_deleted_message(message_4, count: 2)
|
expect(channel_page.messages).to have_deleted_message(message_4, count: 2)
|
||||||
expect(channel_page).to have_deleted_message(message_6)
|
expect(channel_page.messages).to have_no_message(id: message_3.id)
|
||||||
expect(channel_page).to have_no_message(id: message_3.id)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,20 +128,20 @@ RSpec.describe "Deleted message", type: :system do
|
||||||
channel_page.message_thread_indicator(thread.original_message).click
|
channel_page.message_thread_indicator(thread.original_message).click
|
||||||
expect(side_panel).to have_open_thread(thread)
|
expect(side_panel).to have_open_thread(thread)
|
||||||
|
|
||||||
expect(channel_page).to have_message(id: message_1.id)
|
expect(channel_page.messages).to have_message(id: message_2.id)
|
||||||
expect(channel_page).to have_message(id: message_2.id)
|
expect(channel_page.messages).to have_message(id: message_1.id)
|
||||||
expect(open_thread).to have_message(thread_id: thread.id, id: message_4.id)
|
expect(open_thread.messages).to have_message(thread_id: thread.id, id: message_4.id)
|
||||||
expect(open_thread).to have_message(thread_id: thread.id, id: message_5.id)
|
expect(open_thread.messages).to have_message(thread_id: thread.id, id: message_5.id)
|
||||||
|
|
||||||
Chat::Publisher.publish_bulk_delete!(
|
Chat::Publisher.publish_bulk_delete!(
|
||||||
channel_1,
|
channel_1,
|
||||||
[message_1.id, message_2.id, message_4.id, message_5.id].flatten,
|
[message_1.id, message_2.id, message_4.id, message_5.id].flatten,
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(channel_page).to have_no_message(id: message_1.id)
|
expect(channel_page.messages).to have_no_message(id: message_1.id)
|
||||||
expect(channel_page).to have_deleted_message(message_2, count: 2)
|
expect(channel_page.messages).to have_deleted_message(message_2, count: 2)
|
||||||
expect(open_thread).to have_no_message(thread_id: thread.id, id: message_4.id)
|
expect(open_thread.messages).to have_no_message(thread_id: thread.id, id: message_4.id)
|
||||||
expect(open_thread).to have_deleted_message(message_5, count: 2)
|
expect(open_thread.messages).to have_deleted_message(message_5, count: 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -84,11 +84,11 @@ RSpec.describe "Move message to channel", type: :system do
|
||||||
click_button(I18n.t("js.chat.move_to_channel.confirm_move"))
|
click_button(I18n.t("js.chat.move_to_channel.confirm_move"))
|
||||||
|
|
||||||
expect(page).to have_current_path(chat.channel_path(channel_2.slug, channel_2.id))
|
expect(page).to have_current_path(chat.channel_path(channel_2.slug, channel_2.id))
|
||||||
expect(channel_page).to have_message(text: message_1.message)
|
expect(channel_page.messages).to have_message(text: message_1.message)
|
||||||
|
|
||||||
chat_page.visit_channel(channel_1)
|
chat_page.visit_channel(channel_1)
|
||||||
|
|
||||||
expect(channel_page).to have_deleted_message(message_1)
|
expect(channel_page.messages).to have_deleted_message(message_1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -188,13 +188,6 @@ module PageObjects
|
||||||
check_message_presence(exists: false, text: text, id: id)
|
check_message_presence(exists: false, text: text, id: id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_deleted_message?(message, count: 1)
|
|
||||||
has_css?(
|
|
||||||
".chat-channel .chat-message-container[data-id=\"#{message.id}\"] .chat-message-deleted",
|
|
||||||
text: I18n.t("js.chat.deleted", count: count),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_message_presence(exists: true, text: nil, id: nil)
|
def check_message_presence(exists: true, text: nil, id: nil)
|
||||||
css_method = exists ? :has_css? : :has_no_css?
|
css_method = exists ? :has_css? : :has_no_css?
|
||||||
if text
|
if text
|
||||||
|
|
|
@ -144,13 +144,6 @@ module PageObjects
|
||||||
".chat-thread .chat-messages-container .chat-message-container[data-id=\"#{id}\"]"
|
".chat-thread .chat-messages-container .chat-message-container[data-id=\"#{id}\"]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_deleted_message?(message, count: 1)
|
|
||||||
has_css?(
|
|
||||||
".chat-thread .chat-message-container[data-id=\"#{message.id}\"] .chat-message-deleted",
|
|
||||||
text: I18n.t("js.chat.deleted", count: count),
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def open_edit_message(message)
|
def open_edit_message(message)
|
||||||
hover_message(message)
|
hover_message(message)
|
||||||
click_more_button
|
click_more_button
|
||||||
|
|
|
@ -44,6 +44,14 @@ module PageObjects
|
||||||
messages.all? { |message| has_message?(id: message.id, selected: true) }
|
messages.all? { |message| has_message?(id: message.id, selected: true) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_deleted_messages?(*messages)
|
||||||
|
messages.all? { |message| has_message?(id: message.id, deleted: 1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_deleted_message?(message, count: 1)
|
||||||
|
has_message?(id: message.id, deleted: count)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def message
|
def message
|
||||||
|
|
|
@ -226,7 +226,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
await visit(`/chat/c/-/${channelId}`);
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
await deleteMessage(".chat-message-content");
|
await deleteMessage(".chat-message-content");
|
||||||
await restoreMessage(".chat-message-deleted");
|
await restoreMessage(".chat-message-text.-deleted");
|
||||||
|
|
||||||
assertStatusIsRendered(
|
assertStatusIsRendered(
|
||||||
assert,
|
assert,
|
||||||
|
@ -239,7 +239,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
await visit(`/chat/c/-/${channelId}`);
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
await deleteMessage(".chat-message-content");
|
await deleteMessage(".chat-message-content");
|
||||||
await restoreMessage(".chat-message-deleted");
|
await restoreMessage(".chat-message-text.-deleted");
|
||||||
|
|
||||||
loggedInUser().appEvents.trigger("user-status:changed", {
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
[mentionedUser1.id]: newStatus,
|
[mentionedUser1.id]: newStatus,
|
||||||
|
@ -254,7 +254,7 @@ acceptance("Chat | User status on mentions", function (needs) {
|
||||||
await visit(`/chat/c/-/${channelId}`);
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
await deleteMessage(".chat-message-content");
|
await deleteMessage(".chat-message-content");
|
||||||
await restoreMessage(".chat-message-deleted");
|
await restoreMessage(".chat-message-text.-deleted");
|
||||||
|
|
||||||
loggedInUser().appEvents.trigger("user-status:changed", {
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
[mentionedUser1.id]: null,
|
[mentionedUser1.id]: null,
|
||||||
|
|
|
@ -14,7 +14,7 @@ module("Discourse Chat | Component | chat-message-avatar", function (hooks) {
|
||||||
chat_webhook_event: { emoji: ":heart:" },
|
chat_webhook_event: { emoji: ":heart:" },
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageAvatar @message={{this.message}} />`);
|
await render(hbs`<Chat::Message::Avatar @message={{this.message}} />`);
|
||||||
|
|
||||||
assert.strictEqual(query(".chat-emoji-avatar .emoji").title, "heart");
|
assert.strictEqual(query(".chat-emoji-avatar .emoji").title, "heart");
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ module("Discourse Chat | Component | chat-message-avatar", function (hooks) {
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageAvatar @message={{this.message}} />`);
|
await render(hbs`<Chat::Message::Avatar @message={{this.message}} />`);
|
||||||
|
|
||||||
assert.true(exists('.chat-user-avatar [data-user-card="discobot"]'));
|
assert.true(exists('.chat-user-avatar [data-user-card="discobot"]'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,12 +11,16 @@ import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
const template = hbs`
|
||||||
|
<Chat::Message::Info @message={{this.message}} @show={{true}} />
|
||||||
|
`;
|
||||||
|
|
||||||
test("chat_webhook_event", async function (assert) {
|
test("chat_webhook_event", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
chat_webhook_event: { username: "discobot" },
|
chat_webhook_event: { username: "discobot" },
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
query(".chat-message-info__username").innerText.trim(),
|
query(".chat-message-info__username").innerText.trim(),
|
||||||
|
@ -29,11 +33,11 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user", async function (assert) {
|
test("user", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
query(".chat-message-info__username").innerText.trim(),
|
query(".chat-message-info__username").innerText.trim(),
|
||||||
|
@ -42,18 +46,18 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("date", async function (assert) {
|
test("date", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
created_at: moment(),
|
created_at: moment(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.true(exists(".chat-message-info__date"));
|
assert.true(exists(".chat-message-info__date"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("bookmark (with reminder)", async function (assert) {
|
test("bookmark (with reminder)", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
bookmark: Bookmark.create({
|
bookmark: Bookmark.create({
|
||||||
reminder_at: moment(),
|
reminder_at: moment(),
|
||||||
|
@ -61,7 +65,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.true(
|
assert.true(
|
||||||
exists(".chat-message-info__bookmark .d-icon-discourse-bookmark-clock")
|
exists(".chat-message-info__bookmark .d-icon-discourse-bookmark-clock")
|
||||||
|
@ -76,50 +80,48 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.true(exists(".chat-message-info__bookmark .d-icon-bookmark"));
|
assert.true(exists(".chat-message-info__bookmark .d-icon-bookmark"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user status", async function (assert) {
|
test("user status", async function (assert) {
|
||||||
const status = { description: "off to dentist", emoji: "tooth" };
|
const status = { description: "off to dentist", emoji: "tooth" };
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({ user: { status } });
|
||||||
user: { status },
|
|
||||||
});
|
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.true(exists(".chat-message-info__status .user-status-message"));
|
assert.true(exists(".chat-message-info__status .user-status-message"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("reviewable", async function (assert) {
|
test("flag status", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
user_flag_status: 0,
|
user_flag_status: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert
|
||||||
query(".chat-message-info__flag > .svg-icon-title").title,
|
.dom(".chat-message-info__flag > .svg-icon-title")
|
||||||
I18n.t("chat.you_flagged")
|
.hasAttribute("title", I18n.t("chat.you_flagged"));
|
||||||
);
|
});
|
||||||
|
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
test("reviewable", async function (assert) {
|
||||||
|
this.message = fabricators.message({
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
reviewable_id: 1,
|
user_flag_status: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert
|
||||||
query(".chat-message-info__flag a .svg-icon-title").title,
|
.dom(".chat-message-info__flag > .svg-icon-title")
|
||||||
I18n.t("chat.flagged")
|
.hasAttribute("title", I18n.t("chat.you_flagged"));
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("with username classes", async function (assert) {
|
test("with username classes", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
user: {
|
user: {
|
||||||
username: "discobot",
|
username: "discobot",
|
||||||
admin: true,
|
admin: true,
|
||||||
|
@ -129,7 +131,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.dom(".chat-message-info__username.is-staff").exists();
|
assert.dom(".chat-message-info__username.is-staff").exists();
|
||||||
assert.dom(".chat-message-info__username.is-admin").exists();
|
assert.dom(".chat-message-info__username.is-admin").exists();
|
||||||
|
@ -139,11 +141,11 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("without username classes", async function (assert) {
|
test("without username classes", async function (assert) {
|
||||||
this.message = ChatMessage.create(fabricators.channel(), {
|
this.message = fabricators.message({
|
||||||
user: { username: "discobot" },
|
user: { username: "discobot" },
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatMessageInfo @message={{this.message}} />`);
|
await render(template);
|
||||||
|
|
||||||
assert.dom(".chat-message-info__username.is-staff").doesNotExist();
|
assert.dom(".chat-message-info__username.is-staff").doesNotExist();
|
||||||
assert.dom(".chat-message-info__username.is-admin").doesNotExist();
|
assert.dom(".chat-message-info__username.is-admin").doesNotExist();
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { render } from "@ember/test-helpers";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
import hbs from "htmlbars-inline-precompile";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
module(
|
||||||
|
"Discourse Chat | Component | Chat::Message::LeftGutter",
|
||||||
|
function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
const template = hbs`
|
||||||
|
<Chat::Message::LeftGutter @message={{this.message}} />
|
||||||
|
`;
|
||||||
|
|
||||||
|
test("default", async function (assert) {
|
||||||
|
this.message = fabricators.message();
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert.dom(".chat-message-left-gutter__date").exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("with reviewable", async function (assert) {
|
||||||
|
this.message = fabricators.message({ reviewable_id: 1 });
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".chat-message-left-gutter__flag .svg-icon-title")
|
||||||
|
.hasAttribute("title", I18n.t("chat.flagged"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("with flag status", async function (assert) {
|
||||||
|
this.message = fabricators.message({ user_flag_status: 0 });
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".chat-message-left-gutter__flag .svg-icon-title")
|
||||||
|
.hasAttribute("title", I18n.t("chat.you_flagged"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("bookmark", async function (assert) {
|
||||||
|
this.message = fabricators.message({ bookmark: fabricators.bookmark() });
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert.dom(".chat-message-left-gutter__date").exists();
|
||||||
|
assert.dom(".chat-message-left-gutter__bookmark").exists();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { render } from "@ember/test-helpers";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
import hbs from "htmlbars-inline-precompile";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
|
|
||||||
|
module(
|
||||||
|
"Discourse Chat | Component | Chat::Message::MentionWarning",
|
||||||
|
function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
const template = hbs`
|
||||||
|
<Chat::Message::MentionWarning @message={{this.message}} />
|
||||||
|
`;
|
||||||
|
|
||||||
|
test("without memberships", async function (assert) {
|
||||||
|
this.message = fabricators.message();
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
without_membership: [fabricators.user()].map((u) => {
|
||||||
|
return { username: u.username, id: u.id };
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".chat-message-mention-warning__text.-without-membership")
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cannot see channel", async function (assert) {
|
||||||
|
this.message = fabricators.message();
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
cannot_see: [fabricators.user()].map((u) => {
|
||||||
|
return { username: u.username, id: u.id };
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert.dom(".chat-message-mention-warning__text.-cannot-see").exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cannot see channel", async function (assert) {
|
||||||
|
this.message = fabricators.message();
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
cannot_see: [fabricators.user()].map((u) => {
|
||||||
|
return { username: u.username, id: u.id };
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert.dom(".chat-message-mention-warning__text.-cannot-see").exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("too many groups", async function (assert) {
|
||||||
|
this.message = fabricators.message();
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
groups_with_too_many_members: [fabricators.group()].mapBy("name"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".chat-message-mention-warning__text.-groups-with-too-many-members"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("groups with mentions disabled", async function (assert) {
|
||||||
|
this.message = fabricators.message();
|
||||||
|
this.message.mentionWarning = fabricators.messageMentionWarning(
|
||||||
|
this.message,
|
||||||
|
{
|
||||||
|
group_mentions_disabled: [fabricators.group()].mapBy("name"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await render(template);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".chat-message-mention-warning__text.-groups-with-mentions-disabled"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
|
@ -9,11 +9,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
const template = hbs`
|
const template = hbs`
|
||||||
<ChatMessage
|
<ChatMessage @message={{this.message}} />
|
||||||
@message={{this.message}}
|
|
||||||
@messageDidEnterViewport={{fn (noop)}}
|
|
||||||
@messageDidLeaveViewport={{fn (noop)}}
|
|
||||||
/>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
test("Message with edits", async function (assert) {
|
test("Message with edits", async function (assert) {
|
||||||
|
@ -31,7 +27,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
|
||||||
await render(template);
|
await render(template);
|
||||||
|
|
||||||
assert.true(
|
assert.true(
|
||||||
exists(".chat-message-deleted .chat-message-expand"),
|
exists(".chat-message-text.-deleted .chat-message-expand"),
|
||||||
"has the correct css class and expand button within"
|
"has the correct css class and expand button within"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -41,7 +37,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
|
||||||
await render(template);
|
await render(template);
|
||||||
|
|
||||||
assert.true(
|
assert.true(
|
||||||
exists(".chat-message-hidden .chat-message-expand"),
|
exists(".chat-message-text.-hidden .chat-message-expand"),
|
||||||
"has the correct css class and expand button within"
|
"has the correct css class and expand button within"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue