Compare commits
5 Commits
main
...
chat-threa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78dffe2c08 | ||
|
|
b95d9a1d4a | ||
|
|
0517544c23 | ||
|
|
0f49fb0b95 | ||
|
|
62685c1a76 |
18
plugins/chat/app/controllers/api/chat_threads_controller.rb
Normal file
18
plugins/chat/app/controllers/api/chat_threads_controller.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Chat::Api::ChatThreadsController < Chat::Api
|
||||
def show
|
||||
render json:
|
||||
success_json.merge(
|
||||
{
|
||||
thread: {
|
||||
id: params[:thread_id],
|
||||
original_message_user: {
|
||||
username: "test",
|
||||
},
|
||||
original_message_excerpt: "this is a cool message",
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -7,6 +7,7 @@ export default function () {
|
||||
|
||||
this.route("channel", { path: "/c/:channelTitle/:channelId" }, function () {
|
||||
this.route("near-message", { path: "/:messageId" });
|
||||
this.route("thread", { path: "/t/:threadId" });
|
||||
});
|
||||
|
||||
this.route(
|
||||
|
||||
@ -80,6 +80,15 @@
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if this.message.in_reply_to}}
|
||||
<LinkTo
|
||||
@route={{"chat.channel.thread"}}
|
||||
@model={{this.message.id}}
|
||||
class="chat-thread-link"
|
||||
>
|
||||
View Thread
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
<div class={{this.chatMessageClasses}}>
|
||||
{{#if this.message.in_reply_to}}
|
||||
<div
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
<div
|
||||
class={{concat-class
|
||||
"chat-thread-panel"
|
||||
(if this.chat.activeThread "chat-thread-pane--active-thread")
|
||||
}}
|
||||
>
|
||||
<p>Thread ID
|
||||
{{this.chat.activeThread.id}}, started by
|
||||
{{this.chat.activeThread.original_message_user.username}}</p>
|
||||
|
||||
<p>Excerpt: {{this.chat.activeThread.original_message_excerpt}}</p>
|
||||
|
||||
<LinkTo @route="chat.channel" @models={{this.chat.activeChannel.routeModels}}>
|
||||
Close Thread
|
||||
</LinkTo>
|
||||
</div>
|
||||
@ -0,0 +1,17 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatThreadPanel extends Component {
|
||||
@service siteSettings;
|
||||
@service currentUser;
|
||||
@service chat;
|
||||
@service router;
|
||||
|
||||
@action
|
||||
closeThread() {
|
||||
return this.router.transitionTo("chat.channel", {
|
||||
channelId: this.chat.activeChannel.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
import RestModel from "discourse/models/rest";
|
||||
import I18n from "I18n";
|
||||
import User from "discourse/models/user";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export const THREAD_STATUSES = {
|
||||
open: "open",
|
||||
readOnly: "read_only",
|
||||
closed: "closed",
|
||||
archived: "archived",
|
||||
};
|
||||
|
||||
export function threadStatusName(status) {
|
||||
switch (status) {
|
||||
case THREAD_STATUSES.open:
|
||||
return I18n.t("chat.thread_status.open");
|
||||
case THREAD_STATUSES.readOnly:
|
||||
return I18n.t("chat.thread_status.read_only");
|
||||
case THREAD_STATUSES.closed:
|
||||
return I18n.t("chat.thread_status.closed");
|
||||
case THREAD_STATUSES.archived:
|
||||
return I18n.t("chat.thread_status.archived");
|
||||
}
|
||||
}
|
||||
|
||||
const READONLY_STATUSES = [
|
||||
THREAD_STATUSES.closed,
|
||||
THREAD_STATUSES.readOnly,
|
||||
THREAD_STATUSES.archived,
|
||||
];
|
||||
|
||||
const STAFF_READONLY_STATUSES = [
|
||||
THREAD_STATUSES.readOnly,
|
||||
THREAD_STATUSES.archived,
|
||||
];
|
||||
|
||||
export default class ChatThread extends RestModel {
|
||||
@tracked title;
|
||||
@tracked status;
|
||||
|
||||
get escapedTitle() {
|
||||
return escapeExpression(this.title);
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return !this.status || this.status === THREAD_STATUSES.open;
|
||||
}
|
||||
|
||||
get isReadOnly() {
|
||||
return this.status === THREAD_STATUSES.readOnly;
|
||||
}
|
||||
|
||||
get isClosed() {
|
||||
return this.status === THREAD_STATUSES.closed;
|
||||
}
|
||||
|
||||
get isArchived() {
|
||||
return this.status === THREAD_STATUSES.archived;
|
||||
}
|
||||
|
||||
canModifyMessages(user) {
|
||||
if (user.staff) {
|
||||
return !STAFF_READONLY_STATUSES.includes(this.status);
|
||||
}
|
||||
|
||||
return !READONLY_STATUSES.includes(this.status);
|
||||
}
|
||||
}
|
||||
|
||||
ChatThread.reopenClass({
|
||||
create(args) {
|
||||
args = args || {};
|
||||
args.original_message_user = User.create(args.original_message_user);
|
||||
return this._super(args);
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatChannelThread extends DiscourseRoute {
|
||||
@service router;
|
||||
@service chatThreadsManager;
|
||||
@service chat;
|
||||
|
||||
async model(params) {
|
||||
return this.chatThreadsManager.find(params.threadId);
|
||||
}
|
||||
|
||||
afterModel(model) {
|
||||
this.chat.setActiveThread(model);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,12 @@
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import withChatChannel from "./chat-channel-decorator";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
@withChatChannel
|
||||
export default class ChatChannelRoute extends DiscourseRoute {}
|
||||
export default class ChatChannelRoute extends DiscourseRoute {
|
||||
@service chatThreadsManager;
|
||||
|
||||
beforeModel() {
|
||||
this.chatThreadsManager.resetCache();
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import Collection from "../lib/collection";
|
||||
*/
|
||||
export default class ChatApi extends Service {
|
||||
@service chatChannelsManager;
|
||||
@service chatThreadsManager;
|
||||
|
||||
/**
|
||||
* Get a channel by its ID.
|
||||
@ -29,6 +30,21 @@ export default class ChatApi extends Service {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a thread by its ID.
|
||||
* @param {number} threadId - The ID of the thread.
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* this.chatApi.thread(1).then(thread => { ... })
|
||||
*/
|
||||
thread(threadId) {
|
||||
return this.#getRequest(`/threads/${threadId}`).then((result) =>
|
||||
this.chatThreadsManager.store(result.thread)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all accessible category channels of the current user.
|
||||
* @returns {module:Collection}
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
import Service, { inject as service } from "@ember/service";
|
||||
import Promise from "rsvp";
|
||||
import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
/*
|
||||
The ChatThreadsManager service is responsible for managing the loaded chat threads
|
||||
for the current chat channel.
|
||||
|
||||
It provides helpers to facilitate using and managing loaded threads instead of constantly
|
||||
fetching them from the server.
|
||||
*/
|
||||
|
||||
export default class ChatThreadsManager extends Service {
|
||||
@service chatSubscriptionsManager;
|
||||
@service chatApi;
|
||||
@service currentUser;
|
||||
@tracked _cached = new TrackedObject();
|
||||
|
||||
async find(id, options = { fetchIfNotFound: true }) {
|
||||
const existingThread = this.#findStale(id);
|
||||
if (existingThread) {
|
||||
return Promise.resolve(existingThread);
|
||||
} else if (options.fetchIfNotFound) {
|
||||
return this.#find(id);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// whenever the active channel changes, do this
|
||||
resetCache() {
|
||||
this._cached = new TrackedObject();
|
||||
}
|
||||
|
||||
get threads() {
|
||||
return Object.values(this._cached);
|
||||
}
|
||||
|
||||
store(threadObject) {
|
||||
let model = this.#findStale(threadObject.id);
|
||||
|
||||
if (!model) {
|
||||
model = ChatThread.create(threadObject);
|
||||
this.#cache(model);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
async #find(id) {
|
||||
return this.chatApi
|
||||
.thread(id)
|
||||
.catch(popupAjaxError)
|
||||
.then((thread) => {
|
||||
this.#cache(thread);
|
||||
return thread;
|
||||
});
|
||||
}
|
||||
|
||||
#cache(thread) {
|
||||
this._cached[thread.id] = thread;
|
||||
}
|
||||
|
||||
#findStale(id) {
|
||||
return this._cached[id];
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,7 @@ export default class Chat extends Service {
|
||||
@service chatChannelsManager;
|
||||
|
||||
activeChannel = null;
|
||||
activeThread = null;
|
||||
cook = null;
|
||||
presenceChannel = null;
|
||||
sidebarActive = false;
|
||||
@ -120,6 +121,10 @@ export default class Chat extends Service {
|
||||
this.set("activeChannel", channel);
|
||||
}
|
||||
|
||||
setActiveThread(thread) {
|
||||
this.set("activeThread", thread);
|
||||
}
|
||||
|
||||
loadCookFunction(categories) {
|
||||
if (this.cook) {
|
||||
return Promise.resolve(this.cook);
|
||||
|
||||
@ -0,0 +1 @@
|
||||
<ChatThreadPanel />
|
||||
@ -1 +1,4 @@
|
||||
<FullPageChat @targetMessageId={{this.targetMessageId}} />
|
||||
<FullPageChat @targetMessageId={{this.targetMessageId}} />
|
||||
<div id="chat-side-panel-outlet">
|
||||
{{outlet}}
|
||||
</div>
|
||||
@ -0,0 +1,8 @@
|
||||
.chat-thread-pane {
|
||||
background-color: #aee6bd;
|
||||
display: none;
|
||||
|
||||
&--active-thread {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@ -590,6 +590,7 @@ html.has-full-page-chat {
|
||||
|
||||
#main-chat-outlet {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ register_asset "stylesheets/common/chat-channel-preview-card.scss"
|
||||
register_asset "stylesheets/common/chat-channel-info.scss"
|
||||
register_asset "stylesheets/common/chat-draft-channel.scss"
|
||||
register_asset "stylesheets/common/chat-tabs.scss"
|
||||
register_asset "stylesheets/common/chat-thread-panel.scss"
|
||||
register_asset "stylesheets/common/chat-form.scss"
|
||||
register_asset "stylesheets/common/d-progress-bar.scss"
|
||||
register_asset "stylesheets/common/incoming-chat-webhooks.scss"
|
||||
@ -228,6 +229,7 @@ after_initialize do
|
||||
)
|
||||
load File.expand_path("../app/controllers/api/category_chatables_controller.rb", __FILE__)
|
||||
load File.expand_path("../app/controllers/api/hints_controller.rb", __FILE__)
|
||||
load File.expand_path("../app/controllers/api/chat_threads_controller.rb", __FILE__)
|
||||
load File.expand_path("../app/controllers/api/chat_chatables_controller.rb", __FILE__)
|
||||
load File.expand_path("../app/queries/chat_channel_memberships_query.rb", __FILE__)
|
||||
|
||||
@ -596,6 +598,8 @@ after_initialize do
|
||||
|
||||
# Hints for JIT warnings.
|
||||
get "/mentions/groups" => "hints#check_group_mentions", :format => :json
|
||||
|
||||
get "/threads/:thread_id" => "chat_threads#show"
|
||||
end
|
||||
|
||||
# direct_messages_controller routes
|
||||
@ -648,6 +652,8 @@ after_initialize do
|
||||
# /channel -> /c redirects
|
||||
get "/channel/:channel_id", to: redirect("/chat/c/-/%{channel_id}")
|
||||
|
||||
get "#{base_c_route}/t/:thread_id" => "chat#respond"
|
||||
|
||||
base_channel_route = "/channel/:channel_id/:channel_title"
|
||||
redirect_base = "/chat/c/%{channel_title}/%{channel_id}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user