DEV: makes chat modals use the new <DModal /> component (#22495)
This commit also standardize the naming pattern of modals: `<Chat::Modal::FooBar />` and changes css class accordingly. Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
parent
ed2dae6d1a
commit
9830c40386
|
@ -1,5 +1,5 @@
|
|||
<DButton
|
||||
@class="btn-flat d-modal-cancel"
|
||||
@action={{action this.close}}
|
||||
@action={{@close}}
|
||||
@translatedLabel={{i18n "cancel"}}
|
||||
/>
|
|
@ -1,4 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
});
|
|
@ -490,4 +490,20 @@ module("Integration | Component | select-kit/single-select", function (hooks) {
|
|||
|
||||
assert.dom(".single-select.is-expanded").exists();
|
||||
});
|
||||
|
||||
test("options.formName", async function (assert) {
|
||||
setDefaultState(this);
|
||||
await render(hbs`
|
||||
<SingleSelect
|
||||
@value={{this.value}}
|
||||
@content={{this.content}}
|
||||
@options={{hash formName="foo"}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert
|
||||
.dom('input[name="foo"]')
|
||||
.hasAttribute("type", "hidden")
|
||||
.hasAttribute("value", "1");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -311,6 +311,7 @@ export default Component.extend(
|
|||
hiddenValues: null,
|
||||
disabled: false,
|
||||
expandedOnInsert: false,
|
||||
formName: null,
|
||||
},
|
||||
|
||||
autoFilterable: computed("content.[]", "selectKit.filter", function () {
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
data-name={{this.name}}
|
||||
class="select-kit-selected-name selected-name choice"
|
||||
>
|
||||
{{#if this.selectKit.options.formName}}
|
||||
<input
|
||||
type="hidden"
|
||||
name={{this.selectKit.options.formName}}
|
||||
value={{this.value}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.item.icon}}
|
||||
{{d-icon this.item.icon}}
|
||||
{{/if}}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<DModalBody @title="chat.summarization.title">
|
||||
<span>{{i18n "chat.summarization.description"}}</span>
|
||||
<ComboBox
|
||||
@value={{this.sinceHours}}
|
||||
@content={{this.sinceOptions}}
|
||||
@onChange={{action this.summarize}}
|
||||
@valueProperty="value"
|
||||
@class="summarization-since"
|
||||
/>
|
||||
<div class="channel-summary">
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
|
||||
{{#unless this.loading}}
|
||||
<p class="summary-area">{{this.summary}}</p>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DModalCancel @close={{route-action "closeModal"}} />
|
||||
</div>
|
|
@ -1,65 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { action } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class ChannelSumarry extends Component {
|
||||
@tracked sinceHours = null;
|
||||
@tracked loading = false;
|
||||
@tracked availableSummaries = {};
|
||||
@tracked summary = null;
|
||||
sinceOptions = [
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 1 }),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 3 }),
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 6 }),
|
||||
value: 6,
|
||||
},
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 12 }),
|
||||
value: 12,
|
||||
},
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 24 }),
|
||||
value: 24,
|
||||
},
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 72 }),
|
||||
value: 72,
|
||||
},
|
||||
{
|
||||
name: I18n.t("chat.summarization.since", { count: 168 }),
|
||||
value: 168,
|
||||
},
|
||||
];
|
||||
|
||||
@action
|
||||
summarize(value) {
|
||||
this.loading = true;
|
||||
|
||||
if (this.availableSummaries[value]) {
|
||||
this.summary = this.availableSummaries[value];
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ajax(`/chat/api/channels/${this.args.channelId}/summarize`, {
|
||||
method: "GET",
|
||||
data: { since: value },
|
||||
})
|
||||
.then((data) => {
|
||||
this.availableSummaries[this.sinceHours] = data.summary;
|
||||
this.summary = this.availableSummaries[this.sinceHours];
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => (this.loading = false));
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import { action } from "@ember/object";
|
|||
import { schedule } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import ChatNewMessageModal from "discourse/plugins/chat/discourse/components/modal/chat-new-message";
|
||||
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
|
||||
|
||||
export default class ChannelsList extends Component {
|
||||
@service chat;
|
||||
|
@ -30,7 +30,7 @@ export default class ChannelsList extends Component {
|
|||
|
||||
@action
|
||||
openNewMessageModal() {
|
||||
this.modal.show(ChatNewMessageModal);
|
||||
this.modal.show(ChatModalNewMessage);
|
||||
}
|
||||
|
||||
get showMobileDirectMessageButton() {
|
||||
|
|
|
@ -4,13 +4,15 @@ import { action, computed } from "@ember/object";
|
|||
import { schedule } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import ChatNewMessageModal from "discourse/plugins/chat/discourse/components/modal/chat-new-message";
|
||||
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
|
||||
import ChatModalCreateChannel from "discourse/plugins/chat/discourse/components/chat/modal/create-channel";
|
||||
|
||||
const TABS = ["all", "open", "closed", "archived"];
|
||||
|
||||
export default class ChatBrowseView extends Component {
|
||||
@service chatApi;
|
||||
@service modal;
|
||||
|
||||
tagName = "";
|
||||
|
||||
didReceiveAttrs() {
|
||||
|
@ -41,7 +43,7 @@ export default class ChatBrowseView extends Component {
|
|||
|
||||
@action
|
||||
showChatNewMessageModal() {
|
||||
this.modal.show(ChatNewMessageModal);
|
||||
this.modal.show(ChatModalNewMessage);
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -66,7 +68,7 @@ export default class ChatBrowseView extends Component {
|
|||
|
||||
@action
|
||||
createChannel() {
|
||||
showModal("create-channel");
|
||||
this.modal.show(ChatModalCreateChannel);
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<DModalBody @title="chat.channel_archive.title">
|
||||
<div
|
||||
id="chat-channel-archive-modal-inner"
|
||||
class="chat-channel-archive-modal-inner"
|
||||
>
|
||||
<p
|
||||
class="chat-channel-archive-modal-instructions"
|
||||
>{{this.instructionsText}}</p>
|
||||
|
||||
<ChatToTopicSelector
|
||||
@selection={{this.selection}}
|
||||
@topicTitle={{this.topicTitle}}
|
||||
@categoryId={{this.categoryId}}
|
||||
@tags={{this.tags}}
|
||||
@selectedTopicId={{this.selectedTopicId}}
|
||||
@instructionLabels={{this.instructionLabels}}
|
||||
@allowNewMessage={{false}}
|
||||
/>
|
||||
</div>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@disabled={{this.buttonDisabled}}
|
||||
@action={{action "archiveChannel"}}
|
||||
@label="chat.channel_archive.title"
|
||||
@id="chat-confirm-archive-channel"
|
||||
/>
|
||||
</div>
|
|
@ -1,108 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { action } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import {
|
||||
EXISTING_TOPIC_SELECTION,
|
||||
NEW_TOPIC_SELECTION,
|
||||
} from "discourse/plugins/chat/discourse/components/chat-to-topic-selector";
|
||||
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default Component.extend(ModalFunctionality, {
|
||||
chat: service(),
|
||||
chatApi: service(),
|
||||
tagName: "",
|
||||
chatChannel: null,
|
||||
|
||||
selection: NEW_TOPIC_SELECTION,
|
||||
newTopic: equal("selection", NEW_TOPIC_SELECTION),
|
||||
existingTopic: equal("selection", EXISTING_TOPIC_SELECTION),
|
||||
|
||||
saving: false,
|
||||
|
||||
topicTitle: null,
|
||||
categoryId: null,
|
||||
tags: null,
|
||||
selectedTopicId: null,
|
||||
|
||||
@action
|
||||
archiveChannel() {
|
||||
this.set("saving", true);
|
||||
|
||||
return this.chatApi
|
||||
.createChannelArchive(this.chatChannel.id, this._data())
|
||||
.then(() => {
|
||||
this.flash(I18n.t("chat.channel_archive.process_started"), "success");
|
||||
this.chatChannel.status = CHANNEL_STATUSES.archived;
|
||||
|
||||
discourseLater(() => {
|
||||
this.closeModal();
|
||||
}, 3000);
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => this.set("saving", false));
|
||||
},
|
||||
|
||||
_data() {
|
||||
const data = {
|
||||
type: this.selection,
|
||||
};
|
||||
if (this.newTopic) {
|
||||
data.title = this.topicTitle;
|
||||
data.category_id = this.categoryId;
|
||||
data.tags = this.tags;
|
||||
}
|
||||
if (this.existingTopic) {
|
||||
data.topic_id = this.selectedTopicId;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
@discourseComputed("saving", "selectedTopicId", "topicTitle", "selection")
|
||||
buttonDisabled(saving, selectedTopicId, topicTitle) {
|
||||
if (saving) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
this.newTopic &&
|
||||
(!topicTitle ||
|
||||
topicTitle.length < this.siteSettings.min_topic_title_length ||
|
||||
topicTitle.length > this.siteSettings.max_topic_title_length)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.existingTopic && isEmpty(selectedTopicId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
instructionLabels() {
|
||||
const labels = {};
|
||||
labels[NEW_TOPIC_SELECTION] = I18n.t(
|
||||
"chat.selection.new_topic.instructions_channel_archive"
|
||||
);
|
||||
labels[EXISTING_TOPIC_SELECTION] = I18n.t(
|
||||
"chat.selection.existing_topic.instructions_channel_archive"
|
||||
);
|
||||
return labels;
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
instructionsText() {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.channel_archive.instructions", {
|
||||
channelTitle: this.chatChannel.escapedTitle,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
<DModalBody @title="chat.channel_delete.title">
|
||||
<div
|
||||
id="chat-channel-delete-modal-inner"
|
||||
class="chat-channel-delete-modal-inner"
|
||||
>
|
||||
<p class="chat-channel-delete-modal-instructions">
|
||||
{{this.instructionsText}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
@value={{this.channelNameConfirmation}}
|
||||
@id="channel-delete-confirm-name"
|
||||
@placeholderKey="chat.channel_delete.confirm_channel_name"
|
||||
@autocorrect="off"
|
||||
@autocapitalize="off"
|
||||
/>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@disabled={{this.buttonDisabled}}
|
||||
@class="btn-danger"
|
||||
@action={{action "deleteChannel"}}
|
||||
@label="chat.channel_delete.confirm"
|
||||
@id="chat-confirm-delete-channel"
|
||||
/>
|
||||
|
||||
<DButton @label="cancel" @class="btn-flat" @action={{this.closeModal}} />
|
||||
</div>
|
|
@ -1,65 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default Component.extend(ModalFunctionality, {
|
||||
chat: service(),
|
||||
chatApi: service(),
|
||||
router: service(),
|
||||
tagName: "",
|
||||
chatChannel: null,
|
||||
channelNameConfirmation: null,
|
||||
deleting: false,
|
||||
confirmed: false,
|
||||
|
||||
@discourseComputed("deleting", "channelNameConfirmation", "confirmed")
|
||||
buttonDisabled(deleting, channelNameConfirmation, confirmed) {
|
||||
if (deleting || confirmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
isEmpty(channelNameConfirmation) ||
|
||||
channelNameConfirmation.toLowerCase() !==
|
||||
this.chatChannel.title.toLowerCase()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
@action
|
||||
deleteChannel() {
|
||||
this.set("deleting", true);
|
||||
|
||||
return this.chatApi
|
||||
.destroyChannel(this.chatChannel.id, this.channelNameConfirmation)
|
||||
.then(() => {
|
||||
this.set("confirmed", true);
|
||||
this.flash(I18n.t("chat.channel_delete.process_started"), "success");
|
||||
|
||||
discourseLater(() => {
|
||||
this.closeModal();
|
||||
this.router.transitionTo("chat");
|
||||
}, 3000);
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => this.set("deleting", false));
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
instructionsText() {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.channel_delete.instructions", {
|
||||
name: this.chatChannel.escapedTitle,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,8 +1,10 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import I18n from "I18n";
|
||||
import ChatModalArchiveChannel from "discourse/plugins/chat/discourse/components/chat/modal/archive-channel";
|
||||
import ChatModalDeleteChannel from "discourse/plugins/chat/discourse/components/chat/modal/delete-channel";
|
||||
import ChatModalToggleChannelStatus from "discourse/plugins/chat/discourse/components/chat/modal/toggle-channel-status";
|
||||
|
||||
const NOTIFICATION_LEVELS = [
|
||||
{ name: I18n.t("chat.notification_levels.never"), value: "never" },
|
||||
|
@ -41,6 +43,7 @@ export default class ChatChannelSettingsView extends Component {
|
|||
@service siteSettings;
|
||||
@service router;
|
||||
@service dialog;
|
||||
@service modal;
|
||||
|
||||
notificationLevels = NOTIFICATION_LEVELS;
|
||||
mutedOptions = MUTED_OPTIONS;
|
||||
|
@ -107,20 +110,21 @@ export default class ChatChannelSettingsView extends Component {
|
|||
|
||||
@action
|
||||
onArchiveChannel() {
|
||||
const controller = showModal("chat-channel-archive-modal");
|
||||
controller.set("chatChannel", this.args.channel);
|
||||
return this.modal.show(ChatModalArchiveChannel, {
|
||||
model: { channel: this.args.channel },
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onDeleteChannel() {
|
||||
const controller = showModal("chat-channel-delete-modal");
|
||||
controller.set("chatChannel", this.args.channel);
|
||||
return this.modal.show(ChatModalDeleteChannel, {
|
||||
model: { channel: this.args.channel },
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onToggleChannelState() {
|
||||
const controller = showModal("chat-channel-toggle");
|
||||
controller.set("chatChannel", this.args.channel);
|
||||
this.modal.show(ChatModalToggleChannelStatus, { model: this.args.channel });
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<DModalBody @title={{this.modalTitle}}>
|
||||
<div id="chat-channel-toggle" class="chat-channel-toggle">
|
||||
<p>{{this.instructions}}</p>
|
||||
</div>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@action={{action "changeChannelStatus"}}
|
||||
@label={{this.buttonLabel}}
|
||||
@id="chat-channel-toggle-btn"
|
||||
/>
|
||||
</div>
|
|
@ -25,6 +25,7 @@ import {
|
|||
initUserStatusHtml,
|
||||
renderUserStatusHtml,
|
||||
} from "discourse/lib/user-status-on-autocomplete";
|
||||
import ChatModalChannelSummary from "discourse/plugins/chat/discourse/components/chat/modal/channel-summary";
|
||||
|
||||
export default class ChatComposer extends Component {
|
||||
@service capabilities;
|
||||
|
@ -39,6 +40,7 @@ export default class ChatComposer extends Component {
|
|||
@service currentUser;
|
||||
@service chatApi;
|
||||
@service chatDraftsManager;
|
||||
@service modal;
|
||||
|
||||
@tracked isFocused = false;
|
||||
@tracked inProgressUploadsCount = 0;
|
||||
|
@ -377,8 +379,8 @@ export default class ChatComposer extends Component {
|
|||
|
||||
@action
|
||||
showChannelSummaryModal() {
|
||||
showModal("channel-summary").setProperties({
|
||||
channelId: this.args.channel.id,
|
||||
this.modal.show(ChatModalChannelSummary, {
|
||||
model: { channelId: this.args.channel.id },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
<DModalBody @title="chat.move_to_channel.title">
|
||||
<div
|
||||
id="chat-message-move-to-channel-modal-inner"
|
||||
class="chat-message-move-to-channel-modal-inner"
|
||||
>
|
||||
<p>{{this.instructionsText}}</p>
|
||||
</div>
|
||||
|
||||
<ChatChannelChooser
|
||||
@class="chat-move-message-channel-chooser"
|
||||
@content={{this.availableChannels}}
|
||||
@value={{this.destinationChannelId}}
|
||||
@nameProperty="title"
|
||||
/>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@icon="sign-out-alt"
|
||||
@disabled={{this.disableMoveButton}}
|
||||
@action={{this.moveMessages}}
|
||||
@label="chat.move_to_channel.confirm_move"
|
||||
@id="chat-confirm-move-messages-to-channel"
|
||||
/>
|
||||
|
||||
<DButton @label="cancel" @class="btn-flat" @action={{this.closeModal}} />
|
||||
</div>
|
|
@ -5,11 +5,7 @@
|
|||
(if @message.newest "with-last-visit")
|
||||
}}
|
||||
role="button"
|
||||
{{on
|
||||
"click"
|
||||
(fn @fetchMessagesByDate @message.firstMessageOfTheDayAt)
|
||||
passive=true
|
||||
}}
|
||||
{{on "click" this.onDateClick passive=true}}
|
||||
>
|
||||
<div
|
||||
class="chat-message-separator__text-container"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatMessageSeparatorDate extends Component {
|
||||
@action
|
||||
onDateClick() {
|
||||
return this.args.fetchMessagesByDate?.(
|
||||
this.args.message.firstMessageOfTheDayAt
|
||||
);
|
||||
}
|
||||
}
|
|
@ -281,6 +281,7 @@ export default class ChatMessageCreator extends Component {
|
|||
return;
|
||||
} else if (this.query?.length === 0) {
|
||||
this.openChannel(this.selection);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-archive-channel"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.channel_archive.title"}}
|
||||
@flash={{this.flash}}
|
||||
@flashType={{this.flashType}}
|
||||
>
|
||||
<:body>
|
||||
<p class="chat-modal-archive-channel__instructions">
|
||||
{{this.instructionsText}}
|
||||
</p>
|
||||
<ChatToTopicSelector
|
||||
@selection={{this.selection}}
|
||||
@topicTitle={{this.topicTitle}}
|
||||
@categoryId={{this.categoryId}}
|
||||
@tags={{this.tags}}
|
||||
@selectedTopicId={{this.selectedTopicId}}
|
||||
@instructionLabels={{this.instructionLabels}}
|
||||
@allowNewMessage={{false}}
|
||||
/>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@disabled={{this.buttonDisabled}}
|
||||
@action={{this.archiveChannel}}
|
||||
@label="chat.channel_archive.title"
|
||||
@id="chat-confirm-archive-channel"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,112 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import I18n from "I18n";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import {
|
||||
EXISTING_TOPIC_SELECTION,
|
||||
NEW_TOPIC_SELECTION,
|
||||
} from "discourse/plugins/chat/discourse/components/chat-to-topic-selector";
|
||||
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
||||
export default class ChatModalArchiveChannel extends Component {
|
||||
@service chatApi;
|
||||
@service siteSettings;
|
||||
|
||||
@tracked selection = NEW_TOPIC_SELECTION;
|
||||
@tracked saving = false;
|
||||
@tracked topicTitle = null;
|
||||
@tracked categoryId = null;
|
||||
@tracked tags = null;
|
||||
@tracked selectedTopicId = null;
|
||||
@tracked flash;
|
||||
@tracked flashType;
|
||||
|
||||
get channel() {
|
||||
return this.args.model.channel;
|
||||
}
|
||||
|
||||
get newTopic() {
|
||||
return this.selection === NEW_TOPIC_SELECTION;
|
||||
}
|
||||
|
||||
get existingTopic() {
|
||||
return this.selection === EXISTING_TOPIC_SELECTION;
|
||||
}
|
||||
|
||||
get buttonDisabled() {
|
||||
if (this.saving) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.newTopic &&
|
||||
(!this.topicTitle ||
|
||||
this.topicTitle.length < this.siteSettings.min_topic_title_length ||
|
||||
this.topicTitle.length > this.siteSettings.max_topic_title_length)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.existingTopic && isEmpty(this.selectedTopicId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get instructionLabels() {
|
||||
const labels = {};
|
||||
labels[NEW_TOPIC_SELECTION] = I18n.t(
|
||||
"chat.selection.new_topic.instructions_channel_archive"
|
||||
);
|
||||
labels[EXISTING_TOPIC_SELECTION] = I18n.t(
|
||||
"chat.selection.existing_topic.instructions_channel_archive"
|
||||
);
|
||||
return labels;
|
||||
}
|
||||
|
||||
get instructionsText() {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.channel_archive.instructions", {
|
||||
channelTitle: this.channel.escapedTitle,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
archiveChannel() {
|
||||
this.saving = true;
|
||||
|
||||
return this.chatApi
|
||||
.createChannelArchive(this.channel.id, this.#data())
|
||||
.then(() => {
|
||||
this.flash = I18n.t("chat.channel_archive.process_started");
|
||||
this.flashType = "success";
|
||||
this.channel.status = CHANNEL_STATUSES.archived;
|
||||
|
||||
discourseLater(() => {
|
||||
this.args.closeModal();
|
||||
}, 3000);
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => (this.saving = false));
|
||||
}
|
||||
|
||||
#data() {
|
||||
const data = { type: this.selection };
|
||||
if (this.newTopic) {
|
||||
data.title = this.topicTitle;
|
||||
data.category_id = this.categoryId;
|
||||
data.tags = this.tags;
|
||||
}
|
||||
if (this.existingTopic) {
|
||||
data.topic_id = this.selectedTopicId;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-channel-summary"
|
||||
@title={{i18n "chat.summarization.title"}}
|
||||
>
|
||||
<:body>
|
||||
<span>{{i18n "chat.summarization.description"}}</span>
|
||||
<ComboBox
|
||||
@value={{this.sinceHours}}
|
||||
@content={{this.sinceOptions}}
|
||||
@onChange={{this.summarize}}
|
||||
@valueProperty="value"
|
||||
@class="summarization-since"
|
||||
/>
|
||||
<ConditionalLoadingSection @isLoading={{this.loading}}>
|
||||
<p class="summary-area">{{this.summary}}</p>
|
||||
</ConditionalLoadingSection>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DModalCancel @close={{@closeModal}} />
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,47 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { action } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatModalChannelSummary extends Component {
|
||||
@service chatApi;
|
||||
|
||||
@tracked sinceHours = null;
|
||||
@tracked loading = false;
|
||||
@tracked summary = null;
|
||||
|
||||
availableSummaries = {};
|
||||
|
||||
sinceOptions = [1, 3, 6, 12, 24, 72, 168].map((hours) => {
|
||||
return {
|
||||
name: I18n.t("chat.summarization.since", { count: hours }),
|
||||
value: hours,
|
||||
};
|
||||
});
|
||||
|
||||
get channelId() {
|
||||
return this.args.model.channelId;
|
||||
}
|
||||
|
||||
@action
|
||||
summarize(since) {
|
||||
this.loading = true;
|
||||
|
||||
if (this.availableSummaries[since]) {
|
||||
this.summary = this.availableSummaries[since];
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
return this.chatApi
|
||||
.summarize(this.channelId, { since })
|
||||
.then((data) => {
|
||||
this.availableSummaries[this.sinceHours] = data.summary;
|
||||
this.summary = this.availableSummaries[this.sinceHours];
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => (this.loading = false));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-create-channel"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.create_channel.title"}}
|
||||
@flash={{this.flash}}
|
||||
@tagName="form"
|
||||
{{on "submit" this.onSave}}
|
||||
>
|
||||
<:body>
|
||||
<div class="chat-modal-create-channel__control -name">
|
||||
<label for="name" class="chat-modal-create-channel__label">
|
||||
{{i18n "chat.create_channel.name"}}
|
||||
</label>
|
||||
<Input
|
||||
name="name"
|
||||
class="chat-modal-create-channel__input"
|
||||
@type="text"
|
||||
@value={{this.name}}
|
||||
{{on "input" (action this.onNameChange value="target.value")}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="chat-modal-create-channel__control -slug">
|
||||
<label for="slug" class="chat-modal-create-channel__label">
|
||||
{{i18n "chat.create_channel.slug"}}
|
||||
<span>
|
||||
{{d-icon "info-circle"}}
|
||||
<DTooltip>
|
||||
{{i18n "chat.channel_edit_name_slug_modal.slug_description"}}
|
||||
</DTooltip>
|
||||
</span>
|
||||
</label>
|
||||
<Input
|
||||
name="slug"
|
||||
class="chat-modal-create-channel__input"
|
||||
@type="text"
|
||||
@value={{this.slug}}
|
||||
placeholder={{this.autoGeneratedSlug}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="chat-modal-create-channel__control -description">
|
||||
<label for="description" class="chat-modal-create-channel__label">
|
||||
{{i18n "chat.create_channel.description"}}
|
||||
</label>
|
||||
<Input
|
||||
name="description"
|
||||
class="chat-modal-create-channel__input"
|
||||
@type="textarea"
|
||||
@value={{this.description}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="chat-modal-create-channel__control">
|
||||
<label class="chat-modal-create-channel__label">
|
||||
{{i18n "chat.create_channel.choose_category.label"}}
|
||||
</label>
|
||||
<CategoryChooser
|
||||
@value={{this.categoryId}}
|
||||
@onChange={{action this.onCategoryChange}}
|
||||
@options={{hash
|
||||
formName="chatable_id"
|
||||
none="chat.create_channel.choose_category.none"
|
||||
}}
|
||||
/>
|
||||
|
||||
{{#if this.categoryPermissionsHint}}
|
||||
<div
|
||||
class={{concat-class
|
||||
"chat-modal-create-channel__hint"
|
||||
(if this.loadingPermissionHint "loading-permissions")
|
||||
}}
|
||||
>
|
||||
{{this.categoryPermissionsHint}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.autoJoinAvailable}}
|
||||
<div class="chat-modal-create-channel__control -auto-join">
|
||||
<label class="chat-modal-create-channel__label">
|
||||
<Input
|
||||
name="auto_join_users"
|
||||
@type="checkbox"
|
||||
@checked={{this.autoJoinUsers}}
|
||||
/>
|
||||
<div class="auto-join-channel">
|
||||
<span class="chat-modal-create-channel__label-title">
|
||||
{{i18n "chat.settings.auto_join_users_label"}}
|
||||
</span>
|
||||
<p class="chat-modal-create-channel__label-description">
|
||||
{{#if this.categoryName}}
|
||||
{{i18n
|
||||
"chat.settings.auto_join_users_info"
|
||||
category=this.categoryName
|
||||
}}
|
||||
{{else}}
|
||||
{{i18n "chat.settings.auto_join_users_info_no_category"}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.threadingAvailable}}
|
||||
<div class="chat-modal-create-channel__control -threading-toggle">
|
||||
<label class="chat-modal-create-channel__label">
|
||||
<Input
|
||||
name="threading_enabled"
|
||||
@type="checkbox"
|
||||
@checked={{this.threadingEnabled}}
|
||||
/>
|
||||
<div class="threading-channel">
|
||||
<span class="chat-modal-create-channel__label-title">
|
||||
{{i18n "chat.create_channel.threading.label"}}
|
||||
</span>
|
||||
<p class="chat-modal-create-channel__label-description">
|
||||
{{i18n "chat.settings.channel_threading_description"}}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
</:body>
|
||||
<:footer>
|
||||
<button
|
||||
class="btn btn-primary create"
|
||||
disabled={{this.createDisabled}}
|
||||
type="submit"
|
||||
>
|
||||
{{i18n "chat.create_channel.create"}}
|
||||
</button>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -2,14 +2,13 @@ import { escapeExpression } from "discourse/lib/utilities";
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import { cancel } from "@ember/runloop";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import Controller from "@ember/controller";
|
||||
import Component from "@glimmer/component";
|
||||
import I18n from "I18n";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { gt, notEmpty } from "@ember/object/computed";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { isBlank, isPresent } from "@ember/utils";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
const DEFAULT_HINT = htmlSafe(
|
||||
I18n.t("chat.create_channel.choose_category.default_hint", {
|
||||
|
@ -18,9 +17,7 @@ const DEFAULT_HINT = htmlSafe(
|
|||
})
|
||||
);
|
||||
|
||||
export default class CreateChannelController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
export default class ChatModalCreateChannel extends Component {
|
||||
@service chat;
|
||||
@service dialog;
|
||||
@service chatChannelsManager;
|
||||
|
@ -28,21 +25,30 @@ export default class CreateChannelController extends Controller.extend(
|
|||
@service router;
|
||||
@service currentUser;
|
||||
@service siteSettings;
|
||||
@service site;
|
||||
|
||||
category = null;
|
||||
categoryId = null;
|
||||
name = "";
|
||||
slug = "";
|
||||
autoGeneratedSlug = "";
|
||||
description = "";
|
||||
categoryPermissionsHint = null;
|
||||
autoJoinUsers = false;
|
||||
autoJoinWarning = "";
|
||||
loadingPermissionHint = false;
|
||||
threadingEnabled = false;
|
||||
@tracked flash;
|
||||
@tracked name;
|
||||
@tracked category;
|
||||
@tracked categoryId;
|
||||
@tracked autoGeneratedSlug = "";
|
||||
@tracked categoryPermissionsHint;
|
||||
@tracked autoJoinWarning = "";
|
||||
@tracked loadingPermissionHint = false;
|
||||
|
||||
@notEmpty("category") categorySelected;
|
||||
@gt("siteSettings.max_chat_auto_joined_users", 0) autoJoinAvailable;
|
||||
#generateSlugHandler = null;
|
||||
|
||||
willDestroy() {
|
||||
cancel(this.#generateSlugHandler);
|
||||
}
|
||||
|
||||
get autoJoinAvailable() {
|
||||
return this.siteSettings.max_chat_auto_joined_users > 0;
|
||||
}
|
||||
|
||||
get categorySelected() {
|
||||
return isPresent(this.category);
|
||||
}
|
||||
|
||||
get threadingAvailable() {
|
||||
return (
|
||||
|
@ -51,69 +57,88 @@ export default class CreateChannelController extends Controller.extend(
|
|||
);
|
||||
}
|
||||
|
||||
@computed("categorySelected", "name")
|
||||
get createDisabled() {
|
||||
return !this.categorySelected || isBlank(this.name);
|
||||
}
|
||||
|
||||
@computed("categorySelected", "name")
|
||||
get categoryName() {
|
||||
return this.categorySelected && isPresent(this.name)
|
||||
? escapeExpression(this.name)
|
||||
: null;
|
||||
}
|
||||
|
||||
@action
|
||||
onShow() {
|
||||
this.set("categoryPermissionsHint", DEFAULT_HINT);
|
||||
this.categoryPermissionsHint = DEFAULT_HINT;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
cancel(this.generateSlugHandler);
|
||||
this.setProperties({
|
||||
categoryId: null,
|
||||
category: null,
|
||||
name: "",
|
||||
description: "",
|
||||
slug: "",
|
||||
autoGeneratedSlug: "",
|
||||
categoryPermissionsHint: DEFAULT_HINT,
|
||||
autoJoinWarning: "",
|
||||
});
|
||||
@action
|
||||
onCategoryChange(categoryId) {
|
||||
const category = categoryId
|
||||
? this.site.categories.findBy("id", categoryId)
|
||||
: null;
|
||||
this.#updatePermissionsHint(category);
|
||||
|
||||
const name = this.name || category?.name || "";
|
||||
this.categoryId = categoryId;
|
||||
this.category = category;
|
||||
this.name = name;
|
||||
this.#debouncedGenerateSlug(name);
|
||||
}
|
||||
|
||||
_createChannel() {
|
||||
const data = {
|
||||
chatable_id: this.categoryId,
|
||||
name: this.name,
|
||||
slug: this.slug || this.autoGeneratedSlug,
|
||||
description: this.description,
|
||||
auto_join_users: this.autoJoinUsers,
|
||||
threading_enabled: this.threadingEnabled,
|
||||
};
|
||||
@action
|
||||
onNameChange(name) {
|
||||
this.#debouncedGenerateSlug(name);
|
||||
}
|
||||
|
||||
@action
|
||||
onSave(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.createDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
data.auto_join_users = data.auto_join_users === "on";
|
||||
data.slug ??= this.autoGeneratedSlug;
|
||||
data.threading_enabled = data.threading_enabled === "on";
|
||||
|
||||
if (data.auto_join_users) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: this.autoJoinWarning,
|
||||
didConfirm: () => this.#createChannel(data),
|
||||
});
|
||||
} else {
|
||||
this.#createChannel(data);
|
||||
}
|
||||
}
|
||||
|
||||
#createChannel(data) {
|
||||
return this.chatApi
|
||||
.createChannel(data)
|
||||
.then((channel) => {
|
||||
this.send("closeModal");
|
||||
this.args.closeModal();
|
||||
this.chatChannelsManager.follow(channel);
|
||||
this.router.transitionTo("chat.channel", ...channel.routeModels);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.flash(e.jqXHR.responseJSON.errors[0], "error");
|
||||
this.flash = e.jqXHR.responseJSON.errors[0];
|
||||
});
|
||||
}
|
||||
|
||||
_buildCategorySlug(category) {
|
||||
#buildCategorySlug(category) {
|
||||
const parent = category.parentCategory;
|
||||
|
||||
if (parent) {
|
||||
return `${this._buildCategorySlug(parent)}/${category.slug}`;
|
||||
return `${this.#buildCategorySlug(parent)}/${category.slug}`;
|
||||
} else {
|
||||
return category.slug;
|
||||
}
|
||||
}
|
||||
|
||||
_updateAutoJoinConfirmWarning(category, catPermissions) {
|
||||
#updateAutoJoinConfirmWarning(category, catPermissions) {
|
||||
const allowedGroups = catPermissions.allowed_groups;
|
||||
let warning;
|
||||
|
||||
|
@ -158,19 +183,19 @@ export default class CreateChannelController extends Controller.extend(
|
|||
);
|
||||
}
|
||||
|
||||
this.set("autoJoinWarning", warning);
|
||||
this.autoJoinWarning = warning;
|
||||
}
|
||||
|
||||
_updatePermissionsHint(category) {
|
||||
#updatePermissionsHint(category) {
|
||||
if (category) {
|
||||
const fullSlug = this._buildCategorySlug(category);
|
||||
const fullSlug = this.#buildCategorySlug(category);
|
||||
|
||||
this.set("loadingPermissionHint", true);
|
||||
this.loadingPermissionHint = true;
|
||||
|
||||
return this.chatApi
|
||||
.categoryPermissions(category.id)
|
||||
.then((catPermissions) => {
|
||||
this._updateAutoJoinConfirmWarning(category, catPermissions);
|
||||
this.#updateAutoJoinConfirmWarning(category, catPermissions);
|
||||
const allowedGroups = catPermissions.allowed_groups;
|
||||
const settingLink = `/c/${escapeExpression(fullSlug)}/edit/security`;
|
||||
let hint;
|
||||
|
@ -207,77 +232,40 @@ export default class CreateChannelController extends Controller.extend(
|
|||
break;
|
||||
}
|
||||
|
||||
this.set("categoryPermissionsHint", htmlSafe(hint));
|
||||
this.categoryPermissionsHint = htmlSafe(hint);
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("loadingPermissionHint", false);
|
||||
this.loadingPermissionHint = false;
|
||||
});
|
||||
} else {
|
||||
this.set("categoryPermissionsHint", DEFAULT_HINT);
|
||||
this.set("autoJoinWarning", "");
|
||||
this.categoryPermissionsHint = DEFAULT_HINT;
|
||||
this.autoJoinWarning = "";
|
||||
}
|
||||
}
|
||||
|
||||
// intentionally not showing AJAX error for this, we will autogenerate
|
||||
// the slug server-side if they leave it blank
|
||||
_generateSlug(name) {
|
||||
ajax("/slugs.json", { type: "POST", data: { name } }).then((response) => {
|
||||
this.set("autoGeneratedSlug", response.slug);
|
||||
});
|
||||
#generateSlug(name) {
|
||||
return ajax("/slugs.json", { type: "POST", data: { name } }).then(
|
||||
(response) => {
|
||||
this.autoGeneratedSlug = response.slug;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_debouncedGenerateSlug(name) {
|
||||
cancel(this.generateSlugHandler);
|
||||
this._clearAutoGeneratedSlug();
|
||||
#debouncedGenerateSlug(name) {
|
||||
cancel(this.#generateSlugHandler);
|
||||
this.autoGeneratedSlug = "";
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
this.generateSlugHandler = discourseDebounce(
|
||||
|
||||
this.#generateSlugHandler = discourseDebounce(
|
||||
this,
|
||||
this._generateSlug,
|
||||
this.#generateSlug,
|
||||
name,
|
||||
300
|
||||
);
|
||||
}
|
||||
|
||||
_clearAutoGeneratedSlug() {
|
||||
this.set("autoGeneratedSlug", "");
|
||||
}
|
||||
|
||||
@action
|
||||
onCategoryChange(categoryId) {
|
||||
let category = categoryId
|
||||
? this.site.categories.findBy("id", categoryId)
|
||||
: null;
|
||||
this._updatePermissionsHint(category);
|
||||
|
||||
const name = this.name || category?.name || "";
|
||||
this.setProperties({
|
||||
categoryId,
|
||||
category,
|
||||
name,
|
||||
});
|
||||
this._debouncedGenerateSlug(name);
|
||||
}
|
||||
|
||||
@action
|
||||
onNameChange(name) {
|
||||
this._debouncedGenerateSlug(name);
|
||||
}
|
||||
|
||||
@action
|
||||
create() {
|
||||
if (this.createDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.autoJoinUsers) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: this.autoJoinWarning,
|
||||
didConfirm: () => this._createChannel(),
|
||||
});
|
||||
} else {
|
||||
this._createChannel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-delete-channel"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.channel_delete.title"}}
|
||||
@flash={{this.flash}}
|
||||
@flashType={{this.flashType}}
|
||||
>
|
||||
<:body>
|
||||
<p class="chat-modal-delete-channel__instructions">
|
||||
{{this.instructionsText}}
|
||||
</p>
|
||||
|
||||
<TextField
|
||||
@value={{this.channelNameConfirmation}}
|
||||
@id="channel-delete-confirm-name"
|
||||
@placeholderKey="chat.channel_delete.confirm_channel_name"
|
||||
@autocorrect="off"
|
||||
@autocapitalize="off"
|
||||
/>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@disabled={{this.buttonDisabled}}
|
||||
@class="btn-danger"
|
||||
@action={{this.deleteChannel}}
|
||||
@label="chat.channel_delete.confirm"
|
||||
@id="chat-confirm-delete-channel"
|
||||
/>
|
||||
<DButton @label="cancel" @class="btn-flat" @action={{@closeModal}} />
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,68 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import I18n from "I18n";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class ChatModalDeleteChannel extends Component {
|
||||
@service chatApi;
|
||||
@service router;
|
||||
|
||||
@tracked channelNameConfirmation;
|
||||
@tracked deleting = false;
|
||||
@tracked confirmed = false;
|
||||
@tracked flash;
|
||||
@tracked flashType;
|
||||
|
||||
get channel() {
|
||||
return this.args.model.channel;
|
||||
}
|
||||
|
||||
get buttonDisabled() {
|
||||
if (this.deleting || this.confirmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
isEmpty(this.channelNameConfirmation) ||
|
||||
this.channelNameConfirmation.toLowerCase() !==
|
||||
this.channel.title.toLowerCase()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get instructionsText() {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.channel_delete.instructions", {
|
||||
name: this.channel.escapedTitle,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
deleteChannel() {
|
||||
this.deleting = true;
|
||||
|
||||
return this.chatApi
|
||||
.destroyChannel(this.channel.id, this.channelNameConfirmation)
|
||||
.then(() => {
|
||||
this.confirmed = true;
|
||||
this.flash = I18n.t("chat.channel_delete.process_started");
|
||||
this.flashType = "success";
|
||||
|
||||
discourseLater(() => {
|
||||
this.args.closeModal();
|
||||
this.router.transitionTo("chat");
|
||||
}, 3000);
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => (this.deleting = false));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-edit-channel-description"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.channel_edit_description_modal.title"}}
|
||||
@flash={{this.flash}}
|
||||
>
|
||||
<:body>
|
||||
<span class="chat-modal-edit-channel-description__description">{{i18n
|
||||
"chat.channel_edit_description_modal.description"
|
||||
}}</span>
|
||||
<CharCounter
|
||||
@value={{this.editedDescription}}
|
||||
@max={{this.descriptionMaxLength}}
|
||||
>
|
||||
<textarea
|
||||
{{on
|
||||
"input"
|
||||
(action this.onChangeChatChannelDescription value="target.value")
|
||||
}}
|
||||
class="chat-modal-edit-channel-description__description-input"
|
||||
placeholder={{i18n
|
||||
"chat.channel_edit_description_modal.input_placeholder"
|
||||
}}
|
||||
>{{this.editedDescription}}</textarea>
|
||||
</CharCounter>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@class="btn-primary create"
|
||||
@action={{this.onSaveChatChannelDescription}}
|
||||
@label="save"
|
||||
@disabled={{this.isSaveDisabled}}
|
||||
/>
|
||||
<DModalCancel @close={{@closeModal}} />
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,49 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
const DESCRIPTION_MAX_LENGTH = 280;
|
||||
|
||||
export default class ChatModalEditChannelDescription extends Component {
|
||||
@service chatApi;
|
||||
|
||||
@tracked editedDescription = this.channel.description || "";
|
||||
@tracked flash;
|
||||
|
||||
get channel() {
|
||||
return this.args.model;
|
||||
}
|
||||
|
||||
get isSaveDisabled() {
|
||||
return (
|
||||
this.channel.description === this.editedDescription ||
|
||||
this.editedDescription?.length > DESCRIPTION_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
get descriptionMaxLength() {
|
||||
return DESCRIPTION_MAX_LENGTH;
|
||||
}
|
||||
|
||||
@action
|
||||
onSaveChatChannelDescription() {
|
||||
return this.chatApi
|
||||
.updateChannel(this.channel.id, { description: this.editedDescription })
|
||||
.then((result) => {
|
||||
this.channel.description = result.channel.description;
|
||||
this.args.closeModal();
|
||||
})
|
||||
.catch((event) => {
|
||||
if (event.jqXHR?.responseJSON?.errors) {
|
||||
this.flash = event.jqXHR.responseJSON.errors.join("\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeChatChannelDescription(description) {
|
||||
this.flash = null;
|
||||
this.editedDescription = description;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-edit-channel-name"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.channel_edit_name_slug_modal.title"}}
|
||||
@flash={{this.flash}}
|
||||
>
|
||||
<:body>
|
||||
<div class="edit-channel-control">
|
||||
<label for="channel-name" class="edit-channel-label">
|
||||
{{i18n "chat.channel_edit_name_slug_modal.name"}}
|
||||
</label>
|
||||
<Input
|
||||
name="channel-name"
|
||||
class="chat-channel-edit-name-slug-modal__name-input"
|
||||
placeholder={{i18n
|
||||
"chat.channel_edit_name_slug_modal.input_placeholder"
|
||||
}}
|
||||
@type="text"
|
||||
@value={{this.editedName}}
|
||||
{{on "input" (action "onChangeChatChannelName" value="target.value")}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="edit-channel-control">
|
||||
<label for="channel-slug" class="edit-channel-label">
|
||||
{{i18n "chat.channel_edit_name_slug_modal.slug"}}
|
||||
<span>
|
||||
{{d-icon "info-circle"}}
|
||||
<DTooltip>{{i18n
|
||||
"chat.channel_edit_name_slug_modal.slug_description"
|
||||
}}</DTooltip>
|
||||
</span>
|
||||
</label>
|
||||
<Input
|
||||
name="channel-slug"
|
||||
class="chat-channel-edit-name-slug-modal__slug-input"
|
||||
placeholder={{this.autoGeneratedSlug}}
|
||||
{{on "input" (action "onChangeChatChannelSlug" value="target.value")}}
|
||||
@type="text"
|
||||
@value={{this.editedSlug}}
|
||||
/>
|
||||
</div>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@class="btn-primary create"
|
||||
@action={{this.onSave}}
|
||||
@label="save"
|
||||
@disabled={{this.isSaveDisabled}}
|
||||
/>
|
||||
<DModalCancel @close={{@closeModal}} />
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,84 @@
|
|||
import Component from "@glimmer/component";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { cancel } from "@ember/runloop";
|
||||
import { action } from "@ember/object";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatModalEditChannelName extends Component {
|
||||
@service chatApi;
|
||||
@service siteSettings;
|
||||
|
||||
@tracked editedName = this.channel.title;
|
||||
@tracked editedSlug = this.channel.slug;
|
||||
@tracked autoGeneratedSlug = "";
|
||||
@tracked flash;
|
||||
|
||||
#generateSlugHandler = null;
|
||||
|
||||
get channel() {
|
||||
return this.args.model;
|
||||
}
|
||||
|
||||
get isSaveDisabled() {
|
||||
return (
|
||||
(this.channel.title === this.editedName &&
|
||||
this.channel.slug === this.editedSlug) ||
|
||||
this.editedName?.length > this.siteSettings.max_topic_title_length
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
onSave() {
|
||||
return this.chatApi
|
||||
.updateChannel(this.channel.id, {
|
||||
name: this.editedName,
|
||||
slug: this.editedSlug || this.autoGeneratedSlug || this.channel.slug,
|
||||
})
|
||||
.then((result) => {
|
||||
this.channel.title = result.channel.title;
|
||||
this.args.closeModal();
|
||||
})
|
||||
.catch((error) => (this.flash = extractError(error)));
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeChatChannelName(title) {
|
||||
this.flash = null;
|
||||
this.#debouncedGenerateSlug(title);
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeChatChannelSlug() {
|
||||
this.flash = null;
|
||||
this.#debouncedGenerateSlug(this.editedName);
|
||||
}
|
||||
|
||||
#debouncedGenerateSlug(name) {
|
||||
cancel(this.#generateSlugHandler);
|
||||
this.autoGeneratedSlug = "";
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#generateSlugHandler = discourseDebounce(
|
||||
this,
|
||||
this.#generateSlug,
|
||||
name,
|
||||
300
|
||||
);
|
||||
}
|
||||
|
||||
// intentionally not showing AJAX error for this, we will autogenerate
|
||||
// the slug server-side if they leave it blank
|
||||
#generateSlug(name) {
|
||||
return ajax("/slugs.json", { type: "POST", data: { name } }).then(
|
||||
(response) => {
|
||||
this.autoGeneratedSlug = response.slug;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-move-message-to-channel"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.move_to_channel.title"}}
|
||||
>
|
||||
<:body>
|
||||
{{#if this.selectedMessageCount}}
|
||||
<p>{{this.instructionsText}}</p>
|
||||
{{/if}}
|
||||
|
||||
<ChatChannelChooser
|
||||
@class="chat-modal-move-message-to-channel__channel-chooser"
|
||||
@content={{this.availableChannels}}
|
||||
@value={{this.destinationChannelId}}
|
||||
@nameProperty="title"
|
||||
/>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@icon="sign-out-alt"
|
||||
@disabled={{this.disableMoveButton}}
|
||||
@action={{this.moveMessages}}
|
||||
@label="chat.move_to_channel.confirm_move"
|
||||
/>
|
||||
<DButton @label="cancel" @class="btn-flat" @action={{@closeModal}} />
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -1,35 +1,49 @@
|
|||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import Component from "@glimmer/component";
|
||||
import { isBlank } from "@ember/utils";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class MoveToChannelModalInner extends Component {
|
||||
export default class ChatModalMoveMessageToChannel extends Component {
|
||||
@service chat;
|
||||
@service chatApi;
|
||||
@service router;
|
||||
@service chatChannelsManager;
|
||||
|
||||
tagName = "";
|
||||
sourceChannel = null;
|
||||
destinationChannelId = null;
|
||||
selectedMessageIds = null;
|
||||
@tracked destinationChannelId;
|
||||
|
||||
@reads("selectedMessageIds.length") selectedMessageCount;
|
||||
get sourceChannel() {
|
||||
return this.args.model.sourceChannel;
|
||||
}
|
||||
|
||||
get selectedMessageIds() {
|
||||
return this.args.model.selectedMessageIds;
|
||||
}
|
||||
|
||||
get selectedMessageCount() {
|
||||
return this.selectedMessageIds?.length;
|
||||
}
|
||||
|
||||
@computed("destinationChannelId")
|
||||
get disableMoveButton() {
|
||||
return isBlank(this.destinationChannelId);
|
||||
}
|
||||
|
||||
@computed("chatChannelsManager.publicMessageChannels.[]")
|
||||
get availableChannels() {
|
||||
return this.chatChannelsManager.publicMessageChannels.rejectBy(
|
||||
"id",
|
||||
this.sourceChannel.id
|
||||
return (
|
||||
this.args.model.availableChannels ||
|
||||
this.chatChannelsManager.publicMessageChannels
|
||||
).rejectBy("id", this.sourceChannel.id);
|
||||
}
|
||||
|
||||
get instructionsText() {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.move_to_channel.instructions", {
|
||||
channelTitle: this.sourceChannel.escapedTitle,
|
||||
count: this.selectedMessageCount,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,14 +64,4 @@ export default class MoveToChannelModalInner extends Component {
|
|||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@computed()
|
||||
get instructionsText() {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.move_to_channel.instructions", {
|
||||
channelTitle: this.sourceChannel.escapedTitle,
|
||||
count: this.selectedMessageCount,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-new-message"
|
||||
@title="chat.new_message_modal.title"
|
||||
@inline={{@inline}}
|
||||
>
|
||||
<Chat::MessageCreator @onClose={{@closeModal}} />
|
||||
</DModal>
|
|
@ -0,0 +1,26 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-thread-settings"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n "chat.thread.settings"}}
|
||||
>
|
||||
<:body>
|
||||
<label for="thread-title" class="thread-title-label">
|
||||
{{i18n "chat.thread.title"}}
|
||||
</label>
|
||||
<Input
|
||||
name="thread-title"
|
||||
class="chat-modal-thread-settings__title-input"
|
||||
@type="text"
|
||||
@value={{this.editedTitle}}
|
||||
/>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@disabled={{this.buttonDisabled}}
|
||||
@action={{this.saveThread}}
|
||||
@label="save"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -4,25 +4,30 @@ import { tracked } from "@glimmer/tracking";
|
|||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatThreadSettingsModalInner extends Component {
|
||||
export default class ChatModalThreadSettings extends Component {
|
||||
@service chatApi;
|
||||
|
||||
@tracked editedTitle = this.args.thread.title || "";
|
||||
@tracked editedTitle = this.thread.title || "";
|
||||
@tracked saving = false;
|
||||
|
||||
get buttonDisabled() {
|
||||
return this.saving;
|
||||
}
|
||||
|
||||
get thread() {
|
||||
return this.args.model;
|
||||
}
|
||||
|
||||
@action
|
||||
saveThread() {
|
||||
this.saving = true;
|
||||
|
||||
this.chatApi
|
||||
.editThread(this.args.thread.channel.id, this.args.thread.id, {
|
||||
.editThread(this.thread.channel.id, this.thread.id, {
|
||||
title: this.editedTitle,
|
||||
})
|
||||
.then(() => {
|
||||
this.args.thread.title = this.editedTitle;
|
||||
this.thread.title = this.editedTitle;
|
||||
this.args.closeModal();
|
||||
})
|
||||
.catch(popupAjaxError)
|
|
@ -0,0 +1,20 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-modal-toggle-channel-status"
|
||||
@inline={{@inline}}
|
||||
@title={{i18n this.modalTitle}}
|
||||
>
|
||||
<:body>
|
||||
<p
|
||||
class="chat-modal-toggle-channel-status__instructions"
|
||||
>{{this.instructions}}</p>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@action={{this.onStatusChange}}
|
||||
@label={{this.buttonLabel}}
|
||||
@id="chat-channel-toggle-btn"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -1,40 +1,37 @@
|
|||
import Component from "@ember/component";
|
||||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
import I18n from "I18n";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default class ChatChannelToggleView extends Component {
|
||||
@service chat;
|
||||
export default class ChatModalToggleChannelStatus extends Component {
|
||||
@service chatApi;
|
||||
@service router;
|
||||
tagName = "";
|
||||
channel = null;
|
||||
onStatusChange = null;
|
||||
|
||||
@computed("channel.isClosed")
|
||||
get channel() {
|
||||
return this.args.model;
|
||||
}
|
||||
|
||||
get buttonLabel() {
|
||||
if (this.channel.isClosed) {
|
||||
if (this.channel?.isClosed) {
|
||||
return "chat.channel_settings.open_channel";
|
||||
} else {
|
||||
return "chat.channel_settings.close_channel";
|
||||
}
|
||||
}
|
||||
|
||||
@computed("channel.isClosed")
|
||||
get instructions() {
|
||||
if (this.channel.isClosed) {
|
||||
if (this.channel?.isClosed) {
|
||||
return htmlSafe(I18n.t("chat.channel_open.instructions"));
|
||||
} else {
|
||||
return htmlSafe(I18n.t("chat.channel_close.instructions"));
|
||||
}
|
||||
}
|
||||
|
||||
@computed("channel.isClosed")
|
||||
get modalTitle() {
|
||||
if (this.channel.isClosed) {
|
||||
if (this.channel?.isClosed) {
|
||||
return "chat.channel_open.title";
|
||||
} else {
|
||||
return "chat.channel_close.title";
|
||||
|
@ -42,15 +39,16 @@ export default class ChatChannelToggleView extends Component {
|
|||
}
|
||||
|
||||
@action
|
||||
changeChannelStatus() {
|
||||
onStatusChange() {
|
||||
const status = this.channel.isClosed
|
||||
? CHANNEL_STATUSES.open
|
||||
: CHANNEL_STATUSES.closed;
|
||||
|
||||
return this.chatApi
|
||||
.updateChannelStatus(this.channel.id, status)
|
||||
.finally(() => {
|
||||
this.onStatusChange?.(this.channel);
|
||||
.then(() => {
|
||||
this.args.closeModal();
|
||||
this.router.transitionTo("chat.channel", ...this.channel.routeModels);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { clipboardCopyAsync } from "discourse/lib/utilities";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
|
@ -8,10 +7,12 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
|
|||
import { inject as service } from "@ember/service";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import ChatModalMoveMessageToChannel from "discourse/plugins/chat/discourse/components/chat/modal/move-message-to-channel";
|
||||
|
||||
export default class ChatSelectionManager extends Component {
|
||||
@service("composer") topicComposer;
|
||||
@service router;
|
||||
@service modal;
|
||||
@service site;
|
||||
@service("chat-api") api;
|
||||
|
||||
|
@ -42,9 +43,11 @@ export default class ChatSelectionManager extends Component {
|
|||
|
||||
@action
|
||||
openMoveMessageModal() {
|
||||
showModal("chat-message-move-to-channel-modal").setProperties({
|
||||
sourceChannel: this.args.pane.channel,
|
||||
selectedMessageIds: this.args.pane.selectedMessageIds,
|
||||
this.modal.show(ChatModalMoveMessageToChannel, {
|
||||
model: {
|
||||
sourceChannel: this.args.pane.channel,
|
||||
selectedMessageIds: this.args.pane.selectedMessageIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -124,6 +124,7 @@ export default class ChatThreadList extends Component {
|
|||
}
|
||||
|
||||
#unsubscribe() {
|
||||
// TODO (joffrey) In drawer we won't have channel anymore at this point
|
||||
this.messageBus.unsubscribe(
|
||||
`/chat/${this.args.channel.id}`,
|
||||
this.onMessageBus
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import UserChatThreadMembership from "discourse/plugins/chat/discourse/models/user-chat-thread-membership";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import ChatModalThreadSettings from "discourse/plugins/chat/discourse/components/chat/modal/thread-settings";
|
||||
|
||||
export default class ChatThreadHeader extends Component {
|
||||
@service currentUser;
|
||||
|
@ -14,6 +14,7 @@ export default class ChatThreadHeader extends Component {
|
|||
@service chatStateManager;
|
||||
@service chatHistory;
|
||||
@service site;
|
||||
@service modal;
|
||||
|
||||
@tracked persistedNotificationLevel = true;
|
||||
|
||||
|
@ -60,8 +61,7 @@ export default class ChatThreadHeader extends Component {
|
|||
|
||||
@action
|
||||
openThreadSettings() {
|
||||
const controller = showModal("chat-thread-settings-modal");
|
||||
controller.set("thread", this.args.thread);
|
||||
this.modal.show(ChatModalThreadSettings, { model: this.args.thread });
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<DModalBody @title="chat.thread.settings">
|
||||
<div>
|
||||
<label for="thread-title" class="thread-title-label">
|
||||
{{i18n "chat.thread.title"}}
|
||||
</label>
|
||||
<Input
|
||||
name="thread-title"
|
||||
class="thread-title-input"
|
||||
@type="text"
|
||||
@value={{this.editedTitle}}
|
||||
/>
|
||||
</div>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@disabled={{this.buttonDisabled}}
|
||||
@action={{action "saveThread"}}
|
||||
@label="save"
|
||||
/>
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
class="chat-new-message-modal"
|
||||
@title="chat.new_message_modal.title"
|
||||
>
|
||||
<Chat::MessageCreator @onClose={{route-action "closeModal"}} />
|
||||
</DModal>
|
|
@ -1,6 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatNewMessageModal extends Component {
|
||||
@service chat;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::ArchiveChannel>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,20 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import ChatModalArchiveChannel from "discourse/plugins/chat/discourse/components/chat/modal/archive-channel";
|
||||
import Component from "@glimmer/component";
|
||||
|
||||
export default class ChatStyleguideChatModalArchiveChannel extends Component {
|
||||
@service modal;
|
||||
|
||||
channel = fabricators.channel();
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalArchiveChannel, {
|
||||
model: {
|
||||
channel: this.channel,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::ChannelSummary>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,16 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import Component from "@glimmer/component";
|
||||
import ChatModalChannelSummary from "discourse/plugins/chat/discourse/components/chat/modal/channel-summary";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatStyleguideChatModalChannelSummary extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalChannelSummary, {
|
||||
model: { channelId: fabricators.channel().id },
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::CreateChannel>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,13 @@
|
|||
import Component from "@glimmer/component";
|
||||
import ChatModalCreateChannel from "discourse/plugins/chat/discourse/components/chat/modal/create-channel";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatStyleguideChatModalCreateChannel extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalCreateChannel);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::DeleteChannel>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,18 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import ChatModalDeleteChannel from "discourse/plugins/chat/discourse/components/chat/modal/delete-channel";
|
||||
import Component from "@glimmer/component";
|
||||
|
||||
export default class ChatStyleguideChatModalDeleteChannel extends Component {
|
||||
@service modal;
|
||||
|
||||
channel = fabricators.channel();
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalDeleteChannel, {
|
||||
model: { channel: this.channel },
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::EditChannelDescription>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,18 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import ChatModalEditChannelDescription from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-description";
|
||||
import Component from "@glimmer/component";
|
||||
|
||||
export default class ChatStyleguideChatModalEditChannelDescription extends Component {
|
||||
@service modal;
|
||||
|
||||
channel = fabricators.channel();
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalEditChannelDescription, {
|
||||
model: this.channel,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::EditChannelName>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,18 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name";
|
||||
import Component from "@glimmer/component";
|
||||
|
||||
export default class ChatStyleguideChatModalEditChannelName extends Component {
|
||||
@service modal;
|
||||
|
||||
channel = fabricators.channel();
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalEditChannelName, {
|
||||
model: this.channel,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::MoveMessageToChannel>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,26 @@
|
|||
import Component from "@glimmer/component";
|
||||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import ChatModalMoveMessageToChannel from "discourse/plugins/chat/discourse/components/chat/modal/move-message-to-channel";
|
||||
|
||||
export default class ChatStyleguideChatModalMoveMessageToChannel extends Component {
|
||||
@service modal;
|
||||
|
||||
channel = fabricators.channel();
|
||||
selectedMessageIds = [fabricators.message({ channel: this.channel })].mapBy(
|
||||
"id"
|
||||
);
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalMoveMessageToChannel, {
|
||||
model: {
|
||||
sourceChannel: this.channel,
|
||||
selectedMessageIds: [
|
||||
fabricators.message({ channel: this.channel }),
|
||||
].mapBy("id"),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::NewMessage>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,13 @@
|
|||
import Component from "@glimmer/component";
|
||||
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatStyleguideChatModalNewMessage extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalNewMessage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::ThreadSettings>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,16 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import Component from "@glimmer/component";
|
||||
import ChatModalThreadSettings from "discourse/plugins/chat/discourse/components/modal/chat/thread-settings";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatStyleguideChatModalThreadSettings extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalThreadSettings, {
|
||||
model: fabricators.thread(),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<StyleguideExample @title="<Chat::Modal::ToggleChannelStatus>">
|
||||
<Styleguide::Controls::Row>
|
||||
<DButton @translatedLabel="Open modal" @action={{this.openModal}} />
|
||||
</Styleguide::Controls::Row>
|
||||
</StyleguideExample>
|
|
@ -0,0 +1,16 @@
|
|||
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||
import Component from "@glimmer/component";
|
||||
import ChatModalToggleChannelStatus from "discourse/plugins/chat/discourse/components/chat/modal/toggle-channel-status";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatStyleguideChatModalToggleChannelStatus extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
openModal() {
|
||||
return this.modal.show(ChatModalToggleChannelStatus, {
|
||||
model: fabricators.channel(),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,4 +3,17 @@
|
|||
<Styleguide::ChatThreadListItem />
|
||||
<Styleguide::ChatComposerMessageDetails />
|
||||
<Styleguide::ChatHeaderIcon />
|
||||
<Styleguide::ChatMessageMentionWarning />
|
||||
<Styleguide::ChatMessageMentionWarning />
|
||||
|
||||
<h2>Modals</h2>
|
||||
|
||||
<Styleguide::ChatModalArchiveChannel />
|
||||
<Styleguide::ChatModalMoveMessageToChannel />
|
||||
<Styleguide::ChatModalDeleteChannel />
|
||||
<Styleguide::ChatModalEditChannelDescription />
|
||||
<Styleguide::ChatModalEditChannelName />
|
||||
<Styleguide::ChatModalThreadSettings />
|
||||
<Styleguide::ChatModalCreateChannel />
|
||||
<Styleguide::ChatModalToggleChannelStatus />
|
||||
<Styleguide::ChatModalNewMessage />
|
||||
<Styleguide::ChatModalChannelSummary />
|
|
@ -1,8 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default class ChatChannelArchiveModalController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
chatChannel = null;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default class ChatChannelDeleteModalController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
chatChannel = null;
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action, computed } from "@ember/object";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
const DESCRIPTION_MAX_LENGTH = 280;
|
||||
|
||||
export default class ChatChannelEditDescriptionController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service chatApi;
|
||||
@tracked editedDescription = this.model.description || "";
|
||||
|
||||
@computed("model.description", "editedDescription")
|
||||
get isSaveDisabled() {
|
||||
return (
|
||||
this.model.description === this.editedDescription ||
|
||||
this.editedDescription?.length > DESCRIPTION_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
get descriptionMaxLength() {
|
||||
return DESCRIPTION_MAX_LENGTH;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.clearFlash();
|
||||
}
|
||||
|
||||
@action
|
||||
onSaveChatChannelDescription() {
|
||||
return this.chatApi
|
||||
.updateChannel(this.model.id, {
|
||||
description: this.editedDescription,
|
||||
})
|
||||
.then((result) => {
|
||||
this.model.set("description", result.channel.description);
|
||||
this.send("closeModal");
|
||||
})
|
||||
.catch((event) => {
|
||||
if (event.jqXHR?.responseJSON?.errors) {
|
||||
this.flash(event.jqXHR.responseJSON.errors.join("\n"), "error");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeChatChannelDescription(description) {
|
||||
this.clearFlash();
|
||||
this.editedDescription = description;
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { cancel } from "@ember/runloop";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { flashAjaxError } from "discourse/lib/ajax-error";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { inject as service } from "@ember/service";
|
||||
export default class ChatChannelEditTitleController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service chatApi;
|
||||
editedName = "";
|
||||
editedSlug = "";
|
||||
autoGeneratedSlug = "";
|
||||
|
||||
@computed("model.title", "editedName", "editedSlug")
|
||||
get isSaveDisabled() {
|
||||
return (
|
||||
(this.model.title === this.editedName &&
|
||||
this.model.slug === this.editedSlug) ||
|
||||
this.editedName?.length > this.siteSettings.max_topic_title_length
|
||||
);
|
||||
}
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
editedName: this.model.title,
|
||||
editedSlug: this.model.slug,
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.setProperties({
|
||||
editedName: "",
|
||||
editedSlug: "",
|
||||
});
|
||||
this.clearFlash();
|
||||
}
|
||||
|
||||
@action
|
||||
onSaveChatChannelName() {
|
||||
return this.chatApi
|
||||
.updateChannel(this.model.id, {
|
||||
name: this.editedName,
|
||||
slug: this.editedSlug || this.autoGeneratedSlug || this.model.slug,
|
||||
})
|
||||
.then((result) => {
|
||||
this.model.title = result.channel.title;
|
||||
this.send("closeModal");
|
||||
})
|
||||
.catch(flashAjaxError(this));
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeChatChannelName(title) {
|
||||
this.clearFlash();
|
||||
this._debouncedGenerateSlug(title);
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeChatChannelSlug() {
|
||||
this.clearFlash();
|
||||
this._debouncedGenerateSlug(this.editedName);
|
||||
}
|
||||
|
||||
_clearAutoGeneratedSlug() {
|
||||
this.set("autoGeneratedSlug", "");
|
||||
}
|
||||
|
||||
_debouncedGenerateSlug(name) {
|
||||
cancel(this.generateSlugHandler);
|
||||
this._clearAutoGeneratedSlug();
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
this.generateSlugHandler = discourseDebounce(
|
||||
this,
|
||||
this._generateSlug,
|
||||
name,
|
||||
300
|
||||
);
|
||||
}
|
||||
|
||||
// intentionally not showing AJAX error for this, we will autogenerate
|
||||
// the slug server-side if they leave it blank
|
||||
_generateSlug(name) {
|
||||
ajax("/slugs.json", { type: "POST", data: { name } }).then((response) => {
|
||||
this.set("autoGeneratedSlug", response.slug);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,19 +1,25 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { inject as service } from "@ember/service";
|
||||
import ChatModalEditChannelDescription from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-description";
|
||||
import ChatModalEditChannelName from "discourse/plugins/chat/discourse/components/chat/modal/edit-channel-name";
|
||||
|
||||
export default class ChatChannelInfoAboutController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
onEditChatChannelName() {
|
||||
showModal("chat-channel-edit-name-slug", { model: this.model });
|
||||
return this.modal.show(ChatModalEditChannelName, {
|
||||
model: this.model,
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onEditChatChannelDescription() {
|
||||
showModal("chat-channel-edit-description", {
|
||||
return this.modal.show(ChatModalEditChannelDescription, {
|
||||
model: this.model,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class ChatChannelSelectorModalController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@action
|
||||
closeSelf() {
|
||||
this.send("closeModal");
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatChannelToggleController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service chat;
|
||||
@service router;
|
||||
|
||||
chatChannel = null;
|
||||
|
||||
@action
|
||||
channelStatusChanged(channel) {
|
||||
this.send("closeModal");
|
||||
this.router.transitionTo("chat.channel", ...channel.routeModels);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default class ChatMessageMoveToChannelModalController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
chatChannel = null;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default class ChatThreadSettingsModalController extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
thread = null;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { PLATFORM_KEY_MODIFIER } from "discourse/lib/keyboard-shortcuts";
|
||||
import ChatNewMessageModal from "discourse/plugins/chat/discourse/components/modal/chat-new-message";
|
||||
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
|
||||
|
||||
export default {
|
||||
name: "chat-keyboard-shortcuts",
|
||||
|
@ -22,10 +22,10 @@ export default {
|
|||
const chatChannelsManager = container.lookup(
|
||||
"service:chat-channels-manager"
|
||||
);
|
||||
const openChannelSelector = (e) => {
|
||||
const openQuickChannelSelector = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
modal.show(ChatNewMessageModal);
|
||||
modal.show(ChatModalNewMessage);
|
||||
};
|
||||
|
||||
const handleMoveUpShortcut = (e) => {
|
||||
|
@ -126,7 +126,7 @@ export default {
|
|||
withPluginApi("0.12.1", (api) => {
|
||||
api.addKeyboardShortcut(
|
||||
`${PLATFORM_KEY_MODIFIER}+k`,
|
||||
openChannelSelector,
|
||||
openQuickChannelSelector,
|
||||
{
|
||||
global: true,
|
||||
help: {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { emojiUnescape } from "discourse/lib/text";
|
|||
import { decorateUsername } from "discourse/helpers/decorate-username-selector";
|
||||
import { until } from "discourse/lib/formatter";
|
||||
import { inject as service } from "@ember/service";
|
||||
import ChatNewMessageModal from "discourse/plugins/chat/discourse/components/modal/chat-new-message";
|
||||
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
|
||||
|
||||
export default {
|
||||
name: "chat-sidebar",
|
||||
|
@ -379,7 +379,7 @@ export default {
|
|||
id: "startDm",
|
||||
title: I18n.t("chat.direct_messages.new"),
|
||||
action: () => {
|
||||
this.modal.show(ChatNewMessageModal);
|
||||
this.modal.show(ChatModalNewMessage);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -68,6 +68,7 @@ function channelFabricator(args = {}) {
|
|||
description: args.description,
|
||||
chatable: args.chatable || categoryFabricator(),
|
||||
status: CHANNEL_STATUSES.open,
|
||||
slug: args.chatable?.slug || "general",
|
||||
},
|
||||
args
|
||||
)
|
||||
|
|
|
@ -476,7 +476,8 @@ export default class ChatApi extends Service {
|
|||
*
|
||||
* @param {number} channelId - The ID of the channel.
|
||||
* @param {Array<number>} userIds - The IDs of the users to invite.
|
||||
* @param {Array<number>} [messageId] - The ID of a message to highlight when opening the notification.
|
||||
* @param {object} options
|
||||
* @param {number} options.chat_message_id - A message ID to display in the invite.
|
||||
*/
|
||||
invite(channelId, userIds, options = {}) {
|
||||
return ajax(`/chat/${channelId}/invite`, {
|
||||
|
@ -485,6 +486,17 @@ export default class ChatApi extends Service {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize a channel.
|
||||
*
|
||||
* @param {number} channelId - The ID of the channel to summarize.
|
||||
* @param {object} options
|
||||
* @param {number} options.since - Number of hours ago the summary should start (1, 3, 6, 12, 24, 72, 168).
|
||||
*/
|
||||
summarize(channelId, options = {}) {
|
||||
return this.#getRequest(`/channels/${channelId}/summarize`, options);
|
||||
}
|
||||
|
||||
get #basePath() {
|
||||
return "/chat/api";
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<ChannelSummary
|
||||
@channelId={{this.channelId}}
|
||||
@closeModal={{route-action "closeModal"}}
|
||||
/>
|
|
@ -1,4 +0,0 @@
|
|||
<ChatChannelArchiveModalInner
|
||||
@chatChannel={{this.chatChannel}}
|
||||
@closeModal={{route-action "closeModal"}}
|
||||
/>
|
|
@ -1,4 +0,0 @@
|
|||
<ChatChannelDeleteModalInner
|
||||
@chatChannel={{this.chatChannel}}
|
||||
@closeModal={{action "closeModal"}}
|
||||
/>
|
|
@ -1,32 +0,0 @@
|
|||
<DModalBody @title="chat.channel_edit_description_modal.title">
|
||||
<span class="chat-channel-edit-description-modal__description">
|
||||
{{i18n "chat.channel_edit_description_modal.description"}}
|
||||
</span>
|
||||
<CharCounter
|
||||
@value={{this.editedDescription}}
|
||||
@max={{this.descriptionMaxLength}}
|
||||
>
|
||||
<textarea
|
||||
{{on
|
||||
"input"
|
||||
(action this.onChangeChatChannelDescription value="target.value")
|
||||
}}
|
||||
class="chat-channel-edit-description-modal__description-input"
|
||||
placeholder={{i18n
|
||||
"chat.channel_edit_description_modal.input_placeholder"
|
||||
}}
|
||||
>
|
||||
{{this.editedDescription}}
|
||||
</textarea>
|
||||
</CharCounter>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary create"
|
||||
@action={{action "onSaveChatChannelDescription"}}
|
||||
@label="save"
|
||||
@disabled={{this.isSaveDisabled}}
|
||||
/>
|
||||
<DModalCancel @close={{route-action "closeModal"}} />
|
||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||
<DModalBody @title="chat.channel_edit_name_slug_modal.title">
|
||||
<div class="edit-channel-control">
|
||||
<label for="channel-name" class="edit-channel-label">
|
||||
{{i18n "chat.channel_edit_name_slug_modal.name"}}
|
||||
</label>
|
||||
<Input
|
||||
name="channel-name"
|
||||
class="chat-channel-edit-name-slug-modal__name-input"
|
||||
placeholder={{i18n "chat.channel_edit_name_slug_modal.input_placeholder"}}
|
||||
@type="text"
|
||||
@value={{this.editedName}}
|
||||
{{on "input" (action "onChangeChatChannelName" value="target.value")}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="edit-channel-control">
|
||||
<label for="channel-slug" class="edit-channel-label">
|
||||
{{i18n "chat.channel_edit_name_slug_modal.slug"}}
|
||||
<span>
|
||||
{{d-icon "info-circle"}}
|
||||
<DTooltip>{{i18n
|
||||
"chat.channel_edit_name_slug_modal.slug_description"
|
||||
}}</DTooltip>
|
||||
</span>
|
||||
</label>
|
||||
<Input
|
||||
name="channel-slug"
|
||||
class="chat-channel-edit-name-slug-modal__slug-input"
|
||||
placeholder={{this.autoGeneratedSlug}}
|
||||
{{on "input" (action "onChangeChatChannelSlug" value="target.value")}}
|
||||
@type="text"
|
||||
@value={{this.editedSlug}}
|
||||
/>
|
||||
</div>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary create"
|
||||
@action={{action "onSaveChatChannelName"}}
|
||||
@label="save"
|
||||
@disabled={{this.isSaveDisabled}}
|
||||
/>
|
||||
<DModalCancel @close={{route-action "closeModal"}} />
|
||||
</div>
|
|
@ -1,22 +0,0 @@
|
|||
<DModalBody @title="chat.channel_edit_name_modal.title">
|
||||
<input
|
||||
class="chat-channel-edit-name-modal__name-input"
|
||||
placeholder={{i18n "chat.channel_edit_name_modal.input_placeholder"}}
|
||||
{{on "input" (action "onChangeChatChannelName" value="target.value")}}
|
||||
type="text"
|
||||
value={{this.model.title}}
|
||||
/>
|
||||
<span class="chat-channel-edit-name-modal__description">
|
||||
{{i18n "chat.channel_edit_name_modal.description"}}
|
||||
</span>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary create"
|
||||
@action={{action "onSaveChatChannelName"}}
|
||||
@label="save"
|
||||
@disabled={{this.isSaveDisabled}}
|
||||
/>
|
||||
<DModalCancel @close={{route-action "closeModal"}} />
|
||||
</div>
|
|
@ -1 +0,0 @@
|
|||
<ChatChannelSelectorModalInner @close={{this.closeSelf}} />
|
|
@ -1,5 +0,0 @@
|
|||
<ChatChannelToggleView
|
||||
@channel={{this.chatChannel}}
|
||||
@closeModal={{action "closeModal"}}
|
||||
@onStatusChange={{action "channelStatusChanged"}}
|
||||
/>
|
|
@ -1,5 +0,0 @@
|
|||
<ChatMessageMoveToChannelModalInner
|
||||
@sourceChannel={{this.sourceChannel}}
|
||||
@selectedMessageIds={{this.selectedMessageIds}}
|
||||
@closeModal={{action "closeModal"}}
|
||||
/>
|
|
@ -1,4 +0,0 @@
|
|||
<Chat::Thread::SettingsModalInner
|
||||
@thread={{this.thread}}
|
||||
@closeModal={{route-action "closeModal"}}
|
||||
/>
|
|
@ -1,115 +0,0 @@
|
|||
<DModalBody @title="chat.create_channel.title">
|
||||
<div class="create-channel__control -name">
|
||||
<label for="channel-name" class="create-channel__label">
|
||||
{{i18n "chat.create_channel.name"}}
|
||||
</label>
|
||||
<Input
|
||||
name="channel-name"
|
||||
class="create-channel__input"
|
||||
@type="text"
|
||||
@value={{this.name}}
|
||||
{{on "input" (action "onNameChange" value="target.value")}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="create-channel__control -slug">
|
||||
<label for="channel-slug" class="create-channel__label">
|
||||
{{i18n "chat.create_channel.slug"}}
|
||||
<span>
|
||||
{{d-icon "info-circle"}}
|
||||
<DTooltip>
|
||||
{{i18n "chat.channel_edit_name_slug_modal.slug_description"}}
|
||||
</DTooltip>
|
||||
</span>
|
||||
</label>
|
||||
<Input
|
||||
name="channel-slug"
|
||||
class="create-channel__input"
|
||||
@type="text"
|
||||
@value={{this.slug}}
|
||||
placeholder={{this.autoGeneratedSlug}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="create-channel__control -description">
|
||||
<label for="channel-description" class="create-channel__label">
|
||||
{{i18n "chat.create_channel.description"}}
|
||||
</label>
|
||||
<Input
|
||||
name="channel-description"
|
||||
class="create-channel__input"
|
||||
@type="textarea"
|
||||
@value={{this.description}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="create-channel__control">
|
||||
<label class="create-channel__label">
|
||||
{{i18n "chat.create_channel.choose_category.label"}}
|
||||
</label>
|
||||
<CategoryChooser
|
||||
@value={{this.categoryId}}
|
||||
@onChange={{action "onCategoryChange"}}
|
||||
@options={{hash none="chat.create_channel.choose_category.none"}}
|
||||
/>
|
||||
|
||||
{{#if this.categoryPermissionsHint}}
|
||||
<div
|
||||
class={{concat-class
|
||||
"create-channel__hint"
|
||||
(if this.loadingPermissionHint "loading-permissions")
|
||||
}}
|
||||
>
|
||||
{{this.categoryPermissionsHint}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.autoJoinAvailable}}
|
||||
<div class="create-channel__control -auto-join">
|
||||
<label class="create-channel__label">
|
||||
<Input @type="checkbox" @checked={{this.autoJoinUsers}} />
|
||||
<div class="auto-join-channel">
|
||||
<span class="create-channel__label-title">
|
||||
{{i18n "chat.settings.auto_join_users_label"}}
|
||||
</span>
|
||||
<p class="create-channel__label-description">
|
||||
{{#if this.categoryName}}
|
||||
{{i18n
|
||||
"chat.settings.auto_join_users_info"
|
||||
category=this.categoryName
|
||||
}}
|
||||
{{else}}
|
||||
{{i18n "chat.settings.auto_join_users_info_no_category"}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.threadingAvailable}}
|
||||
<div class="create-channel__control -threading-toggle">
|
||||
<label class="create-channel__label">
|
||||
<Input @type="checkbox" @checked={{this.threadingEnabled}} />
|
||||
<div class="threading-channel">
|
||||
<span class="create-channel__label-title">
|
||||
{{i18n "chat.create_channel.threading.label"}}
|
||||
</span>
|
||||
<p class="create-channel__label-description">
|
||||
{{i18n "chat.settings.channel_threading_description"}}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary create"
|
||||
@action={{action "create"}}
|
||||
@label="chat.create_channel.create"
|
||||
@disabled={{this.createDisabled}}
|
||||
/>
|
||||
</div>
|
|
@ -16,19 +16,6 @@ html.ios-device.keyboard-visible body #main-outlet .full-page-chat {
|
|||
padding-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.chat-message-move-to-channel-modal-modal {
|
||||
.modal-inner-container {
|
||||
.chat-move-message-channel-chooser {
|
||||
width: 100%;
|
||||
.category-chat-badge {
|
||||
.d-icon {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uppy-is-drag-over .chat-composer .drop-a-file {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
|
@ -315,38 +302,6 @@ body.has-full-page-chat {
|
|||
}
|
||||
}
|
||||
|
||||
.chat-channel-archive-modal-inner {
|
||||
.chat-to-topic-selector {
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.radios {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.radio-label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
details {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
.select-kit.combo-box.category-chooser {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-channel-archive-modal-inner {
|
||||
.chat-to-topic-selector {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.user-preferences .chat-setting .controls {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -139,25 +139,3 @@ input.channel-members-view__search-input {
|
|||
padding: 0.5rem 0;
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
||||
// Channel info edit description modal
|
||||
.chat-channel-edit-description-modal {
|
||||
.exceeded-word-count {
|
||||
.chat-channel-edit-description-modal__description-input {
|
||||
outline: 1px solid var(--danger);
|
||||
border: 1px solid var(--danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-channel-edit-description-modal__description-input {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.chat-channel-edit-description-modal__description {
|
||||
display: flex;
|
||||
padding-bottom: 0.75rem;
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
.chat-modal-archive-channel {
|
||||
.chat-to-topic-selector {
|
||||
width: auto;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.radios {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.radio-label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
details {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
.select-kit.combo-box.category-chooser {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.channel-summary-modal {
|
||||
.chat-modal-channel-summary {
|
||||
.summarization-since,
|
||||
.summary-area {
|
||||
margin: 10px 0 10px 0;
|
|
@ -1,4 +1,4 @@
|
|||
.create-channel-modal {
|
||||
.chat-modal-create-channel {
|
||||
.modal-inner-container {
|
||||
width: 500px;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
|||
}
|
||||
|
||||
.select-kit.combo-box,
|
||||
.create-channel__input,
|
||||
&__input,
|
||||
#choose-topic-title {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
|
@ -22,18 +22,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.create-channel__hint {
|
||||
&__hint {
|
||||
font-size: var(--font-down-1);
|
||||
padding-top: 0.25rem;
|
||||
color: var(--secondary-low);
|
||||
}
|
||||
|
||||
.create-channel__control,
|
||||
&__control,
|
||||
.edit-channel-control {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.create-channel__label-description {
|
||||
&__label-description {
|
||||
margin: 0;
|
||||
padding-top: 0.25rem;
|
||||
color: var(--secondary-low);
|
|
@ -0,0 +1,20 @@
|
|||
.chat-modal-edit-channel-description {
|
||||
.exceeded-word-count {
|
||||
.chat-modal-edit-channel-description__description-input {
|
||||
outline: 1px solid var(--danger);
|
||||
border: 1px solid var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
&__description-input {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: flex;
|
||||
padding-bottom: 0.75rem;
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
.chat-modal-move-message-to-channel {
|
||||
&__channel-chooser {
|
||||
width: 100%;
|
||||
.category-chat-badge {
|
||||
.d-icon {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.chat-new-message-modal {
|
||||
.chat-modal-new-message {
|
||||
& + .modal-backdrop {
|
||||
opacity: 1;
|
||||
background: transparent;
|
|
@ -41,7 +41,6 @@
|
|||
@import "chat-upload-drop-zone";
|
||||
@import "chat-transcript";
|
||||
@import "core-extensions";
|
||||
@import "create-channel-modal";
|
||||
@import "dc-filter-input";
|
||||
@import "full-page-chat-header";
|
||||
@import "incoming-chat-webhooks";
|
||||
|
@ -53,9 +52,14 @@
|
|||
@import "chat-thread-list-header";
|
||||
@import "chat-thread-unread-indicator";
|
||||
@import "chat-thread-participants";
|
||||
@import "channel-summary-modal";
|
||||
@import "chat-message-mention-warning";
|
||||
@import "chat-message-error";
|
||||
@import "chat-new-message-modal";
|
||||
@import "chat-message-creator";
|
||||
@import "chat-user-avatar";
|
||||
@import "chat-modal-new-message";
|
||||
@import "chat-modal-archive-channel";
|
||||
@import "chat-modal-edit-channel-description";
|
||||
@import "chat-modal-create-channel";
|
||||
@import "chat-modal-create-channel";
|
||||
@import "chat-modal-channel-summary";
|
||||
@import "chat-modal-move-message-to-channel";
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.modal-chat-thread-settings {
|
||||
.modal-inner-container {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
&__title-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue