mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
This commit will now allow us to track read position in a thread and returns to this position when you open the thread. Note this commit is also extracting the following components to make it possible: - `<ChatMessagesScroller />` - `<ChatMessagesContainer />` The `UpdateUserThreadLastRead` has been updated to allow this. Various refactorings have also been done to the code and specs to improve the support of last read.
615 lines
19 KiB
JavaScript
615 lines
19 KiB
JavaScript
import Service, { service } from "@ember/service";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership";
|
|
import Collection from "../lib/collection";
|
|
|
|
/**
|
|
* Chat API service. Provides methods to interact with the chat API.
|
|
*
|
|
* @module ChatApi
|
|
* @implements {@ember/service}
|
|
*/
|
|
export default class ChatApi extends Service {
|
|
@service chat;
|
|
@service chatChannelsManager;
|
|
|
|
channel(channelId) {
|
|
return this.#getRequest(`/channels/${channelId}`);
|
|
}
|
|
|
|
channelThreadMessages(channelId, threadId, params = {}) {
|
|
return this.#getRequest(
|
|
`/channels/${channelId}/threads/${threadId}/messages?${new URLSearchParams(
|
|
params
|
|
).toString()}`
|
|
);
|
|
}
|
|
|
|
channelMessages(channelId, params = {}) {
|
|
return this.#getRequest(
|
|
`/channels/${channelId}/messages?${new URLSearchParams(
|
|
params
|
|
).toString()}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Flags a message in a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {number} messageId - The ID of the message to flag.
|
|
* @param {object} params - Params of the flag.
|
|
* @param {integer} params.flag_type_id
|
|
* @param {string} [params.message]
|
|
* @param {boolean} [params.is_warning]
|
|
* @param {boolean} [params.queue_for_review]
|
|
* @param {boolean} [params.take_action]
|
|
* @returns {Promise}
|
|
*
|
|
* @example
|
|
*
|
|
* this.chatApi.flagMessage(5, 1);
|
|
*/
|
|
flagMessage(channelId, messageId, params = {}) {
|
|
return this.#postRequest(
|
|
`/channels/${channelId}/messages/${messageId}/flags`,
|
|
params
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get a thread in a channel by its ID.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {number} threadId - The ID of the thread.
|
|
* @returns {Promise}
|
|
*
|
|
* @example
|
|
*
|
|
* this.chatApi.thread(5, 1).then(thread => { ... })
|
|
*/
|
|
thread(channelId, threadId) {
|
|
return this.#getRequest(`/channels/${channelId}/threads/${threadId}`);
|
|
}
|
|
|
|
/**
|
|
* Loads all threads for a channel.
|
|
* For now we only get the 50 threads ordered
|
|
* by the last message sent by the user then the
|
|
* thread creation date, later we will paginate
|
|
* and add filters.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @returns {Promise}
|
|
*/
|
|
threads(channelId, handler) {
|
|
return new Collection(
|
|
`${this.#basePath}/channels/${channelId}/threads`,
|
|
handler
|
|
);
|
|
}
|
|
|
|
/**
|
|
* List all accessible category channels of the current user.
|
|
* @returns {Collection}
|
|
*
|
|
* @example
|
|
*
|
|
* this.chatApi.channels.then(channels => { ... })
|
|
*/
|
|
channels(params = {}) {
|
|
return new Collection(
|
|
`${this.#basePath}/channels`,
|
|
(response) => {
|
|
return response.channels.map((channel) =>
|
|
this.chatChannelsManager.store(channel)
|
|
);
|
|
},
|
|
params
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Moves messages from one channel to another.
|
|
* @param {number} channelId - The ID of the original channel.
|
|
* @param {object} data - Params of the move.
|
|
* @param {Array.<number>} data.message_ids - IDs of the moved messages.
|
|
* @param {number} data.destination_channel_id - ID of the channel where the messages are moved to.
|
|
* @returns {Promise}
|
|
*
|
|
* @example
|
|
*
|
|
* this.chatApi
|
|
* .moveChannelMessages(1, {
|
|
* message_ids: [2, 3],
|
|
* destination_channel_id: 4,
|
|
* }).then(() => { ... })
|
|
*/
|
|
moveChannelMessages(channelId, data = {}) {
|
|
return this.#postRequest(`/channels/${channelId}/messages/moves`, {
|
|
move: data,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Destroys a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @returns {Promise}
|
|
*
|
|
* @example
|
|
*
|
|
* this.chatApi.destroyChannel(1).then(() => { ... })
|
|
*/
|
|
destroyChannel(channelId) {
|
|
return this.#deleteRequest(`/channels/${channelId}`);
|
|
}
|
|
|
|
/**
|
|
* Creates a channel.
|
|
* @param {object} data - Params of the channel.
|
|
* @param {string} data.name - The name of the channel.
|
|
* @param {string} data.chatable_id - The category of the channel.
|
|
* @param {string} data.description - The description of the channel.
|
|
* @param {boolean} [data.auto_join_users] - Should users join this channel automatically.
|
|
* @returns {Promise}
|
|
*
|
|
* @example
|
|
*
|
|
* this.chatApi
|
|
* .createChannel({ name: "foo", chatable_id: 1, description "bar" })
|
|
* .then((channel) => { ... })
|
|
*/
|
|
createChannel(data = {}) {
|
|
return this.#postRequest("/channels", { channel: data }).then((response) =>
|
|
this.chatChannelsManager.store(response.channel)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Lists chat permissions for a category.
|
|
* @param {number} categoryId - ID of the category.
|
|
* @returns {Promise}
|
|
*/
|
|
categoryPermissions(categoryId) {
|
|
return this.#getRequest(`/category-chatables/${categoryId}/permissions`);
|
|
}
|
|
|
|
/**
|
|
* Sends a message.
|
|
* @param {number} channelId - ID of the channel.
|
|
* @param {object} data - Params of the message.
|
|
* @param {string} data.message - The raw content of the message in markdown.
|
|
* @param {string} data.cooked - The cooked content of the message.
|
|
* @param {number} [data.in_reply_to_id] - The ID of the replied-to message.
|
|
* @param {number} [data.staged_id] - The staged ID of the message before it was persisted.
|
|
* @param {number} [data.thread_id] - The ID of the thread where this message should be posted.
|
|
* @param {number} [data.topic_id] - The ID of the currently visible topic in drawer mode.
|
|
* @param {number} [data.post_ids] - The ID of the currently visible posts in drawer mode.
|
|
* @param {Array.<number>} [data.upload_ids] - Array of upload ids linked to the message.
|
|
* @returns {Promise}
|
|
*/
|
|
sendMessage(channelId, data = {}) {
|
|
return ajax(`/chat/${channelId}`, {
|
|
ignoreUnsent: false,
|
|
type: "POST",
|
|
data,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stop streaming of a message
|
|
* @param {number} channelId - ID of the channel.
|
|
* @param {number} messageId - ID of the message.
|
|
* @returns {Promise}
|
|
*/
|
|
stopMessageStreaming(channelId, messageId) {
|
|
return this.#deleteRequest(
|
|
`/channels/${channelId}/messages/${messageId}/streaming`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Trashes (soft deletes) a chat message.
|
|
* @param {number} channelId - ID of the channel.
|
|
* @param {number} messageId - ID of the message.
|
|
* @returns {Promise}
|
|
*/
|
|
trashMessage(channelId, messageId) {
|
|
return this.#deleteRequest(`/channels/${channelId}/messages/${messageId}`);
|
|
}
|
|
|
|
/**
|
|
* Creates a channel archive.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {object} data - Params of the archive.
|
|
* @param {string} data.selection - "new_topic" or "existing_topic".
|
|
* @param {string} [data.title] - Title of the topic when creating a new topic.
|
|
* @param {string} [data.category_id] - ID of the category used when creating a new topic.
|
|
* @param {Array.<string>} [data.tags] - tags used when creating a new topic.
|
|
* @param {string} [data.topic_id] - ID of the topic when using an existing topic.
|
|
* @returns {Promise}
|
|
*/
|
|
createChannelArchive(channelId, data = {}) {
|
|
return this.#postRequest(`/channels/${channelId}/archives`, {
|
|
archive: data,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {object} data - Params of the archive.
|
|
* @param {string} [data.description] - Description of the channel.
|
|
* @param {string} [data.name] - Name of the channel.
|
|
* @returns {Promise}
|
|
*/
|
|
updateChannel(channelId, data = {}) {
|
|
return this.#putRequest(`/channels/${channelId}`, { channel: data });
|
|
}
|
|
|
|
/**
|
|
* Creates a thread.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {number} originalMessageId - The ID of the original message.
|
|
* @param {object} data - Params of the thread.
|
|
* @param {string} [data.title] - Title of the thread.
|
|
* @returns {Promise}
|
|
*/
|
|
createThread(channelId, originalMessageId, data = {}) {
|
|
return this.#postRequest(`/channels/${channelId}/threads`, {
|
|
title: data.title,
|
|
original_message_id: originalMessageId,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates the status of a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {string} status - The new status, can be "open" or "closed".
|
|
* @returns {Promise}
|
|
*/
|
|
updateChannelStatus(channelId, status) {
|
|
return this.#putRequest(`/channels/${channelId}/status`, { status });
|
|
}
|
|
|
|
/**
|
|
* Lists members of a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @returns {Collection}
|
|
*/
|
|
listChannelMemberships(channelId, params = {}) {
|
|
return new Collection(
|
|
`${this.#basePath}/channels/${channelId}/memberships`,
|
|
(response) => {
|
|
return response.memberships.map((membership) =>
|
|
UserChatChannelMembership.create(membership)
|
|
);
|
|
},
|
|
params
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Lists public and direct message channels of the current user.
|
|
* @returns {Promise}
|
|
*/
|
|
listCurrentUserChannels() {
|
|
return this.#getRequest("/me/channels");
|
|
}
|
|
|
|
/**
|
|
* Makes current user follow a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @returns {Promise}
|
|
*/
|
|
followChannel(channelId) {
|
|
return this.#postRequest(`/channels/${channelId}/memberships/me`).then(
|
|
(result) => UserChatChannelMembership.create(result.membership)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Makes current user unfollow a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @returns {Promise}
|
|
*/
|
|
unfollowChannel(channelId) {
|
|
return this.#deleteRequest(
|
|
`/channels/${channelId}/memberships/me/follows`
|
|
).then((result) => UserChatChannelMembership.create(result.membership));
|
|
}
|
|
|
|
/**
|
|
* Destroys the membership of current user on a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @returns {Promise}
|
|
*/
|
|
async leaveChannel(channelId) {
|
|
await this.#deleteRequest(`/channels/${channelId}/memberships/me`);
|
|
const channel = await this.chatChannelsManager.find(channelId, {
|
|
fetchIfNotFound: false,
|
|
});
|
|
if (channel) {
|
|
this.chatChannelsManager.remove(channel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the list of tracked threads for the current user.
|
|
*
|
|
* @returns {Promise}
|
|
*/
|
|
userThreads(handler) {
|
|
return new Collection(`${this.#basePath}/me/threads`, handler);
|
|
}
|
|
|
|
/**
|
|
* Update notifications settings of current user for a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {object} data - The settings to modify.
|
|
* @param {boolean} [data.muted] - Mutes the channel.
|
|
* @param {string} [data.desktop_notification_level] - Notifications level on desktop: never, mention or always.
|
|
* @param {string} [data.mobile_notification_level] - Notifications level on mobile: never, mention or always.
|
|
* @returns {Promise}
|
|
*/
|
|
updateCurrentUserChannelNotificationsSettings(channelId, data = {}) {
|
|
return this.#putRequest(
|
|
`/channels/${channelId}/notifications-settings/me`,
|
|
{ notifications_settings: data }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update notifications settings of current user for a thread.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {number} threadId - The ID of the thread.
|
|
* @param {object} data - The settings to modify.
|
|
* @param {boolean} [data.notification_level] - The new notification level, c.f. Chat::NotificationLevels. Threads only support
|
|
* "regular" and "tracking" for now.
|
|
* @returns {Promise}
|
|
*/
|
|
updateCurrentUserThreadNotificationsSettings(channelId, threadId, data) {
|
|
return this.#putRequest(
|
|
`/channels/${channelId}/threads/${threadId}/notifications-settings/me`,
|
|
{ notification_level: data.notificationLevel }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Saves a draft for the channel, which includes message contents and uploads.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {object} data - The draft data, see ChatMessage.toJSONDraft() for more details.
|
|
* @returns {Promise}
|
|
*/
|
|
saveDraft(channelId, data, options = {}) {
|
|
let endpoint = `/chat/api/channels/${channelId}`;
|
|
if (options.threadId) {
|
|
endpoint += `/threads/${options.threadId}`;
|
|
}
|
|
endpoint += "/drafts";
|
|
|
|
return ajax(endpoint, {
|
|
type: "POST",
|
|
data: {
|
|
data,
|
|
},
|
|
ignoreUnsent: false,
|
|
})
|
|
.then(() => {
|
|
this.chat.markNetworkAsReliable();
|
|
})
|
|
.catch((error) => {
|
|
// we ignore a draft which can't be saved because it's too big
|
|
// and only deal with network error for now
|
|
if (!error.jqXHR?.responseJSON?.errors?.length) {
|
|
this.chat.markNetworkAsUnreliable();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adds or removes an emoji reaction for a message inside a channel.
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {number} messageId - The ID of the message to react on.
|
|
* @param {string} emoji - The text version of the emoji without colons, e.g. tada
|
|
* @param {string} reaction - Either "add" or "remove"
|
|
* @returns {Promise}
|
|
*/
|
|
publishReaction(channelId, messageId, emoji, reactAction) {
|
|
return ajax(`/chat/${channelId}/react/${messageId}`, {
|
|
type: "PUT",
|
|
data: {
|
|
react_action: reactAction,
|
|
emoji,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Restores a single deleted chat message in a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel for the message being restored.
|
|
* @param {number} messageId - The ID of the message being restored.
|
|
*/
|
|
restoreMessage(channelId, messageId) {
|
|
return this.#putRequest(
|
|
`/channels/${channelId}/messages/${messageId}/restore`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Rebakes the cooked HTML of a single message in a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel for the message being restored.
|
|
* @param {number} messageId - The ID of the message being restored.
|
|
*/
|
|
rebakeMessage(channelId, messageId) {
|
|
return ajax(`/chat/${channelId}/${messageId}/rebake`, {
|
|
type: "PUT",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Saves an edit to a message's contents in a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel for the message being edited.
|
|
* @param {number} messageId - The ID of the message being edited.
|
|
* @param {object} data - Params of the edit.
|
|
* @param {string} data.new_message - The edited content of the message.
|
|
* @param {Array<number>} data.upload_ids - The uploads attached to the message after editing.
|
|
*/
|
|
editMessage(channelId, messageId, data) {
|
|
return this.#putRequest(
|
|
`/channels/${channelId}/messages/${messageId}`,
|
|
data
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Marks messages for all of a user's chat channel memberships as read.
|
|
*
|
|
* @returns {Promise}
|
|
*/
|
|
markAllChannelsAsRead() {
|
|
return this.#putRequest(`/channels/read`);
|
|
}
|
|
|
|
/**
|
|
* Lists all possible chatables.
|
|
*
|
|
* @param {term} string - The term to search for. # prefix will scope to channels, @ to users.
|
|
*
|
|
* @returns {Promise}
|
|
*/
|
|
chatables(args = {}) {
|
|
return this.#getRequest("/chatables", args);
|
|
}
|
|
|
|
/**
|
|
* 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?message_id=${messageId}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Marks messages for a single user chat thread 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 thread being marked as read.
|
|
* @param {number} threadId - The ID of the thread being marked as read.
|
|
* @param {number} messageId - The ID of the message being marked as read.
|
|
* @returns {Promise}
|
|
*/
|
|
markThreadAsRead(channelId, threadId, messageId) {
|
|
return this.#putRequest(
|
|
`/channels/${channelId}/threads/${threadId}/read?message_id=${messageId}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updates settings of a thread.
|
|
*
|
|
* @param {number} channelId - The ID of the channel for the thread being edited.
|
|
* @param {number} threadId - The ID of the thread being edited.
|
|
* @param {object} data - Params of the edit.
|
|
* @param {string} data.title - The new title for the thread.
|
|
*/
|
|
editThread(channelId, threadId, data) {
|
|
return this.#putRequest(`/channels/${channelId}/threads/${threadId}`, data);
|
|
}
|
|
|
|
/**
|
|
* Generate a quote for a list of messages.
|
|
*
|
|
* @param {number} channelId - The ID of the channel containing the messages.
|
|
* @param {Array<number>} messageIds - The IDs of the messages to quote.
|
|
*/
|
|
generateQuote(channelId, messageIds) {
|
|
return ajax(`/chat/${channelId}/quote`, {
|
|
type: "POST",
|
|
data: { message_ids: messageIds },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Invite users to a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {Array<number>} userIds - The IDs of the users to invite.
|
|
* @param {object} options
|
|
* @param {number} options.chat_message_id - A message ID to display in the invite.
|
|
*/
|
|
invite(channelId, userIds, options = {}) {
|
|
return this.#postRequest(`/channels/${channelId}/invites`, {
|
|
user_ids: userIds,
|
|
message_id: options.messageId,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Summarize a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel to summarize.
|
|
* @param {object} options
|
|
* @param {number} options.since - Number of hours ago the summary should start (1, 3, 6, 12, 24, 72, 168).
|
|
*/
|
|
summarize(channelId, options = {}) {
|
|
return this.#getRequest(`/channels/${channelId}/summarize`, options);
|
|
}
|
|
|
|
/**
|
|
* Add members to a channel.
|
|
*
|
|
* @param {number} channelId - The ID of the channel.
|
|
* @param {object} targets
|
|
* @param {Array<string>} targets.usernames - The usernames of the users to add.
|
|
* @param {Array<string>} targets.groups - The groups names of the groups to add.
|
|
*/
|
|
addMembersToChannel(channelId, targets) {
|
|
return this.#postRequest(`/channels/${channelId}/memberships`, {
|
|
usernames: targets.usernames,
|
|
groups: targets.groups,
|
|
});
|
|
}
|
|
|
|
get #basePath() {
|
|
return "/chat/api";
|
|
}
|
|
|
|
#getRequest(endpoint, data = {}) {
|
|
return ajax(`${this.#basePath}${endpoint}`, {
|
|
type: "GET",
|
|
data,
|
|
});
|
|
}
|
|
|
|
#putRequest(endpoint, data = {}) {
|
|
return ajax(`${this.#basePath}${endpoint}`, {
|
|
type: "PUT",
|
|
data,
|
|
});
|
|
}
|
|
|
|
#postRequest(endpoint, data = {}) {
|
|
return ajax(`${this.#basePath}${endpoint}`, {
|
|
type: "POST",
|
|
data,
|
|
});
|
|
}
|
|
|
|
#deleteRequest(endpoint, data = {}) {
|
|
return ajax(`${this.#basePath}${endpoint}`, {
|
|
type: "DELETE",
|
|
data,
|
|
});
|
|
}
|
|
}
|