DEV: routable chat part 2 (#20232)

This commit is expanding on previous work making everything chat working through an URL.

Improves drawer templates to be all URLs
Implements some kind of router for the drawer
Removes few remaining actions for opening channels
This commit is contained in:
Joffrey JAFFEUX 2023-02-14 11:27:07 +01:00 committed by GitHub
parent e636abeb0d
commit f12724b5a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 374 additions and 445 deletions

View File

@ -18,6 +18,7 @@ export default Component.extend({
searchIndex: 0,
loading: false,
chatChannelsManager: service(),
router: service(),
didInsertElement() {
this._super(...arguments);
@ -103,13 +104,13 @@ export default Component.extend({
return this.fetchOrCreateChannelForUser(channel).then((response) => {
const newChannel = this.chatChannelsManager.store(response.channel);
return this.chatChannelsManager.follow(newChannel).then((c) => {
this.chat.openChannel(c);
this.router.transitionTo("chat.channel", ...c.routeModels);
this.close();
});
});
} else {
return this.chatChannelsManager.follow(channel).then((c) => {
this.chat.openChannel(c);
this.router.transitionTo("chat.channel", ...c.routeModels);
this.close();
});
}

View File

@ -22,7 +22,6 @@
<ChatLivePane
@chatChannel={{this.previewedChannel}}
@includeHeader={{false}}
@onSwitchChannel={{action "onSwitchFromDraftChannel"}}
/>
{{/if}}
</div>

View File

@ -7,7 +7,6 @@ export default class ChatDraftChannelScreen extends Component {
@service chat;
@service router;
tagName = "";
onSwitchChannel = null;
@action
onCancelChatDraft() {
@ -22,7 +21,6 @@ export default class ChatDraftChannelScreen extends Component {
@action
onSwitchFromDraftChannel(channel) {
channel.set("isDraft", false);
this.onSwitchChannel?.(channel);
}
_fetchPreviewedChannel(users) {

View File

@ -1,4 +1,5 @@
{{#if this.chatStateManager.isDrawerActive}}
<div
data-chat-channel-id={{this.chat.activeChannel.id}}
class={{concat-class
@ -10,128 +11,14 @@
>
<div class="chat-drawer-container">
<div class="chat-drawer-resizer"></div>
<div
role="region"
aria-label={{i18n "chat.aria_roles.header"}}
class="chat-drawer-header"
>
{{#if
(and this.draftChannelView this.chatStateManager.isDrawerExpanded)
}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<LinkTo
title={{i18n "chat.return_to_list"}}
class="chat-drawer-header__return-to-channels-btn"
@route="chat"
>
{{d-icon "chevron-left"}}
</LinkTo>
</div>
</div>
<span class="chat-drawer-header__title">
<div class="chat-drawer-header__top-line">
{{i18n "chat.direct_message_creator.title"}}
</div>
</span>
{{else if this.chatView}}
{{#if this.chatStateManager.isDrawerExpanded}}
<LinkTo
title={{i18n "chat.return_to_list"}}
class="chat-drawer-header__return-to-channels-btn"
@route="chat"
>
{{d-icon "chevron-left"}}
</LinkTo>
{{/if}}
{{#if this.chat.activeChannel}}
{{#if this.chatStateManager.isDrawerExpanded}}
<LinkTo
@route={{this.infoTabRoute}}
@models={{this.chat.activeChannel.routeModels}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChatChannelTitle @channel={{this.chat.activeChannel}} />
</div>
</LinkTo>
{{else}}
<div
role="button"
{{on "click" (action "toggleExpand")}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChatChannelTitle @channel={{this.chat.activeChannel}}>
{{#if this.unreadCount}}
<span
class="chat-unread-count"
>{{this.unreadCount}}</span>
{{/if}}
</ChatChannelTitle>
</div>
</div>
{{/if}}
{{/if}}
{{else}}
<span class="chat-drawer-header__title">
<div class="chat-drawer-header__top-line">
{{i18n "chat.heading"}}
</div>
</span>
{{/if}}
<div class="chat-drawer-header__right-actions">
<div class="chat-drawer-header__top-line {{this.topLineClass}}">
{{#if this.chatStateManager.isDrawerExpanded}}
<DButton
@icon="discourse-expand"
class="btn-flat btn-link chat-drawer-header__full-screen-btn"
@title={{"chat.open_full_page"}}
@action={{this.openInFullPage}}
/>
{{/if}}
<FlatButton
@icon={{this.expandIcon}}
@class="chat-drawer-header__expand-btn"
@action={{action "toggleExpand"}}
@title="chat.collapse"
/>
{{#if this.showClose}}
<FlatButton
@icon="times"
@action={{action "close"}}
@title="chat.close"
@class="chat-drawer-header__close-btn"
/>
{{/if}}
</div>
</div>
</div>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
{{#if (and this.chatView this.chat.activeChannel)}}
<ChatLivePane
@chatChannel={{this.chat.activeChannel}}
@onSwitchChannel={{action "switchChannel"}}
/>
{{else if this.draftChannelView}}
<ChatDraftChannelScreen
@onSwitchChannel={{action "switchChannel"}}
/>
{{else}}
<ChannelsList
@onOpenView={{action "openURL"}}
@onSelect={{action "switchChannel"}}
/>
{{/if}}
</div>
{{/if}}
<this.chatDrawerRouter.component
@params={{this.chatDrawerRouter.params}}
@openURL={{this.openURL}}
@openInFullPage={{this.openInFullPage}}
@toggleExpand={{this.toggleExpand}}
@close={{this.close}}
@drawerActions={{this.drawerActions}}
/>
</div>
</div>
{{/if}}

View File

@ -1,35 +1,22 @@
import Component from "@ember/component";
import discourseComputed, {
bind,
observes,
} from "discourse-common/utils/decorators";
import { bind, observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
import {
CHAT_VIEW,
DRAFT_CHANNEL_VIEW,
LIST_VIEW,
} from "discourse/plugins/chat/discourse/services/chat";
import { equal } from "@ember/object/computed";
import { cancel, next, schedule, throttle } from "@ember/runloop";
import { cancel, throttle } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";
export default Component.extend({
tagName: "",
listView: equal("view", LIST_VIEW),
chatView: equal("view", CHAT_VIEW),
draftChannelView: equal("view", DRAFT_CHANNEL_VIEW),
chat: service(),
router: service(),
chatDrawerSize: service(),
chatChannelsManager: service(),
chatStateManager: service(),
chatDrawerRouter: service(),
loading: false,
showClose: true, // TODO - false when on same topic
sizeTimer: null,
rafTimer: null,
view: null,
hasUnreadMessages: false,
drawerStyle: null,
@ -42,12 +29,6 @@ export default Component.extend({
this._checkSize();
this.appEvents.on("chat:open-url", this, "openURL");
this.appEvents.on("chat:toggle-close", this, "close");
this.appEvents.on("chat:open-channel", this, "switchChannel");
this.appEvents.on(
"chat:open-channel-at-message",
this,
"openChannelAtMessage"
);
this.appEvents.on("composer:closed", this, "_checkSize");
this.appEvents.on("composer:opened", this, "_checkSize");
this.appEvents.on("composer:resized", this, "_checkSize");
@ -74,12 +55,6 @@ export default Component.extend({
if (this.appEvents) {
this.appEvents.off("chat:open-url", this, "openURL");
this.appEvents.off("chat:toggle-close", this, "close");
this.appEvents.off("chat:open-channel", this, "switchChannel");
this.appEvents.off(
"chat:open-channel-at-message",
this,
"openChannelAtMessage"
);
this.appEvents.off("composer:closed", this, "_checkSize");
this.appEvents.off("composer:opened", this, "_checkSize");
this.appEvents.off("composer:resized", this, "_checkSize");
@ -109,26 +84,6 @@ export default Component.extend({
this.appEvents.trigger("chat:rerender-header");
},
@discourseComputed("chatStateManager.isDrawerExpanded")
topLineClass(expanded) {
const baseClass = "chat-drawer-header__top-line";
return expanded ? `${baseClass}--expanded` : `${baseClass}--collapsed`;
},
@discourseComputed("chatStateManager.isDrawerExpanded", "chat.activeChannel")
displayMembers(expanded, channel) {
return expanded && !channel?.isDirectMessageChannel;
},
@discourseComputed("displayMembers")
infoTabRoute(displayMembers) {
if (displayMembers) {
return "chat.channel.info.members";
}
return "chat.channel.info.settings";
},
computeDrawerStyle() {
const { width, height } = this.chatDrawerSize.size;
let style = `width: ${escapeExpression((width || "0").toString())}px;`;
@ -136,8 +91,12 @@ export default Component.extend({
this.set("drawerStyle", htmlSafe(style));
},
openChannelAtMessage(channel, messageId) {
this.chat.openChannel(channel, messageId);
get drawerActions() {
return {
openInFullPage: this.openInFullPage,
close: this.close,
toggleExpand: this.toggleExpand,
};
},
@bind
@ -204,74 +163,22 @@ export default Component.extend({
);
},
@discourseComputed("chatStateManager.isDrawerExpanded")
expandIcon(expanded) {
if (expanded) {
return "angle-double-down";
} else {
return "angle-double-up";
}
},
@discourseComputed("chat.activeChannel.currentUserMembership.unread_count")
unreadCount(count) {
return count || 0;
},
@action
openURL(URL = null) {
openURL(url = null) {
this.chat.activeChannel = null;
this.chatStateManager.didOpenDrawer(URL);
const route = this._buildRouteFromURL(
URL || this.chatStateManager.lastKnownChatURL
);
switch (route.name) {
case "chat":
this.set("view", LIST_VIEW);
this.appEvents.trigger("chat:float-toggled", false);
return;
case "chat.draft-channel":
this.set("view", DRAFT_CHANNEL_VIEW);
this.appEvents.trigger("chat:float-toggled", false);
return;
case "chat.channel":
return this._openChannel(
route.params.channelId,
this._highlightCb(route.queryParams.messageId)
);
case "chat.channel.near-message":
return this._openChannel(
route.parent.params.channelId,
this._highlightCb(route.params.messageId)
);
case "chat.channel-legacy":
return this._openChannel(
route.params.channelId,
this._highlightCb(route.queryParams.messageId)
);
}
this.chatStateManager.didOpenDrawer(url);
this.chatDrawerRouter.stateFor(this._routeFromURL(url));
},
_highlightCb(messageId) {
if (messageId) {
return () => {
this.appEvents.trigger("chat-live-pane:highlight-message", messageId);
};
_routeFromURL(url) {
let route = this.router.recognize(url || "/");
// ember might recognize the index subroute
if (route.localName === "index") {
route = route.parent;
}
},
_openChannel(channelId, afterRenderFunc = null) {
return this.chatChannelsManager.find(channelId).then((channel) => {
this.chat.activeChannel = channel;
this.set("view", CHAT_VIEW);
this.appEvents.trigger("chat:float-toggled", false);
if (afterRenderFunc) {
schedule("afterRender", afterRenderFunc);
}
});
return route;
},
@action
@ -298,61 +205,10 @@ export default Component.extend({
this.computeDrawerStyle();
this.chatStateManager.didCloseDrawer();
this.chat.activeChannel = null;
this.appEvents.trigger("chat:float-toggled", true);
},
@action
didResize(element, { width, height }) {
this.chatDrawerSize.size = { width, height };
},
@action
switchChannel(channel) {
// we need next here to ensure we correctly let the time for routes transitions
// eg: deactivate hook of full page chat routes will set activeChannel to null
next(() => {
if (this.isDestroying || this.isDestroyed) {
return;
}
this.chat.activeChannel = channel;
if (!channel) {
const URL = this._buildURLFromState(LIST_VIEW);
this.openURL(URL);
return;
}
const URL = this._buildURLFromState(CHAT_VIEW, channel);
this.openURL(URL);
});
},
_buildRouteFromURL(URL) {
let route = this.router.recognize(URL || "/");
// ember might recognize the index subroute
if (route.localName === "index") {
route = route.parent;
}
return route;
},
_buildURLFromState(view, channel = null) {
switch (view) {
case LIST_VIEW:
return "/chat";
case DRAFT_CHANNEL_VIEW:
return "/chat/draft-channel";
case CHAT_VIEW:
if (channel) {
return `/chat/c/${channel.slug || "-"}/${channel.id}`;
} else {
return "/chat";
}
default:
return "/chat";
}
},
});

View File

@ -0,0 +1,25 @@
<ChatDrawer::Header>
<ChatDrawer::Header::LeftActions />
<ChatDrawer::Header::ChannelTitle
@channel={{this.chat.activeChannel}}
@drawerActions={{@drawerActions}}
/>
<ChatDrawer::Header::RightActions @drawerActions={{@drawerActions}} />
</ChatDrawer::Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div
class="chat-drawer-content"
{{did-insert this.fetchChannel}}
{{did-update this.fetchChannel @params.channelId}}
>
{{#if this.chat.activeChannel}}
<ChatLivePane
@targetMessageId={{readonly @params.messageId}}
@chatChannel={{this.chat.activeChannel}}
/>
{{/if}}
</div>
{{/if}}

View File

@ -0,0 +1,23 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
export default class ChatDrawerChannel extends Component {
@service appEvents;
@service chat;
@service chatStateManager;
@service chatChannelsManager;
@action
fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
return this.chatChannelsManager
.find(this.args.params.channelId)
.then((channel) => {
this.chat.activeChannel = channel;
});
}
}

View File

@ -0,0 +1,11 @@
<ChatDrawer::Header>
<ChatDrawer::Header::LeftActions />
<ChatDrawer::Header::Title @title="chat.direct_message_creator.title" />
<ChatDrawer::Header::RightActions @drawerActions={{@drawerActions}} />
</ChatDrawer::Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChatDraftChannelScreen />
</div>
{{/if}}

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerDraftChannel extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,7 @@
<div
role="region"
aria-label={{i18n "chat.aria_roles.header"}}
class="chat-drawer-header"
>
{{yield}}
</div>

View File

@ -0,0 +1,7 @@
<LinkTo
title={{i18n "chat.return_to_list"}}
class="chat-drawer-header__return-to-channels-btn"
@route="chat"
>
{{d-icon "chevron-left"}}
</LinkTo>

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerHeaderBackLink extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,33 @@
{{#if @channel}}
{{#if @this.chatStateManager.isDrawerExpanded}}
<LinkTo
@route={{if
@channel.isDirectMessageChannel
"chat.channel.info.settings"
"chat.channel.info.members"
}}
@models={{@channel.routeModels}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChatChannelTitle @channel={{@channel}} />
</div>
</LinkTo>
{{else}}
<div
role="button"
{{on "click" @drawerActions.toggleExpand}}
class="chat-drawer-header__title"
>
<div class="chat-drawer-header__top-line">
<ChatChannelTitle @channel={{@channel}}>
{{#if @channel.currentUserMembership.unreadCount}}
<span class="chat-unread-count">
{{@channel.currentUserMembership.unreadCount}}
</span>
{{/if}}
</ChatChannelTitle>
</div>
</div>
{{/if}}
{{/if}}

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerChannelHeaderTitle extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,6 @@
<DButton
@icon="times"
@action={{@close}}
@title="chat.close"
@class="btn-flat btn-link chat-drawer-header__close-btn"
/>

View File

@ -0,0 +1,8 @@
{{#if this.chatStateManager.isDrawerExpanded}}
<DButton
@icon="discourse-expand"
class="btn-flat btn-link chat-drawer-header__full-screen-btn"
@title={{"chat.open_full_page"}}
@action={{@openInFullPage}}
/>
{{/if}}

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerHeaderFullPageButton extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,7 @@
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-header__left-actions">
<div class="chat-drawer-header__top-line">
<ChatDrawer::Header::BackLink />
</div>
</div>
{{/if}}

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerHeaderLeftActions extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,11 @@
<div class="chat-drawer-header__right-actions">
<div class="chat-drawer-header__top-line">
<ChatDrawer::Header::FullPageButton
@openInFullPage={{@drawerActions.openInFullPage}}
/>
<ChatDrawer::Header::ToggleExpandButton
@toggleExpand={{@drawerActions.toggleExpand}}
/>
<ChatDrawer::Header::CloseButton @close={{@drawerActions.close}} />
</div>
</div>

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerHeaderRightActions extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,5 @@
<span class="chat-drawer-header__title">
<div class="chat-drawer-header__top-line">
{{i18n @title}}
</div>
</span>

View File

@ -0,0 +1,10 @@
<DButton
@icon={{if
this.chatStateManager.isDrawerExpanded
"angle-double-down"
"angle-double-up"
}}
@class="btn-flat btn-link chat-drawer-header__expand-btn"
@action={{@toggleExpand}}
@title="chat.collapse"
/>

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerHeaderToggleExpandButton extends Component {
@service chatStateManager;
}

View File

@ -0,0 +1,10 @@
<ChatDrawer::Header>
<ChatDrawer::Header::Title @title="chat.heading" />
<ChatDrawer::Header::RightActions @drawerActions={{@drawerActions}} />
</ChatDrawer::Header>
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChannelsList />
</div>
{{/if}}

View File

@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatDrawerIndex extends Component {
@service chatStateManager;
}

View File

@ -81,7 +81,6 @@
@afterReactionAdded={{action "reStickScrollIfNeeded"}}
@isHovered={{eq message.id this.hoveredMessageId}}
@onHoverMessage={{action "onHoverMessage"}}
@onSwitchChannel={{this.onSwitchChannel}}
@resendStagedMessage={{this.resendStagedMessage}}
/>
{{/each}}

View File

@ -45,7 +45,6 @@ export default Component.extend({
loadingMorePast: false,
loadingMoreFuture: false,
hoveredMessageId: null,
onSwitchChannel: null,
allPastMessagesLoaded: false,
sendingLoading: false,
@ -993,57 +992,58 @@ export default Component.extend({
}
this.set("_nextStagedMessageId", this._nextStagedMessageId + 1);
const cooked = this.cook(message);
const stagedId = this._nextStagedMessageId;
let data = {
message,
cooked,
staged_id: stagedId,
upload_ids: uploads.map((upload) => upload.id),
};
if (this.replyToMsg) {
data.in_reply_to_id = this.replyToMsg.id;
}
return this.chat.loadCookFunction(this.site.categories).then((cook) => {
const cooked = cook(message);
const stagedId = this._nextStagedMessageId;
let data = {
message,
cooked,
staged_id: stagedId,
upload_ids: uploads.map((upload) => upload.id),
};
if (this.replyToMsg) {
data.in_reply_to_id = this.replyToMsg.id;
}
// Start ajax request but don't return here, we want to stage the message instantly when all messages are loaded.
// Otherwise, we'll fetch latest and scroll to the one we just created.
// Return a resolved promise below.
const msgCreationPromise = this.chatApi
.sendMessage(this.chatChannel.id, data)
.catch((error) => {
this._onSendError(data.staged_id, error);
})
.finally(() => {
if (this._selfDeleted) {
return;
}
this.set("sendingLoading", false);
});
// Start ajax request but don't return here, we want to stage the message instantly when all messages are loaded.
// Otherwise, we'll fetch latest and scroll to the one we just created.
// Return a resolved promise below.
const msgCreationPromise = this.chatApi
.sendMessage(this.chatChannel.id, data)
.catch((error) => {
this._onSendError(data.staged_id, error);
})
.finally(() => {
if (this._selfDeleted) {
return;
}
this.set("sendingLoading", false);
});
if (this.details?.can_load_more_future) {
msgCreationPromise.then(() => this._fetchAndScrollToLatest());
} else {
const stagedMessage = this._prepareSingleMessage(
// We need to add the user and created at for presentation of staged message
{
message,
cooked,
stagedId,
uploads: cloneJSON(uploads),
staged: true,
user: this.currentUser,
in_reply_to: this.replyToMsg,
created_at: new Date(),
},
this.messages[this.messages.length - 1]
);
this.messages.pushObject(stagedMessage);
this._stickScrollToBottom();
}
if (this.details?.can_load_more_future) {
msgCreationPromise.then(() => this._fetchAndScrollToLatest());
} else {
const stagedMessage = this._prepareSingleMessage(
// We need to add the user and created at for presentation of staged message
{
message,
cooked,
stagedId,
uploads: cloneJSON(uploads),
staged: true,
user: this.currentUser,
in_reply_to: this.replyToMsg,
created_at: new Date(),
},
this.messages[this.messages.length - 1]
);
this.messages.pushObject(stagedMessage);
this._stickScrollToBottom();
}
this._resetAfterSend();
this.appEvents.trigger("chat-composer:reply-to-set", null);
return Promise.resolve();
this._resetAfterSend();
this.appEvents.trigger("chat-composer:reply-to-set", null);
});
},
async _upsertChannelWithMessage(channel, message, uploads) {
@ -1063,7 +1063,7 @@ export default Component.extend({
upload_ids: (uploads || []).mapBy("id"),
},
}).then(() => {
this.onSwitchChannel(c);
this.router.transitionTo("chat.channel", "-", c.id);
})
);
},

View File

@ -12,6 +12,7 @@ export default class MoveToChannelModalInner extends Component {
@service chatApi;
@service router;
@service chatChannelsManager;
tagName = "";
sourceChannel = null;
destinationChannelId = null;
@ -40,7 +41,9 @@ export default class MoveToChannelModalInner extends Component {
destination_channel_id: this.destinationChannelId,
})
.then((response) => {
return this.chat.openChannelAtMessage(
this.router.transitionTo(
"chat.channel.near-message",
"-",
response.destination_channel_id,
response.first_moved_message_id
);

View File

@ -609,7 +609,7 @@ export default Component.extend({
return this.chatChannelsManager
.getChannel(this.chatChannel.id)
.then((reactedChannel) => {
this.onSwitchChannel(reactedChannel);
this.router.transitionTo("chat.channel", "-", reactedChannel.id);
});
}
});

View File

@ -23,7 +23,6 @@ export default Component.extend({
router: service(),
chatStateManager: service(),
isLoading: false,
onSwitchChannel: null,
init() {
this._super(...arguments);

View File

@ -2,7 +2,6 @@
<ChatLivePane
@chatChannel={{this.chat.activeChannel}}
@onBackClick={{action "navigateToIndex"}}
@onSwitchChannel={{action "switchChannel"}}
@targetMessageId={{readonly @targetMessageId}}
/>
{{/if}}

View File

@ -76,9 +76,4 @@ export default Component.extend({
navigateToIndex() {
this.router.transitionTo("chat.index");
},
@action
switchChannel(channel) {
return this.chat.openChannel(channel);
},
});

View File

@ -1,7 +1,3 @@
{{#if this.isDisplayed}}
<ChannelsList
@onSelect={{action "switchChannel"}}
@toggleSection={{this.toggleSection}}
@inSidebar={{true}}
/>
<ChannelsList @toggleSection={{this.toggleSection}} @inSidebar={{true}} />
{{/if}}

View File

@ -1,10 +1,11 @@
import Component from "@ember/component";
import { action, computed } from "@ember/object";
import { computed } from "@ember/object";
import { inject as service } from "@ember/service";
export default class SidebarChannels extends Component {
@service chat;
@service router;
tagName = "";
toggleSection = null;
@ -12,9 +13,4 @@ export default class SidebarChannels extends Component {
get isDisplayed() {
return this.chat.userCanChat;
}
@action
switchChannel(channel) {
this.chat.openChannel(channel);
}
}

View File

@ -4,13 +4,15 @@ import { inject as service } from "@ember/service";
export default class UserCardChatButton extends Component {
@service chat;
@service appEvents;
@service router;
@action
startChatting() {
this.chat
.upsertDmChannelForUsernames([this.user.username])
.then((chatChannel) => {
this.chat.openChannel(chatChannel);
this.router.transitionTo("chat.channel", ...chatChannel.routeModels);
this.appEvents.trigger("card:close");
});
}

View File

@ -7,12 +7,13 @@ export default class ChatChannelToggleController extends Controller.extend(
ModalFunctionality
) {
@service chat;
@service router;
chatChannel = null;
@action
channelStatusChanged(channel) {
this.send("closeModal");
this.chat.openChannel(channel);
this.router.transitionTo("chat.channel", ...channel.routeModels);
}
}

View File

@ -1,5 +1,4 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class ChatChannelController extends Controller {
@ -9,9 +8,4 @@ export default class ChatChannelController extends Controller {
// Backwards-compatibility
queryParams = ["messageId"];
@action
switchChannel(channel) {
this.chat.openChannel(channel);
}
}

View File

@ -1,12 +1,6 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class ChatDraftChannelController extends Controller {
@service chat;
@action
onSwitchChannel(channel) {
return this.chat.openChannel(channel);
}
}

View File

@ -1,12 +1,6 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class ChatIndexController extends Controller {
@service chat;
@action
selectChannel(channel) {
return this.chat.openChannel(channel);
}
}

View File

@ -25,6 +25,7 @@ export default class CreateChannelController extends Controller.extend(
@service dialog;
@service chatChannelsManager;
@service chatApi;
@service router;
category = null;
categoryId = null;
@ -83,7 +84,7 @@ export default class CreateChannelController extends Controller.extend(
.then((channel) => {
this.send("closeModal");
this.chatChannelsManager.follow(channel);
this.chat.openChannel(channel);
this.router.transitionTo("chat.channel", ...channel.routeModels);
})
.catch((e) => {
this.flash(e.jqXHR.responseJSON.errors[0], "error");

View File

@ -16,7 +16,7 @@ export default class ChatIndexRoute extends DiscourseRoute {
const id = this.chat.getIdealFirstChannelId();
if (id) {
return this.chatChannelsManager.find(id).then((c) => {
return this.chat.openChannel(c);
return this.router.transitionTo("chat.channel", ...c.routeModels);
});
} else {
return this.router.transitionTo("chat.browse");

View File

@ -37,17 +37,17 @@ export default class ChatRoute extends DiscourseRoute {
) {
transition.abort();
let URL = transition.intent.url;
let url = transition.intent.url;
if (transition.targetName.startsWith("chat.channel")) {
URL ??= this.router.urlFor(
url ??= this.router.urlFor(
transition.targetName,
...transition.intent.contexts
);
} else {
URL ??= this.router.urlFor(transition.targetName);
url ??= this.router.urlFor(transition.targetName);
}
this.appEvents.trigger("chat:open-url", URL);
this.appEvents.trigger("chat:open-url", url);
return;
}

View File

@ -0,0 +1,41 @@
import Service, { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import ChatDrawerDraftChannel from "discourse/plugins/chat/discourse/components/chat-drawer/draft-channel";
import ChatDrawerChannel from "discourse/plugins/chat/discourse/components/chat-drawer/channel";
import ChatDrawerIndex from "discourse/plugins/chat/discourse/components/chat-drawer/index";
const COMPONENTS_MAP = {
"chat.draft-channel": { name: ChatDrawerDraftChannel },
"chat.channel": { name: ChatDrawerChannel },
chat: { name: ChatDrawerIndex },
"chat.channel.near-message": {
name: ChatDrawerChannel,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
messageId: route.params.messageId,
};
},
},
"chat.channel-legacy": {
name: ChatDrawerChannel,
extractParams: (route) => {
return {
channelId: route.params.channelId,
messageId: route.queryParams.messageId,
};
},
},
};
export default class ChatDrawerRouter extends Service {
@service router;
@tracked component = null;
@tracked params = null;
stateFor(route) {
const component = COMPONENTS_MAP[route.name];
this.params = component?.extractParams?.(route) || route.params;
this.component = component?.name || ChatDrawerIndex;
}
}

View File

@ -42,12 +42,12 @@ export default class ChatStateManager extends Service {
this.set("isSidePanelExpanded", false);
}
didOpenDrawer(URL = null) {
didOpenDrawer(url = null) {
this.set("isDrawerActive", true);
this.set("isDrawerExpanded", true);
if (URL) {
this.storeChatURL(URL);
if (url) {
this.storeChatURL(url);
}
this.chat.updatePresence();
@ -99,12 +99,12 @@ export default class ChatStateManager extends Service {
return this.isFullPageActive || this.isDrawerActive;
}
storeAppURL(URL = null) {
this._appURL = URL || this.router.currentURL;
storeAppURL(url = null) {
this._appURL = url || this.router.currentURL;
}
storeChatURL(URL = null) {
this._chatURL = URL || this.router.currentURL;
storeChatURL(url = null) {
this._chatURL = url || this.router.currentURL;
}
get lastKnownAppURL() {

View File

@ -188,7 +188,10 @@ export default class Chat extends Service {
currentList[currentChannelIndex + (directionUp ? -1 : 1)];
if (nextChannelInSameList) {
// You're navigating in the same list of channels, just use index +- 1
return this.openChannel(nextChannelInSameList);
return this.router.transitionTo(
"chat.channel",
...nextChannelInSameList.routeModels
);
}
// You need to go to the next list of channels, if it exists.
@ -198,7 +201,10 @@ export default class Chat extends Service {
: nextList[0];
if (nextChannel.id !== activeChannel.id) {
return this.openChannel(nextChannel);
return this.router.transitionTo(
"chat.channel",
...nextChannel.routeModels
);
}
}
@ -262,57 +268,14 @@ export default class Chat extends Service {
);
}
async openChannelAtMessage(channelId, messageId = null) {
return this.chatChannelsManager.find(channelId).then((channel) => {
return this._openFoundChannelAtMessage(channel, messageId);
});
}
async openChannel(channel) {
return this._openFoundChannelAtMessage(channel);
}
async _openFoundChannelAtMessage(channel, messageId = null) {
if (
(this.router.currentRouteName === "chat.channel" ||
this.router.currentRouteName === "chat.channel.near-message") &&
this.activeChannel?.id === channel.id
) {
this.activeChannel = channel;
this._fireOpenMessageAppEvent(messageId);
return Promise.resolve();
}
this.activeChannel = channel;
if (
this.chatStateManager.isFullPageActive ||
this.site.mobileView ||
this.chatStateManager.isFullPagePreferred
) {
if (messageId) {
return this.router.transitionTo(
_fireOpenFloatAppEvent(channel, messageId = null) {
messageId
? this.router.transitionTo(
"chat.channel.near-message",
...channel.routeModels,
messageId
);
} else {
return this.router.transitionTo("chat.channel", ...channel.routeModels);
}
} else {
this._fireOpenFloatAppEvent(channel, messageId);
return Promise.resolve();
}
}
_fireOpenFloatAppEvent(channel, messageId = null) {
messageId
? this.appEvents.trigger(
"chat:open-channel-at-message",
channel,
messageId
)
: this.appEvents.trigger("chat:open-channel", channel);
: this.router.transitionTo("chat.channel", ...channel.routeModels);
}
_fireOpenMessageAppEvent(messageId) {

View File

@ -1 +1 @@
<ChatDraftChannelScreen @onSwitchChannel={{action "onSwitchChannel"}} />
<ChatDraftChannelScreen />

View File

@ -1 +1 @@
<ChannelsList @onSelect={{action "selectChannel"}} />
<ChannelsList />