WIP: Mark all chat channels read
This commit is contained in:
parent
aeab38aff1
commit
6f15403f91
13
plugins/chat/app/controllers/api_controller.rb
Normal file
13
plugins/chat/app/controllers/api_controller.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Chat::Api < Chat::ChatBaseController
|
||||
before_action :ensure_logged_in
|
||||
before_action :ensure_can_chat
|
||||
|
||||
private
|
||||
|
||||
def ensure_can_chat
|
||||
raise Discourse::NotFound unless SiteSetting.chat_enabled
|
||||
guardian.ensure_can_chat!
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Chat::Api::ChatTrackingController < Chat::Api
|
||||
def read
|
||||
channel_id = params[:channel_id]
|
||||
message_id = params[:message_id]
|
||||
end
|
||||
end
|
||||
36
plugins/chat/app/controllers/chat_base_controller.rb
Normal file
36
plugins/chat/app/controllers/chat_base_controller.rb
Normal file
@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Chat::ChatBaseController < ::ApplicationController
|
||||
before_action :ensure_logged_in
|
||||
before_action :ensure_can_chat
|
||||
|
||||
include Chat::WithServiceHelper
|
||||
|
||||
private
|
||||
|
||||
def ensure_can_chat
|
||||
raise Discourse::NotFound unless SiteSetting.chat_enabled
|
||||
guardian.ensure_can_chat!
|
||||
end
|
||||
|
||||
def set_channel_and_chatable_with_access_check(chat_channel_id: nil)
|
||||
params.require(:chat_channel_id) if chat_channel_id.blank?
|
||||
id_or_name = chat_channel_id || params[:chat_channel_id]
|
||||
@chat_channel = Chat::ChatChannelFetcher.find_with_access_check(id_or_name, guardian)
|
||||
@chatable = @chat_channel.chatable
|
||||
end
|
||||
|
||||
def default_actions_for_service
|
||||
proc do
|
||||
on_success { render(json: success_json) }
|
||||
on_failure { render(json: failed_json, status: 422) }
|
||||
on_failed_policy(:invalid_access) { raise Discourse::InvalidAccess }
|
||||
on_failed_contract do
|
||||
render(
|
||||
json: failed_json.merge(errors: result[:"result.contract.default"].errors.full_messages),
|
||||
status: 400,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
447
plugins/chat/app/controllers/chat_controller.rb
Normal file
447
plugins/chat/app/controllers/chat_controller.rb
Normal file
@ -0,0 +1,447 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Chat::ChatController < Chat::ChatBaseController
|
||||
PAST_MESSAGE_LIMIT = 40
|
||||
FUTURE_MESSAGE_LIMIT = 40
|
||||
PAST = "past"
|
||||
FUTURE = "future"
|
||||
CHAT_DIRECTIONS = [PAST, FUTURE]
|
||||
|
||||
# Other endpoints use set_channel_and_chatable_with_access_check, but
|
||||
# these endpoints require a standalone find because they need to be
|
||||
# able to get deleted channels and recover them.
|
||||
before_action :find_chatable, only: %i[enable_chat disable_chat]
|
||||
before_action :find_chat_message,
|
||||
only: %i[delete restore lookup_message edit_message rebake message_link]
|
||||
before_action :set_channel_and_chatable_with_access_check,
|
||||
except: %i[
|
||||
respond
|
||||
enable_chat
|
||||
disable_chat
|
||||
message_link
|
||||
lookup_message
|
||||
set_user_chat_status
|
||||
dismiss_retention_reminder
|
||||
flag
|
||||
]
|
||||
|
||||
def respond
|
||||
render
|
||||
end
|
||||
|
||||
def enable_chat
|
||||
chat_channel = ChatChannel.with_deleted.find_by(chatable: @chatable)
|
||||
|
||||
guardian.ensure_can_join_chat_channel!(chat_channel) if chat_channel
|
||||
|
||||
if chat_channel && chat_channel.trashed?
|
||||
chat_channel.recover!
|
||||
elsif chat_channel
|
||||
return render_json_error I18n.t("chat.already_enabled")
|
||||
else
|
||||
chat_channel = @chatable.chat_channel
|
||||
guardian.ensure_can_join_chat_channel!(chat_channel)
|
||||
end
|
||||
|
||||
success = chat_channel.save
|
||||
if success && chat_channel.chatable_has_custom_fields?
|
||||
@chatable.custom_fields[Chat::HAS_CHAT_ENABLED] = true
|
||||
@chatable.save!
|
||||
end
|
||||
|
||||
if success
|
||||
membership = Chat::ChatChannelMembershipManager.new(channel).follow(user)
|
||||
render_serialized(chat_channel, ChatChannelSerializer, membership: membership)
|
||||
else
|
||||
render_json_error(chat_channel)
|
||||
end
|
||||
|
||||
Chat::ChatChannelMembershipManager.new(channel).follow(user)
|
||||
end
|
||||
|
||||
def disable_chat
|
||||
chat_channel = ChatChannel.with_deleted.find_by(chatable: @chatable)
|
||||
guardian.ensure_can_join_chat_channel!(chat_channel)
|
||||
return render json: success_json if chat_channel.trashed?
|
||||
chat_channel.trash!(current_user)
|
||||
|
||||
success = chat_channel.save
|
||||
if success
|
||||
if chat_channel.chatable_has_custom_fields?
|
||||
@chatable.custom_fields.delete(Chat::HAS_CHAT_ENABLED)
|
||||
@chatable.save!
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
else
|
||||
render_json_error(chat_channel)
|
||||
end
|
||||
end
|
||||
|
||||
def create_message
|
||||
raise Discourse::InvalidAccess if current_user.silenced?
|
||||
|
||||
Chat::ChatMessageRateLimiter.run!(current_user)
|
||||
|
||||
@user_chat_channel_membership =
|
||||
Chat::ChatChannelMembershipManager.new(@chat_channel).find_for_user(
|
||||
current_user,
|
||||
following: true,
|
||||
)
|
||||
raise Discourse::InvalidAccess unless @user_chat_channel_membership
|
||||
|
||||
reply_to_msg_id = params[:in_reply_to_id]
|
||||
if reply_to_msg_id
|
||||
rm = ChatMessage.find(reply_to_msg_id)
|
||||
raise Discourse::NotFound if rm.chat_channel_id != @chat_channel.id
|
||||
end
|
||||
|
||||
content = params[:message]
|
||||
|
||||
chat_message_creator =
|
||||
Chat::ChatMessageCreator.create(
|
||||
chat_channel: @chat_channel,
|
||||
user: current_user,
|
||||
in_reply_to_id: reply_to_msg_id,
|
||||
content: content,
|
||||
staged_id: params[:staged_id],
|
||||
upload_ids: params[:upload_ids],
|
||||
)
|
||||
|
||||
return render_json_error(chat_message_creator.error) if chat_message_creator.failed?
|
||||
|
||||
@user_chat_channel_membership.update!(
|
||||
last_read_message_id: chat_message_creator.chat_message.id,
|
||||
)
|
||||
|
||||
if @chat_channel.direct_message_channel?
|
||||
# If any of the channel users is ignoring, muting, or preventing DMs from
|
||||
# the current user then we shold not auto-follow the channel once again or
|
||||
# publish the new channel.
|
||||
user_ids_allowing_communication =
|
||||
UserCommScreener.new(
|
||||
acting_user: current_user,
|
||||
target_user_ids: @chat_channel.user_chat_channel_memberships.pluck(:user_id),
|
||||
).allowing_actor_communication
|
||||
|
||||
if user_ids_allowing_communication.any?
|
||||
ChatPublisher.publish_new_channel(
|
||||
@chat_channel,
|
||||
@chat_channel.chatable.users.where(id: user_ids_allowing_communication),
|
||||
)
|
||||
|
||||
@chat_channel
|
||||
.user_chat_channel_memberships
|
||||
.where(user_id: user_ids_allowing_communication)
|
||||
.update_all(following: true)
|
||||
end
|
||||
end
|
||||
|
||||
ChatPublisher.publish_user_tracking_state(
|
||||
current_user,
|
||||
@chat_channel.id,
|
||||
chat_message_creator.chat_message.id,
|
||||
)
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def edit_message
|
||||
chat_message_updater =
|
||||
Chat::ChatMessageUpdater.update(
|
||||
guardian: guardian,
|
||||
chat_message: @message,
|
||||
new_content: params[:new_message],
|
||||
upload_ids: params[:upload_ids] || [],
|
||||
)
|
||||
|
||||
return render_json_error(chat_message_updater.error) if chat_message_updater.failed?
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def update_user_last_read
|
||||
with_service(Chat::Service::UpdateUserLastRead, channel_id: params[:chat_channel_id]) do
|
||||
on_failed_policy(:ensure_message_id_recency) do
|
||||
raise Discourse::InvalidParameters.new(:message_id)
|
||||
end
|
||||
on_failed_policy(:ensure_message_exists) { raise Discourse::NotFound }
|
||||
on_model_not_found(:active_membership) { raise Discourse::NotFound }
|
||||
on_model_not_found(:channel) { raise Discourse::NotFound }
|
||||
end
|
||||
end
|
||||
|
||||
def messages
|
||||
page_size = params[:page_size]&.to_i || 1000
|
||||
direction = params[:direction].to_s
|
||||
message_id = params[:message_id]
|
||||
if page_size > 50 ||
|
||||
(
|
||||
message_id.blank? ^ direction.blank? &&
|
||||
(direction.present? && !CHAT_DIRECTIONS.include?(direction))
|
||||
)
|
||||
raise Discourse::InvalidParameters
|
||||
end
|
||||
|
||||
messages = preloaded_chat_message_query.where(chat_channel: @chat_channel)
|
||||
messages = messages.with_deleted if guardian.can_moderate_chat?(@chatable)
|
||||
|
||||
if message_id.present?
|
||||
condition = direction == PAST ? "<" : ">"
|
||||
messages = messages.where("id #{condition} ?", message_id.to_i)
|
||||
end
|
||||
|
||||
# NOTE: This order is reversed when we return the ChatView below if the direction
|
||||
# is not FUTURE.
|
||||
order = direction == FUTURE ? "ASC" : "DESC"
|
||||
messages = messages.order("created_at #{order}, id #{order}").limit(page_size).to_a
|
||||
|
||||
can_load_more_past = nil
|
||||
can_load_more_future = nil
|
||||
|
||||
if direction == FUTURE
|
||||
can_load_more_future = messages.size == page_size
|
||||
elsif direction == PAST
|
||||
can_load_more_past = messages.size == page_size
|
||||
else
|
||||
# When direction is blank, we'll return the latest messages.
|
||||
can_load_more_future = false
|
||||
can_load_more_past = messages.size == page_size
|
||||
end
|
||||
|
||||
chat_view =
|
||||
ChatView.new(
|
||||
chat_channel: @chat_channel,
|
||||
chat_messages: direction == FUTURE ? messages : messages.reverse,
|
||||
user: current_user,
|
||||
can_load_more_past: can_load_more_past,
|
||||
can_load_more_future: can_load_more_future,
|
||||
)
|
||||
render_serialized(chat_view, ChatViewSerializer, root: false)
|
||||
end
|
||||
|
||||
def react
|
||||
params.require(%i[message_id emoji react_action])
|
||||
guardian.ensure_can_react!
|
||||
|
||||
Chat::ChatMessageReactor.new(current_user, @chat_channel).react!(
|
||||
message_id: params[:message_id],
|
||||
react_action: params[:react_action].to_sym,
|
||||
emoji: params[:emoji],
|
||||
)
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def delete
|
||||
guardian.ensure_can_delete_chat!(@message, @chatable)
|
||||
|
||||
ChatMessageDestroyer.new.trash_message(@message, current_user)
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
def restore
|
||||
chat_channel = @message.chat_channel
|
||||
guardian.ensure_can_restore_chat!(@message, chat_channel.chatable)
|
||||
updated = @message.recover!
|
||||
if updated
|
||||
ChatPublisher.publish_restore!(chat_channel, @message)
|
||||
render json: success_json
|
||||
else
|
||||
render_json_error(@message)
|
||||
end
|
||||
end
|
||||
|
||||
def rebake
|
||||
guardian.ensure_can_rebake_chat_message!(@message)
|
||||
@message.rebake!(invalidate_oneboxes: true)
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def message_link
|
||||
raise Discourse::NotFound if @message.blank? || @message.deleted_at.present?
|
||||
raise Discourse::NotFound if @message.chat_channel.blank?
|
||||
set_channel_and_chatable_with_access_check(chat_channel_id: @message.chat_channel_id)
|
||||
render json:
|
||||
success_json.merge(
|
||||
chat_channel_id: @chat_channel.id,
|
||||
chat_channel_title: @chat_channel.title(current_user),
|
||||
)
|
||||
end
|
||||
|
||||
def lookup_message
|
||||
set_channel_and_chatable_with_access_check(chat_channel_id: @message.chat_channel_id)
|
||||
|
||||
messages = preloaded_chat_message_query.where(chat_channel: @chat_channel)
|
||||
messages = messages.with_deleted if guardian.can_moderate_chat?(@chatable)
|
||||
|
||||
past_messages =
|
||||
messages
|
||||
.where("created_at < ?", @message.created_at)
|
||||
.order(created_at: :desc)
|
||||
.limit(PAST_MESSAGE_LIMIT)
|
||||
|
||||
future_messages =
|
||||
messages
|
||||
.where("created_at > ?", @message.created_at)
|
||||
.order(created_at: :asc)
|
||||
.limit(FUTURE_MESSAGE_LIMIT)
|
||||
|
||||
can_load_more_past = past_messages.count == PAST_MESSAGE_LIMIT
|
||||
can_load_more_future = future_messages.count == FUTURE_MESSAGE_LIMIT
|
||||
messages = [past_messages.reverse, [@message], future_messages].reduce([], :concat)
|
||||
chat_view =
|
||||
ChatView.new(
|
||||
chat_channel: @chat_channel,
|
||||
chat_messages: messages,
|
||||
user: current_user,
|
||||
can_load_more_past: can_load_more_past,
|
||||
can_load_more_future: can_load_more_future,
|
||||
)
|
||||
render_serialized(chat_view, ChatViewSerializer, root: false)
|
||||
end
|
||||
|
||||
def set_user_chat_status
|
||||
params.require(:chat_enabled)
|
||||
|
||||
current_user.user_option.update(chat_enabled: params[:chat_enabled])
|
||||
render json: { chat_enabled: current_user.user_option.chat_enabled }
|
||||
end
|
||||
|
||||
def invite_users
|
||||
params.require(:user_ids)
|
||||
|
||||
users =
|
||||
User
|
||||
.includes(:groups)
|
||||
.joins(:user_option)
|
||||
.where(user_options: { chat_enabled: true })
|
||||
.not_suspended
|
||||
.where(id: params[:user_ids])
|
||||
users.each do |user|
|
||||
guardian = Guardian.new(user)
|
||||
if guardian.can_chat? && guardian.can_join_chat_channel?(@chat_channel)
|
||||
data = {
|
||||
message: "chat.invitation_notification",
|
||||
chat_channel_id: @chat_channel.id,
|
||||
chat_channel_title: @chat_channel.title(user),
|
||||
chat_channel_slug: @chat_channel.slug,
|
||||
invited_by_username: current_user.username,
|
||||
}
|
||||
data[:chat_message_id] = params[:chat_message_id] if params[:chat_message_id]
|
||||
user.notifications.create(
|
||||
notification_type: Notification.types[:chat_invitation],
|
||||
high_priority: true,
|
||||
data: data.to_json,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def dismiss_retention_reminder
|
||||
params.require(:chatable_type)
|
||||
guardian.ensure_can_chat!
|
||||
unless ChatChannel.chatable_types.include?(params[:chatable_type])
|
||||
raise Discourse::InvalidParameters
|
||||
end
|
||||
|
||||
field =
|
||||
(
|
||||
if ChatChannel.public_channel_chatable_types.include?(params[:chatable_type])
|
||||
:dismissed_channel_retention_reminder
|
||||
else
|
||||
:dismissed_dm_retention_reminder
|
||||
end
|
||||
)
|
||||
current_user.user_option.update(field => true)
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def quote_messages
|
||||
params.require(:message_ids)
|
||||
|
||||
message_ids = params[:message_ids].map(&:to_i)
|
||||
markdown =
|
||||
ChatTranscriptService.new(
|
||||
@chat_channel,
|
||||
current_user,
|
||||
messages_or_ids: message_ids,
|
||||
).generate_markdown
|
||||
render json: success_json.merge(markdown: markdown)
|
||||
end
|
||||
|
||||
def flag
|
||||
RateLimiter.new(current_user, "flag_chat_message", 4, 1.minutes).performed!
|
||||
|
||||
permitted_params =
|
||||
params.permit(
|
||||
%i[chat_message_id flag_type_id message is_warning take_action queue_for_review],
|
||||
)
|
||||
|
||||
chat_message =
|
||||
ChatMessage.includes(:chat_channel, :revisions).find(permitted_params[:chat_message_id])
|
||||
|
||||
flag_type_id = permitted_params[:flag_type_id].to_i
|
||||
|
||||
if !ReviewableScore.types.values.include?(flag_type_id)
|
||||
raise Discourse::InvalidParameters.new(:flag_type_id)
|
||||
end
|
||||
|
||||
set_channel_and_chatable_with_access_check(chat_channel_id: chat_message.chat_channel_id)
|
||||
|
||||
result =
|
||||
Chat::ChatReviewQueue.new.flag_message(chat_message, guardian, flag_type_id, permitted_params)
|
||||
|
||||
if result[:success]
|
||||
render json: success_json
|
||||
else
|
||||
render_json_error(result[:errors])
|
||||
end
|
||||
end
|
||||
|
||||
def set_draft
|
||||
if params[:data].present?
|
||||
ChatDraft.find_or_initialize_by(
|
||||
user: current_user,
|
||||
chat_channel_id: @chat_channel.id,
|
||||
).update!(data: params[:data])
|
||||
else
|
||||
ChatDraft.where(user: current_user, chat_channel_id: @chat_channel.id).destroy_all
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def preloaded_chat_message_query
|
||||
query =
|
||||
ChatMessage
|
||||
.includes(in_reply_to: [:user, chat_webhook_event: [:incoming_chat_webhook]])
|
||||
.includes(:revisions)
|
||||
.includes(user: :primary_group)
|
||||
.includes(chat_webhook_event: :incoming_chat_webhook)
|
||||
.includes(reactions: :user)
|
||||
.includes(:bookmarks)
|
||||
.includes(:uploads)
|
||||
.includes(chat_channel: :chatable)
|
||||
|
||||
query = query.includes(user: :user_status) if SiteSetting.enable_user_status
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
def find_chatable
|
||||
@chatable = Category.find_by(id: params[:chatable_id])
|
||||
guardian.ensure_can_moderate_chat!(@chatable)
|
||||
end
|
||||
|
||||
def find_chat_message
|
||||
@message = preloaded_chat_message_query.with_deleted
|
||||
@message = @message.where(chat_channel_id: params[:chat_channel_id]) if params[:chat_channel_id]
|
||||
@message = @message.find_by(id: params[:message_id])
|
||||
raise Discourse::NotFound unless @message
|
||||
end
|
||||
end
|
||||
@ -5,6 +5,9 @@ module Chat
|
||||
@_result
|
||||
end
|
||||
|
||||
# @param service [Class] A class including {Chat::Service::Base}
|
||||
# @param dependencies [kwargs] Any additional params to load into the service context,
|
||||
# in addition to controller @params.
|
||||
def with_service(service, default_actions: true, **dependencies, &block)
|
||||
object = self
|
||||
merged_block =
|
||||
|
||||
84
plugins/chat/app/services/update_user_last_read.rb
Normal file
84
plugins/chat/app/services/update_user_last_read.rb
Normal file
@ -0,0 +1,84 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Chat
|
||||
module Service
|
||||
# Service responsible for updating the last read message id of a membership.
|
||||
#
|
||||
# @example
|
||||
# Chat::Service::UpdateUserLastRead.call(channel_id: 2, message_id: 3, guardian: guardian)
|
||||
#
|
||||
class UpdateUserLastRead
|
||||
include Base
|
||||
|
||||
# @!method call(user_id:, channel_id:, message_id:, guardian:)
|
||||
# @param [Integer] channel_id
|
||||
# @param [Integer] message_id
|
||||
# @param [Guardian] guardian
|
||||
# @return [Chat::Service::Base::Context]
|
||||
|
||||
contract
|
||||
model :channel
|
||||
model :active_membership
|
||||
policy :invalid_access
|
||||
policy :ensure_message_exists
|
||||
policy :ensure_message_id_recency
|
||||
step :update_last_read_message_id
|
||||
step :mark_associated_mentions_as_read
|
||||
step :publish_new_last_read_to_clients
|
||||
|
||||
# @!visibility private
|
||||
class Contract
|
||||
attribute :message_id, :integer
|
||||
attribute :channel_id, :integer
|
||||
|
||||
validates :message_id, :channel_id, presence: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_channel(contract:, **)
|
||||
ChatChannel.find_by(id: contract.channel_id)
|
||||
end
|
||||
|
||||
def fetch_active_membership(guardian:, channel:, **)
|
||||
Chat::ChatChannelMembershipManager.new(channel).find_for_user(
|
||||
guardian.user,
|
||||
following: true,
|
||||
)
|
||||
end
|
||||
|
||||
def invalid_access(guardian:, active_membership:, **)
|
||||
guardian.can_join_chat_channel?(active_membership.chat_channel)
|
||||
end
|
||||
|
||||
def ensure_message_exists(channel:, contract:, **)
|
||||
ChatMessage.with_deleted.exists?(chat_channel_id: channel.id, id: contract.message_id)
|
||||
end
|
||||
|
||||
def ensure_message_id_recency(contract:, active_membership:, **)
|
||||
!active_membership.last_read_message_id ||
|
||||
contract.message_id >= active_membership.last_read_message_id
|
||||
end
|
||||
|
||||
def update_last_read_message_id(contract:, active_membership:, **)
|
||||
active_membership.update!(last_read_message_id: contract.message_id)
|
||||
end
|
||||
|
||||
def mark_associated_mentions_as_read(active_membership:, contract:, **)
|
||||
Notification
|
||||
.where(notification_type: Notification.types[:chat_mention])
|
||||
.where(user: active_membership.user)
|
||||
.where(read: false)
|
||||
.joins("INNER JOIN chat_mentions ON chat_mentions.notification_id = notifications.id")
|
||||
.joins("INNER JOIN chat_messages ON chat_mentions.chat_message_id = chat_messages.id")
|
||||
.where("chat_messages.id <= ?", contract.message_id)
|
||||
.where("chat_messages.chat_channel_id = ?", active_membership.chat_channel.id)
|
||||
.update_all(read: true)
|
||||
end
|
||||
|
||||
def publish_new_last_read_to_clients(guardian:, channel:, contract:, **)
|
||||
ChatPublisher.publish_user_tracking_state(guardian.user, channel.id, contract.message_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -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,12 @@ export default {
|
||||
appEvents.trigger("chat:toggle-close", event);
|
||||
};
|
||||
|
||||
const markAllChannelsRead = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
chatChannelsManager.markAllChannelsRead();
|
||||
};
|
||||
|
||||
withPluginApi("0.12.1", (api) => {
|
||||
api.addKeyboardShortcut(`${KEY_MODIFIER}+k`, openChannelSelector, {
|
||||
global: true,
|
||||
@ -201,6 +210,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",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@ -282,6 +282,13 @@ export default class ChatApi extends Service {
|
||||
);
|
||||
}
|
||||
|
||||
updateCurrentUserTracking({ channelId, messageId } = {}) {
|
||||
return this.#putRequest(`/tracking/read/me`, {
|
||||
channel_id: channelId,
|
||||
message_id: 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,16 @@ export default class ChatChannelsManager extends Service {
|
||||
});
|
||||
}
|
||||
|
||||
@debounce(300)
|
||||
async markAllChannelsRead() {
|
||||
return this.chatApi.updateCurrentUserTracking().then(() => {
|
||||
this.channels.forEach((channel) => {
|
||||
channel.currentUserMembership.unread_count = 0;
|
||||
channel.currentUserMembership.unread_mentions = 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get unreadCount() {
|
||||
let count = 0;
|
||||
this.publicMessageChannels.forEach((channel) => {
|
||||
|
||||
@ -598,6 +598,7 @@ en:
|
||||
composer_code: "%{shortcut} Code (composer only)"
|
||||
drawer_open: "%{shortcut} Open chat drawer"
|
||||
drawer_close: "%{shortcut} Close chat drawer"
|
||||
mark_all_channels_read: "%{shortcut} Mark all channels read"
|
||||
topic_statuses:
|
||||
chat:
|
||||
help: "Chat is enabled for this topic"
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
# * +on_model_not_found(name)+: will execute the provided block if the service
|
||||
# fails and its model is not present
|
||||
#
|
||||
# Default actions for each of these are defined in [Chat::BaseController#default_actions_for_service]
|
||||
#
|
||||
# @example In a controller
|
||||
# def create
|
||||
# with_service MyService do
|
||||
|
||||
@ -381,25 +381,6 @@ after_initialize do
|
||||
end
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::Chat::Engine, at: "/chat"
|
||||
|
||||
get "/admin/plugins/chat" => "chat/admin/incoming_webhooks#index",
|
||||
:constraints => StaffConstraint.new
|
||||
post "/admin/plugins/chat/hooks" => "chat/admin/incoming_webhooks#create",
|
||||
:constraints => StaffConstraint.new
|
||||
put "/admin/plugins/chat/hooks/:incoming_chat_webhook_id" =>
|
||||
"chat/admin/incoming_webhooks#update",
|
||||
:constraints => StaffConstraint.new
|
||||
delete "/admin/plugins/chat/hooks/:incoming_chat_webhook_id" =>
|
||||
"chat/admin/incoming_webhooks#destroy",
|
||||
:constraints => StaffConstraint.new
|
||||
get "u/:username/preferences/chat" => "users#preferences",
|
||||
:constraints => {
|
||||
username: RouteFormat.username,
|
||||
}
|
||||
end
|
||||
|
||||
if defined?(DiscourseAutomation)
|
||||
add_automation_scriptable("send_chat_message") do
|
||||
field :chat_channel_id, component: :text, required: true
|
||||
|
||||
Reference in New Issue
Block a user