mirror of
https://github.com/discourse/discourse.git
synced 2025-06-05 14:07:30 +08:00
FEATURE: Sort thread list by unread threads first (#22272)
* FEATURE: Sort thread list by unread threads first This commit changes the thread list to show the threads that have unread messages at the top of the list sorted by the last reply date + time, then all other threads sorted by last reply date + time. This also fixes some issues by removing the last_reply relationship on the thread, which did not work for complex querying scenarios because its order would be discarded. * FIX: Various fixes for thread list loading * Use the channel.threadsManager and find the channel first rather than use activeChannel in the threads manager, otherwise we may be looking at differenct channels. * Look at threadsManager directly instead of storing result for threads list otherwise it can get out of sync because of replace: true in other places we are loading threads into the store. * Fix sorting for thread.last_reply, needed a resort.
This commit is contained in:
@ -213,6 +213,12 @@ export default class ChatLivePane extends Component {
|
||||
thread,
|
||||
{ replace: true }
|
||||
);
|
||||
|
||||
this.#preloadThreadTrackingState(
|
||||
storedThread,
|
||||
result.tracking.thread_tracking
|
||||
);
|
||||
|
||||
const originalMessage = messages.findBy(
|
||||
"id",
|
||||
storedThread.originalMessage.id
|
||||
@ -323,6 +329,12 @@ export default class ChatLivePane extends Component {
|
||||
thread,
|
||||
{ replace: true }
|
||||
);
|
||||
|
||||
this.#preloadThreadTrackingState(
|
||||
storedThread,
|
||||
result.tracking.thread_tracking
|
||||
);
|
||||
|
||||
const originalMessage = messages.findBy(
|
||||
"id",
|
||||
storedThread.originalMessage.id
|
||||
@ -1098,4 +1110,15 @@ export default class ChatLivePane extends Component {
|
||||
cancel(this._laterComputeHandler);
|
||||
cancel(this._debounceFetchMessagesHandler);
|
||||
}
|
||||
|
||||
#preloadThreadTrackingState(storedThread, threadTracking) {
|
||||
if (!threadTracking[storedThread.id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
storedThread.tracking.unreadCount =
|
||||
threadTracking[storedThread.id].unread_count;
|
||||
storedThread.tracking.mentionCount =
|
||||
threadTracking[storedThread.id].mention_count;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
{{#if this.loading}}
|
||||
{{loading-spinner size="medium"}}
|
||||
{{else}}
|
||||
{{#each this.threads as |thread|}}
|
||||
{{#each this.sortedThreads as |thread|}}
|
||||
<Chat::ThreadList::Item @thread={{thread}} />
|
||||
{{else}}
|
||||
<div class="chat-thread-list__no-threads">
|
||||
|
@ -6,9 +6,49 @@ import { inject as service } from "@ember/service";
|
||||
export default class ChatThreadList extends Component {
|
||||
@service chat;
|
||||
|
||||
@tracked threads;
|
||||
@tracked loading = true;
|
||||
|
||||
// NOTE: This replicates sort logic from the server. We need this because
|
||||
// the thread unread count + last reply date + time update when new messages
|
||||
// are sent to the thread, and we want the list to react in realtime to this.
|
||||
get sortedThreads() {
|
||||
if (!this.args.channel.threadsManager.threads) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.args.channel.threadsManager.threads.sort((threadA, threadB) => {
|
||||
// If both are unread we just want to sort by last reply date + time descending.
|
||||
if (threadA.tracking.unreadCount && threadB.tracking.unreadCount) {
|
||||
if (
|
||||
threadA.preview.lastReplyCreatedAt >
|
||||
threadB.preview.lastReplyCreatedAt
|
||||
) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If one is unread and the other is not, we want to sort the unread one first.
|
||||
if (threadA.tracking.unreadCount) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (threadB.tracking.unreadCount) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If both are read, we want to sort by last reply date + time descending.
|
||||
if (
|
||||
threadA.preview.lastReplyCreatedAt > threadB.preview.lastReplyCreatedAt
|
||||
) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get shouldRender() {
|
||||
return !!this.args.channel;
|
||||
}
|
||||
@ -16,21 +56,13 @@ export default class ChatThreadList extends Component {
|
||||
@action
|
||||
loadThreads() {
|
||||
this.loading = true;
|
||||
this.args.channel.threadsManager
|
||||
.index(this.args.channel.id)
|
||||
.then((result) => {
|
||||
if (result.meta.channel_id === this.args.channel.id) {
|
||||
this.threads = result.threads;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
this.args.channel.threadsManager.index(this.args.channel.id).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
teardown() {
|
||||
this.loading = true;
|
||||
this.threads = null;
|
||||
}
|
||||
}
|
||||
|
@ -38,21 +38,21 @@ export default class ChatThreadsManager {
|
||||
}
|
||||
|
||||
async index(channelId) {
|
||||
return this.#loadIndex(channelId).then((result) => {
|
||||
const threads = result.threads.map((thread) => {
|
||||
return this.chat.activeChannel.threadsManager.store(
|
||||
this.chat.activeChannel,
|
||||
thread,
|
||||
{ replace: true }
|
||||
return this.chatChannelsManager.find(channelId).then((channel) => {
|
||||
return this.#loadIndex(channelId).then((result) => {
|
||||
const threads = result.threads.map((thread) => {
|
||||
return channel.threadsManager.store(channel, thread, {
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
|
||||
this.chatTrackingStateManager.setupChannelThreadState(
|
||||
channel,
|
||||
result.tracking
|
||||
);
|
||||
|
||||
return { threads, meta: result.meta };
|
||||
});
|
||||
|
||||
this.chatTrackingStateManager.setupChannelThreadState(
|
||||
this.chat.activeChannel,
|
||||
result.tracking
|
||||
);
|
||||
|
||||
return { threads, meta: result.meta };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,9 @@ export default class ChatThreadPreview {
|
||||
|
||||
this.replyCount = args.reply_count || args.replyCount || 0;
|
||||
this.lastReplyId = args.last_reply_id || args.lastReplyId;
|
||||
this.lastReplyCreatedAt =
|
||||
args.last_reply_created_at || args.lastReplyCreatedAt;
|
||||
this.lastReplyCreatedAt = new Date(
|
||||
args.last_reply_created_at || args.lastReplyCreatedAt
|
||||
);
|
||||
this.lastReplyExcerpt = args.last_reply_excerpt || args.lastReplyExcerpt;
|
||||
this.lastReplyUser = args.last_reply_user || args.lastReplyUser;
|
||||
this.participantCount =
|
||||
|
@ -34,10 +34,6 @@ export default class ChatTrackingStateManager extends Service {
|
||||
|
||||
setupChannelThreadState(channel, threadTracking) {
|
||||
channel.threadsManager.threads.forEach((thread) => {
|
||||
// TODO (martin) Since we didn't backfill data for thread membership,
|
||||
// there are cases where we are getting threads the user "participated"
|
||||
// in but don't have tracking state for them. We need a migration to
|
||||
// backfill this data.
|
||||
if (threadTracking[thread.id.toString()]) {
|
||||
this.#setState(thread, threadTracking[thread.id.toString()]);
|
||||
}
|
||||
|
Reference in New Issue
Block a user