Compare commits

...
This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.

5 Commits

Author SHA1 Message Date
Martin Brennan
78dffe2c08
Merge branch 'main' into chat-thread-panel-sketch 2023-02-08 10:54:53 +10:00
Martin Brennan
b95d9a1d4a
DEV: Sketch of how the chat thread panel will work with new routes 2023-02-07 13:15:23 +10:00
Joffrey JAFFEUX
0517544c23 fix tests 2023-02-06 21:55:27 +01:00
Joffrey JAFFEUX
0f49fb0b95 fix spec 2023-02-06 20:23:35 +01:00
Joffrey JAFFEUX
62685c1a76 DEV: refactors routes to simplify using outlet
This work will allow us to have an {{outlet}} chat.channel route and use it for threads as a sidepanel.
2023-02-06 14:27:35 +01:00
16 changed files with 273 additions and 2 deletions

View 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

View File

@ -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(

View File

@ -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

View File

@ -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>

View File

@ -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,
});
}
}

View File

@ -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);
},
});

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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}

View File

@ -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];
}
}

View File

@ -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);

View File

@ -0,0 +1 @@
<ChatThreadPanel />

View File

@ -1 +1,4 @@
<FullPageChat @targetMessageId={{this.targetMessageId}} />
<FullPageChat @targetMessageId={{this.targetMessageId}} />
<div id="chat-side-panel-outlet">
{{outlet}}
</div>

View File

@ -0,0 +1,8 @@
.chat-thread-pane {
background-color: #aee6bd;
display: none;
&--active-thread {
display: block;
}
}

View File

@ -590,6 +590,7 @@ html.has-full-page-chat {
#main-chat-outlet {
min-height: 0;
display: flex;
}
}
}

View File

@ -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}"