UX: groups deleted messages (#21411)
Any continuous series of deleted messages will now be grouped into one single expand button.
This commit is contained in:
parent
ae3231e140
commit
cb5e5f3e5b
|
@ -108,7 +108,7 @@ export default class ChatLivePane extends Component {
|
||||||
|
|
||||||
// Technically we could keep messages to avoid re-fetching them, but
|
// Technically we could keep messages to avoid re-fetching them, but
|
||||||
// it's not worth the complexity for now
|
// it's not worth the complexity for now
|
||||||
this.args.channel?.messagesManager?.clearMessages();
|
this.args.channel?.clearMessages();
|
||||||
|
|
||||||
if (this._loadedChannelId !== this.args.channel?.id) {
|
if (this._loadedChannelId !== this.args.channel?.id) {
|
||||||
this._unsubscribeToUpdates(this._loadedChannelId);
|
this._unsubscribeToUpdates(this._loadedChannelId);
|
||||||
|
@ -183,7 +183,7 @@ export default class ChatLivePane extends Component {
|
||||||
results
|
results
|
||||||
);
|
);
|
||||||
|
|
||||||
this.args.channel.messages = messages;
|
this.args.channel.addMessages(messages);
|
||||||
this.args.channel.details = meta;
|
this.args.channel.details = meta;
|
||||||
|
|
||||||
if (this.requestedTargetMessageId) {
|
if (this.requestedTargetMessageId) {
|
||||||
|
@ -224,8 +224,8 @@ export default class ChatLivePane extends Component {
|
||||||
const loadingMoreKey = `loadingMore${capitalize(direction)}`;
|
const loadingMoreKey = `loadingMore${capitalize(direction)}`;
|
||||||
|
|
||||||
const canLoadMore = loadingPast
|
const canLoadMore = loadingPast
|
||||||
? this.#messagesManager.canLoadMorePast
|
? this.args.channel?.canLoadMorePast
|
||||||
: this.#messagesManager.canLoadMoreFuture;
|
: this.args.channel?.canLoadMoreFuture;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!canLoadMore ||
|
!canLoadMore ||
|
||||||
|
@ -276,7 +276,7 @@ export default class ChatLivePane extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.args.channel.details = meta;
|
this.args.channel.details = meta;
|
||||||
this.#messagesManager.addMessages(messages);
|
this.args.channel.addMessages(messages);
|
||||||
|
|
||||||
// Edge case for IOS to avoid blank screens
|
// Edge case for IOS to avoid blank screens
|
||||||
// and/or scrolling to bottom losing track of scroll position
|
// and/or scrolling to bottom losing track of scroll position
|
||||||
|
@ -367,7 +367,7 @@ export default class ChatLivePane extends Component {
|
||||||
|
|
||||||
@debounce(100)
|
@debounce(100)
|
||||||
highlightOrFetchMessage(messageId) {
|
highlightOrFetchMessage(messageId) {
|
||||||
const message = this.#messagesManager?.findMessage(messageId);
|
const message = this.args.channel?.findMessage(messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
this.scrollToMessage(message.id, {
|
this.scrollToMessage(message.id, {
|
||||||
highlight: true,
|
highlight: true,
|
||||||
|
@ -388,7 +388,7 @@ export default class ChatLivePane extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = this.#messagesManager?.findMessage(messageId);
|
const message = this.args.channel?.findMessage(messageId);
|
||||||
if (message?.deletedAt && opts.autoExpand) {
|
if (message?.deletedAt && opts.autoExpand) {
|
||||||
message.expanded = true;
|
message.expanded = true;
|
||||||
}
|
}
|
||||||
|
@ -485,7 +485,7 @@ export default class ChatLivePane extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#messagesManager?.canLoadMoreFuture) {
|
if (this.args.channel?.canLoadMoreFuture) {
|
||||||
this._fetchAndScrollToLatest();
|
this._fetchAndScrollToLatest();
|
||||||
} else if (this.args.channel.messages?.length > 0) {
|
} else if (this.args.channel.messages?.length > 0) {
|
||||||
this.scrollToMessage(
|
this.scrollToMessage(
|
||||||
|
@ -534,9 +534,9 @@ export default class ChatLivePane extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeMessage(msgData) {
|
removeMessage(msgData) {
|
||||||
const message = this.#messagesManager.findMessage(msgData.id);
|
const message = this.args.channel?.findMessage(msgData.id);
|
||||||
if (message) {
|
if (message) {
|
||||||
this.#messagesManager.removeMessage(message);
|
this.args.channel?.removeMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +557,7 @@ export default class ChatLivePane extends Component {
|
||||||
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
||||||
const stagedMessage = handleStagedMessage(
|
const stagedMessage = handleStagedMessage(
|
||||||
this.args.channel,
|
this.args.channel,
|
||||||
this.#messagesManager,
|
this.args.channel.messagesManager,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (stagedMessage) {
|
if (stagedMessage) {
|
||||||
|
@ -565,20 +565,19 @@ export default class ChatLivePane extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#messagesManager.canLoadMoreFuture) {
|
if (this.args.channel?.canLoadMoreFuture) {
|
||||||
// If we can load more messages, we just notice the user of new messages
|
// If we can load more messages, we just notice the user of new messages
|
||||||
this.hasNewMessages = true;
|
this.hasNewMessages = true;
|
||||||
} else if (this.#isTowardsBottom()) {
|
} else if (this.#isTowardsBottom()) {
|
||||||
// If we are at the bottom, we append the message and scroll to it
|
// If we are at the bottom, we append the message and scroll to it
|
||||||
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
||||||
|
this.args.channel.addMessages([message]);
|
||||||
this.#messagesManager.addMessages([message]);
|
|
||||||
this.scrollToLatestMessage();
|
this.scrollToLatestMessage();
|
||||||
this.updateLastReadMessage();
|
this.updateLastReadMessage();
|
||||||
} else {
|
} else {
|
||||||
// If we are almost at the bottom, we append the message and notice the user
|
// If we are almost at the bottom, we append the message and notice the user
|
||||||
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
||||||
this.#messagesManager.addMessages([message]);
|
this.args.channel.addMessages([message]);
|
||||||
this.hasNewMessages = true;
|
this.hasNewMessages = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,10 +588,6 @@ export default class ChatLivePane extends Component {
|
||||||
return this.isDestroying || this.isDestroyed;
|
return this.isDestroying || this.isDestroyed;
|
||||||
}
|
}
|
||||||
|
|
||||||
get #messagesManager() {
|
|
||||||
return this.args.channel?.messagesManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onSendMessage(message) {
|
onSendMessage(message) {
|
||||||
if (message.editing) {
|
if (message.editing) {
|
||||||
|
@ -711,7 +706,7 @@ export default class ChatLivePane extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSendError(id, error) {
|
_onSendError(id, error) {
|
||||||
const stagedMessage = this.#messagesManager.findStagedMessage(id);
|
const stagedMessage = this.args.channel.findStagedMessage(id);
|
||||||
if (stagedMessage) {
|
if (stagedMessage) {
|
||||||
if (error.jqXHR?.responseJSON?.errors?.length) {
|
if (error.jqXHR?.responseJSON?.errors?.length) {
|
||||||
// only network errors are retryable
|
// only network errors are retryable
|
||||||
|
|
|
@ -1,202 +1,206 @@
|
||||||
{{! template-lint-disable no-invalid-interactive }}
|
{{! template-lint-disable no-invalid-interactive }}
|
||||||
|
|
||||||
{{#if (eq @context "channel")}}
|
{{#if this.shouldRender}}
|
||||||
<ChatMessageSeparatorDate @message={{@message}} />
|
{{#if (eq @context "channel")}}
|
||||||
<ChatMessageSeparatorNew @message={{@message}} />
|
<ChatMessageSeparatorDate @message={{@message}} />
|
||||||
{{/if}}
|
<ChatMessageSeparatorNew @message={{@message}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
{{will-destroy this.teardownChatMessage}}
|
{{will-destroy this.teardownChatMessage}}
|
||||||
{{did-insert this.decorateCookedMessage}}
|
{{did-insert this.decorateCookedMessage}}
|
||||||
{{did-update this.decorateCookedMessage @message.id}}
|
{{did-update this.decorateCookedMessage @message.id}}
|
||||||
{{did-update this.decorateCookedMessage @message.version}}
|
{{did-update this.decorateCookedMessage @message.version}}
|
||||||
{{on "touchmove" this.handleTouchMove passive=true}}
|
{{on "touchmove" this.handleTouchMove passive=true}}
|
||||||
{{on "touchstart" this.handleTouchStart passive=true}}
|
{{on "touchstart" this.handleTouchStart passive=true}}
|
||||||
{{on "touchend" this.handleTouchEnd passive=true}}
|
{{on "touchend" this.handleTouchEnd passive=true}}
|
||||||
{{on "mouseenter" this.onMouseEnter}}
|
{{on "mouseenter" this.onMouseEnter}}
|
||||||
{{on "mouseleave" this.onMouseLeave}}
|
{{on "mouseleave" this.onMouseLeave}}
|
||||||
{{on "mousemove" this.onMouseMove}}
|
{{on "mousemove" this.onMouseMove}}
|
||||||
class={{concat-class
|
class={{concat-class
|
||||||
"chat-message-container"
|
"chat-message-container"
|
||||||
(if this.pane.selectingMessages "selecting-messages")
|
(if this.pane.selectingMessages "selecting-messages")
|
||||||
(if @message.highlighted "highlighted")
|
(if @message.highlighted "highlighted")
|
||||||
}}
|
}}
|
||||||
data-id={{@message.id}}
|
data-id={{@message.id}}
|
||||||
data-thread-id={{@message.thread.id}}
|
data-thread-id={{@message.thread.id}}
|
||||||
{{chat/track-message
|
{{chat/track-message
|
||||||
(hash
|
(hash
|
||||||
didEnterViewport=(fn @messageDidEnterViewport @message)
|
didEnterViewport=(fn @messageDidEnterViewport @message)
|
||||||
didLeaveViewport=(fn @messageDidLeaveViewport @message)
|
didLeaveViewport=(fn @messageDidLeaveViewport @message)
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{{#if this.show}}
|
{{#if this.show}}
|
||||||
{{#if this.pane.selectingMessages}}
|
{{#if this.pane.selectingMessages}}
|
||||||
<Input
|
<Input
|
||||||
@type="checkbox"
|
@type="checkbox"
|
||||||
class="chat-message-selector"
|
class="chat-message-selector"
|
||||||
@checked={{@message.selected}}
|
@checked={{@message.selected}}
|
||||||
{{on "click" this.toggleChecked}}
|
{{on "click" this.toggleChecked}}
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.deletedAndCollapsed}}
|
|
||||||
<div class="chat-message-deleted">
|
|
||||||
<DButton
|
|
||||||
@class="btn-flat chat-message-expand"
|
|
||||||
@action={{this.expand}}
|
|
||||||
@label="chat.deleted"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
{{/if}}
|
||||||
{{else if this.hiddenAndCollapsed}}
|
|
||||||
<div class="chat-message-hidden">
|
|
||||||
<DButton
|
|
||||||
@class="btn-flat chat-message-expand"
|
|
||||||
@action={{this.expand}}
|
|
||||||
@label="chat.hidden"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div
|
|
||||||
class={{concat-class
|
|
||||||
"chat-message"
|
|
||||||
(if @message.staged "chat-message-staged")
|
|
||||||
(if @message.deletedAt "deleted")
|
|
||||||
(if (and @message.inReplyTo (not this.hideReplyToInfo)) "is-reply")
|
|
||||||
(if this.showThreadIndicator "is-threaded")
|
|
||||||
(if this.hideUserInfo "user-info-hidden")
|
|
||||||
(if @message.error "errored")
|
|
||||||
(if @message.bookmark "chat-message-bookmarked")
|
|
||||||
(if (eq @message.id this.chat.activeMessage.model.id) "is-active")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{{#unless this.hideReplyToInfo}}
|
|
||||||
<ChatMessageInReplyToIndicator @message={{@message}} />
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{#if this.hideUserInfo}}
|
{{#if this.deletedAndCollapsed}}
|
||||||
<ChatMessageLeftGutter @message={{@message}} />
|
<div class="chat-message-deleted">
|
||||||
{{else}}
|
<DButton
|
||||||
<ChatMessageAvatar @message={{@message}} />
|
@class="btn-flat chat-message-expand"
|
||||||
{{/if}}
|
@action={{this.expand}}
|
||||||
|
@translatedLabel={{this.deletedMessageLabel}}
|
||||||
<div class="chat-message-content">
|
/>
|
||||||
{{#unless this.hideUserInfo}}
|
</div>
|
||||||
<ChatMessageInfo @message={{@message}} />
|
{{else if this.hiddenAndCollapsed}}
|
||||||
|
<div class="chat-message-hidden">
|
||||||
|
<DButton
|
||||||
|
@class="btn-flat chat-message-expand"
|
||||||
|
@action={{this.expand}}
|
||||||
|
@label="chat.hidden"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div
|
||||||
|
class={{concat-class
|
||||||
|
"chat-message"
|
||||||
|
(if @message.staged "chat-message-staged")
|
||||||
|
(if @message.deletedAt "deleted")
|
||||||
|
(if (and @message.inReplyTo (not this.hideReplyToInfo)) "is-reply")
|
||||||
|
(if this.showThreadIndicator "is-threaded")
|
||||||
|
(if this.hideUserInfo "user-info-hidden")
|
||||||
|
(if @message.error "errored")
|
||||||
|
(if @message.bookmark "chat-message-bookmarked")
|
||||||
|
(if (eq @message.id this.chat.activeMessage.model.id) "is-active")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{{#unless this.hideReplyToInfo}}
|
||||||
|
<ChatMessageInReplyToIndicator @message={{@message}} />
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
<ChatMessageText
|
{{#if this.hideUserInfo}}
|
||||||
@cooked={{@message.cooked}}
|
<ChatMessageLeftGutter @message={{@message}} />
|
||||||
@uploads={{@message.uploads}}
|
{{else}}
|
||||||
@edited={{@message.edited}}
|
<ChatMessageAvatar @message={{@message}} />
|
||||||
>
|
{{/if}}
|
||||||
{{#if @message.reactions.length}}
|
|
||||||
<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|}}
|
<div class="chat-message-content">
|
||||||
<ChatMessageReaction
|
{{#unless this.hideUserInfo}}
|
||||||
@reaction={{reaction}}
|
<ChatMessageInfo @message={{@message}} />
|
||||||
@onReaction={{this.messageInteractor.react}}
|
{{/unless}}
|
||||||
@message={{@message}}
|
|
||||||
@showTooltip={{true}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
{{#if this.chat.userCanInteractWithChat}}
|
<ChatMessageText
|
||||||
{{#unless this.site.mobileView}}
|
@cooked={{@message.cooked}}
|
||||||
<DButton
|
@uploads={{@message.uploads}}
|
||||||
@class="chat-message-react-btn"
|
@edited={{@message.edited}}
|
||||||
@action={{this.messageInteractor.openEmojiPicker}}
|
>
|
||||||
@icon="discourse-emojis"
|
{{#if @message.reactions.length}}
|
||||||
@title="chat.react"
|
<div class="chat-message-reaction-list">
|
||||||
@forwardEvent={{true}}
|
{{#if this.reactionLabel}}
|
||||||
|
<div class="reaction-users-list">
|
||||||
|
{{replace-emoji this.reactionLabel}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each @message.reactions as |reaction|}}
|
||||||
|
<ChatMessageReaction
|
||||||
|
@reaction={{reaction}}
|
||||||
|
@onReaction={{this.messageInteractor.react}}
|
||||||
|
@message={{@message}}
|
||||||
|
@showTooltip={{true}}
|
||||||
/>
|
/>
|
||||||
{{/unless}}
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if this.chat.userCanInteractWithChat}}
|
||||||
|
{{#unless this.site.mobileView}}
|
||||||
|
<DButton
|
||||||
|
@class="chat-message-react-btn"
|
||||||
|
@action={{this.messageInteractor.openEmojiPicker}}
|
||||||
|
@icon="discourse-emojis"
|
||||||
|
@title="chat.react"
|
||||||
|
@forwardEvent={{true}}
|
||||||
|
/>
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</ChatMessageText>
|
||||||
|
|
||||||
|
{{#if @message.error}}
|
||||||
|
<div class="chat-send-error">
|
||||||
|
{{#if (eq @message.error "network_error")}}
|
||||||
|
<DButton
|
||||||
|
class="retry-staged-message-btn"
|
||||||
|
@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}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</ChatMessageText>
|
|
||||||
|
|
||||||
{{#if @message.error}}
|
{{#if this.mentionWarning}}
|
||||||
<div class="chat-send-error">
|
<div class="alert alert-info chat-message-mention-warning">
|
||||||
{{#if (eq @message.error "network_error")}}
|
{{#if this.mentionWarning.invitation_sent}}
|
||||||
<DButton
|
{{d-icon "check"}}
|
||||||
class="retry-staged-message-btn"
|
<span>
|
||||||
@action={{fn @resendStagedMessage @message}}
|
{{i18n
|
||||||
@icon="exclamation-circle"
|
"chat.mention_warning.invitations_sent"
|
||||||
>
|
count=this.mentionWarning.without_membership.length
|
||||||
<span class="retry-staged-message-btn__title">
|
}}
|
||||||
{{i18n "chat.retry_staged_message.title"}}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="retry-staged-message-btn__action">
|
{{else}}
|
||||||
{{i18n "chat.retry_staged_message.action"}}
|
<FlatButton
|
||||||
</span>
|
@class="dismiss-mention-warning"
|
||||||
</DButton>
|
@title="chat.mention_warning.dismiss"
|
||||||
{{else}}
|
@action={{this.dismissMentionWarning}}
|
||||||
{{@message.error}}
|
@icon="times"
|
||||||
{{/if}}
|
/>
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.mentionWarning}}
|
{{#if this.mentionWarning.cannot_see}}
|
||||||
<div class="alert alert-info chat-message-mention-warning">
|
<p class="warning-item cannot-see">
|
||||||
{{#if this.mentionWarning.invitation_sent}}
|
{{this.mentionedCannotSeeText}}
|
||||||
{{d-icon "check"}}
|
</p>
|
||||||
<span>
|
{{/if}}
|
||||||
{{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}}
|
{{#if this.mentionWarning.without_membership}}
|
||||||
<p class="warning-item cannot-see">
|
<p class="warning-item without-membership">
|
||||||
{{this.mentionedCannotSeeText}}
|
<span>{{this.mentionedWithoutMembershipText}}</span>
|
||||||
</p>
|
<a
|
||||||
{{/if}}
|
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.without_membership}}
|
{{#if this.mentionWarning.groups_with_too_many_members}}
|
||||||
<p class="warning-item without-membership">
|
<p
|
||||||
<span>{{this.mentionedWithoutMembershipText}}</span>
|
class="warning-item"
|
||||||
<a
|
>{{this.groupsWithTooManyMembers}}</p>
|
||||||
class="invite-link"
|
{{/if}}
|
||||||
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}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#if this.mentionWarning.groups_with_too_many_members}}
|
{{#if this.showThreadIndicator}}
|
||||||
<p class="warning-item">{{this.groupsWithTooManyMembers}}</p>
|
<ChatMessageThreadIndicator @message={{@message}} />
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
{{#if this.showThreadIndicator}}
|
|
||||||
<ChatMessageThreadIndicator @message={{@message}} />
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
{{/if}}
|
|
@ -69,9 +69,43 @@ export default class ChatMessage extends Component {
|
||||||
return !this.args.message?.expanded;
|
return !this.args.message?.expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get deletedMessageLabel() {
|
||||||
|
let count = 1;
|
||||||
|
|
||||||
|
const recursiveCount = (message) => {
|
||||||
|
const previousMessage = message.previousMessage;
|
||||||
|
if (previousMessage?.deletedAt) {
|
||||||
|
count++;
|
||||||
|
recursiveCount(previousMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
recursiveCount(this.args.message);
|
||||||
|
|
||||||
|
return I18n.t("chat.deleted", { count });
|
||||||
|
}
|
||||||
|
|
||||||
|
get shouldRender() {
|
||||||
|
return (
|
||||||
|
this.args.message.expanded ||
|
||||||
|
!this.args.message.deletedAt ||
|
||||||
|
(this.args.message.deletedAt && !this.args.message.nextMessage?.deletedAt)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
expand() {
|
expand() {
|
||||||
|
const recursiveExpand = (message) => {
|
||||||
|
const previousMessage = message.previousMessage;
|
||||||
|
if (previousMessage?.deletedAt) {
|
||||||
|
previousMessage.expanded = true;
|
||||||
|
recursiveExpand(previousMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.args.message.expanded = true;
|
this.args.message.expanded = true;
|
||||||
|
|
||||||
|
recursiveExpand(this.args.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -215,9 +215,9 @@ export default class ChatMessageInteractor {
|
||||||
bulkSelect(checked) {
|
bulkSelect(checked) {
|
||||||
const channel = this.message.channel;
|
const channel = this.message.channel;
|
||||||
const lastSelectedIndex = channel.findIndexOfMessage(
|
const lastSelectedIndex = channel.findIndexOfMessage(
|
||||||
this.pane.lastSelectedMessage
|
this.pane.lastSelectedMessage.id
|
||||||
);
|
);
|
||||||
const newlySelectedIndex = channel.findIndexOfMessage(this.message);
|
const newlySelectedIndex = channel.findIndexOfMessage(this.message.id);
|
||||||
const sortedIndices = [lastSelectedIndex, newlySelectedIndex].sort(
|
const sortedIndices = [lastSelectedIndex, newlySelectedIndex].sort(
|
||||||
(a, b) => a - b
|
(a, b) => a - b
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,7 @@ export default class ChatMessagesManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessages() {
|
clearMessages() {
|
||||||
|
this.messages.forEach((message) => (message.manager = null));
|
||||||
this.messages.clear();
|
this.messages.clear();
|
||||||
|
|
||||||
this.canLoadMoreFuture = null;
|
this.canLoadMoreFuture = null;
|
||||||
|
@ -19,6 +20,10 @@ export default class ChatMessagesManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessages(messages = []) {
|
addMessages(messages = []) {
|
||||||
|
messages.forEach((message) => {
|
||||||
|
message.manager = this;
|
||||||
|
});
|
||||||
|
|
||||||
this.messages = this.messages
|
this.messages = this.messages
|
||||||
.concat(messages)
|
.concat(messages)
|
||||||
.uniqBy("id")
|
.uniqBy("id")
|
||||||
|
@ -40,4 +45,8 @@ export default class ChatMessagesManager {
|
||||||
(message) => message.staged && message.id === stagedMessageId
|
(message) => message.staged && message.id === stagedMessageId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findIndexOfMessage(id) {
|
||||||
|
return this.messages.findIndex((m) => m.id === id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,14 +68,30 @@ export default class ChatChannel extends RestModel {
|
||||||
threadsManager = new ChatThreadsManager(getOwner(this));
|
threadsManager = new ChatThreadsManager(getOwner(this));
|
||||||
messagesManager = new ChatMessagesManager(getOwner(this));
|
messagesManager = new ChatMessagesManager(getOwner(this));
|
||||||
|
|
||||||
findIndexOfMessage(message) {
|
findIndexOfMessage(id) {
|
||||||
return this.messages.findIndex((m) => m.id === message.id);
|
return this.messagesManager.findIndexOfMessage(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
findStagedMessage(id) {
|
||||||
|
return this.messagesManager.findStagedMessage(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
findMessage(id) {
|
findMessage(id) {
|
||||||
return this.messagesManager.findMessage(id);
|
return this.messagesManager.findMessage(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addMessages(messages) {
|
||||||
|
this.messagesManager.addMessages(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessages() {
|
||||||
|
this.messagesManager.clearMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(message) {
|
||||||
|
this.messagesManager.removeMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
get messages() {
|
get messages() {
|
||||||
return this.messagesManager.messages;
|
return this.messagesManager.messages;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +104,10 @@ export default class ChatChannel extends RestModel {
|
||||||
return this.messagesManager.canLoadMoreFuture;
|
return this.messagesManager.canLoadMoreFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canLoadMorePast() {
|
||||||
|
return this.messagesManager.canLoadMorePast;
|
||||||
|
}
|
||||||
|
|
||||||
get escapedTitle() {
|
get escapedTitle() {
|
||||||
return escapeExpression(this.title);
|
return escapeExpression(this.title);
|
||||||
}
|
}
|
||||||
|
@ -186,10 +206,10 @@ export default class ChatChannel extends RestModel {
|
||||||
|
|
||||||
if (message.inReplyTo) {
|
if (message.inReplyTo) {
|
||||||
if (!this.threadingEnabled) {
|
if (!this.threadingEnabled) {
|
||||||
this.messagesManager.addMessages([message]);
|
this.addMessages([message]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.messagesManager.addMessages([message]);
|
this.addMessages([message]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,8 @@ export default class ChatMessage {
|
||||||
@tracked message;
|
@tracked message;
|
||||||
@tracked thread;
|
@tracked thread;
|
||||||
@tracked threadReplyCount;
|
@tracked threadReplyCount;
|
||||||
|
@tracked manager = null;
|
||||||
|
|
||||||
@tracked _cooked;
|
@tracked _cooked;
|
||||||
|
|
||||||
constructor(channel, args = {}) {
|
constructor(channel, args = {}) {
|
||||||
|
@ -193,17 +195,17 @@ export default class ChatMessage {
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get index() {
|
get index() {
|
||||||
return this.channel.messages.indexOf(this);
|
return this.manager?.messages?.indexOf(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get previousMessage() {
|
get previousMessage() {
|
||||||
return this.channel?.messages?.objectAt?.(this.index - 1);
|
return this.manager?.messages?.objectAt?.(this.index - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get nextMessage() {
|
get nextMessage() {
|
||||||
return this.channel?.messages?.objectAt?.(this.index + 1);
|
return this.manager?.messages?.objectAt?.(this.index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementVersion() {
|
incrementVersion() {
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
color: var(--primary-low-mid);
|
color: var(--primary-low-mid);
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
|
||||||
|
.d-button-label {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
|
@ -81,7 +81,10 @@ en:
|
||||||
collapse: "Collapse Chat Drawer"
|
collapse: "Collapse Chat Drawer"
|
||||||
expand: "Expand Chat Drawer"
|
expand: "Expand Chat Drawer"
|
||||||
confirm_flag: "Are you sure you want to flag %{username}'s message?"
|
confirm_flag: "Are you sure you want to flag %{username}'s message?"
|
||||||
deleted: "A message was deleted. [view]"
|
deleted:
|
||||||
|
one: "A message was deleted. [view]"
|
||||||
|
other: "%{count} messages were deleted. [view all]"
|
||||||
|
|
||||||
hidden: "A message was hidden. [view]"
|
hidden: "A message was hidden. [view]"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
edited: "edited"
|
edited: "edited"
|
||||||
|
|
|
@ -4,7 +4,7 @@ RSpec.describe "Deleted message", type: :system, js: true do
|
||||||
let(:chat_page) { PageObjects::Pages::Chat.new }
|
let(:chat_page) { PageObjects::Pages::Chat.new }
|
||||||
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
||||||
|
|
||||||
fab!(:current_user) { Fabricate(:user) }
|
fab!(:current_user) { Fabricate(:admin) }
|
||||||
fab!(:channel_1) { Fabricate(:category_channel) }
|
fab!(:channel_1) { Fabricate(:category_channel) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -21,7 +21,33 @@ RSpec.describe "Deleted message", type: :system, js: true 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(page).to have_content(I18n.t("js.chat.deleted"))
|
expect(channel_page).to have_deleted_message(
|
||||||
|
OpenStruct.new(id: last_message["data-id"]),
|
||||||
|
count: 1,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when deleting multiple messages" do
|
||||||
|
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
fab!(:message_3) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
fab!(:message_4) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
fab!(:message_5) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
fab!(:message_6) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
|
||||||
|
it "groups them" do
|
||||||
|
chat_page.visit_channel(channel_1)
|
||||||
|
|
||||||
|
channel_page.delete_message(message_1)
|
||||||
|
channel_page.delete_message(message_3)
|
||||||
|
channel_page.delete_message(message_4)
|
||||||
|
channel_page.delete_message(message_6)
|
||||||
|
|
||||||
|
expect(channel_page).to have_deleted_message(message_1)
|
||||||
|
expect(channel_page).to have_deleted_message(message_4, count: 2)
|
||||||
|
expect(channel_page).to have_deleted_message(message_6)
|
||||||
|
expect(channel_page).to have_no_message(id: message_3.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -65,9 +91,9 @@ RSpec.describe "Deleted message", type: :system, js: true do
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(channel_page).to have_no_message(id: message_1.id)
|
expect(channel_page).to have_no_message(id: message_1.id)
|
||||||
expect(channel_page).to have_no_message(id: message_2.id)
|
expect(channel_page).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).to have_no_message(thread_id: thread.id, id: message_4.id)
|
||||||
expect(open_thread).to have_no_message(thread_id: thread.id, id: message_5.id)
|
expect(open_thread).to have_deleted_message(message_5, count: 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,8 +87,7 @@ RSpec.describe "Move message to channel", type: :system, js: true do
|
||||||
|
|
||||||
chat.visit_channel(channel_1)
|
chat.visit_channel(channel_1)
|
||||||
|
|
||||||
expect(page).to have_no_content(message_1.message)
|
expect(channel).to have_deleted_message(message_1)
|
||||||
expect(page).to have_content(I18n.t("js.chat.deleted"))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -172,6 +172,13 @@ 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
|
||||||
|
|
|
@ -77,6 +77,13 @@ module PageObjects
|
||||||
def message_by_id_selector(id)
|
def message_by_id_selector(id)
|
||||||
".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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue