mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 20:03:15 +08:00
UX: implements swipe on row channel (#23436)
On mobile swiping a channel row will now show a "Remove" option. Holding this to the end will now remove this row from your list of followed direct message channels. Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
This commit is contained in:
parent
87d0336f05
commit
b8d5f951f6
|
@ -43,7 +43,14 @@
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="public-channels" class={{this.publicChannelClasses}}>
|
<div
|
||||||
|
id="public-channels"
|
||||||
|
class={{concat-class
|
||||||
|
"channels-list-container"
|
||||||
|
"public-channels"
|
||||||
|
(if this.inSidebar "collapsible-sidebar-section")
|
||||||
|
}}
|
||||||
|
>
|
||||||
{{#if this.publicChannelsEmpty}}
|
{{#if this.publicChannelsEmpty}}
|
||||||
<div class="public-channel-empty-message">
|
<div class="public-channel-empty-message">
|
||||||
<span class="channel-title">{{i18n "chat.no_public_channels"}}</span>
|
<span class="channel-title">{{i18n "chat.no_public_channels"}}</span>
|
||||||
|
|
|
@ -65,12 +65,6 @@ export default class ChannelsList extends Component {
|
||||||
return this.chat.userCanDirectMessage;
|
return this.chat.userCanDirectMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
get publicChannelClasses() {
|
|
||||||
return `channels-list-container public-channels ${
|
|
||||||
this.inSidebar ? "collapsible-sidebar-section" : ""
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayPublicChannels() {
|
get displayPublicChannels() {
|
||||||
if (!this.siteSettings.enable_public_channels) {
|
if (!this.siteSettings.enable_public_channels) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { LinkTo } from "@ember/routing";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import eq from "truth-helpers/helpers/eq";
|
||||||
|
import and from "truth-helpers/helpers/and";
|
||||||
|
import ChatChannelTitle from "discourse/plugins/chat/discourse/components/chat-channel-title";
|
||||||
|
import ChatChannelMetadata from "discourse/plugins/chat/discourse/components/chat-channel-metadata";
|
||||||
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
|
import ToggleChannelMembershipButton from "discourse/plugins/chat/discourse/components/toggle-channel-membership-button";
|
||||||
|
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { modifier } from "ember-modifier";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
import { cancel } from "@ember/runloop";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
|
||||||
|
const RESET_CLASS = "-reset";
|
||||||
|
const FADEOUT_CLASS = "-fade-out";
|
||||||
|
|
||||||
|
export default class ChatChannelRow extends Component {
|
||||||
|
<template>
|
||||||
|
{{! template-lint-disable modifier-name-case }}
|
||||||
|
<LinkTo
|
||||||
|
@route="chat.channel"
|
||||||
|
@models={{@channel.routeModels}}
|
||||||
|
class={{concatClass
|
||||||
|
"chat-channel-row"
|
||||||
|
(if @channel.focused "focused")
|
||||||
|
(if @channel.currentUserMembership.muted "muted")
|
||||||
|
(if @options.leaveButton "can-leave")
|
||||||
|
(if (eq this.chat.activeChannel.id @channel.id) "active")
|
||||||
|
(if this.channelHasUnread "has-unread")
|
||||||
|
}}
|
||||||
|
tabindex="0"
|
||||||
|
data-chat-channel-id={{@channel.id}}
|
||||||
|
{{didInsert this.startTrackingStatus}}
|
||||||
|
{{willDestroy this.stopTrackingStatus}}
|
||||||
|
{{(if this.shouldHandleSwipe (modifier this.registerSwipableRow))}}
|
||||||
|
{{(if this.shouldHandleSwipe (modifier this.handleSwipe))}}
|
||||||
|
{{(if this.shouldRemoveChannel (modifier this.onRemoveChannel))}}
|
||||||
|
{{(if this.shouldResetRow (modifier this.onResetRow))}}
|
||||||
|
>
|
||||||
|
<ChatChannelTitle @channel={{@channel}} />
|
||||||
|
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
|
||||||
|
|
||||||
|
{{#if
|
||||||
|
(and @options.leaveButton @channel.isFollowing this.site.desktopView)
|
||||||
|
}}
|
||||||
|
<ToggleChannelMembershipButton
|
||||||
|
@channel={{@channel}}
|
||||||
|
@options={{hash
|
||||||
|
leaveClass="btn-flat chat-channel-leave-btn"
|
||||||
|
labelType="none"
|
||||||
|
leaveIcon="times"
|
||||||
|
leaveTitle=(if
|
||||||
|
@channel.isDirectMessageChannel
|
||||||
|
this.leaveDirectMessageLabel
|
||||||
|
this.leaveChannelLabel
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.shouldHandleSwipe}}
|
||||||
|
<div
|
||||||
|
class={{concatClass
|
||||||
|
"chat-channel-row__action-btn"
|
||||||
|
(if this.isCancelling "-cancel" "-leave")
|
||||||
|
}}
|
||||||
|
{{this.registerActionButton}}
|
||||||
|
{{this.positionActionButton}}
|
||||||
|
>
|
||||||
|
{{#if this.isCancelling}}
|
||||||
|
{{this.cancelActionLabel}}
|
||||||
|
{{else}}
|
||||||
|
{{this.removeActionLabel}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</LinkTo>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
@service router;
|
||||||
|
@service chat;
|
||||||
|
@service capabilities;
|
||||||
|
@service currentUser;
|
||||||
|
@service site;
|
||||||
|
@service api;
|
||||||
|
|
||||||
|
@tracked shouldRemoveChannel = false;
|
||||||
|
@tracked hasReachedThreshold = false;
|
||||||
|
@tracked isCancelling = false;
|
||||||
|
@tracked shouldResetRow = false;
|
||||||
|
@tracked actionButton;
|
||||||
|
@tracked swipableRow;
|
||||||
|
|
||||||
|
positionActionButton = modifier((element) => {
|
||||||
|
element.style.left = "100%";
|
||||||
|
});
|
||||||
|
|
||||||
|
registerActionButton = modifier((element) => {
|
||||||
|
this.actionButton = element;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSwipableRow = modifier((element) => {
|
||||||
|
this.swipableRow = element;
|
||||||
|
});
|
||||||
|
|
||||||
|
onRemoveChannel = modifier(() => {
|
||||||
|
this.swipableRow.classList.add(FADEOUT_CLASS);
|
||||||
|
|
||||||
|
const handler = discourseLater(() => {
|
||||||
|
this.chat.unfollowChannel(this.args.channel).catch(popupAjaxError);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancel(handler);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
handleSwipe = modifier((element) => {
|
||||||
|
element.addEventListener("touchstart", this.onSwipeStart, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
element.addEventListener("touchmove", this.onSwipe);
|
||||||
|
element.addEventListener("touchend", this.onSwipeEnd);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
element.removeEventListener("touchstart", this.onSwipeStart);
|
||||||
|
element.removeEventListener("touchmove", this.onSwipe);
|
||||||
|
element.removeEventListener("touchend", this.onSwipeEnd);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
onResetRow = modifier(() => {
|
||||||
|
this.swipableRow.classList.add(RESET_CLASS);
|
||||||
|
this.swipableRow.style.left = "0px";
|
||||||
|
|
||||||
|
const handler = discourseLater(() => {
|
||||||
|
this.isCancelling = false;
|
||||||
|
this.hasReachedThreshold = false;
|
||||||
|
this.shouldResetRow = false;
|
||||||
|
this.swipableRow.classList.remove(RESET_CLASS);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancel(handler);
|
||||||
|
this.swipableRow.classList.remove(RESET_CLASS);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
_lastX = null;
|
||||||
|
_towardsThreshold = false;
|
||||||
|
|
||||||
|
@bind
|
||||||
|
onSwipeStart(event) {
|
||||||
|
this.hasReachedThreshold = false;
|
||||||
|
this.isCancelling = false;
|
||||||
|
this._lastX = this.initialX = event.changedTouches[0].screenX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
onSwipe(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const touchX = event.changedTouches[0].screenX;
|
||||||
|
const diff = this.initialX - touchX;
|
||||||
|
|
||||||
|
// we don't state to be too sensitive to the touch
|
||||||
|
if (Math.abs(this._lastX - touchX) > 5) {
|
||||||
|
this._towardsThreshold = this._lastX >= touchX;
|
||||||
|
this._lastX = touchX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensures we will go back to the initial position when swiping very fast
|
||||||
|
if (diff < 10) {
|
||||||
|
this.isCancelling = false;
|
||||||
|
this.hasReachedThreshold = false;
|
||||||
|
this.swipableRow.style.left = "0px";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff >= window.innerWidth / 3) {
|
||||||
|
this.isCancelling = false;
|
||||||
|
this.hasReachedThreshold = true;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.isCancelling = !this._towardsThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actionButton.style.width = diff + "px";
|
||||||
|
this.swipableRow.style.left = -(this.initialX - touchX) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
onSwipeEnd(event) {
|
||||||
|
this._lastX = null;
|
||||||
|
const diff = this.initialX - event.changedTouches[0].screenX;
|
||||||
|
|
||||||
|
if (diff >= window.innerWidth / 3) {
|
||||||
|
this.swipableRow.style.left = "0px";
|
||||||
|
this.shouldRemoveChannel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isCancelling = true;
|
||||||
|
this.shouldResetRow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shouldHandleSwipe() {
|
||||||
|
return this.capabilities.touch && this.args.channel.isDirectMessageChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
get cancelActionLabel() {
|
||||||
|
return I18n.t("cancel_value");
|
||||||
|
}
|
||||||
|
|
||||||
|
get removeActionLabel() {
|
||||||
|
return I18n.t("chat.remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
get leaveDirectMessageLabel() {
|
||||||
|
return I18n.t("chat.direct_messages.leave");
|
||||||
|
}
|
||||||
|
|
||||||
|
get leaveChannelLabel() {
|
||||||
|
return I18n.t("chat.channel_settings.leave_channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
get channelHasUnread() {
|
||||||
|
return this.args.channel.tracking.unreadCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get #firstDirectMessageUser() {
|
||||||
|
return this.args.channel?.chatable?.users?.firstObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
startTrackingStatus() {
|
||||||
|
this.#firstDirectMessageUser?.trackStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
stopTrackingStatus() {
|
||||||
|
this.#firstDirectMessageUser?.stopTrackingStatus();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
<LinkTo
|
|
||||||
@route="chat.channel"
|
|
||||||
@models={{@channel.routeModels}}
|
|
||||||
class={{concat-class
|
|
||||||
"chat-channel-row"
|
|
||||||
(if @channel.focused "focused")
|
|
||||||
(if @channel.currentUserMembership.muted "muted")
|
|
||||||
(if @options.leaveButton "can-leave")
|
|
||||||
(if (eq this.chat.activeChannel.id @channel.id) "active")
|
|
||||||
(if this.channelHasUnread "has-unread")
|
|
||||||
}}
|
|
||||||
tabindex="0"
|
|
||||||
data-chat-channel-id={{@channel.id}}
|
|
||||||
{{did-insert this.startTrackingStatus}}
|
|
||||||
{{will-destroy this.stopTrackingStatus}}
|
|
||||||
>
|
|
||||||
<ChatChannelTitle @channel={{@channel}} />
|
|
||||||
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
|
|
||||||
|
|
||||||
{{#if (and @options.leaveButton @channel.isFollowing this.site.desktopView)}}
|
|
||||||
<ToggleChannelMembershipButton
|
|
||||||
@channel={{@channel}}
|
|
||||||
@options={{hash
|
|
||||||
leaveClass="btn-flat chat-channel-leave-btn"
|
|
||||||
labelType="none"
|
|
||||||
leaveIcon="times"
|
|
||||||
leaveTitle=(if
|
|
||||||
@channel.isDirectMessageChannel
|
|
||||||
(i18n "chat.direct_messages.leave")
|
|
||||||
(i18n "chat.channel_settings.leave_channel")
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</LinkTo>
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { inject as service } from "@ember/service";
|
|
||||||
import Component from "@glimmer/component";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
|
|
||||||
export default class ChatChannelRow extends Component {
|
|
||||||
@service router;
|
|
||||||
@service chat;
|
|
||||||
@service currentUser;
|
|
||||||
@service site;
|
|
||||||
|
|
||||||
@action
|
|
||||||
startTrackingStatus() {
|
|
||||||
this.#firstDirectMessageUser?.trackStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
stopTrackingStatus() {
|
|
||||||
this.#firstDirectMessageUser?.stopTrackingStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
get channelHasUnread() {
|
|
||||||
return this.args.channel.tracking.unreadCount > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get #firstDirectMessageUser() {
|
|
||||||
return this.args.channel?.chatable?.users?.firstObject;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,6 +12,26 @@ import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
|
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import ChatMessageSeparatorDate from "discourse/plugins/chat/discourse/components/chat-message-separator-date";
|
||||||
|
import ChatMessageSeparatorNew from "discourse/plugins/chat/discourse/components/chat-message-separator-new";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import ChatMessageInReplyToIndicator from "discourse/plugins/chat/discourse/components/chat-message-in-reply-to-indicator";
|
||||||
|
import ChatMessageLeftGutter from "discourse/plugins/chat/discourse/components/chat/message/left-gutter";
|
||||||
|
import ChatMessageAvatar from "discourse/plugins/chat/discourse/components/chat/message/avatar";
|
||||||
|
import ChatMessageError from "discourse/plugins/chat/discourse/components/chat/message/error";
|
||||||
|
import ChatMessageInfo from "discourse/plugins/chat/discourse/components/chat/message/info";
|
||||||
|
import ChatMessageText from "discourse/plugins/chat/discourse/components/chat-message-text";
|
||||||
|
import ChatMessageReaction from "discourse/plugins/chat/discourse/components/chat-message-reaction";
|
||||||
|
import ChatMessageThreadIndicator from "discourse/plugins/chat/discourse/components/chat-message-thread-indicator";
|
||||||
|
import eq from "truth-helpers/helpers/eq";
|
||||||
|
import not from "truth-helpers/helpers/not";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { Input } from "@ember/component";
|
||||||
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
|
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||||
|
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||||
|
import ChatOnLongPress from "discourse/plugins/chat/discourse/modifiers/chat/on-long-press";
|
||||||
|
|
||||||
let _chatMessageDecorators = [];
|
let _chatMessageDecorators = [];
|
||||||
let _tippyInstances = [];
|
let _tippyInstances = [];
|
||||||
|
@ -28,6 +48,139 @@ export const MENTION_KEYWORDS = ["here", "all"];
|
||||||
export const MESSAGE_CONTEXT_THREAD = "thread";
|
export const MESSAGE_CONTEXT_THREAD = "thread";
|
||||||
|
|
||||||
export default class ChatMessage extends Component {
|
export default class ChatMessage extends Component {
|
||||||
|
<template>
|
||||||
|
{{! template-lint-disable no-invalid-interactive }}
|
||||||
|
{{! template-lint-disable modifier-name-case }}
|
||||||
|
{{#if this.shouldRender}}
|
||||||
|
{{#if (eq @context "channel")}}
|
||||||
|
<ChatMessageSeparatorDate
|
||||||
|
@fetchMessagesByDate={{@fetchMessagesByDate}}
|
||||||
|
@message={{@message}}
|
||||||
|
/>
|
||||||
|
<ChatMessageSeparatorNew @message={{@message}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={{concatClass
|
||||||
|
"chat-message-container"
|
||||||
|
(if this.pane.selectingMessages "-selectable")
|
||||||
|
(if @message.highlighted "-highlighted")
|
||||||
|
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
|
||||||
|
(if @message.staged "-staged" "-persisted")
|
||||||
|
(if this.hasActiveState "-active")
|
||||||
|
(if @message.bookmark "-bookmarked")
|
||||||
|
(if @message.deletedAt "-deleted")
|
||||||
|
(if @message.selected "-selected")
|
||||||
|
(if @message.error "-errored")
|
||||||
|
(if this.showThreadIndicator "has-thread-indicator")
|
||||||
|
(if this.hideUserInfo "-user-info-hidden")
|
||||||
|
(if this.hasReply "has-reply")
|
||||||
|
}}
|
||||||
|
data-id={{@message.id}}
|
||||||
|
data-thread-id={{@message.thread.id}}
|
||||||
|
{{didInsert this.didInsertMessage}}
|
||||||
|
{{didUpdate this.didUpdateMessageId @message.id}}
|
||||||
|
{{didUpdate this.didUpdateMessageVersion @message.version}}
|
||||||
|
{{willDestroy this.willDestroyMessage}}
|
||||||
|
{{on "mouseenter" this.onMouseEnter passive=true}}
|
||||||
|
{{on "mouseleave" this.onMouseLeave passive=true}}
|
||||||
|
{{on "mousemove" this.onMouseMove passive=true}}
|
||||||
|
{{ChatOnLongPress
|
||||||
|
this.onLongPressStart
|
||||||
|
this.onLongPressEnd
|
||||||
|
this.onLongPressCancel
|
||||||
|
}}
|
||||||
|
...attributes
|
||||||
|
>
|
||||||
|
{{#if this.show}}
|
||||||
|
{{#if this.pane.selectingMessages}}
|
||||||
|
<Input
|
||||||
|
@type="checkbox"
|
||||||
|
class="chat-message-selector"
|
||||||
|
@checked={{@message.selected}}
|
||||||
|
{{on "click" this.toggleChecked}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.deletedAndCollapsed}}
|
||||||
|
<div class="chat-message-text -deleted">
|
||||||
|
<DButton
|
||||||
|
@action={{this.expand}}
|
||||||
|
@translatedLabel={{this.deletedMessageLabel}}
|
||||||
|
class="btn-flat chat-message-expand"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else if this.hiddenAndCollapsed}}
|
||||||
|
<div class="chat-message-text -hidden">
|
||||||
|
<DButton
|
||||||
|
@action={{this.expand}}
|
||||||
|
@label="chat.hidden"
|
||||||
|
class="btn-flat chat-message-expand"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="chat-message">
|
||||||
|
{{#unless this.hideReplyToInfo}}
|
||||||
|
<ChatMessageInReplyToIndicator @message={{@message}} />
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#if this.hideUserInfo}}
|
||||||
|
<ChatMessageLeftGutter @message={{@message}} />
|
||||||
|
{{else}}
|
||||||
|
<ChatMessageAvatar @message={{@message}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="chat-message-content">
|
||||||
|
<ChatMessageInfo
|
||||||
|
@message={{@message}}
|
||||||
|
@show={{not this.hideUserInfo}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChatMessageText
|
||||||
|
@cooked={{@message.cooked}}
|
||||||
|
@uploads={{@message.uploads}}
|
||||||
|
@edited={{@message.edited}}
|
||||||
|
>
|
||||||
|
{{#if @message.reactions.length}}
|
||||||
|
<div class="chat-message-reaction-list">
|
||||||
|
{{#each @message.reactions as |reaction|}}
|
||||||
|
<ChatMessageReaction
|
||||||
|
@reaction={{reaction}}
|
||||||
|
@onReaction={{this.messageInteractor.react}}
|
||||||
|
@message={{@message}}
|
||||||
|
@showTooltip={{true}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if this.shouldRenderOpenEmojiPickerButton}}
|
||||||
|
<DButton
|
||||||
|
@action={{this.messageInteractor.openEmojiPicker}}
|
||||||
|
@icon="discourse-emojis"
|
||||||
|
@title="chat.react"
|
||||||
|
@forwardEvent={{true}}
|
||||||
|
class="chat-message-react-btn"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</ChatMessageText>
|
||||||
|
|
||||||
|
<ChatMessageError
|
||||||
|
@message={{@message}}
|
||||||
|
@onRetry={{@resendStagedMessage}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if this.showThreadIndicator}}
|
||||||
|
<ChatMessageThreadIndicator @message={{@message}} />
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
|
|
||||||
@service site;
|
@service site;
|
||||||
@service dialog;
|
@service dialog;
|
||||||
@service currentUser;
|
@service currentUser;
|
|
@ -1,130 +0,0 @@
|
||||||
{{! template-lint-disable no-invalid-interactive }}
|
|
||||||
|
|
||||||
{{#if this.shouldRender}}
|
|
||||||
{{#if (eq @context "channel")}}
|
|
||||||
<ChatMessageSeparatorDate
|
|
||||||
@fetchMessagesByDate={{@fetchMessagesByDate}}
|
|
||||||
@message={{@message}}
|
|
||||||
/>
|
|
||||||
<ChatMessageSeparatorNew @message={{@message}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class={{concat-class
|
|
||||||
"chat-message-container"
|
|
||||||
(if this.pane.selectingMessages "-selectable")
|
|
||||||
(if @message.highlighted "-highlighted")
|
|
||||||
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
|
|
||||||
(if @message.staged "-staged" "-persisted")
|
|
||||||
(if this.hasActiveState "-active")
|
|
||||||
(if @message.bookmark "-bookmarked")
|
|
||||||
(if @message.deletedAt "-deleted")
|
|
||||||
(if @message.selected "-selected")
|
|
||||||
(if @message.error "-errored")
|
|
||||||
(if this.showThreadIndicator "has-thread-indicator")
|
|
||||||
(if this.hideUserInfo "-user-info-hidden")
|
|
||||||
(if this.hasReply "has-reply")
|
|
||||||
}}
|
|
||||||
data-id={{@message.id}}
|
|
||||||
data-thread-id={{@message.thread.id}}
|
|
||||||
{{did-insert this.didInsertMessage}}
|
|
||||||
{{did-update this.didUpdateMessageId @message.id}}
|
|
||||||
{{did-update this.didUpdateMessageVersion @message.version}}
|
|
||||||
{{will-destroy this.willDestroyMessage}}
|
|
||||||
{{on "mouseenter" this.onMouseEnter passive=true}}
|
|
||||||
{{on "mouseleave" this.onMouseLeave passive=true}}
|
|
||||||
{{on "mousemove" this.onMouseMove passive=true}}
|
|
||||||
{{chat/on-long-press
|
|
||||||
this.onLongPressStart
|
|
||||||
this.onLongPressEnd
|
|
||||||
this.onLongPressCancel
|
|
||||||
}}
|
|
||||||
...attributes
|
|
||||||
>
|
|
||||||
{{#if this.show}}
|
|
||||||
{{#if this.pane.selectingMessages}}
|
|
||||||
<Input
|
|
||||||
@type="checkbox"
|
|
||||||
class="chat-message-selector"
|
|
||||||
@checked={{@message.selected}}
|
|
||||||
{{on "click" this.toggleChecked}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.deletedAndCollapsed}}
|
|
||||||
<div class="chat-message-text -deleted">
|
|
||||||
<DButton
|
|
||||||
@action={{this.expand}}
|
|
||||||
@translatedLabel={{this.deletedMessageLabel}}
|
|
||||||
class="btn-flat chat-message-expand"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{else if this.hiddenAndCollapsed}}
|
|
||||||
<div class="chat-message-text -hidden">
|
|
||||||
<DButton
|
|
||||||
@action={{this.expand}}
|
|
||||||
@label="chat.hidden"
|
|
||||||
class="btn-flat chat-message-expand"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="chat-message">
|
|
||||||
{{#unless this.hideReplyToInfo}}
|
|
||||||
<ChatMessageInReplyToIndicator @message={{@message}} />
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{#if this.hideUserInfo}}
|
|
||||||
<Chat::Message::LeftGutter @message={{@message}} />
|
|
||||||
{{else}}
|
|
||||||
<Chat::Message::Avatar @message={{@message}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="chat-message-content">
|
|
||||||
<Chat::Message::Info
|
|
||||||
@message={{@message}}
|
|
||||||
@show={{not this.hideUserInfo}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ChatMessageText
|
|
||||||
@cooked={{@message.cooked}}
|
|
||||||
@uploads={{@message.uploads}}
|
|
||||||
@edited={{@message.edited}}
|
|
||||||
>
|
|
||||||
{{#if @message.reactions.length}}
|
|
||||||
<div class="chat-message-reaction-list">
|
|
||||||
{{#each @message.reactions as |reaction|}}
|
|
||||||
<ChatMessageReaction
|
|
||||||
@reaction={{reaction}}
|
|
||||||
@onReaction={{this.messageInteractor.react}}
|
|
||||||
@message={{@message}}
|
|
||||||
@showTooltip={{true}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
{{#if this.shouldRenderOpenEmojiPickerButton}}
|
|
||||||
<DButton
|
|
||||||
@action={{this.messageInteractor.openEmojiPicker}}
|
|
||||||
@icon="discourse-emojis"
|
|
||||||
@title="chat.react"
|
|
||||||
@forwardEvent={{true}}
|
|
||||||
class="chat-message-react-btn"
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</ChatMessageText>
|
|
||||||
|
|
||||||
<Chat::Message::Error
|
|
||||||
@message={{@message}}
|
|
||||||
@onRetry={{@resendStagedMessage}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if this.showThreadIndicator}}
|
|
||||||
<ChatMessageThreadIndicator @message={{@message}} />
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
146
plugins/chat/assets/stylesheets/common/chat-channel-row.scss
Normal file
146
plugins/chat/assets/stylesheets/common/chat-channel-row.scss
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
.chat-channel-row {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary-high);
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: var(--primary-low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
background: var(--primary-very-low);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.can-leave:hover {
|
||||||
|
.toggle-channel-membership-button.-leave {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-channel-metadata {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
.chat-channel-title {
|
||||||
|
&,
|
||||||
|
.category-chat-name,
|
||||||
|
.dm-usernames {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon-lock {
|
||||||
|
background-color: var(--primary-low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.muted {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-channel-title {
|
||||||
|
&__users-count {
|
||||||
|
width: var(--channel-list-avatar-size);
|
||||||
|
height: var(--channel-list-avatar-size);
|
||||||
|
padding: 0;
|
||||||
|
font-size: var(--font-up-1);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
.chat-user-avatar {
|
||||||
|
img {
|
||||||
|
width: calc(var(--channel-list-avatar-size) - 2px);
|
||||||
|
height: calc(var(--channel-list-avatar-size) - 2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&__user-info {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
&__usernames {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
.user-status-message {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
|
||||||
|
&-description {
|
||||||
|
color: var(--primary-medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-channel-metadata {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
|
||||||
|
&__date {
|
||||||
|
color: var(--primary-high);
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-channel-unread-indicator {
|
||||||
|
@include chat-unread-indicator;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
|
||||||
|
&.-urgent {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
min-width: 0.6em;
|
||||||
|
padding: 0.3em 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unfollowing {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-channel-membership-button.-leave {
|
||||||
|
display: none;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.badge-wrapper {
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
margin-left: 0.3em;
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,151 +88,4 @@
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-channel-row {
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--primary-high);
|
|
||||||
|
|
||||||
@media (hover: none) {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--primary-low);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
background: var(--primary-very-low);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.can-leave:hover {
|
|
||||||
.toggle-channel-membership-button.-leave {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-channel-metadata {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
.chat-channel-title {
|
|
||||||
&,
|
|
||||||
.category-chat-name,
|
|
||||||
.dm-usernames {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.d-icon-lock {
|
|
||||||
background-color: var(--primary-low);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:visited {
|
|
||||||
color: var(--primary-high);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.muted {
|
|
||||||
opacity: 0.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-channel-title {
|
|
||||||
&__users-count {
|
|
||||||
width: var(--channel-list-avatar-size);
|
|
||||||
height: var(--channel-list-avatar-size);
|
|
||||||
padding: 0;
|
|
||||||
font-size: var(--font-up-1);
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
.chat-user-avatar {
|
|
||||||
img {
|
|
||||||
width: calc(var(--channel-list-avatar-size) - 2px);
|
|
||||||
height: calc(var(--channel-list-avatar-size) - 2px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&__user-info {
|
|
||||||
@include ellipsis;
|
|
||||||
}
|
|
||||||
&__usernames {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
.user-status-message {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: var(--font-down-2);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
|
|
||||||
&-description {
|
|
||||||
color: var(--primary-medium);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-channel-metadata {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
|
|
||||||
&__date {
|
|
||||||
color: var(--primary-high);
|
|
||||||
font-size: var(--font-down-2);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-channel-unread-indicator {
|
|
||||||
@include chat-unread-indicator;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
|
|
||||||
&.-urgent {
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
min-width: 0.6em;
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unfollowing {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-channel-membership-button.-leave {
|
|
||||||
display: none;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
.badge-wrapper {
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji {
|
|
||||||
margin-left: 0.3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,3 +63,4 @@
|
||||||
@import "chat-modal-channel-summary";
|
@import "chat-modal-channel-summary";
|
||||||
@import "chat-modal-move-message-to-channel";
|
@import "chat-modal-move-message-to-channel";
|
||||||
@import "chat-scroll-to-bottom";
|
@import "chat-scroll-to-bottom";
|
||||||
|
@import "chat-channel-row";
|
||||||
|
|
54
plugins/chat/assets/stylesheets/mobile/chat-channel-row.scss
Normal file
54
plugins/chat/assets/stylesheets/mobile/chat-channel-row.scss
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
.chat-channel-row {
|
||||||
|
height: 4em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
border-radius: 0;
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
transition: height 0.25s ease-in-out, opacity 0.25s ease-out;
|
||||||
|
transform-origin: top center;
|
||||||
|
will-change: height, left;
|
||||||
|
|
||||||
|
&__action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
|
||||||
|
&.-cancel {
|
||||||
|
background: var(--primary-very-low);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-leave {
|
||||||
|
background: var(--danger);
|
||||||
|
color: var(--primary-very-low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__action-btn-icon {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-fade-out {
|
||||||
|
background-color: var(--danger-low);
|
||||||
|
height: 0 !important;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-reset {
|
||||||
|
transition: left 0.25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-channel-metadata {
|
||||||
|
.chat-channel-unread-indicator {
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
&__date {
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,24 +10,7 @@
|
||||||
|
|
||||||
.channels-list-container {
|
.channels-list-container {
|
||||||
background: var(--secondary);
|
background: var(--secondary);
|
||||||
}
|
overflow: hidden;
|
||||||
|
|
||||||
.chat-channel-row {
|
|
||||||
height: 4em;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
border-radius: 0;
|
|
||||||
border-bottom: 1px solid var(--primary-low);
|
|
||||||
|
|
||||||
.chat-channel-metadata {
|
|
||||||
.chat-channel-unread-indicator {
|
|
||||||
font-size: var(--font-down-2);
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
&__date {
|
|
||||||
font-size: var(--font-down-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-channel-divider {
|
.chat-channel-divider {
|
||||||
|
|
|
@ -14,3 +14,4 @@
|
||||||
@import "chat-modal-thread-settings";
|
@import "chat-modal-thread-settings";
|
||||||
@import "chat-message-thread-indicator";
|
@import "chat-message-thread-indicator";
|
||||||
@import "chat-message-creator";
|
@import "chat-message-creator";
|
||||||
|
@import "chat-channel-row";
|
||||||
|
|
|
@ -83,6 +83,7 @@ en:
|
||||||
|
|
||||||
click_to_join: "Click here to view available channels."
|
click_to_join: "Click here to view available channels."
|
||||||
close: "Close"
|
close: "Close"
|
||||||
|
remove: "Remove"
|
||||||
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?"
|
||||||
|
|
|
@ -27,7 +27,10 @@ RSpec.describe "List channels | mobile", type: :system, mobile: true do
|
||||||
context "when not member of the channel" do
|
context "when not member of the channel" do
|
||||||
it "doesn’t show the channel" do
|
it "doesn’t show the channel" do
|
||||||
visit("/chat")
|
visit("/chat")
|
||||||
expect(page.find(".public-channels")).to have_no_content(category_channel_1.name)
|
|
||||||
|
expect(page.find(".public-channels", visible: :all)).to have_no_content(
|
||||||
|
category_channel_1.name,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user