mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FIX: allows selection of messages in threads (#22119)
This commit fixes the selection of message in threads and also applies various refactorings - improves specs and especially page objects/components - makes the channel/thread panes responsible of the state - adds an animationend modifier - continues to follow the logic of "state" should be displayed as data attributes on component by having a new `data-selected` attribute on chat messages
This commit is contained in:
parent
f3afc8bf85
commit
79a260a6bb
@ -68,14 +68,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{{#if this.pane.selectingMessages}}
|
{{#if this.pane.selectingMessages}}
|
||||||
<ChatSelectionManager
|
<Chat::SelectionManager
|
||||||
@selectedMessageIds={{this.pane.selectedMessageIds}}
|
@enableMove={{and
|
||||||
@chatChannel={{@channel}}
|
(not @channel.isDirectMessageChannel)
|
||||||
@cancelSelecting={{action
|
@channel.canModerate
|
||||||
this.pane.cancelSelecting
|
|
||||||
@channel.selectedMessages
|
|
||||||
}}
|
}}
|
||||||
@context="channel"
|
@pane={{this.pane}}
|
||||||
/>
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if (or @channel.isDraft @channel.isFollowing)}}
|
{{#if (or @channel.isDraft @channel.isFollowing)}}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
}}
|
}}
|
||||||
data-id={{@message.id}}
|
data-id={{@message.id}}
|
||||||
data-thread-id={{@message.thread.id}}
|
data-thread-id={{@message.thread.id}}
|
||||||
|
data-selected={{@message.selected}}
|
||||||
{{chat/track-message
|
{{chat/track-message
|
||||||
(hash
|
(hash
|
||||||
didEnterViewport=(fn @messageDidEnterViewport @message)
|
didEnterViewport=(fn @messageDidEnterViewport @message)
|
||||||
|
@ -1,133 +0,0 @@
|
|||||||
import Component from "@ember/component";
|
|
||||||
import { action, computed } from "@ember/object";
|
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
import { clipboardCopyAsync } from "discourse/lib/utilities";
|
|
||||||
import { getOwner } from "discourse-common/lib/get-owner";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import { isTesting } from "discourse-common/config/environment";
|
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
import { schedule } from "@ember/runloop";
|
|
||||||
import { inject as service } from "@ember/service";
|
|
||||||
import getURL from "discourse-common/lib/get-url";
|
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
|
||||||
import { MESSAGE_CONTEXT_THREAD } from "discourse/plugins/chat/discourse/components/chat-message";
|
|
||||||
|
|
||||||
export default class ChatSelectionManager extends Component {
|
|
||||||
@service router;
|
|
||||||
tagName = "";
|
|
||||||
chatChannel = null;
|
|
||||||
context = null;
|
|
||||||
selectedMessageIds = null;
|
|
||||||
chatCopySuccess = false;
|
|
||||||
showChatCopySuccess = false;
|
|
||||||
cancelSelecting = null;
|
|
||||||
|
|
||||||
@computed("selectedMessageIds.length")
|
|
||||||
get anyMessagesSelected() {
|
|
||||||
return this.selectedMessageIds.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("chatChannel.isDirectMessageChannel", "chatChannel.canModerate")
|
|
||||||
get showMoveMessageButton() {
|
|
||||||
return (
|
|
||||||
this.context !== MESSAGE_CONTEXT_THREAD &&
|
|
||||||
!this.chatChannel.isDirectMessageChannel &&
|
|
||||||
this.chatChannel.canModerate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
async generateQuote() {
|
|
||||||
const response = await ajax(
|
|
||||||
getURL(`/chat/${this.chatChannel.id}/quote.json`),
|
|
||||||
{
|
|
||||||
data: { message_ids: this.selectedMessageIds },
|
|
||||||
type: "POST",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Blob([response.markdown], {
|
|
||||||
type: "text/plain",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
openMoveMessageModal() {
|
|
||||||
showModal("chat-message-move-to-channel-modal").setProperties({
|
|
||||||
sourceChannel: this.chatChannel,
|
|
||||||
selectedMessageIds: this.selectedMessageIds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
async quoteMessages() {
|
|
||||||
let quoteMarkdown;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const quoteMarkdownBlob = await this.generateQuote();
|
|
||||||
quoteMarkdown = await quoteMarkdownBlob.text();
|
|
||||||
} catch (error) {
|
|
||||||
popupAjaxError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = getOwner(this);
|
|
||||||
const composer = container.lookup("controller:composer");
|
|
||||||
const openOpts = {};
|
|
||||||
|
|
||||||
if (this.chatChannel.isCategoryChannel) {
|
|
||||||
openOpts.categoryId = this.chatChannel.chatableId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.site.mobileView) {
|
|
||||||
// go to the relevant chatable (e.g. category) and open the
|
|
||||||
// composer to insert text
|
|
||||||
if (this.chatChannel.chatableUrl) {
|
|
||||||
this.router.transitionTo(this.chatChannel.chatableUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
await composer.focusComposer({
|
|
||||||
fallbackToNewTopic: true,
|
|
||||||
insertText: quoteMarkdown,
|
|
||||||
openOpts,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// open the composer and insert text, reply to the current
|
|
||||||
// topic if there is one, use the active draft if there is one
|
|
||||||
const topic = container.lookup("controller:topic");
|
|
||||||
await composer.focusComposer({
|
|
||||||
fallbackToNewTopic: true,
|
|
||||||
topic: topic?.model,
|
|
||||||
insertText: quoteMarkdown,
|
|
||||||
openOpts,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
async copyMessages() {
|
|
||||||
try {
|
|
||||||
this.set("chatCopySuccess", false);
|
|
||||||
|
|
||||||
if (!isTesting()) {
|
|
||||||
// clipboard API throws errors in tests
|
|
||||||
await clipboardCopyAsync(this.generateQuote);
|
|
||||||
this.set("chatCopySuccess", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("showChatCopySuccess", true);
|
|
||||||
|
|
||||||
schedule("afterRender", () => {
|
|
||||||
const element = document.querySelector(".chat-selection-message");
|
|
||||||
element?.addEventListener("animationend", () => {
|
|
||||||
if (this.isDestroying || this.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("showChatCopySuccess", false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
popupAjaxError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,15 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.chatThreadPane.selectingMessages}}
|
{{#if this.chatThreadPane.selectingMessages}}
|
||||||
<ChatSelectionManager
|
<Chat::SelectionManager @pane={{this.chatThreadPane}} />
|
||||||
@selectedMessageIds={{this.chatThreadPane.selectedMessageIds}}
|
|
||||||
@chatChannel={{@channel}}
|
|
||||||
@cancelSelecting={{action
|
|
||||||
this.chatThreadPane.cancelSelecting
|
|
||||||
@channel.selectedMessages
|
|
||||||
}}
|
|
||||||
@context="thread"
|
|
||||||
/>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<Chat::Composer::Thread
|
<Chat::Composer::Thread
|
||||||
@channel={{@channel}}
|
@channel={{@channel}}
|
||||||
|
@ -1,41 +1,30 @@
|
|||||||
<div
|
<div class="chat-selection-management">
|
||||||
class={{concat-class
|
<div class="chat-selection-management__buttons">
|
||||||
"chat-selection-management"
|
|
||||||
(if this.chatCopySuccess "chat-copy-success")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="chat-selection-management-buttons">
|
|
||||||
<DButton
|
<DButton
|
||||||
@id="chat-quote-btn"
|
@id="chat-quote-btn"
|
||||||
@class="btn-secondary"
|
|
||||||
@icon="quote-left"
|
@icon="quote-left"
|
||||||
@label="chat.selection.quote_selection"
|
@label="chat.selection.quote_selection"
|
||||||
@title="chat.selection.quote_selection"
|
|
||||||
@disabled={{not this.anyMessagesSelected}}
|
@disabled={{not this.anyMessagesSelected}}
|
||||||
@action={{action "quoteMessages"}}
|
@action={{this.quoteMessages}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{{#if this.site.desktopView}}
|
{{#if this.site.desktopView}}
|
||||||
<DButton
|
<DButton
|
||||||
@id="chat-copy-btn"
|
@id="chat-copy-btn"
|
||||||
@class="btn-secondary"
|
|
||||||
@icon="copy"
|
@icon="copy"
|
||||||
@label="chat.selection.copy"
|
@label="chat.selection.copy"
|
||||||
@title="chat.selection.copy"
|
|
||||||
@disabled={{not this.anyMessagesSelected}}
|
@disabled={{not this.anyMessagesSelected}}
|
||||||
@action={{action "copyMessages"}}
|
@action={{this.copyMessages}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.showMoveMessageButton}}
|
{{#if this.enableMove}}
|
||||||
<DButton
|
<DButton
|
||||||
@id="chat-move-to-channel-btn"
|
@id="chat-move-to-channel-btn"
|
||||||
@class="btn-secondary"
|
|
||||||
@icon="sign-out-alt"
|
@icon="sign-out-alt"
|
||||||
@label="chat.selection.move_selection_to_channel"
|
@label="chat.selection.move_selection_to_channel"
|
||||||
@title="chat.selection.move_selection_to_channel"
|
|
||||||
@disabled={{not this.anyMessagesSelected}}
|
@disabled={{not this.anyMessagesSelected}}
|
||||||
@action={{action "openMoveMessageModal"}}
|
@action={{this.openMoveMessageModal}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
@ -44,14 +33,16 @@
|
|||||||
@icon="times"
|
@icon="times"
|
||||||
@class="btn-secondary cancel-btn"
|
@class="btn-secondary cancel-btn"
|
||||||
@label="chat.selection.cancel"
|
@label="chat.selection.cancel"
|
||||||
@title="chat.selection.cancel"
|
@action={{@pane.cancelSelecting}}
|
||||||
@action={{this.cancelSelecting}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.showChatCopySuccess}}
|
{{#if this.showCopySuccess}}
|
||||||
<div class="chat-selection-message">
|
<span
|
||||||
|
class="chat-selection-management__copy-success"
|
||||||
|
{{chat/on-animation-end (fn (mut this.showCopySuccess) false)}}
|
||||||
|
>
|
||||||
{{i18n "chat.quote.copy_success"}}
|
{{i18n "chat.quote.copy_success"}}
|
||||||
</div>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
@ -0,0 +1,103 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import { clipboardCopyAsync } from "discourse/lib/utilities";
|
||||||
|
import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
|
import { isTesting } from "discourse-common/config/environment";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class ChatSelectionManager extends Component {
|
||||||
|
@service("composer") topicComposer;
|
||||||
|
@service router;
|
||||||
|
@service site;
|
||||||
|
@service("chat-api") api;
|
||||||
|
|
||||||
|
@tracked showCopySuccess = false;
|
||||||
|
|
||||||
|
get enableMove() {
|
||||||
|
return this.args.enableMove ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get anyMessagesSelected() {
|
||||||
|
return this.args.pane.selectedMessageIds.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
async generateQuote() {
|
||||||
|
const { markdown } = await this.api.generateQuote(
|
||||||
|
this.args.pane.channel.id,
|
||||||
|
this.args.pane.selectedMessageIds
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Blob([markdown], { type: "text/plain" });
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
openMoveMessageModal() {
|
||||||
|
showModal("chat-message-move-to-channel-modal").setProperties({
|
||||||
|
sourceChannel: this.args.pane.channel,
|
||||||
|
selectedMessageIds: this.args.pane.selectedMessageIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async quoteMessages() {
|
||||||
|
let quoteMarkdown;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const quoteMarkdownBlob = await this.generateQuote();
|
||||||
|
quoteMarkdown = await quoteMarkdownBlob.text();
|
||||||
|
} catch (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const openOpts = {};
|
||||||
|
if (this.args.pane.channel.isCategoryChannel) {
|
||||||
|
openOpts.categoryId = this.args.pane.channel.chatableId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.site.mobileView) {
|
||||||
|
// go to the relevant chatable (e.g. category) and open the
|
||||||
|
// composer to insert text
|
||||||
|
if (this.args.pane.channel.chatableUrl) {
|
||||||
|
this.router.transitionTo(this.args.pane.channel.chatableUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.topicComposer.focusComposer({
|
||||||
|
fallbackToNewTopic: true,
|
||||||
|
insertText: quoteMarkdown,
|
||||||
|
openOpts,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// open the composer and insert text, reply to the current
|
||||||
|
// topic if there is one, use the active draft if there is one
|
||||||
|
const container = getOwner(this);
|
||||||
|
const topic = container.lookup("controller:topic");
|
||||||
|
await this.topicComposer.focusComposer({
|
||||||
|
fallbackToNewTopic: true,
|
||||||
|
topic: topic?.model,
|
||||||
|
insertText: quoteMarkdown,
|
||||||
|
openOpts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async copyMessages() {
|
||||||
|
try {
|
||||||
|
this.showCopySuccess = false;
|
||||||
|
|
||||||
|
if (!isTesting()) {
|
||||||
|
// clipboard API throws errors in tests
|
||||||
|
await clipboardCopyAsync(this.generateQuote);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showCopySuccess = true;
|
||||||
|
} catch (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -336,4 +336,8 @@ export default class ChatChannel {
|
|||||||
method: "PUT",
|
method: "PUT",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearSelectedMessages() {
|
||||||
|
this.selectedMessages.forEach((message) => (message.selected = false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,10 @@ export default class ChatThread {
|
|||||||
return this.messagesManager.findLastUserMessage(user);
|
return this.messagesManager.findLastUserMessage(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearSelectedMessages() {
|
||||||
|
this.selectedMessages.forEach((message) => (message.selected = false));
|
||||||
|
}
|
||||||
|
|
||||||
get routeModels() {
|
get routeModels() {
|
||||||
return [...this.channel.routeModels, this.id];
|
return [...this.channel.routeModels, this.id];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import Modifier from "ember-modifier";
|
||||||
|
import { registerDestructor } from "@ember/destroyable";
|
||||||
|
import { cancel, schedule } from "@ember/runloop";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default class ChatOnAnimationEnd extends Modifier {
|
||||||
|
constructor(owner, args) {
|
||||||
|
super(owner, args);
|
||||||
|
registerDestructor(this, (instance) => instance.cleanup());
|
||||||
|
}
|
||||||
|
|
||||||
|
modify(element, [fn]) {
|
||||||
|
this.element = element;
|
||||||
|
this.fn = fn;
|
||||||
|
|
||||||
|
this.handler = schedule("afterRender", () => {
|
||||||
|
this.element.addEventListener("animationend", this.handleAnimationEnd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
handleAnimationEnd() {
|
||||||
|
if (this.isDestroying || this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fn?.(this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
cancel(this.handler);
|
||||||
|
this.element?.removeEventListener("animationend", this.handleAnimationEnd);
|
||||||
|
}
|
||||||
|
}
|
@ -431,6 +431,19 @@ export default class ChatApi extends Service {
|
|||||||
return this.#putRequest(`/channels/${channelId}/threads/${threadId}`, data);
|
return this.#putRequest(`/channels/${channelId}/threads/${threadId}`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a quote for a list of messages.
|
||||||
|
*
|
||||||
|
* @param {number} channelId - The ID of the channel containing the messages.
|
||||||
|
* @param {Array<number>} messageIds - The IDs of the messages to quote.
|
||||||
|
*/
|
||||||
|
generateQuote(channelId, messageIds) {
|
||||||
|
return ajax(`/chat/${channelId}/quote`, {
|
||||||
|
type: "POST",
|
||||||
|
data: { message_ids: messageIds },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get #basePath() {
|
get #basePath() {
|
||||||
return "/chat/api";
|
return "/chat/api";
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,22 @@ export default class ChatChannelPane extends Service {
|
|||||||
@tracked lastSelectedMessage = null;
|
@tracked lastSelectedMessage = null;
|
||||||
@tracked sending = false;
|
@tracked sending = false;
|
||||||
|
|
||||||
get selectedMessageIds() {
|
|
||||||
return this.chat.activeChannel?.selectedMessages?.mapBy("id") || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get channel() {
|
get channel() {
|
||||||
return this.chat.activeChannel;
|
return this.chat.activeChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
get selectedMessages() {
|
||||||
cancelSelecting(selectedMessages) {
|
return this.channel?.selectedMessages;
|
||||||
this.selectingMessages = false;
|
}
|
||||||
|
|
||||||
selectedMessages.forEach((message) => {
|
get selectedMessageIds() {
|
||||||
message.selected = false;
|
return this.selectedMessages.mapBy("id");
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
cancelSelecting() {
|
||||||
|
this.selectingMessages = false;
|
||||||
|
this.channel.clearSelectedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -32,8 +33,4 @@ export default class ChatChannelPane extends Service {
|
|||||||
this.lastSelectedMessage = message;
|
this.lastSelectedMessage = message;
|
||||||
this.selectingMessages = true;
|
this.selectingMessages = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastMessage() {
|
|
||||||
return this.chat.activeChannel.messages.lastObject;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,20 @@ export default class ChatThreadPane extends ChatChannelPane {
|
|||||||
@service chat;
|
@service chat;
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
|
get thread() {
|
||||||
|
return this.channel?.activeThread;
|
||||||
|
}
|
||||||
|
|
||||||
get isOpened() {
|
get isOpened() {
|
||||||
return this.router.currentRoute.name === "chat.channel.thread";
|
return this.router.currentRoute.name === "chat.channel.thread";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get selectedMessages() {
|
||||||
|
return this.thread?.selectedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
await this.router.transitionTo(
|
await this.router.transitionTo("chat.channel", ...this.channel.routeModels);
|
||||||
"chat.channel",
|
|
||||||
...this.chat.activeChannel.routeModels
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async open(thread) {
|
async open(thread) {
|
||||||
@ -22,8 +27,4 @@ export default class ChatThreadPane extends ChatChannelPane {
|
|||||||
...thread.routeModels
|
...thread.routeModels
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedMessageIds() {
|
|
||||||
return this.chat.activeChannel.activeThread.selectedMessages.mapBy("id");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-selection-management-buttons {
|
&__buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-selection-message {
|
&__copy-success {
|
||||||
animation: chat-quote-message-background-fade-highlight 2s ease-out 3s;
|
animation: chat-quote-message-background-fade-highlight 2s ease-out 3s;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
background-color: var(--success-low);
|
background-color: var(--success-low);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.chat-selection-management {
|
.chat-selection-management {
|
||||||
.chat-selection-management-buttons {
|
&__buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
RSpec.describe "Channel message selection", type: :system do
|
|
||||||
fab!(:current_user) { Fabricate(:user) }
|
|
||||||
fab!(:channel_1) { Fabricate(:chat_channel) }
|
|
||||||
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) }
|
|
||||||
|
|
||||||
let(:chat_page) { PageObjects::Pages::Chat.new }
|
|
||||||
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
|
||||||
let(:thread_page) { PageObjects::Pages::ChatThread.new }
|
|
||||||
|
|
||||||
before do
|
|
||||||
chat_system_bootstrap
|
|
||||||
channel_1.add(current_user)
|
|
||||||
sign_in(current_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can select multiple messages" do
|
|
||||||
chat_page.visit_channel(channel_1)
|
|
||||||
channel_page.select_message(message_1)
|
|
||||||
|
|
||||||
expect(page).to have_css(".chat-selection-management")
|
|
||||||
|
|
||||||
channel_page.message_by_id(message_2.id).find(".chat-message-selector").click
|
|
||||||
expect(page).to have_css(".chat-message-selector:checked", count: 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can shift + click to select messages between the first and last" do
|
|
||||||
chat_page.visit_channel(channel_1)
|
|
||||||
channel_page.select_message(message_1)
|
|
||||||
|
|
||||||
expect(page).to have_css(".chat-selection-management")
|
|
||||||
|
|
||||||
channel_page.message_by_id(message_3.id).find(".chat-message-selector").click(:shift)
|
|
||||||
expect(page).to have_css(".chat-message-selector:checked", count: 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when visiting another channel" do
|
|
||||||
fab!(:channel_2) { Fabricate(:chat_channel) }
|
|
||||||
|
|
||||||
before { channel_2.add(current_user) }
|
|
||||||
|
|
||||||
it "resets message selection" do
|
|
||||||
chat_page.visit_channel(channel_1)
|
|
||||||
channel_page.select_message(message_1)
|
|
||||||
|
|
||||||
expect(page).to have_css(".chat-selection-management")
|
|
||||||
|
|
||||||
chat_page.visit_channel(channel_2)
|
|
||||||
|
|
||||||
expect(page).to have_no_css(".chat-selection-management")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when in a thread" do
|
|
||||||
fab!(:thread_message_1) do
|
|
||||||
Chat::MessageCreator.create(
|
|
||||||
chat_channel: channel_1,
|
|
||||||
in_reply_to_id: message_1.id,
|
|
||||||
user: Fabricate(:user),
|
|
||||||
content: Faker::Lorem.paragraph,
|
|
||||||
).chat_message
|
|
||||||
end
|
|
||||||
|
|
||||||
fab!(:thread_message_2) do
|
|
||||||
Chat::MessageCreator.create(
|
|
||||||
chat_channel: channel_1,
|
|
||||||
in_reply_to_id: message_1.id,
|
|
||||||
user: Fabricate(:user),
|
|
||||||
content: Faker::Lorem.paragraph,
|
|
||||||
).chat_message
|
|
||||||
end
|
|
||||||
|
|
||||||
fab!(:thread_message_3) do
|
|
||||||
Chat::MessageCreator.create(
|
|
||||||
chat_channel: channel_1,
|
|
||||||
in_reply_to_id: message_1.id,
|
|
||||||
user: Fabricate(:user),
|
|
||||||
content: Faker::Lorem.paragraph,
|
|
||||||
).chat_message
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
SiteSetting.enable_experimental_chat_threaded_discussions = true
|
|
||||||
channel_1.update!(threading_enabled: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can select multiple messages" do
|
|
||||||
chat_page.visit_thread(thread_message_1.thread)
|
|
||||||
thread_page.select_message(thread_message_1)
|
|
||||||
|
|
||||||
expect(thread_page).to have_css(".chat-selection-management")
|
|
||||||
|
|
||||||
thread_page.message_by_id(thread_message_2.id).find(".chat-message-selector").click
|
|
||||||
|
|
||||||
expect(thread_page).to have_css(".chat-message-selector:checked", count: 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can shift + click to select messages between the first and last" do
|
|
||||||
chat_page.visit_thread(thread_message_1.thread)
|
|
||||||
thread_page.select_message(thread_message_1)
|
|
||||||
|
|
||||||
expect(thread_page).to have_css(".chat-selection-management")
|
|
||||||
|
|
||||||
thread_page.message_by_id(thread_message_3.id).find(".chat-message-selector").click(:shift)
|
|
||||||
|
|
||||||
expect(page).to have_css(".chat-message-selector:checked", count: 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -18,9 +18,9 @@ RSpec.describe "Move message to channel", type: :system do
|
|||||||
|
|
||||||
it "is not available" do
|
it "is not available" do
|
||||||
chat_page.visit_channel(channel_1)
|
chat_page.visit_channel(channel_1)
|
||||||
channel_page.select_message(message_1)
|
channel_page.messages.select(message_1)
|
||||||
|
|
||||||
expect(page).to have_no_content(I18n.t("js.chat.selection.move_selection_to_channel"))
|
expect(channel_page.selection_management).to have_no_move_action
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when can moderate channel" do
|
context "when can moderate channel" do
|
||||||
@ -37,9 +37,9 @@ RSpec.describe "Move message to channel", type: :system do
|
|||||||
|
|
||||||
it "is available" do
|
it "is available" do
|
||||||
chat_page.visit_channel(channel_1)
|
chat_page.visit_channel(channel_1)
|
||||||
channel_page.select_message(message_1)
|
channel_page.messages.select(message_1)
|
||||||
|
|
||||||
expect(page).to have_content(I18n.t("js.chat.selection.move_selection_to_channel"))
|
expect(channel_page.selection_management).to have_move_action
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -57,9 +57,9 @@ RSpec.describe "Move message to channel", type: :system do
|
|||||||
|
|
||||||
it "is not available" do
|
it "is not available" do
|
||||||
chat_page.visit_channel(dm_channel_1)
|
chat_page.visit_channel(dm_channel_1)
|
||||||
channel_page.select_message(message_1)
|
channel_page.messages.select(message_1)
|
||||||
|
|
||||||
expect(page).to have_no_content(I18n.t("js.chat.selection.move_selection_to_channel"))
|
expect(channel_page.selection_management).to have_no_move_action
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -77,8 +77,8 @@ RSpec.describe "Move message to channel", type: :system do
|
|||||||
|
|
||||||
it "moves the message" do
|
it "moves the message" do
|
||||||
chat_page.visit_channel(channel_1)
|
chat_page.visit_channel(channel_1)
|
||||||
channel_page.select_message(message_1)
|
channel_page.messages.select(message_1)
|
||||||
click_button(I18n.t("js.chat.selection.move_selection_to_channel"))
|
channel_page.selection_management.move
|
||||||
find(".chat-move-message-channel-chooser").click
|
find(".chat-move-message-channel-chooser").click
|
||||||
find("[data-value='#{channel_2.id}']").click
|
find("[data-value='#{channel_2.id}']").click
|
||||||
click_button(I18n.t("js.chat.move_to_channel.confirm_move"))
|
click_button(I18n.t("js.chat.move_to_channel.confirm_move"))
|
||||||
|
@ -11,6 +11,15 @@ module PageObjects
|
|||||||
@messages ||= PageObjects::Components::Chat::Messages.new(".chat-channel")
|
@messages ||= PageObjects::Components::Chat::Messages.new(".chat-channel")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def selection_management
|
||||||
|
@selection_management ||=
|
||||||
|
PageObjects::Components::Chat::SelectionManagement.new(".chat-channel")
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_selected_messages?(*messages)
|
||||||
|
self.messages.has_selected_messages?(*messages)
|
||||||
|
end
|
||||||
|
|
||||||
def replying_to?(message)
|
def replying_to?(message)
|
||||||
find(".chat-channel .chat-reply", text: message.message)
|
find(".chat-channel .chat-reply", text: message.message)
|
||||||
end
|
end
|
||||||
@ -76,16 +85,6 @@ module PageObjects
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_message(message)
|
|
||||||
if page.has_css?("html.mobile-view", wait: 0)
|
|
||||||
click_message_action_mobile(message, "select")
|
|
||||||
else
|
|
||||||
hover_message(message)
|
|
||||||
click_more_button
|
|
||||||
find("[data-value='select']").click
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def click_more_button
|
def click_more_button
|
||||||
find(".more-buttons").click
|
find(".more-buttons").click
|
||||||
end
|
end
|
||||||
|
@ -20,6 +20,15 @@ module PageObjects
|
|||||||
@header ||= PageObjects::Components::Chat::ThreadHeader.new(".chat-thread")
|
@header ||= PageObjects::Components::Chat::ThreadHeader.new(".chat-thread")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def selection_management
|
||||||
|
@selection_management ||=
|
||||||
|
PageObjects::Components::Chat::SelectionManagement.new(".chat-channel")
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_selected_messages?(*messages)
|
||||||
|
self.messages.has_selected_messages?(*messages)
|
||||||
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
header.find(".chat-thread__close").click
|
header.find(".chat-thread__close").click
|
||||||
end
|
end
|
||||||
@ -110,12 +119,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 select_message(message)
|
|
||||||
hover_message(message)
|
|
||||||
click_more_button
|
|
||||||
find("[data-value='select']").click
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_deleted_message?(message, count: 1)
|
def has_deleted_message?(message, count: 1)
|
||||||
has_css?(
|
has_css?(
|
||||||
".chat-thread .chat-message-container[data-id=\"#{message.id}\"] .chat-message-deleted",
|
".chat-thread .chat-message-container[data-id=\"#{message.id}\"] .chat-message-deleted",
|
||||||
|
@ -5,6 +5,7 @@ module PageObjects
|
|||||||
module Chat
|
module Chat
|
||||||
class Message < PageObjects::Components::Base
|
class Message < PageObjects::Components::Base
|
||||||
attr_reader :context
|
attr_reader :context
|
||||||
|
attr_reader :component
|
||||||
|
|
||||||
SELECTOR = ".chat-message-container"
|
SELECTOR = ".chat-message-container"
|
||||||
|
|
||||||
@ -16,31 +17,80 @@ module PageObjects
|
|||||||
exists?(**args, does_not_exist: true)
|
exists?(**args, does_not_exist: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def exists?(**args)
|
def hover
|
||||||
text = args[:text]
|
message_by_id(message.id).hover
|
||||||
|
end
|
||||||
|
|
||||||
selectors = SELECTOR
|
def select(shift: false)
|
||||||
selectors += "[data-id=\"#{args[:id]}\"]" if args[:id]
|
if component[:class].include?("selecting-message")
|
||||||
selectors += ".is-persisted" if args[:persisted]
|
message_selector = component.find(".chat-message-selector")
|
||||||
selectors += ".is-staged" if args[:staged]
|
if shift
|
||||||
|
message_selector.click(:shift)
|
||||||
|
else
|
||||||
|
message_selector.click
|
||||||
|
end
|
||||||
|
|
||||||
if args[:deleted]
|
return
|
||||||
selectors += ".is-deleted"
|
|
||||||
text = I18n.t("js.chat.deleted", count: args[:deleted])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if page.has_css?("html.mobile-view", wait: 0)
|
||||||
|
component.click(delay: 0.6)
|
||||||
|
page.find(".chat-message-actions [data-id=\"select\"]").click
|
||||||
|
else
|
||||||
|
component.hover
|
||||||
|
click_more_button
|
||||||
|
page.find("[data-value='select']").click
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(**args)
|
||||||
|
selector = build_selector(**args)
|
||||||
|
text = args[:text]
|
||||||
|
text = I18n.t("js.chat.deleted", count: args[:deleted]) if args[:deleted]
|
||||||
|
|
||||||
|
if text
|
||||||
|
@component =
|
||||||
|
find(context).find("#{selector} .chat-message-text", text: /#{Regexp.escape(text)}/)
|
||||||
|
else
|
||||||
|
@component = page.find(context).find(selector)
|
||||||
|
end
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def exists?(**args)
|
||||||
|
selector = build_selector(**args)
|
||||||
|
text = args[:text]
|
||||||
|
text = I18n.t("js.chat.deleted", count: args[:deleted]) if args[:deleted]
|
||||||
|
|
||||||
selector_method = args[:does_not_exist] ? :has_no_selector? : :has_selector?
|
selector_method = args[:does_not_exist] ? :has_no_selector? : :has_selector?
|
||||||
|
|
||||||
if text
|
if text
|
||||||
find(context).send(
|
page.find(context).send(
|
||||||
selector_method,
|
selector_method,
|
||||||
selectors + " " + ".chat-message-text",
|
selector + " " + ".chat-message-text",
|
||||||
text: /#{Regexp.escape(text)}/,
|
text: /#{Regexp.escape(text)}/,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
find(context).send(selector_method, selectors)
|
page.find(context).send(selector_method, selector)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def click_more_button
|
||||||
|
page.find(".more-buttons").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_selector(**args)
|
||||||
|
selector = SELECTOR
|
||||||
|
selector += "[data-id=\"#{args[:id]}\"]" if args[:id]
|
||||||
|
selector += "[data-selected]" if args[:selected]
|
||||||
|
selector += ".is-persisted" if args[:persisted]
|
||||||
|
selector += ".is-staged" if args[:staged]
|
||||||
|
selector += ".is-deleted" if args[:deleted]
|
||||||
|
selector
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -13,11 +13,23 @@ module PageObjects
|
|||||||
end
|
end
|
||||||
|
|
||||||
def component
|
def component
|
||||||
find(context)
|
page.find(context)
|
||||||
end
|
end
|
||||||
|
|
||||||
def message
|
def select(args)
|
||||||
PageObjects::Components::Chat::Message.new(context + " " + SELECTOR)
|
find(args).select
|
||||||
|
end
|
||||||
|
|
||||||
|
def shift_select(args)
|
||||||
|
find(args).select(shift: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(args)
|
||||||
|
if args.is_a?(Hash)
|
||||||
|
message.find(**args)
|
||||||
|
else
|
||||||
|
message.find(id: args.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_message?(**args)
|
def has_message?(**args)
|
||||||
@ -27,6 +39,16 @@ module PageObjects
|
|||||||
def has_no_message?(**args)
|
def has_no_message?(**args)
|
||||||
message.does_not_exist?(**args)
|
message.does_not_exist?(**args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_selected_messages?(*messages)
|
||||||
|
messages.all? { |message| has_message?(id: message.id, selected: true) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def message
|
||||||
|
PageObjects::Components::Chat::Message.new("#{context} #{SELECTOR}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Components
|
||||||
|
module Chat
|
||||||
|
class SelectionManagement < PageObjects::Components::Base
|
||||||
|
attr_reader :context
|
||||||
|
|
||||||
|
SELECTOR = ".chat-selection-management"
|
||||||
|
|
||||||
|
def initialize(context)
|
||||||
|
@context = context
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible?
|
||||||
|
find(context).has_css?(SELECTOR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def not_visible?
|
||||||
|
find(context).has_no_css?(SELECTOR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_no_move_action?
|
||||||
|
has_no_button?(selector_for("move"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_move_action?
|
||||||
|
has_button?(selector_for("move"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def component
|
||||||
|
find(context).find(SELECTOR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cancel
|
||||||
|
click_button("cancel")
|
||||||
|
end
|
||||||
|
|
||||||
|
def quote
|
||||||
|
click_button("quote")
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy
|
||||||
|
click_button("copy")
|
||||||
|
end
|
||||||
|
|
||||||
|
def move
|
||||||
|
click_button("move")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def selector_for(action)
|
||||||
|
case action
|
||||||
|
when "quote"
|
||||||
|
"chat-quote-btn"
|
||||||
|
when "copy"
|
||||||
|
"chat-copy-btn"
|
||||||
|
when "cancel"
|
||||||
|
"chat-cancel-selection-btn"
|
||||||
|
when "move"
|
||||||
|
"chat-move-to-channel-btn"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_button(action)
|
||||||
|
find_button(selector_for(action), disabled: false).click
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
52
plugins/chat/spec/system/select_message/channel_spec.rb
Normal file
52
plugins/chat/spec/system/select_message/channel_spec.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe "Chat | Select message | channel", type: :system do
|
||||||
|
fab!(:current_user) { Fabricate(:user) }
|
||||||
|
fab!(:channel_1) { Fabricate(:chat_channel) }
|
||||||
|
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) }
|
||||||
|
|
||||||
|
let(:chat_page) { PageObjects::Pages::Chat.new }
|
||||||
|
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
||||||
|
|
||||||
|
before do
|
||||||
|
chat_system_bootstrap
|
||||||
|
channel_1.add(current_user)
|
||||||
|
sign_in(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can select multiple messages" do
|
||||||
|
chat_page.visit_channel(channel_1)
|
||||||
|
|
||||||
|
channel_page.messages.select(message_1)
|
||||||
|
channel_page.messages.select(message_2)
|
||||||
|
|
||||||
|
expect(channel_page).to have_selected_messages(message_1, message_2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can shift + click to select messages between the first and last" do
|
||||||
|
chat_page.visit_channel(channel_1)
|
||||||
|
channel_page.messages.select(message_1)
|
||||||
|
channel_page.messages.shift_select(message_3)
|
||||||
|
|
||||||
|
expect(channel_page).to have_selected_messages(message_1, message_2, message_3)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when visiting another channel" do
|
||||||
|
fab!(:channel_2) { Fabricate(:chat_channel) }
|
||||||
|
|
||||||
|
before { channel_2.add(current_user) }
|
||||||
|
|
||||||
|
it "resets message selection" do
|
||||||
|
chat_page.visit_channel(channel_1)
|
||||||
|
channel_page.messages.select(message_1)
|
||||||
|
|
||||||
|
expect(channel_page.selection_management).to be_visible
|
||||||
|
|
||||||
|
chat_page.visit_channel(channel_2)
|
||||||
|
|
||||||
|
expect(channel_page.selection_management).to be_not_visible
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
53
plugins/chat/spec/system/select_message/thread_spec.rb
Normal file
53
plugins/chat/spec/system/select_message/thread_spec.rb
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe "Chat | Select message | thread", type: :system do
|
||||||
|
fab!(:current_user) { Fabricate(:user) }
|
||||||
|
fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) }
|
||||||
|
fab!(:original_message) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
|
||||||
|
let(:chat_page) { PageObjects::Pages::Chat.new }
|
||||||
|
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
||||||
|
let(:thread_page) { PageObjects::Pages::ChatThread.new }
|
||||||
|
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_experimental_chat_threaded_discussions = true
|
||||||
|
chat_system_bootstrap
|
||||||
|
channel_1.add(current_user)
|
||||||
|
sign_in(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
fab!(:thread_message_1) do
|
||||||
|
Fabricate(:chat_message, chat_channel: channel_1, in_reply_to: original_message)
|
||||||
|
end
|
||||||
|
fab!(:thread_message_2) do
|
||||||
|
Fabricate(:chat_message, chat_channel: channel_1, in_reply_to: original_message)
|
||||||
|
end
|
||||||
|
fab!(:thread_message_3) do
|
||||||
|
Fabricate(:chat_message, chat_channel: channel_1, in_reply_to: original_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_experimental_chat_threaded_discussions = true
|
||||||
|
channel_1.update!(threading_enabled: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can select multiple messages" do
|
||||||
|
chat_page.visit_thread(thread_message_1.thread)
|
||||||
|
thread_page.messages.select(thread_message_1)
|
||||||
|
thread_page.messages.select(thread_message_2)
|
||||||
|
|
||||||
|
expect(thread_page).to have_selected_messages(thread_message_1, thread_message_2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can shift + click to select messages between the first and last" do
|
||||||
|
chat_page.visit_thread(thread_message_1.thread)
|
||||||
|
thread_page.messages.select(thread_message_1)
|
||||||
|
thread_page.messages.shift_select(thread_message_3)
|
||||||
|
|
||||||
|
expect(thread_page).to have_selected_messages(
|
||||||
|
thread_message_1,
|
||||||
|
thread_message_2,
|
||||||
|
thread_message_3,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
@ -7,7 +7,7 @@ RSpec.describe "Quoting chat message transcripts", type: :system do
|
|||||||
|
|
||||||
let(:cdp) { PageObjects::CDP.new }
|
let(:cdp) { PageObjects::CDP.new }
|
||||||
let(:chat_page) { PageObjects::Pages::Chat.new }
|
let(:chat_page) { PageObjects::Pages::Chat.new }
|
||||||
let(:chat_channel_page) { PageObjects::Pages::ChatChannel.new }
|
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
||||||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@ -16,38 +16,11 @@ RSpec.describe "Quoting chat message transcripts", type: :system do
|
|||||||
sign_in(current_user)
|
sign_in(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_message_desktop(message)
|
|
||||||
if page.has_css?(".chat-message-container.selecting-messages", wait: 0)
|
|
||||||
chat_channel_page.message_by_id(message.id).find(".chat-message-selector").click
|
|
||||||
else
|
|
||||||
chat_channel_page.message_by_id(message.id).hover
|
|
||||||
expect(page).to have_css(".chat-message-actions .more-buttons")
|
|
||||||
find(".chat-message-actions .more-buttons").click
|
|
||||||
find(".select-kit-row[data-value=\"select\"]").click
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def click_selection_button(button)
|
|
||||||
selector =
|
|
||||||
case button
|
|
||||||
when "quote"
|
|
||||||
"chat-quote-btn"
|
|
||||||
when "copy"
|
|
||||||
"chat-copy-btn"
|
|
||||||
when "cancel"
|
|
||||||
"chat-cancel-selection-btn"
|
|
||||||
when "move"
|
|
||||||
"chat-move-to-channel-btn"
|
|
||||||
end
|
|
||||||
find_button(selector, disabled: false, wait: 5).click
|
|
||||||
end
|
|
||||||
|
|
||||||
def copy_messages_to_clipboard(messages)
|
def copy_messages_to_clipboard(messages)
|
||||||
messages = Array.wrap(messages)
|
messages = Array.wrap(messages)
|
||||||
messages.each { |message| select_message_desktop(message) }
|
messages.each { |message| channel_page.messages.select(message) }
|
||||||
expect(chat_channel_page).to have_selection_management
|
channel_page.selection_management.copy
|
||||||
click_selection_button("copy")
|
expect(page).to have_selector(".chat-selection-management__copy-success")
|
||||||
expect(page).to have_selector(".chat-copy-success")
|
|
||||||
clip_text = cdp.read_clipboard
|
clip_text = cdp.read_clipboard
|
||||||
expect(clip_text.chomp).to eq(generate_transcript(messages, current_user))
|
expect(clip_text.chomp).to eq(generate_transcript(messages, current_user))
|
||||||
clip_text
|
clip_text
|
||||||
@ -140,8 +113,8 @@ RSpec.describe "Quoting chat message transcripts", type: :system do
|
|||||||
chat_page.visit_channel(chat_channel_1)
|
chat_page.visit_channel(chat_channel_1)
|
||||||
|
|
||||||
clip_text = copy_messages_to_clipboard(message_1)
|
clip_text = copy_messages_to_clipboard(message_1)
|
||||||
click_selection_button("cancel")
|
channel_page.selection_management.cancel
|
||||||
chat_channel_page.send_message(clip_text)
|
channel_page.send_message(clip_text)
|
||||||
|
|
||||||
expect(page).to have_selector(".chat-message", count: 2)
|
expect(page).to have_selector(".chat-message", count: 2)
|
||||||
expect(page).to have_css(".chat-transcript")
|
expect(page).to have_css(".chat-transcript")
|
||||||
@ -155,9 +128,8 @@ RSpec.describe "Quoting chat message transcripts", type: :system do
|
|||||||
|
|
||||||
it "opens the topic composer with correct state" do
|
it "opens the topic composer with correct state" do
|
||||||
chat_page.visit_channel(chat_channel_1)
|
chat_page.visit_channel(chat_channel_1)
|
||||||
|
channel_page.messages.select(message_1)
|
||||||
select_message_desktop(message_1)
|
channel_page.selection_management.quote
|
||||||
click_selection_button("quote")
|
|
||||||
|
|
||||||
expect(topic_page).to have_expanded_composer
|
expect(topic_page).to have_expanded_composer
|
||||||
expect(topic_page).to have_composer_content(generate_transcript(message_1, current_user))
|
expect(topic_page).to have_composer_content(generate_transcript(message_1, current_user))
|
||||||
@ -181,8 +153,8 @@ RSpec.describe "Quoting chat message transcripts", type: :system do
|
|||||||
it "first navigates to the channel's category before opening the topic composer with the quote prefilled",
|
it "first navigates to the channel's category before opening the topic composer with the quote prefilled",
|
||||||
mobile: true do
|
mobile: true do
|
||||||
chat_page.visit_channel(chat_channel_1)
|
chat_page.visit_channel(chat_channel_1)
|
||||||
chat_channel_page.select_message(message_1)
|
channel_page.messages.select(message_1)
|
||||||
click_selection_button("quote")
|
channel_page.selection_management.quote
|
||||||
|
|
||||||
expect(topic_page).to have_expanded_composer
|
expect(topic_page).to have_expanded_composer
|
||||||
expect(topic_page).to have_composer_content(generate_transcript(message_1, current_user))
|
expect(topic_page).to have_composer_content(generate_transcript(message_1, current_user))
|
||||||
|
Loading…
Reference in New Issue
Block a user