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:
parent
517c9e7782
commit
7dafd275ac
|
@ -28,6 +28,7 @@ registerUnbound("format-date", function (val, params) {
|
|||
format,
|
||||
title,
|
||||
leaveAgo,
|
||||
prefix: params.prefix,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>"
|
||||
);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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] > *,
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
.chat-message-thread-indicator {
|
||||
width: min-content;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
|
||||
.chat-drawer & {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
transition: transform 400ms;
|
||||
transform: scale(1);
|
||||
|
||||
&.is-long-pressed {
|
||||
&.-active {
|
||||
animation: scale-animation 400ms;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
.chat-thread {
|
||||
&__body {
|
||||
margin: 0 1px 0 0;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.chat-messages-scroll {
|
||||
padding: 10px 10px 0 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -45,8 +45,6 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
|
|||
)
|
||||
),
|
||||
channel,
|
||||
afterExpand: () => {},
|
||||
onHoverMessage: () => {},
|
||||
messageDidEnterViewport: () => {},
|
||||
messageDidLeaveViewport: () => {},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue