FIX: various mobile chat improvements (#22132)

- FIX: improves reactions and thread indicator touch event on mobile
These "buttons" are located inside a scroll list which makes them very specific. The general idea is to ensure these events are passive and are not bubbling to the parent.
- DEV: moves state on top level message node
- FIX: ensures popover arrow has the correct border
- FIX: makes a message expanded by default
- FIX applies the same ios scroll fix on thread and channel
- UI: better active/hover state for thread indicator
- UI: attempts to follow more closely our BEM naming scheme
- FIX: reduces bottom padding on message with thread indicator and user info hidden
- UI: add padding for first message in thread
- FIX: prevents actions backdrop to open thread
- UI: makes thread indicator resizable
This commit is contained in:
Joffrey JAFFEUX 2023-06-16 11:36:43 +02:00 committed by GitHub
parent 517c9e7782
commit 7dafd275ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 492 additions and 264 deletions

View File

@ -28,6 +28,7 @@ registerUnbound("format-date", function (val, params) {
format,
title,
leaveAgo,
prefix: params.prefix,
})
);
}

View File

@ -102,6 +102,11 @@ export function autoUpdatingRelativeAge(date, options) {
append += "' title='" + longDate(date);
}
let prefix = "";
if (options.prefix) {
prefix = options.prefix + " ";
}
return (
"<span class='relative-date" +
append +
@ -110,6 +115,7 @@ export function autoUpdatingRelativeAge(date, options) {
"' data-format='" +
format +
"'>" +
prefix +
relAge +
"</span>"
);

View File

@ -260,6 +260,9 @@ module("Unit | Utility | formatter", function (hooks) {
assert.strictEqual(elem.dataset.time, d.getTime().toString());
assert.strictEqual(elem.title, "");
assert.strictEqual(elem.innerHTML, "1 day");
elem = domFromString(autoUpdatingRelativeAge(d, { prefix: "test" }))[0];
assert.strictEqual(elem.innerHTML, "test 1d");
});
test("updateRelativeAge", function (assert) {

View File

@ -15,8 +15,14 @@ $d-popover-border: var(--primary-low);
top: -10px;
}
.tippy-svg-arrow > svg {
color: $d-popover-background;
#tippy-rounded-arrow {
.svg-content {
fill: $d-popover-background;
}
.svg-arrow {
fill: $d-popover-border;
}
}
[data-tooltip] > *,

View File

@ -9,7 +9,7 @@
<div
role="button"
class="collapse-area"
{{on "touchstart" this.collapseMenu passive=true bubbles=false}}
{{on "touchstart" this.collapseMenu passive=false bubbles=false}}
>
</div>

View File

@ -15,8 +15,6 @@ export default class ChatMessageActionsMobile extends Component {
@tracked hasExpandedReply = false;
@tracked showFadeIn = false;
messageActions = null;
get message() {
return this.chat.activeMessage.model;
}
@ -49,7 +47,8 @@ export default class ChatMessageActionsMobile extends Component {
}
@action
collapseMenu() {
collapseMenu(event) {
event.preventDefault();
this.#onCloseMenu();
}

View File

@ -1,18 +1,17 @@
{{#if (and @reaction this.emojiUrl)}}
<button
type="button"
{{on "click" this.handleClick bubbles=false}}
{{on "touchstart" this.handleClick bubbles=false}}
tabindex="0"
class={{concat-class
"chat-message-reaction"
(if @reaction.reacted "reacted")
(if this.isActive "-active")
}}
data-emoji-name={{@reaction.emoji}}
data-tippy-content={{this.popoverContent}}
title={{this.emojiString}}
{{did-insert this.setupTooltip}}
{{will-destroy this.teardownTooltip}}
{{did-insert this.setup}}
{{will-destroy this.teardown}}
{{did-update this.refreshTooltip this.popoverContent}}
>
<img

View File

@ -2,29 +2,116 @@ import Component from "@glimmer/component";
import { action } from "@ember/object";
import { emojiUnescape, emojiUrlFor } from "discourse/lib/text";
import I18n from "I18n";
import { schedule } from "@ember/runloop";
import { cancel } from "@ember/runloop";
import { inject as service } from "@ember/service";
import setupPopover from "discourse/lib/d-popover";
import discourseLater from "discourse-common/lib/later";
import { tracked } from "@glimmer/tracking";
export default class ChatMessageReaction extends Component {
@service capabilities;
@service currentUser;
@tracked isActive = false;
get showCount() {
return this.args.showCount ?? true;
}
@action
setupTooltip(element) {
if (this.args.showTooltip) {
schedule("afterRender", () => {
this._tippyInstance?.destroy();
this._tippyInstance = setupPopover(element, {
interactive: false,
allowHTML: true,
delay: 250,
});
setup(element) {
this.setupListeners(element);
this.setupTooltip(element);
}
@action
teardown() {
cancel(this.longPressHandler);
this.teardownTooltip();
}
@action
setupListeners(element) {
this.element = element;
if (this.capabilities.touch) {
this.element.addEventListener("touchstart", this.onTouchStart, {
passive: true,
});
this.element.addEventListener("touchmove", this.cancelTouch, {
passive: true,
});
this.element.addEventListener("touchend", this.onTouchEnd);
this.element.addEventListener("touchCancel", this.cancelTouch);
}
this.element.addEventListener("click", this.handleClick, {
passive: true,
});
}
@action
teardownListeners() {
if (this.capabilities.touch) {
this.element.removeEventListener("touchstart", this.onTouchStart, {
passive: true,
});
this.element.removeEventListener("touchmove", this.cancelTouch, {
passive: true,
});
this.element.removeEventListener("touchend", this.onTouchEnd);
this.element.removeEventListener("touchCancel", this.cancelTouch);
}
this.element.removeEventListener("click", this.handleClick, {
passive: true,
});
}
@action
onTouchStart(event) {
event.stopPropagation();
this.isActive = true;
this.longPressHandler = discourseLater(() => {
this.touching = false;
}, 400);
this.touching = true;
}
@action
cancelTouch() {
cancel(this.longPressHandler);
this._tippyInstance?.hide();
this.touching = false;
this.isActive = false;
}
@action
onTouchEnd(event) {
if (this.touching) {
this.handleClick(event);
}
cancel(this.longPressHandler);
this._tippyInstance?.hide();
this.isActive = false;
}
@action
setupTooltip(element) {
this._tippyInstance = setupPopover(element, {
trigger: "mouseenter",
interactive: false,
allowHTML: true,
offset: [0, 10],
onShow(instance) {
if (instance.props.content === "") {
return false;
}
},
});
}
@action
@ -34,7 +121,7 @@ export default class ChatMessageReaction extends Component {
@action
refreshTooltip() {
this._tippyInstance?.setContent(this.popoverContent);
this._tippyInstance?.setContent(this.popoverContent || "");
}
get emojiString() {
@ -53,6 +140,8 @@ export default class ChatMessageReaction extends Component {
this.args.reaction.emoji,
this.args.reaction.reacted ? "remove" : "add"
);
this._tippyInstance?.clearDelayTimeouts();
}
get popoverContent() {
@ -97,7 +186,7 @@ export default class ChatMessageReaction extends Component {
if (unnamedUserCount > 0) {
return I18n.t("chat.reactions.you_multiple_users_and_more", {
emoji: this.args.reaction.emoji,
commaSeparatedUsernames: this._joinUsernames(usernames),
commaSeparatedUsernames: this.#joinUsernames(usernames),
count: unnamedUserCount,
});
}
@ -105,7 +194,7 @@ export default class ChatMessageReaction extends Component {
return I18n.t("chat.reactions.you_and_multiple_users", {
emoji: this.args.reaction.emoji,
username: usernames.pop(),
commaSeparatedUsernames: this._joinUsernames(usernames),
commaSeparatedUsernames: this.#joinUsernames(usernames),
});
}
@ -134,7 +223,7 @@ export default class ChatMessageReaction extends Component {
if (unnamedUserCount > 0) {
return I18n.t("chat.reactions.multiple_users_and_more", {
emoji: this.args.reaction.emoji,
commaSeparatedUsernames: this._joinUsernames(usernames),
commaSeparatedUsernames: this.#joinUsernames(usernames),
count: unnamedUserCount,
});
}
@ -142,11 +231,11 @@ export default class ChatMessageReaction extends Component {
return I18n.t("chat.reactions.multiple_users", {
emoji: this.args.reaction.emoji,
username: usernames.pop(),
commaSeparatedUsernames: this._joinUsernames(usernames),
commaSeparatedUsernames: this.#joinUsernames(usernames),
});
}
_joinUsernames(usernames) {
#joinUsernames(usernames) {
return usernames.join(I18n.t("word_connector.comma"));
}
}

View File

@ -1,7 +1,12 @@
<LinkTo
@route="chat.channel.thread"
@models={{@message.thread.routeModels}}
class="chat-message-thread-indicator"
<div
class={{concat-class
"chat-message-thread-indicator"
(if this.isActive "-active")
}}
{{did-insert this.setup}}
{{will-destroy this.teardown}}
role="button"
title={{i18n "chat.threads.open"}}
>
{{#unless this.chatStateManager.isDrawerActive}}
<div class="chat-message-thread-indicator__last-reply-avatar">
@ -28,14 +33,14 @@
{{@message.thread.preview.lastReplyUser.username}}
</span>
</div>
<span><span class="chat-message-thread-indicator__last-reply-label">{{i18n
"chat.thread.last_reply"
}}</span>{{format-date
<span class="chat-message-thread-indicator__last-reply-container">
{{format-date
@message.thread.preview.lastReplyCreatedAt
leaveAgo="true"
}}</span>
|
prefix=(i18n "chat.thread.last_reply")
}}
</span>
<span class="separator">|</span>
<span class="chat-message-thread-indicator__replies-count">
{{i18n "chat.thread.replies" count=@message.thread.preview.replyCount}}
</span>
@ -49,4 +54,4 @@
<div class="chat-message-thread-indicator__participants">
<Chat::Thread::Participants @thread={{@message.thread}} />
</div>
</LinkTo>
</div>

View File

@ -1,10 +1,89 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { escapeExpression } from "discourse/lib/utilities";
import { action } from "@ember/object";
import { bind } from "discourse-common/utils/decorators";
import { tracked } from "@glimmer/tracking";
export default class ChatMessageThreadIndicator extends Component {
@service site;
@service capabilities;
@service chat;
@service chatStateManager;
@service router;
@service site;
@tracked isActive = false;
@action
setup(element) {
this.element = element;
if (this.capabilities.touch) {
this.element.addEventListener("touchstart", this.onTouchStart, {
passive: true,
});
this.element.addEventListener("touchmove", this.cancelTouch, {
passive: true,
});
this.element.addEventListener("touchend", this.onTouchEnd);
this.element.addEventListener("touchCancel", this.cancelTouch);
}
this.element.addEventListener("click", this.openThread, {
passive: true,
});
}
@action
teardown() {
if (this.capabilities.touch) {
this.element.removeEventListener("touchstart", this.onTouchStart, {
passive: true,
});
this.element.removeEventListener("touchmove", this.cancelTouch, {
passive: true,
});
this.element.removeEventListener("touchend", this.onTouchEnd);
this.element.removeEventListener("touchCancel", this.cancelTouch);
}
this.element.removeEventListener("click", this.openThread, {
passive: true,
});
}
@bind
onTouchStart(event) {
this.isActive = true;
event.stopPropagation();
this.touching = true;
}
@bind
onTouchEnd() {
this.isActive = false;
if (this.touching) {
this.openThread();
}
}
@bind
cancelTouch() {
this.isActive = false;
this.touching = false;
}
@bind
openThread() {
this.chat.activeMessage = null;
this.router.transitionTo(
"chat.channel.thread",
...this.args.message.thread.routeModels
);
}
get threadTitle() {
return escapeExpression(this.args.message.threadTitle);

View File

@ -7,10 +7,27 @@
{{/if}}
<div
{{will-destroy this.teardownChatMessage}}
{{did-insert this.decorateCookedMessage}}
{{did-update this.decorateCookedMessage @message.id}}
{{did-update this.decorateCookedMessage @message.version}}
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}}
@ -19,18 +36,6 @@
this.handleLongPressEnd
this.onLongPressCancel
}}
class={{concat-class
"chat-message-container"
(if this.pane.selectingMessages "selecting-messages")
(if @message.highlighted "highlighted")
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
(if @message.staged "is-staged" "is-persisted")
(if @message.deletedAt "is-deleted")
(if (eq @message.id this.chat.activeMessage.model.id) "is-active")
}}
data-id={{@message.id}}
data-thread-id={{@message.thread.id}}
data-selected={{@message.selected}}
{{chat/track-message
(hash
didEnterViewport=(fn @messageDidEnterViewport @message)
@ -65,20 +70,7 @@
/>
</div>
{{else}}
<div
{{did-insert this.refreshStatusOnMentions}}
{{did-update this.refreshStatusOnMentions @message.version}}
{{did-update this.initMentionedUsers @message.version}}
class={{concat-class
"chat-message"
(if @message.deletedAt "deleted")
(if (and @message.inReplyTo (not this.hideReplyToInfo)) "is-reply")
(if this.showThreadIndicator "is-threaded")
(if this.hideUserInfo "user-info-hidden")
(if @message.error "errored")
(if @message.bookmark "chat-message-bookmarked")
}}
>
<div class="chat-message">
{{#unless this.hideReplyToInfo}}
<ChatMessageInReplyToIndicator @message={{@message}} />
{{/unless}}
@ -195,9 +187,9 @@
{{/if}}
{{#if this.mentionWarning.groups_with_too_many_members}}
<p
class="warning-item"
>{{this.groupsWithTooManyMembers}}</p>
<p class="warning-item">
{{this.groupsWithTooManyMembers}}
</p>
{{/if}}
{{/if}}
</div>

View File

@ -12,6 +12,7 @@ import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-mes
import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
import { tracked } from "@glimmer/tracking";
let _chatMessageDecorators = [];
@ -41,6 +42,8 @@ export default class ChatMessage extends Component {
@service chatChannelsManager;
@service router;
@tracked isActive = false;
@optionalService adminTools;
constructor() {
@ -123,7 +126,7 @@ export default class ChatMessage extends Component {
}
@action
teardownChatMessage() {
willDestroyMessage() {
cancel(this._invitationSentTimer);
cancel(this._disableMessageActionsHandler);
this.#teardownMentionedUsers();
@ -132,10 +135,6 @@ export default class ChatMessage extends Component {
@action
refreshStatusOnMentions() {
schedule("afterRender", () => {
if (!this.messageContainer) {
return;
}
this.args.message.mentionedUsers.forEach((user) => {
const href = `/u/${user.username.toLowerCase()}`;
const mentions = this.messageContainer.querySelectorAll(
@ -149,13 +148,28 @@ export default class ChatMessage extends Component {
});
}
@action
didInsertMessage(element) {
this.messageContainer = element;
this.decorateCookedMessage();
this.refreshStatusOnMentions();
}
@action
didUpdateMessageId() {
this.decorateCookedMessage();
}
@action
didUpdateMessageVersion() {
this.decorateCookedMessage();
this.refreshStatusOnMentions();
this.initMentionedUsers();
}
@action
decorateCookedMessage() {
schedule("afterRender", () => {
if (!this.messageContainer) {
return;
}
_chatMessageDecorators.forEach((decorator) => {
decorator.call(this, this.messageContainer, this.args.message.channel);
});
@ -174,13 +188,6 @@ export default class ChatMessage extends Component {
});
}
get messageContainer() {
const id = this.args.message?.id;
if (id) {
return document.querySelector(`.chat-message-container[data-id='${id}']`);
}
}
get show() {
return (
!this.args.message?.deletedAt ||
@ -251,6 +258,10 @@ export default class ChatMessage extends Component {
return;
}
if (!this.args.message.expanded) {
return;
}
this.chat.activeMessage = {
model: this.args.message,
context: this.args.context,
@ -258,13 +269,17 @@ export default class ChatMessage extends Component {
}
@action
handleLongPressStart(element) {
element.classList.add("is-long-pressed");
handleLongPressStart() {
if (!this.args.message.expanded) {
return;
}
this.isActive = true;
}
@action
onLongPressCancel(element) {
element.classList.remove("is-long-pressed");
onLongPressCancel() {
this.isActive = false;
// this a tricky bit of code which is needed to prevent the long press
// from triggering a click on the message actions panel when releasing finger press
@ -279,8 +294,8 @@ export default class ChatMessage extends Component {
}
@action
handleLongPressEnd(element) {
element.classList.remove("is-long-pressed");
handleLongPressEnd() {
this.isActive = false;
if (isZoomed()) {
// if zoomed don't handle long press
@ -294,6 +309,17 @@ export default class ChatMessage extends Component {
this._setActiveMessage();
}
get hasActiveState() {
return (
this.isActive ||
this.chat.activeMessage?.model?.id === this.args.message.id
);
}
get hasReply() {
return this.args.inReplyTo && !this.hideReplyToInfo;
}
get hideUserInfo() {
const message = this.args.message;

View File

@ -327,31 +327,22 @@ export default class ChatThreadPanel extends Component {
// we now use this hack to disable it
@bind
forceRendering(callback) {
schedule("afterRender", () => {
if (this._selfDeleted) {
return;
}
if (this.capabilities.isIOS) {
this.scrollable.style.overflow = "hidden";
}
if (!this.scrollable) {
return;
}
callback?.();
if (this.capabilities.isIOS) {
this.scrollable.style.overflow = "hidden";
}
callback?.();
if (this.capabilities.isIOS) {
discourseLater(() => {
if (!this.scrollable) {
if (this.capabilities.isIOS) {
next(() => {
schedule("afterRender", () => {
if (this._selfDeleted || !this.scrollable) {
return;
}
this.scrollable.style.overflow = "auto";
}, 50);
}
});
});
});
}
}
@action

View File

@ -29,14 +29,13 @@ export default class ChatMessage {
@tracked draft;
@tracked channelId;
@tracked createdAt;
@tracked deletedAt;
@tracked uploads;
@tracked excerpt;
@tracked reactions;
@tracked reviewableId;
@tracked user;
@tracked inReplyTo;
@tracked expanded;
@tracked expanded = true;
@tracked bookmark;
@tracked userFlagStatus;
@tracked hidden;
@ -54,6 +53,7 @@ export default class ChatMessage {
@tracked manager;
@tracked threadTitle;
@tracked _deletedAt;
@tracked _cooked;
constructor(channel, args = {}) {
@ -69,7 +69,9 @@ export default class ChatMessage {
this.hidden = args.hidden || false;
this.chatWebhookEvent = args.chatWebhookEvent || args.chat_webhook_event;
this.createdAt = args.createdAt || args.created_at;
this.deletedAt = args.deletedAt || args.deleted_at;
this._deletedAt = args.deletedAt || args.deleted_at;
this.expanded =
this.hidden || this._deletedAt ? false : args.expanded || true;
this.excerpt = args.excerpt;
this.reviewableId = args.reviewableId || args.reviewable_id;
this.userFlagStatus = args.userFlagStatus || args.user_flag_status;
@ -130,6 +132,16 @@ export default class ChatMessage {
return !this.staged && !this.error;
}
get deletedAt() {
return this._deletedAt;
}
set deletedAt(value) {
this._deletedAt = value;
this.incrementVersion();
return this._deletedAt;
}
get cooked() {
return this._cooked;
}

View File

@ -7,7 +7,6 @@ import discourseLater from "discourse-common/lib/later";
function cancelEvent(event) {
event.stopPropagation();
event.preventDefault();
}
export default class ChatOnLongPress extends Modifier {
@ -33,7 +32,7 @@ export default class ChatOnLongPress extends Modifier {
this.onLongPressEnd = onLongPressEnd || (() => {});
this.onLongPressCancel = onLongPressCancel || (() => {});
element.addEventListener("touchstart", this.handleTouchStart, {
this.element.addEventListener("touchstart", this.handleTouchStart, {
passive: true,
});
}
@ -42,11 +41,13 @@ export default class ChatOnLongPress extends Modifier {
onCancel() {
cancel(this.timeout);
this.element.removeEventListener("touchmove", this.onCancel, {
passive: true,
});
this.element.removeEventListener("touchend", this.onCancel);
this.element.removeEventListener("touchcancel", this.onCancel);
if (this.capabilities.touch) {
this.element.removeEventListener("touchmove", this.onCancel, {
passive: true,
});
this.element.removeEventListener("touchend", this.onCancel);
this.element.removeEventListener("touchcancel", this.onCancel);
}
this.onLongPressCancel(this.element);
}

View File

@ -137,7 +137,7 @@ html.ios-device.keyboard-visible body #main-outlet .full-page-chat {
.chat-message-container {
display: grid;
&.selecting-messages {
&.-selectable {
grid-template-columns: 1.5em 1fr;
}
@ -191,7 +191,7 @@ html.ios-device.keyboard-visible body #main-outlet .full-page-chat {
display: flex;
align-items: center;
.chat-message:not(.is-reply) & {
.chat-message-container:not(.has-reply) & {
width: var(--message-left-width);
flex-shrink: 0;
}

View File

@ -12,13 +12,25 @@
border-radius: 8px;
color: var(--primary);
> * {
pointer-events: none;
}
&:visited,
&:hover {
color: var(--primary);
}
&:hover {
box-shadow: var(--shadow-dropdown-lite);
.touch & {
&.-active {
box-shadow: var(--shadow-dropdown);
}
}
.no-touch & {
&:hover {
box-shadow: var(--shadow-dropdown);
}
}
&__participants {
@ -35,20 +47,34 @@
&__last-reply-metadata {
display: flex;
align-items: center;
align-items: flex-end;
gap: 0.25rem;
color: var(--primary-medium);
font-size: var(--font-down-1);
.separator {
font-size: var(--font-down-1);
}
}
&__last-reply-label {
margin-right: 0.25rem;
&__last-reply-container {
display: inline-flex;
align-items: flex-end;
font-size: var(--font-down-1);
min-width: 0;
.relative-date {
@include ellipsis;
}
}
&__last-reply-user {
font-weight: bold;
color: var(--secondary-low);
}
&__last-reply-username {
font-weight: bold;
color: var(--secondary-low);
font-size: var(--font-up-1);
}
&__last-reply-excerpt {
@ -62,7 +88,15 @@
flex-shrink: 1;
}
&:hover {
.chat-message-thread-indicator__replies-count {
text-decoration: underline;
}
}
&__replies-count {
@include ellipsis;
color: var(--tertiary);
font-size: var(--font-down-1);
}
}

View File

@ -28,56 +28,10 @@
@include chat-reaction;
}
&.chat-action {
background-color: var(--highlight-bg);
}
&.errored {
color: var(--primary-medium);
}
&.deleted {
background-color: var(--danger-low);
}
.not-mobile-device &.deleted:hover {
background-color: var(--danger-hover);
}
&.is-reply {
display: grid;
grid-template-columns: var(--message-left-width) 1fr;
grid-template-rows: 30px auto;
grid-template-areas:
"replyto replyto"
"avatar message";
.chat-user-avatar {
grid-area: avatar;
}
.chat-message-content {
grid-area: message;
}
}
&.is-threaded {
display: grid;
grid-template-columns: var(--message-left-width) 1fr;
grid-template-rows: auto;
grid-template-areas:
"avatar message"
"threadindicator threadindicator";
padding: 0.65rem 1rem !important;
.chat-user-avatar {
grid-area: avatar;
}
.chat-message-content {
grid-area: message;
}
}
.chat-message-content {
display: flex;
flex-direction: column;
@ -200,77 +154,105 @@
}
}
.chat-message-container.highlighted .chat-message {
background-color: var(--tertiary-low) !important;
.touch .chat-message-container {
&.-active {
background: var(--d-hover);
border-radius: 5px;
&.-bookmarked {
background: var(--highlight-low);
}
}
}
.chat-messages-container {
.chat-message {
&.chat-message-bookmarked {
background: var(--highlight-bg);
}
.no-touch .chat-message-container {
&:hover,
&-active {
background: var(--d-hover);
}
&:hover {
.chat-message-reaction-list .chat-message-react-btn {
display: none;
display: inline-block;
}
}
.chat-message-container {
background-color: var(--secondary);
.touch & {
&:active,
&.is-active {
background: var(--d-hover);
border-radius: 5px;
}
&.chat-message-bookmarked {
&:active,
&.is-active {
background: var(--highlight-low);
}
}
&.-active,
&:hover {
&.-bookmarked {
background: var(--highlight-medium);
}
.no-touch & {
&.is-active,
&:hover,
&:active {
background: var(--d-hover);
}
&.-deleted {
background-color: var(--danger-medium);
}
&:hover {
.chat-message-react-btn {
display: inline-block;
}
}
&.chat-message-bookmarked {
&.is-active,
&:hover {
background: var(--highlight-medium);
}
}
&.-highlighted {
background-color: var(--tertiary-medium);
}
}
}
.chat-message-flagged {
display: inline-block;
color: var(--danger);
height: 100%;
padding: 0 0.3em;
cursor: pointer;
.chat-message-container {
background-color: var(--secondary);
.flag-count,
.d-icon {
color: var(--danger);
&.-errored {
color: var(--primary-medium);
}
}
.chat-action-text {
font-style: italic;
&.-deleted {
background-color: var(--danger-low);
padding-block: 0.25rem;
}
&.-bookmarked {
background: var(--highlight-bg);
}
&.-highlighted {
background-color: var(--tertiary-low);
}
&.has-reply {
display: grid;
grid-template-columns: var(--message-left-width) 1fr;
grid-template-rows: 30px auto;
grid-template-areas:
"replyto replyto"
"avatar message";
.chat-user-avatar {
grid-area: avatar;
}
.chat-message-content {
grid-area: message;
}
}
&.has-thread-indicator {
.chat-message {
display: grid;
grid-template-columns: var(--message-left-width) 1fr;
grid-template-rows: auto;
grid-template-areas:
"avatar message"
"threadindicator threadindicator";
padding-bottom: 0.65rem !important;
.chat-user-avatar {
grid-area: avatar;
}
.chat-message-content {
grid-area: message;
}
}
}
.chat-message-reaction-list .chat-message-react-btn {
display: none;
}
}
.has-full-page-chat .chat-message .onebox:not(img),
@ -333,7 +315,7 @@
&:hover,
&:focus,
&:active {
.-active & {
background: none !important;
}

View File

@ -21,23 +21,17 @@
.chat-channel {
.chat-messages-container {
.chat-message {
&.is-reply {
grid-template-columns: var(--message-left-width) 1fr;
}
&.has-reply {
grid-template-columns: var(--message-left-width) 1fr;
}
.chat-user {
width: var(--message-left-width);
}
.chat-user {
width: var(--message-left-width);
}
}
}
}
.chat-message:not(.user-info-hidden) {
padding: 0.65rem 1rem 0.15rem;
}
.chat-message-text {
img:not(.emoji):not(.avatar, .onebox-avatar-inline) {
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
@ -51,8 +45,16 @@
}
}
.chat-message.user-info-hidden {
padding: 0.15rem 1rem;
.chat-message-container:not(.-user-info-hidden) {
.chat-message {
padding: 0.65rem 1rem 0.15rem;
}
}
.chat-message-container.-user-info-hidden {
.chat-message {
padding: 0.15rem 1rem;
}
.chat-time {
color: var(--secondary-medium);

View File

@ -1,6 +1,4 @@
.chat-message-thread-indicator {
width: min-content;
min-width: 400px;
max-width: 600px;
.chat-drawer & {

View File

@ -2,8 +2,10 @@
--channel-list-avatar-size: 38px;
}
.chat-message:not(.user-info-hidden) {
padding-top: 0.75em;
.chat-message-container:not(.-user-info-hidden) {
.chat-message {
padding-top: 0.75em;
}
}
html.has-full-page-chat {

View File

@ -25,7 +25,7 @@
transition: transform 400ms;
transform: scale(1);
&.is-long-pressed {
&.-active {
animation: scale-animation 400ms;
}
}

View File

@ -1,6 +1,9 @@
.chat-thread {
&__body {
margin: 0 1px 0 0;
padding: 0 10px;
}
.chat-messages-scroll {
padding: 10px 10px 0 10px;
}
}

View File

@ -22,7 +22,7 @@ RSpec.describe "Chat message - channel", type: :system do
channel.hover_message(message_1)
expect(page).to have_css(
".chat-channel[data-id='#{channel_1.id}'] .chat-message-container[data-id='#{message_1.id}'].is-active",
".chat-channel[data-id='#{channel_1.id}'] .chat-message-container[data-id='#{message_1.id}'].-active",
)
end
end

View File

@ -30,7 +30,7 @@ RSpec.describe "Chat message - thread", type: :system do
thread_page.hover_message(first_message)
expect(page).to have_css(
".chat-thread[data-id='#{thread_1.id}'] [data-id='#{first_message.id}'].chat-message-container.is-active",
".chat-thread[data-id='#{thread_1.id}'] [data-id='#{first_message.id}'].chat-message-container.-active",
)
end
end

View File

@ -19,7 +19,7 @@ RSpec.describe "Deleted message", type: :system do
chat_page.visit_channel(channel_1)
channel_page.send_message("aaaaaaaaaaaaaaaaaaaa")
expect(page).to have_css(".is-persisted")
expect(page).to have_css(".-persisted")
last_message = find(".chat-message-container:last-child")
channel_page.delete_message(OpenStruct.new(id: last_message["data-id"]))

View File

@ -68,7 +68,7 @@ module PageObjects
end
def click_message_action_mobile(message, message_action)
expand_message_actions_mobile(message, delay: 0.6)
expand_message_actions_mobile(message, delay: 0.4)
find(".chat-message-actions [data-id=\"#{message_action}\"]").click
end
@ -143,7 +143,7 @@ module PageObjects
end
def has_bookmarked_message?(message)
within(message_by_id(message.id)) { find(".chat-message-bookmarked") }
find(message_by_id_selector(message.id) + ".-bookmarked")
end
def find_reaction(message, emoji)

View File

@ -22,7 +22,7 @@ module PageObjects
end
def select(shift: false)
if component[:class].include?("selecting-message")
if component[:class].include?("-selectable")
message_selector = component.find(".chat-message-selector")
if shift
message_selector.click(:shift)
@ -85,10 +85,10 @@ module PageObjects
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 += ".-selected" if args[:selected]
selector += ".-persisted" if args[:persisted]
selector += ".-staged" if args[:staged]
selector += ".-deleted" if args[:deleted]
selector
end
end

View File

@ -45,8 +45,6 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
)
),
channel,
afterExpand: () => {},
onHoverMessage: () => {},
messageDidEnterViewport: () => {},
messageDidLeaveViewport: () => {},
};