mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
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:
@ -1,146 +1,33 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { LinkTo } from "@ember/routing";
|
||||||
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 formatDate from "discourse/helpers/format-date";
|
import formatDate from "discourse/helpers/format-date";
|
||||||
import replaceEmoji from "discourse/helpers/replace-emoji";
|
import replaceEmoji from "discourse/helpers/replace-emoji";
|
||||||
import htmlSafe from "discourse-common/helpers/html-safe";
|
import htmlSafe from "discourse-common/helpers/html-safe";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
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 ChatThreadParticipants from "./chat-thread-participants";
|
||||||
import ChatUserAvatar from "./chat-user-avatar";
|
import ChatUserAvatar from "./chat-user-avatar";
|
||||||
|
|
||||||
export default class ChatMessageThreadIndicator extends Component {
|
export default class ChatMessageThreadIndicator extends Component {
|
||||||
@service capabilities;
|
|
||||||
@service chat;
|
|
||||||
@service chatStateManager;
|
|
||||||
@service router;
|
|
||||||
@service site;
|
|
||||||
|
|
||||||
@tracked isActive = false;
|
|
||||||
|
|
||||||
get interactiveUser() {
|
get interactiveUser() {
|
||||||
return this.args.interactiveUser ?? true;
|
return this.args.interactiveUser ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
get threadMessageRoute() {
|
||||||
setup(element) {
|
return [
|
||||||
this.element = element;
|
...this.args.message.thread.routeModels,
|
||||||
|
this.args.message.thread.preview.lastReplyId,
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<LinkTo
|
||||||
class={{concatClass
|
class="chat-message-thread-indicator"
|
||||||
"chat-message-thread-indicator"
|
@route="chat.channel.thread.near-message"
|
||||||
(if this.isActive "-active")
|
@models={{this.threadMessageRoute}}
|
||||||
}}
|
|
||||||
{{didInsert this.setup}}
|
|
||||||
{{willDestroy this.teardown}}
|
|
||||||
role="button"
|
|
||||||
title={{i18n "chat.threads.open"}}
|
title={{i18n "chat.threads.open"}}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="chat-message-thread-indicator__last-reply-avatar">
|
<div class="chat-message-thread-indicator__last-reply-avatar">
|
||||||
<ChatUserAvatar
|
<ChatUserAvatar
|
||||||
@user={{@message.thread.preview.lastReplyUser}}
|
@user={{@message.thread.preview.lastReplyUser}}
|
||||||
@ -167,6 +54,6 @@ export default class ChatMessageThreadIndicator extends Component {
|
|||||||
<div class="chat-message-thread-indicator__last-reply-excerpt">
|
<div class="chat-message-thread-indicator__last-reply-excerpt">
|
||||||
{{replaceEmoji (htmlSafe @message.thread.preview.lastReplyExcerpt)}}
|
{{replaceEmoji (htmlSafe @message.thread.preview.lastReplyExcerpt)}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</LinkTo>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
@ -589,7 +589,10 @@ export default class ChatMessage extends Component {
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if this.hideUserInfo}}
|
{{#if this.hideUserInfo}}
|
||||||
<ChatMessageLeftGutter @message={{@message}} />
|
<ChatMessageLeftGutter
|
||||||
|
@message={{@message}}
|
||||||
|
@threadContext={{this.threadContext}}
|
||||||
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<ChatMessageAvatar @message={{@message}} />
|
<ChatMessageAvatar @message={{@message}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
@ -142,7 +143,7 @@ export default class ChatMessageInfo extends Component {
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<span class="chat-message-info__date">
|
<span class="chat-message-info__date">
|
||||||
{{formatChatDate @message}}
|
{{formatChatDate @message (hash threadContext=@threadContext)}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{{#if @message.bookmark}}
|
{{#if @message.bookmark}}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { eq } from "truth-helpers";
|
import { eq } from "truth-helpers";
|
||||||
@ -25,7 +26,10 @@ export default class ChatMessageLeftGutter extends Component {
|
|||||||
</div>
|
</div>
|
||||||
{{else if this.site.desktopView}}
|
{{else if this.site.desktopView}}
|
||||||
<span class="chat-message-left-gutter__date">
|
<span class="chat-message-left-gutter__date">
|
||||||
{{formatChatDate @message "tiny"}}
|
{{formatChatDate
|
||||||
|
@message
|
||||||
|
(hash mode="tiny" threadContext=@threadContext)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if @message.bookmark}}
|
{{#if @message.bookmark}}
|
||||||
|
@ -3,14 +3,14 @@ import User from "discourse/models/user";
|
|||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
export default function formatChatDate(message, mode) {
|
export default function formatChatDate(message, options = {}) {
|
||||||
const currentUser = User.current();
|
const currentUser = User.current();
|
||||||
const tz = currentUser ? currentUser.user_option.timezone : moment.tz.guess();
|
const tz = currentUser ? currentUser.user_option.timezone : moment.tz.guess();
|
||||||
const date = moment(new Date(message.createdAt), tz);
|
const date = moment(new Date(message.createdAt), tz);
|
||||||
|
|
||||||
const title = date.format(I18n.t("dates.long_with_year"));
|
const title = date.format(I18n.t("dates.long_with_year"));
|
||||||
const display =
|
const display =
|
||||||
mode === "tiny"
|
options.mode === "tiny"
|
||||||
? date.format(I18n.t("chat.dates.time_tiny"))
|
? date.format(I18n.t("chat.dates.time_tiny"))
|
||||||
: date.format(I18n.t("dates.time"));
|
: 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>`
|
`<span title='${title}' tabindex="-1" class='chat-time'>${display}</span>`
|
||||||
);
|
);
|
||||||
} else {
|
} 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(
|
return htmlSafe(
|
||||||
`<a title='${title}' tabindex="-1" class='chat-time' href='${url}'>${display}</a>`
|
`<a title='${title}' tabindex="-1" class='chat-time' href='${url}'>${display}</a>`
|
||||||
);
|
);
|
||||||
|
@ -29,12 +29,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.touch & {
|
|
||||||
&.-active {
|
|
||||||
box-shadow: var(--shadow-dropdown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-touch & {
|
.no-touch & {
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: var(--shadow-dropdown);
|
box-shadow: var(--shadow-dropdown);
|
||||||
|
@ -19,4 +19,19 @@ module("Discourse Chat | Unit | Helpers | format-chat-date", function (hooks) {
|
|||||||
`/chat/c/-/${channel.id}/${this.message.id}`
|
`/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}`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user