FEATURE: Mark all chat channels read with a shortcut (#20629)

This commit adds a keyboard shortcut (Shift+ESC) for chat which marks all
of the chat channels that the user is currently a following member of as read,
updating their `last_read_message_id`. This is done via a new service.

It also includes some refactors and controller changes:

* The old mark message read route from `ChatController` is now supplanted
  by the `Chat::Api::ReadsController#update` route.
* The new controller can handle either marking a single or all messages read,
  and uses the correct service based on the route and params.
* The `UpdateUserLastRead` service is now used (it wasn't before), and has been slightly
  updated to just use the guardian user ID.
This commit is contained in:
Martin Brennan
2023-03-22 13:24:07 +10:00
committed by GitHub
parent 786f7503b4
commit a0381157e9
19 changed files with 619 additions and 216 deletions
@@ -17,6 +17,9 @@ export default {
const router = container.lookup("service:router");
const appEvents = container.lookup("service:app-events");
const chatStateManager = container.lookup("service:chat-state-manager");
const chatChannelsManager = container.lookup(
"service:chat-channels-manager"
);
const openChannelSelector = (e) => {
e.preventDefault();
e.stopPropagation();
@@ -92,6 +95,15 @@ export default {
appEvents.trigger("chat:toggle-close", event);
};
const markAllChannelsRead = (event) => {
event.preventDefault();
event.stopPropagation();
if (chatStateManager.isActive) {
chatChannelsManager.markAllChannelsRead();
}
};
withPluginApi("0.12.1", (api) => {
api.addKeyboardShortcut(`${KEY_MODIFIER}+k`, openChannelSelector, {
global: true,
@@ -201,6 +213,21 @@ export default {
},
},
});
api.addKeyboardShortcut(
`shift+esc`,
(event) => markAllChannelsRead(event),
{
global: true,
help: {
category: "chat",
name: "chat.keyboard_shortcuts.mark_all_channels_read",
definition: {
keys1: ["shift", "esc"],
keysDelimiter: "plus",
},
},
}
);
});
},
};
@@ -168,7 +168,9 @@ export default class ChatChannel extends RestModel {
return;
}
return ajax(`/chat/${this.id}/read/${messageId}.json`, {
// TODO (martin) Change this to use chatApi service once we change this
// class not to use RestModel
return ajax(`/chat/api/channels/${this.id}/read/${messageId}`, {
method: "PUT",
}).then(() => {
this.currentUserMembership.last_read_message_id = messageId;
@@ -286,6 +286,28 @@ export default class ChatApi extends Service {
);
}
/**
* Marks messages for all of a user's chat channel memberships as read.
*
* @returns {Promise}
*/
markAllChannelsAsRead() {
return this.#putRequest(`/channels/read`);
}
/**
* Marks messages for a single user chat channel membership as read. If no
* message ID is provided, then the latest message for the channel is fetched
* on the server and used for the last read message.
*
* @param {number} channelId - The ID of the channel for the message being marked as read.
* @param {number} [messageId] - The ID of the message being marked as read.
* @returns {Promise}
*/
markChannelAsRead(channelId, messageId = null) {
return this.#putRequest(`/channels/${channelId}/read/${messageId}`);
}
get #basePath() {
return "/chat/api";
}
@@ -1,4 +1,5 @@
import Service, { inject as service } from "@ember/service";
import { debounce } from "discourse-common/utils/decorators";
import Promise from "rsvp";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
import { tracked } from "@glimmer/tracking";
@@ -82,6 +83,19 @@ export default class ChatChannelsManager extends Service {
});
}
@debounce(300)
async markAllChannelsRead() {
return this.chatApi.markAllChannelsAsRead().then((response) => {
response.updated_memberships.forEach((membership) => {
let channel = this.channels.findBy("id", membership.channel_id);
if (channel) {
channel.currentUserMembership.unread_count = 0;
channel.currentUserMembership.unread_mentions = 0;
}
});
});
}
remove(model) {
this.chatSubscriptionsManager.stopChannelSubscription(model);
delete this._cached[model.id];