FIX: improves linking of thread messages (#26095)

- The thread preview is now a regular link and can be right clicked
- left gutter date, and regular date of a thread message will not correctly link to the thread's message
This commit is contained in:
Joffrey JAFFEUX 2024-03-08 09:09:42 +01:00 committed by GitHub
parent 57df0d526e
commit 21a7ebf1bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 48 additions and 136 deletions

View File

@ -1,146 +1,33 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import { LinkTo } from "@ember/routing";
import formatDate from "discourse/helpers/format-date";
import replaceEmoji from "discourse/helpers/replace-emoji";
import htmlSafe from "discourse-common/helpers/html-safe";
import i18n from "discourse-common/helpers/i18n";
import getURL from "discourse-common/lib/get-url";
import { bind } from "discourse-common/utils/decorators";
import ChatThreadParticipants from "./chat-thread-participants";
import ChatUserAvatar from "./chat-user-avatar";
export default class ChatMessageThreadIndicator extends Component {
@service capabilities;
@service chat;
@service chatStateManager;
@service router;
@service site;
@tracked isActive = false;
get interactiveUser() {
return this.args.interactiveUser ?? true;
}
@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("mousedown", this.openThread, {
passive: true,
});
this.element.addEventListener("keydown", 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("mousedown", this.openThread, {
passive: true,
});
this.element.removeEventListener("keydown", 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(event) {
if (event?.type === "keydown" && event?.key !== "Enter") {
return;
}
// handle middle mouse
if (
event?.type === "mousedown" &&
(event?.which === 2 || event?.shiftKey)
) {
window.open(
getURL(
this.router.urlFor(
"chat.channel.thread",
...this.args.message.thread.routeModels
)
),
"_blank"
);
return;
}
this.chat.activeMessage = null;
this.router.transitionTo(
"chat.channel.thread",
...this.args.message.thread.routeModels
);
get threadMessageRoute() {
return [
...this.args.message.thread.routeModels,
this.args.message.thread.preview.lastReplyId,
];
}
<template>
<div
class={{concatClass
"chat-message-thread-indicator"
(if this.isActive "-active")
}}
{{didInsert this.setup}}
{{willDestroy this.teardown}}
role="button"
<LinkTo
class="chat-message-thread-indicator"
@route="chat.channel.thread.near-message"
@models={{this.threadMessageRoute}}
title={{i18n "chat.threads.open"}}
tabindex="0"
...attributes
>
<div class="chat-message-thread-indicator__last-reply-avatar">
<ChatUserAvatar
@user={{@message.thread.preview.lastReplyUser}}
@ -167,6 +54,6 @@ export default class ChatMessageThreadIndicator extends Component {
<div class="chat-message-thread-indicator__last-reply-excerpt">
{{replaceEmoji (htmlSafe @message.thread.preview.lastReplyExcerpt)}}
</div>
</div>
</LinkTo>
</template>
}

View File

@ -589,7 +589,10 @@ export default class ChatMessage extends Component {
{{/unless}}
{{#if this.hideUserInfo}}
<ChatMessageLeftGutter @message={{@message}} />
<ChatMessageLeftGutter
@message={{@message}}
@threadContext={{this.threadContext}}
/>
{{else}}
<ChatMessageAvatar @message={{@message}} />
{{/if}}

View File

@ -1,4 +1,5 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { LinkTo } from "@ember/routing";
@ -142,7 +143,7 @@ export default class ChatMessageInfo extends Component {
{{/if}}
<span class="chat-message-info__date">
{{formatChatDate @message}}
{{formatChatDate @message (hash threadContext=@threadContext)}}
</span>
{{#if @message.bookmark}}

View File

@ -1,4 +1,5 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { eq } from "truth-helpers";
@ -25,7 +26,10 @@ export default class ChatMessageLeftGutter extends Component {
</div>
{{else if this.site.desktopView}}
<span class="chat-message-left-gutter__date">
{{formatChatDate @message "tiny"}}
{{formatChatDate
@message
(hash mode="tiny" threadContext=@threadContext)
}}
</span>
{{/if}}
{{#if @message.bookmark}}

View File

@ -3,14 +3,14 @@ import User from "discourse/models/user";
import getURL from "discourse-common/lib/get-url";
import I18n from "discourse-i18n";
export default function formatChatDate(message, mode) {
export default function formatChatDate(message, options = {}) {
const currentUser = User.current();
const tz = currentUser ? currentUser.user_option.timezone : moment.tz.guess();
const date = moment(new Date(message.createdAt), tz);
const title = date.format(I18n.t("dates.long_with_year"));
const display =
mode === "tiny"
options.mode === "tiny"
? date.format(I18n.t("chat.dates.time_tiny"))
: date.format(I18n.t("dates.time"));
@ -19,7 +19,15 @@ export default function formatChatDate(message, mode) {
`<span title='${title}' tabindex="-1" class='chat-time'>${display}</span>`
);
} else {
const url = getURL(`/chat/c/-/${message.channel.id}/${message.id}`);
let url;
if (options.threadContext) {
url = getURL(
`/chat/c/-/${message.channel.id}/t/${message.thread.id}/${message.id}`
);
} else {
url = getURL(`/chat/c/-/${message.channel.id}/${message.id}`);
}
return htmlSafe(
`<a title='${title}' tabindex="-1" class='chat-time' href='${url}'>${display}</a>`
);

View File

@ -29,12 +29,6 @@
}
}
.touch & {
&.-active {
box-shadow: var(--shadow-dropdown);
}
}
.no-touch & {
&:hover {
box-shadow: var(--shadow-dropdown);

View File

@ -19,4 +19,19 @@ module("Discourse Chat | Unit | Helpers | format-chat-date", function (hooks) {
`/chat/c/-/${channel.id}/${this.message.id}`
);
});
test("link to chat message thread", async function (assert) {
const channel = fabricators.channel();
const thread = fabricators.thread();
this.message = fabricators.message({ channel, thread });
await render(
hbs`{{format-chat-date this.message (hash threadContext=true)}}`
);
assert.equal(
query(".chat-time").getAttribute("href"),
`/chat/c/-/${channel.id}/t/${thread.id}/${this.message.id}`
);
});
});