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:
Joffrey JAFFEUX 2023-09-11 14:51:13 +02:00 committed by GitHub
parent 87d0336f05
commit b8d5f951f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 621 additions and 366 deletions

View File

@ -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>

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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}}

View 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;
}
}

View File

@ -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;
}
}
} }

View File

@ -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";

View 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);
}
}
}

View File

@ -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 {

View File

@ -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";

View File

@ -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?"

View File

@ -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 "doesnt show the channel" do it "doesnt 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