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("channel", { path: "/c/:channelTitle/:channelId" }, function () {
|
||||||
this.route("near-message", { path: "/:messageId" });
|
this.route("near-message", { path: "/:messageId" });
|
||||||
|
this.route("thread", { path: "/t/:threadId" });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route(
|
this.route(
|
||||||
|
|||||||
@ -80,6 +80,15 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{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}}>
|
<div class={{this.chatMessageClasses}}>
|
||||||
{{#if this.message.in_reply_to}}
|
{{#if this.message.in_reply_to}}
|
||||||
<div
|
<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 DiscourseRoute from "discourse/routes/discourse";
|
||||||
import withChatChannel from "./chat-channel-decorator";
|
import withChatChannel from "./chat-channel-decorator";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
@withChatChannel
|
@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 {
|
export default class ChatApi extends Service {
|
||||||
@service chatChannelsManager;
|
@service chatChannelsManager;
|
||||||
|
@service chatThreadsManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a channel by its ID.
|
* 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.
|
* List all accessible category channels of the current user.
|
||||||
* @returns {module:Collection}
|
* @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;
|
@service chatChannelsManager;
|
||||||
|
|
||||||
activeChannel = null;
|
activeChannel = null;
|
||||||
|
activeThread = null;
|
||||||
cook = null;
|
cook = null;
|
||||||
presenceChannel = null;
|
presenceChannel = null;
|
||||||
sidebarActive = false;
|
sidebarActive = false;
|
||||||
@ -120,6 +121,10 @@ export default class Chat extends Service {
|
|||||||
this.set("activeChannel", channel);
|
this.set("activeChannel", channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setActiveThread(thread) {
|
||||||
|
this.set("activeThread", thread);
|
||||||
|
}
|
||||||
|
|
||||||
loadCookFunction(categories) {
|
loadCookFunction(categories) {
|
||||||
if (this.cook) {
|
if (this.cook) {
|
||||||
return Promise.resolve(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 {
|
#main-chat-outlet {
|
||||||
min-height: 0;
|
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-channel-info.scss"
|
||||||
register_asset "stylesheets/common/chat-draft-channel.scss"
|
register_asset "stylesheets/common/chat-draft-channel.scss"
|
||||||
register_asset "stylesheets/common/chat-tabs.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/chat-form.scss"
|
||||||
register_asset "stylesheets/common/d-progress-bar.scss"
|
register_asset "stylesheets/common/d-progress-bar.scss"
|
||||||
register_asset "stylesheets/common/incoming-chat-webhooks.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/category_chatables_controller.rb", __FILE__)
|
||||||
load File.expand_path("../app/controllers/api/hints_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/controllers/api/chat_chatables_controller.rb", __FILE__)
|
||||||
load File.expand_path("../app/queries/chat_channel_memberships_query.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.
|
# Hints for JIT warnings.
|
||||||
get "/mentions/groups" => "hints#check_group_mentions", :format => :json
|
get "/mentions/groups" => "hints#check_group_mentions", :format => :json
|
||||||
|
|
||||||
|
get "/threads/:thread_id" => "chat_threads#show"
|
||||||
end
|
end
|
||||||
|
|
||||||
# direct_messages_controller routes
|
# direct_messages_controller routes
|
||||||
@ -648,6 +652,8 @@ after_initialize do
|
|||||||
# /channel -> /c redirects
|
# /channel -> /c redirects
|
||||||
get "/channel/:channel_id", to: redirect("/chat/c/-/%{channel_id}")
|
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"
|
base_channel_route = "/channel/:channel_id/:channel_title"
|
||||||
redirect_base = "/chat/c/%{channel_title}/%{channel_id}"
|
redirect_base = "/chat/c/%{channel_title}/%{channel_id}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user