mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Modernize topic-bulk-actions (#22186)
Introduces new plugin api for adding bulk topic actions: Example: ```js api.addBulkActionButton({ label: "super_plugin.bulk.enhance", icon: "magic", class: "btn-default", visible: ({ currentUser, siteSettings }) => siteSettings.super_plugin_enabled && currentUser.staff, async action({ setComponent }) { await doSomething(this.model.topics); setComponent(MyBulkModal); }, }); ```
This commit is contained in:
parent
3c69570b75
commit
2a96064e6b
@ -0,0 +1,9 @@
|
|||||||
|
<p>{{i18n "topics.bulk.choose_append_tags"}}</p>
|
||||||
|
|
||||||
|
<p><TagChooser @tags={{this.tags}} @categoryId={{@categoryId}} /></p>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@action={{fn @performAndRefresh (hash type="append_tags" tags=this.tags)}}
|
||||||
|
@disabled={{not this.tags}}
|
||||||
|
@label="topics.bulk.append_tags"
|
||||||
|
/>
|
@ -0,0 +1,6 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class AppendTags extends Component {
|
||||||
|
@tracked tags = [];
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<p>{{i18n "topics.bulk.choose_new_category"}}</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<CategoryChooser
|
||||||
|
@value={{this.categoryId}}
|
||||||
|
@onChange={{action (mut this.categoryId)}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ConditionalLoadingSpinner @condition={{@loading}}>
|
||||||
|
<DButton
|
||||||
|
@action={{this.changeCategory}}
|
||||||
|
@label="topics.bulk.change_category"
|
||||||
|
/>
|
||||||
|
</ConditionalLoadingSpinner>
|
@ -0,0 +1,17 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
|
||||||
|
export default class ChangeCategory extends Component {
|
||||||
|
categoryId = 0;
|
||||||
|
|
||||||
|
@action
|
||||||
|
async changeCategory() {
|
||||||
|
await this.args.forEachPerformed(
|
||||||
|
{
|
||||||
|
type: "change_category",
|
||||||
|
category_id: this.categoryId,
|
||||||
|
},
|
||||||
|
(t) => t.set("category_id", this.categoryId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<p>{{i18n "topics.bulk.choose_new_tags"}}</p>
|
||||||
|
|
||||||
|
<p><TagChooser @tags={{this.tags}} @categoryId={{@categoryId}} /></p>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@action={{fn @performAndRefresh (hash type="change_tags" tags=this.tags)}}
|
||||||
|
@disabled={{not this.tags}}
|
||||||
|
@label="topics.bulk.change_tags"
|
||||||
|
/>
|
@ -0,0 +1,6 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class ChangeTags extends Component {
|
||||||
|
@tracked tags = [];
|
||||||
|
}
|
@ -16,6 +16,6 @@
|
|||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
@disabled={{this.disabled}}
|
@disabled={{this.disabled}}
|
||||||
@action={{action "changeNotificationLevel"}}
|
@action={{this.changeNotificationLevel}}
|
||||||
@label="topics.bulk.change_notification_level"
|
@label="topics.bulk.change_notification_level"
|
||||||
/>
|
/>
|
@ -0,0 +1,28 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { empty } from "@ember/object/computed";
|
||||||
|
import { topicLevels } from "discourse/lib/notification-levels";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
|
||||||
|
// Support for changing the notification level of various topics
|
||||||
|
export default class NotificationLevel extends Component {
|
||||||
|
notificationLevelId = null;
|
||||||
|
|
||||||
|
@empty("notificationLevelId") disabled;
|
||||||
|
|
||||||
|
get notificationLevels() {
|
||||||
|
return topicLevels.map((level) => ({
|
||||||
|
id: level.id.toString(),
|
||||||
|
name: I18n.t(`topic.notifications.${level.key}.title`),
|
||||||
|
description: I18n.t(`topic.notifications.${level.key}.description`),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
changeNotificationLevel() {
|
||||||
|
this.args.performAndRefresh({
|
||||||
|
type: "change_notification_level",
|
||||||
|
notification_level_id: this.notificationLevelId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
import Controller, { inject as controller } from "@ember/controller";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import { empty } from "@ember/object/computed";
|
|
||||||
import { topicLevels } from "discourse/lib/notification-levels";
|
|
||||||
|
|
||||||
// Support for changing the notification level of various topics
|
|
||||||
export default Controller.extend({
|
|
||||||
topicBulkActions: controller(),
|
|
||||||
notificationLevelId: null,
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
notificationLevels() {
|
|
||||||
return topicLevels.map((level) => {
|
|
||||||
return {
|
|
||||||
id: level.id.toString(),
|
|
||||||
name: I18n.t(`topic.notifications.${level.key}.title`),
|
|
||||||
description: I18n.t(`topic.notifications.${level.key}.description`),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
disabled: empty("notificationLevelId"),
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
changeNotificationLevel() {
|
|
||||||
this.topicBulkActions.performAndRefresh({
|
|
||||||
type: "change_notification_level",
|
|
||||||
notification_level_id: this.notificationLevelId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,163 +1,231 @@
|
|||||||
import { alias, empty } from "@ember/object/computed";
|
|
||||||
import Controller, { inject as controller } from "@ember/controller";
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { action } from "@ember/object";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import Topic from "discourse/models/topic";
|
import Topic from "discourse/models/topic";
|
||||||
|
import ChangeCategory from "../components/bulk-actions/change-category";
|
||||||
|
import NotificationLevel from "../components/bulk-actions/notification-level";
|
||||||
|
import ChangeTags from "../components/bulk-actions/change-tags";
|
||||||
|
import AppendTags from "../components/bulk-actions/append-tags";
|
||||||
|
|
||||||
import { inject as service } from "@ember/service";
|
const _customButtons = [];
|
||||||
|
|
||||||
const _buttons = [];
|
export function _addBulkButton(opts) {
|
||||||
|
_customButtons.push({
|
||||||
const alwaysTrue = () => true;
|
label: opts.label,
|
||||||
|
|
||||||
function identity() {}
|
|
||||||
|
|
||||||
function addBulkButton(action, key, opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
const btn = {
|
|
||||||
action,
|
|
||||||
label: `topics.bulk.${key}`,
|
|
||||||
icon: opts.icon,
|
icon: opts.icon,
|
||||||
buttonVisible: opts.buttonVisible || alwaysTrue,
|
|
||||||
enabledSetting: opts.enabledSetting,
|
|
||||||
class: opts.class,
|
class: opts.class,
|
||||||
};
|
visible: opts.visible,
|
||||||
|
action: opts.action,
|
||||||
_buttons.push(btn);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default buttons
|
export function clearBulkButtons() {
|
||||||
addBulkButton("showChangeCategory", "change_category", {
|
_customButtons.length = 0;
|
||||||
icon: "pencil-alt",
|
}
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) => !topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("closeTopics", "close_topics", {
|
|
||||||
icon: "lock",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) => !topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("archiveTopics", "archive_topics", {
|
|
||||||
icon: "folder",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) => !topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("archiveMessages", "archive_topics", {
|
|
||||||
icon: "folder",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) => topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("moveMessagesToInbox", "move_messages_to_inbox", {
|
|
||||||
icon: "folder",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) => topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("showNotificationLevel", "notification_level", {
|
|
||||||
icon: "d-regular",
|
|
||||||
class: "btn-default",
|
|
||||||
});
|
|
||||||
addBulkButton("deletePostTiming", "defer", {
|
|
||||||
icon: "circle",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible() {
|
|
||||||
return this.currentUser.user_option.enable_defer;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addBulkButton("unlistTopics", "unlist_topics", {
|
|
||||||
icon: "far-eye-slash",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) =>
|
|
||||||
topics.some((t) => t.visible) && !topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("relistTopics", "relist_topics", {
|
|
||||||
icon: "far-eye",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible: (topics) =>
|
|
||||||
topics.some((t) => !t.visible) && !topics.some((t) => t.isPrivateMessage),
|
|
||||||
});
|
|
||||||
addBulkButton("resetBumpDateTopics", "reset_bump_dates", {
|
|
||||||
icon: "anchor",
|
|
||||||
class: "btn-default",
|
|
||||||
buttonVisible() {
|
|
||||||
return this.currentUser.canManageTopic;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addBulkButton("showTagTopics", "change_tags", {
|
|
||||||
icon: "tag",
|
|
||||||
class: "btn-default",
|
|
||||||
enabledSetting: "tagging_enabled",
|
|
||||||
buttonVisible() {
|
|
||||||
return this.currentUser.canManageTopic;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addBulkButton("showAppendTagTopics", "append_tags", {
|
|
||||||
icon: "tag",
|
|
||||||
class: "btn-default",
|
|
||||||
enabledSetting: "tagging_enabled",
|
|
||||||
buttonVisible() {
|
|
||||||
return this.currentUser.canManageTopic;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addBulkButton("removeTags", "remove_tags", {
|
|
||||||
icon: "tag",
|
|
||||||
class: "btn-default",
|
|
||||||
enabledSetting: "tagging_enabled",
|
|
||||||
buttonVisible() {
|
|
||||||
return this.currentUser.canManageTopic;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
addBulkButton("deleteTopics", "delete", {
|
|
||||||
icon: "trash-alt",
|
|
||||||
class: "btn-danger delete-topics",
|
|
||||||
buttonVisible() {
|
|
||||||
return this.currentUser.staff;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Modal for performing bulk actions on topics
|
// Modal for performing bulk actions on topics
|
||||||
export default Controller.extend(ModalFunctionality, {
|
export default class TopicBulkActions extends Controller.extend(
|
||||||
userPrivateMessages: controller("user-private-messages"),
|
ModalFunctionality
|
||||||
dialog: service(),
|
) {
|
||||||
tags: null,
|
@service currentUser;
|
||||||
emptyTags: empty("tags"),
|
@service siteSettings;
|
||||||
categoryId: alias("model.category.id"),
|
@service dialog;
|
||||||
processedTopicCount: 0,
|
@controller("user-private-messages") userPrivateMessages;
|
||||||
isGroup: alias("userPrivateMessages.isGroup"),
|
|
||||||
groupFilter: alias("userPrivateMessages.groupFilter"),
|
@tracked loading = false;
|
||||||
|
@tracked showProgress = false;
|
||||||
|
@tracked processedTopicCount = 0;
|
||||||
|
@tracked activeComponent = null;
|
||||||
|
|
||||||
|
defaultButtons = [
|
||||||
|
{
|
||||||
|
label: "topics.bulk.change_category",
|
||||||
|
icon: "pencil-alt",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) => !topics.some((t) => t.isPrivateMessage),
|
||||||
|
action({ setComponent }) {
|
||||||
|
setComponent(ChangeCategory);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.close_topics",
|
||||||
|
icon: "lock",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) => !topics.some((t) => t.isPrivateMessage),
|
||||||
|
action({ forEachPerformed }) {
|
||||||
|
forEachPerformed({ type: "close" }, (t) => t.set("closed", true));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.archive_topics",
|
||||||
|
icon: "folder",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) => !topics.some((t) => t.isPrivateMessage),
|
||||||
|
action({ forEachPerformed }) {
|
||||||
|
forEachPerformed({ type: "archive" }, (t) => t.set("archived", true));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.archive_topics",
|
||||||
|
icon: "folder",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) => topics.some((t) => t.isPrivateMessage),
|
||||||
|
action: ({ performAndRefresh }) => {
|
||||||
|
let params = { type: "archive_messages" };
|
||||||
|
if (this.userPrivateMessages.isGroup) {
|
||||||
|
params.group = this.userPrivateMessages.groupFilter;
|
||||||
|
}
|
||||||
|
performAndRefresh(params);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.move_messages_to_inbox",
|
||||||
|
icon: "folder",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) => topics.some((t) => t.isPrivateMessage),
|
||||||
|
action: ({ performAndRefresh }) => {
|
||||||
|
let params = { type: "move_messages_to_inbox" };
|
||||||
|
if (this.userPrivateMessages.isGroup) {
|
||||||
|
params.group = this.userPrivateMessages.groupFilter;
|
||||||
|
}
|
||||||
|
performAndRefresh(params);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.notification_level",
|
||||||
|
icon: "d-regular",
|
||||||
|
class: "btn-default",
|
||||||
|
action({ setComponent }) {
|
||||||
|
setComponent(NotificationLevel);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.defer",
|
||||||
|
icon: "circle",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ currentUser }) => currentUser.user_option.enable_defer,
|
||||||
|
action({ performAndRefresh }) {
|
||||||
|
performAndRefresh({ type: "destroy_post_timing" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.unlist_topics",
|
||||||
|
icon: "far-eye-slash",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) =>
|
||||||
|
topics.some((t) => t.visible) &&
|
||||||
|
!topics.some((t) => t.isPrivateMessage),
|
||||||
|
action({ forEachPerformed }) {
|
||||||
|
forEachPerformed({ type: "unlist" }, (t) => t.set("visible", false));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.relist_topics",
|
||||||
|
icon: "far-eye",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ topics }) =>
|
||||||
|
topics.some((t) => !t.visible) &&
|
||||||
|
!topics.some((t) => t.isPrivateMessage),
|
||||||
|
action({ forEachPerformed }) {
|
||||||
|
forEachPerformed({ type: "relist" }, (t) => t.set("visible", true));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.reset_bump_dates",
|
||||||
|
icon: "anchor",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ currentUser }) => currentUser.canManageTopic,
|
||||||
|
action({ performAndRefresh }) {
|
||||||
|
performAndRefresh({ type: "reset_bump_dates" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.change_tags",
|
||||||
|
icon: "tag",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ currentUser, siteSettings }) =>
|
||||||
|
siteSettings.tagging_enabled && currentUser.canManageTopic,
|
||||||
|
action({ setComponent }) {
|
||||||
|
setComponent(ChangeTags);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.append_tags",
|
||||||
|
icon: "tag",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ currentUser, siteSettings }) =>
|
||||||
|
siteSettings.tagging_enabled && currentUser.canManageTopic,
|
||||||
|
action({ setComponent }) {
|
||||||
|
setComponent(AppendTags);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.remove_tags",
|
||||||
|
icon: "tag",
|
||||||
|
class: "btn-default",
|
||||||
|
visible: ({ currentUser, siteSettings }) =>
|
||||||
|
siteSettings.tagging_enabled && currentUser.canManageTopic,
|
||||||
|
action: ({ performAndRefresh, topics }) => {
|
||||||
|
this.dialog.deleteConfirm({
|
||||||
|
message: I18n.t("topics.bulk.confirm_remove_tags", {
|
||||||
|
count: topics.length,
|
||||||
|
}),
|
||||||
|
didConfirm: () => performAndRefresh({ type: "remove_tags" }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "topics.bulk.delete",
|
||||||
|
icon: "trash-alt",
|
||||||
|
class: "btn-danger delete-topics",
|
||||||
|
visible: ({ currentUser }) => currentUser.staff,
|
||||||
|
action({ performAndRefresh }) {
|
||||||
|
performAndRefresh({ type: "delete" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
get buttons() {
|
||||||
|
return [...this.defaultButtons, ..._customButtons].filter(({ visible }) => {
|
||||||
|
if (visible) {
|
||||||
|
return visible({
|
||||||
|
topics: this.model.topics,
|
||||||
|
category: this.model.category,
|
||||||
|
currentUser: this.currentUser,
|
||||||
|
siteSettings: this.siteSettings,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
const topics = this.get("model.topics");
|
this.modal.set("modalClass", "topic-bulk-actions-modal small");
|
||||||
this.set(
|
this.activeComponent = null;
|
||||||
"buttons",
|
}
|
||||||
_buttons.filter((b) => {
|
|
||||||
if (b.enabledSetting && !this.siteSettings[b.enabledSetting]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return b.buttonVisible.call(this, topics);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.set("modal.modalClass", "topic-bulk-actions-modal small");
|
|
||||||
this.send("changeBulkTemplate", "modal/bulk-actions-buttons");
|
|
||||||
},
|
|
||||||
|
|
||||||
perform(operation) {
|
async perform(operation) {
|
||||||
this.set("processedTopicCount", 0);
|
this.loading = true;
|
||||||
if (this.get("model.topics").length > 20) {
|
|
||||||
this.send("changeBulkTemplate", "modal/bulk-progress");
|
if (this.model.topics.length > 20) {
|
||||||
|
this.showProgress = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set("loading", true);
|
try {
|
||||||
|
return this._processChunks(operation);
|
||||||
return this._processChunks(operation)
|
} catch {
|
||||||
.catch(() => {
|
this.dialog.alert(I18n.t("generic_error"));
|
||||||
this.dialog.alert(I18n.t("generic_error"));
|
} finally {
|
||||||
})
|
this.loading = false;
|
||||||
.finally(() => {
|
this.processedTopicCount = 0;
|
||||||
this.set("loading", false);
|
this.showProgress = false;
|
||||||
});
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_generateTopicChunks(allTopics) {
|
_generateTopicChunks(allTopics) {
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
@ -165,168 +233,70 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
const chunks = [];
|
const chunks = [];
|
||||||
|
|
||||||
while (startIndex < allTopics.length) {
|
while (startIndex < allTopics.length) {
|
||||||
let topics = allTopics.slice(startIndex, startIndex + chunkSize);
|
const topics = allTopics.slice(startIndex, startIndex + chunkSize);
|
||||||
chunks.push(topics);
|
chunks.push(topics);
|
||||||
startIndex += chunkSize;
|
startIndex += chunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chunks;
|
return chunks;
|
||||||
},
|
}
|
||||||
|
|
||||||
_processChunks(operation) {
|
_processChunks(operation) {
|
||||||
const allTopics = this.get("model.topics");
|
const allTopics = this.model.topics;
|
||||||
const topicChunks = this._generateTopicChunks(allTopics);
|
const topicChunks = this._generateTopicChunks(allTopics);
|
||||||
const topicIds = [];
|
const topicIds = [];
|
||||||
|
|
||||||
const tasks = topicChunks.map((topics) => () => {
|
const tasks = topicChunks.map((topics) => async () => {
|
||||||
return Topic.bulkOperation(topics, operation).then((result) => {
|
const result = await Topic.bulkOperation(topics, operation);
|
||||||
this.set(
|
this.processedTopicCount = this.processedTopicCount + topics.length;
|
||||||
"processedTopicCount",
|
return result;
|
||||||
this.get("processedTopicCount") + topics.length
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const resolveNextTask = () => {
|
const resolveNextTask = async () => {
|
||||||
if (tasks.length === 0) {
|
if (tasks.length === 0) {
|
||||||
const topics = topicIds.map((id) => allTopics.findBy("id", id));
|
const topics = topicIds.map((id) => allTopics.findBy("id", id));
|
||||||
return resolve(topics);
|
return resolve(topics);
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks
|
const task = tasks.shift();
|
||||||
.shift()()
|
|
||||||
.then((result) => {
|
try {
|
||||||
if (result && result.topic_ids) {
|
const result = await task();
|
||||||
topicIds.push(...result.topic_ids);
|
if (result?.topic_ids) {
|
||||||
}
|
topicIds.push(...result.topic_ids);
|
||||||
resolveNextTask();
|
}
|
||||||
})
|
resolveNextTask();
|
||||||
.catch(reject);
|
} catch {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
resolveNextTask();
|
resolveNextTask();
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
forEachPerformed(operation, cb) {
|
@action
|
||||||
this.perform(operation).then((topics) => {
|
setComponent(component) {
|
||||||
if (topics) {
|
this.activeComponent = component;
|
||||||
topics.forEach(cb);
|
}
|
||||||
(this.refreshClosure || identity)();
|
|
||||||
this.send("closeModal");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
performAndRefresh(operation) {
|
@action
|
||||||
return this.perform(operation).then(() => {
|
async forEachPerformed(operation, cb) {
|
||||||
(this.refreshClosure || identity)();
|
const topics = await this.perform(operation);
|
||||||
|
|
||||||
|
if (topics) {
|
||||||
|
topics.forEach(cb);
|
||||||
|
this.refreshClosure?.();
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
});
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
actions: {
|
@action
|
||||||
showTagTopics() {
|
async performAndRefresh(operation) {
|
||||||
this.set("tags", "");
|
await this.perform(operation);
|
||||||
this.set("action", "changeTags");
|
|
||||||
this.set("label", "change_tags");
|
|
||||||
this.set("title", "choose_new_tags");
|
|
||||||
this.send("changeBulkTemplate", "bulk-tag");
|
|
||||||
},
|
|
||||||
|
|
||||||
changeTags() {
|
this.refreshClosure?.();
|
||||||
this.performAndRefresh({ type: "change_tags", tags: this.tags });
|
this.send("closeModal");
|
||||||
},
|
}
|
||||||
|
}
|
||||||
showAppendTagTopics() {
|
|
||||||
this.set("tags", null);
|
|
||||||
this.set("action", "appendTags");
|
|
||||||
this.set("label", "append_tags");
|
|
||||||
this.set("title", "choose_append_tags");
|
|
||||||
this.send("changeBulkTemplate", "bulk-tag");
|
|
||||||
},
|
|
||||||
|
|
||||||
appendTags() {
|
|
||||||
this.performAndRefresh({ type: "append_tags", tags: this.tags });
|
|
||||||
},
|
|
||||||
|
|
||||||
showChangeCategory() {
|
|
||||||
this.send("changeBulkTemplate", "modal/bulk-change-category");
|
|
||||||
},
|
|
||||||
|
|
||||||
showNotificationLevel() {
|
|
||||||
this.send("changeBulkTemplate", "modal/bulk-notification-level");
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteTopics() {
|
|
||||||
this.performAndRefresh({ type: "delete" });
|
|
||||||
},
|
|
||||||
|
|
||||||
closeTopics() {
|
|
||||||
this.forEachPerformed({ type: "close" }, (t) => t.set("closed", true));
|
|
||||||
},
|
|
||||||
|
|
||||||
archiveTopics() {
|
|
||||||
this.forEachPerformed({ type: "archive" }, (t) =>
|
|
||||||
t.set("archived", true)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
archiveMessages() {
|
|
||||||
let params = { type: "archive_messages" };
|
|
||||||
if (this.isGroup) {
|
|
||||||
params.group = this.groupFilter;
|
|
||||||
}
|
|
||||||
this.performAndRefresh(params);
|
|
||||||
},
|
|
||||||
|
|
||||||
moveMessagesToInbox() {
|
|
||||||
let params = { type: "move_messages_to_inbox" };
|
|
||||||
if (this.isGroup) {
|
|
||||||
params.group = this.groupFilter;
|
|
||||||
}
|
|
||||||
this.performAndRefresh(params);
|
|
||||||
},
|
|
||||||
|
|
||||||
unlistTopics() {
|
|
||||||
this.forEachPerformed({ type: "unlist" }, (t) => t.set("visible", false));
|
|
||||||
},
|
|
||||||
|
|
||||||
relistTopics() {
|
|
||||||
this.forEachPerformed({ type: "relist" }, (t) => t.set("visible", true));
|
|
||||||
},
|
|
||||||
|
|
||||||
resetBumpDateTopics() {
|
|
||||||
this.performAndRefresh({ type: "reset_bump_dates" });
|
|
||||||
},
|
|
||||||
|
|
||||||
changeCategory() {
|
|
||||||
const categoryId = parseInt(this.newCategoryId, 10) || 0;
|
|
||||||
|
|
||||||
this.perform({ type: "change_category", category_id: categoryId }).then(
|
|
||||||
(topics) => {
|
|
||||||
topics.forEach((t) => t.set("category_id", categoryId));
|
|
||||||
(this.refreshClosure || identity)();
|
|
||||||
this.send("closeModal");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
deletePostTiming() {
|
|
||||||
this.performAndRefresh({ type: "destroy_post_timing" });
|
|
||||||
},
|
|
||||||
|
|
||||||
removeTags() {
|
|
||||||
this.dialog.deleteConfirm({
|
|
||||||
message: I18n.t("topics.bulk.confirm_remove_tags", {
|
|
||||||
count: this.get("model.topics").length,
|
|
||||||
}),
|
|
||||||
didConfirm: () => this.performAndRefresh({ type: "remove_tags" }),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { addBulkButton };
|
|
||||||
|
@ -120,12 +120,13 @@ import { registerModelTransformer } from "discourse/lib/model-transformers";
|
|||||||
import { registerCustomUserNavMessagesDropdownRow } from "discourse/controllers/user-private-messages";
|
import { registerCustomUserNavMessagesDropdownRow } from "discourse/controllers/user-private-messages";
|
||||||
import { registerFullPageSearchType } from "discourse/controllers/full-page-search";
|
import { registerFullPageSearchType } from "discourse/controllers/full-page-search";
|
||||||
import { registerHashtagType } from "discourse/lib/hashtag-autocomplete";
|
import { registerHashtagType } from "discourse/lib/hashtag-autocomplete";
|
||||||
|
import { _addBulkButton } from "discourse/controllers/topic-bulk-actions";
|
||||||
|
|
||||||
// If you add any methods to the API ensure you bump up the version number
|
// If you add any methods to the API ensure you bump up the version number
|
||||||
// based on Semantic Versioning 2.0.0. Please update the changelog at
|
// based on Semantic Versioning 2.0.0. Please update the changelog at
|
||||||
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||||
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
||||||
export const PLUGIN_API_VERSION = "1.7.0";
|
export const PLUGIN_API_VERSION = "1.7.1";
|
||||||
|
|
||||||
// This helper prevents us from applying the same `modifyClass` over and over in test mode.
|
// This helper prevents us from applying the same `modifyClass` over and over in test mode.
|
||||||
function canModify(klass, type, resolverName, changes) {
|
function canModify(klass, type, resolverName, changes) {
|
||||||
@ -1962,15 +1963,15 @@ class PluginApi {
|
|||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @params {Object} arg - An object
|
* @param {Object} arg - An object
|
||||||
* @params {string} arg.categoryId - The id of the category
|
* @param {string} arg.categoryId - The id of the category
|
||||||
* @params {string} arg.prefixType - The type of prefix to use. Can be "icon", "image", "text" or "span".
|
* @param {string} arg.prefixType - The type of prefix to use. Can be "icon", "image", "text" or "span".
|
||||||
* @params {string} arg.prefixValue - The value of the prefix to use.
|
* @param {string} arg.prefixValue - The value of the prefix to use.
|
||||||
* For "icon", pass in the name of a FontAwesome 5 icon.
|
* For "icon", pass in the name of a FontAwesome 5 icon.
|
||||||
* For "image", pass in the src of the image.
|
* For "image", pass in the src of the image.
|
||||||
* For "text", pass in the text to display.
|
* For "text", pass in the text to display.
|
||||||
* For "span", pass in an array containing two hex color values. Example: `[FF0000, 000000]`.
|
* For "span", pass in an array containing two hex color values. Example: `[FF0000, 000000]`.
|
||||||
* @params {string} arg.prefixColor - The color of the prefix to use. Example: "FF0000".
|
* @param {string} arg.prefixColor - The color of the prefix to use. Example: "FF0000".
|
||||||
*/
|
*/
|
||||||
registerCustomCategorySectionLinkPrefix({
|
registerCustomCategorySectionLinkPrefix({
|
||||||
categoryId,
|
categoryId,
|
||||||
@ -2000,10 +2001,10 @@ class PluginApi {
|
|||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @params {Object} arg - An object
|
* @param {Object} arg - An object
|
||||||
* @params {string} arg.tagName - The name of the tag
|
* @param {string} arg.tagName - The name of the tag
|
||||||
* @params {string} arg.prefixValue - The name of a FontAwesome 5 icon.
|
* @param {string} arg.prefixValue - The name of a FontAwesome 5 icon.
|
||||||
* @params {string} arg.prefixColor - The color represented using hexadecimal to use for the prefix. Example: "#FF0000" or "#FFF".
|
* @param {string} arg.prefixColor - The color represented using hexadecimal to use for the prefix. Example: "#FF0000" or "#FFF".
|
||||||
*/
|
*/
|
||||||
registerCustomTagSectionLinkPrefixIcon({
|
registerCustomTagSectionLinkPrefixIcon({
|
||||||
tagName,
|
tagName,
|
||||||
@ -2279,6 +2280,49 @@ class PluginApi {
|
|||||||
registerHashtagType(type, typeClassInstance) {
|
registerHashtagType(type, typeClassInstance) {
|
||||||
registerHashtagType(type, typeClassInstance);
|
registerHashtagType(type, typeClassInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a button to the bulk topic actions modal.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* api.addBulkActionButton({
|
||||||
|
* label: "super_plugin.bulk.enhance",
|
||||||
|
* icon: "magic",
|
||||||
|
* class: "btn-default",
|
||||||
|
* visible: ({ currentUser, siteSettings }) => siteSettings.super_plugin_enabled && currentUser.staff,
|
||||||
|
* async action({ setComponent }) {
|
||||||
|
* await doSomething(this.model.topics);
|
||||||
|
* setComponent(MyBulkModal);
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @callback buttonVisibilityCallback
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {Topic[]} opts.topics - the selected topic for the bulk action
|
||||||
|
* @param {Category} opts.category - the category in which the action is performed (if applicable)
|
||||||
|
* @param {User} opts.currentUser
|
||||||
|
* @param {SiteSettings} opts.siteSettings
|
||||||
|
* @returns {Boolean} - whether the button should be visible or not
|
||||||
|
*
|
||||||
|
* @callback buttonAction
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {Topic[]} opts.topics - the selected topic for the bulk action
|
||||||
|
* @param {Category} opts.category - the category in which the action is performed (if applicable)
|
||||||
|
* @param {function} opts.setComponent - render a template in the bulk action modal (pass in an imported component)
|
||||||
|
* @param {function} opts.performAndRefresh
|
||||||
|
* @param {function} opts.forEachPerformed
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {string} opts.label
|
||||||
|
* @param {string} opts.icon
|
||||||
|
* @param {string} opts.class
|
||||||
|
* @param {buttonVisibilityCallback} opts.visible
|
||||||
|
* @param {buttonAction} opts.action
|
||||||
|
*/
|
||||||
|
addBulkActionButton(opts) {
|
||||||
|
_addBulkButton(opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number
|
// from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
<p>{{i18n (concat "topics.bulk." this.title)}}</p>
|
|
||||||
|
|
||||||
<p><TagChooser @tags={{this.tags}} @categoryId={{this.categoryId}} /></p>
|
|
||||||
|
|
||||||
<DButton
|
|
||||||
@action={{action this.action}}
|
|
||||||
@disabled={{this.emptyTags}}
|
|
||||||
@label={{concat "topics.bulk." this.label}}
|
|
||||||
/>
|
|
@ -1,15 +0,0 @@
|
|||||||
<p>{{i18n "topics.bulk.choose_new_category"}}</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<CategoryChooser
|
|
||||||
@value={{this.newCategoryId}}
|
|
||||||
@onChange={{action (mut this.newCategoryId)}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
|
||||||
<DButton
|
|
||||||
@action={{action "changeCategory"}}
|
|
||||||
@label="topics.bulk.change_category"
|
|
||||||
/>
|
|
||||||
</ConditionalLoadingSpinner>
|
|
@ -1,3 +0,0 @@
|
|||||||
<p>{{html-safe
|
|
||||||
(i18n "topics.bulk.progress" count=this.processedTopicCount)
|
|
||||||
}}</p>
|
|
@ -1,6 +1,40 @@
|
|||||||
<DModalBody>
|
<DModalBody>
|
||||||
<p>{{html-safe
|
<p>
|
||||||
(i18n "topics.bulk.selected" count=this.model.topics.length)
|
{{html-safe (i18n "topics.bulk.selected" count=this.model.topics.length)}}
|
||||||
}}</p>
|
</p>
|
||||||
{{outlet "bulkOutlet"}}
|
|
||||||
|
{{#if this.showProgress}}
|
||||||
|
<p>
|
||||||
|
{{html-safe (i18n "topics.bulk.progress" count=this.processedTopicCount)}}
|
||||||
|
</p>
|
||||||
|
{{else if this.activeComponent}}
|
||||||
|
<this.activeComponent
|
||||||
|
@loading={{this.loading}}
|
||||||
|
@topics={{this.model.topics}}
|
||||||
|
@category={{this.model.category}}
|
||||||
|
@setComponent={{this.setComponent}}
|
||||||
|
@forEachPerformed={{this.forEachPerformed}}
|
||||||
|
@performAndRefresh={{this.performAndRefresh}}
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<div class="bulk-buttons">
|
||||||
|
{{#each this.buttons as |button|}}
|
||||||
|
<DButton
|
||||||
|
@action={{fn
|
||||||
|
button.action
|
||||||
|
(hash
|
||||||
|
topics=this.model.topics
|
||||||
|
category=this.model.category
|
||||||
|
setComponent=this.setComponent
|
||||||
|
performAndRefresh=this.performAndRefresh
|
||||||
|
forEachPerformed=this.forEachPerformed
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@label={{button.label}}
|
||||||
|
@icon={{button.icon}}
|
||||||
|
class={{button.class}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</DModalBody>
|
</DModalBody>
|
@ -1,9 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
acceptance,
|
acceptance,
|
||||||
count,
|
count,
|
||||||
exists,
|
|
||||||
invisible,
|
invisible,
|
||||||
query,
|
|
||||||
queryAll,
|
queryAll,
|
||||||
updateCurrentUser,
|
updateCurrentUser,
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
@ -35,85 +33,86 @@ acceptance("Topic - Bulk Actions", function (needs) {
|
|||||||
|
|
||||||
await click(".bulk-select-actions");
|
await click(".bulk-select-actions");
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query("#discourse-modal-title").innerHTML.includes(
|
.dom("#discourse-modal-title")
|
||||||
I18n.t("topics.bulk.actions")
|
.hasText(I18n.t("topics.bulk.actions"), "it opens bulk-select modal");
|
||||||
),
|
|
||||||
"it opens bulk-select modal"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.change_category")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.change_category"),
|
||||||
"it shows an option to change category"
|
"it shows an option to change category"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.close_topics")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.close_topics"),
|
||||||
"it shows an option to close topics"
|
"it shows an option to close topics"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.archive_topics")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.archive_topics"),
|
||||||
"it shows an option to archive topics"
|
"it shows an option to archive topics"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.notification_level")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.notification_level"),
|
||||||
"it shows an option to update notification level"
|
"it shows an option to update notification level"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.defer")),
|
.dom(".bulk-buttons")
|
||||||
"it shows an option to reset read"
|
.includesText(
|
||||||
);
|
I18n.t("topics.bulk.defer"),
|
||||||
|
"it shows an option to reset read"
|
||||||
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.unlist_topics")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.unlist_topics"),
|
||||||
"it shows an option to unlist topics"
|
"it shows an option to unlist topics"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.reset_bump_dates")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.reset_bump_dates"),
|
||||||
"it shows an option to reset bump dates"
|
"it shows an option to reset bump dates"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.change_tags")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.change_tags"),
|
||||||
"it shows an option to replace tags"
|
"it shows an option to replace tags"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.append_tags")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.append_tags"),
|
||||||
"it shows an option to append tags"
|
"it shows an option to append tags"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.remove_tags")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.remove_tags"),
|
||||||
"it shows an option to remove all tags"
|
"it shows an option to remove all tags"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.delete")),
|
.dom(".bulk-buttons")
|
||||||
"it shows an option to delete topics"
|
.includesText(
|
||||||
);
|
I18n.t("topics.bulk.delete"),
|
||||||
|
"it shows an option to delete topics"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("bulk select - delete topics", async function (assert) {
|
test("bulk select - delete topics", async function (assert) {
|
||||||
@ -127,7 +126,7 @@ acceptance("Topic - Bulk Actions", function (needs) {
|
|||||||
await click(".bulk-select-actions");
|
await click(".bulk-select-actions");
|
||||||
await click(".modal-body .delete-topics");
|
await click(".modal-body .delete-topics");
|
||||||
|
|
||||||
assert.ok(
|
assert.true(
|
||||||
invisible(".topic-bulk-actions-modal"),
|
invisible(".topic-bulk-actions-modal"),
|
||||||
"it closes the bulk select modal"
|
"it closes the bulk select modal"
|
||||||
);
|
);
|
||||||
@ -164,10 +163,9 @@ acceptance("Topic - Bulk Actions", function (needs) {
|
|||||||
test("bulk select is not available for users who are not staff or TL4", async function (assert) {
|
test("bulk select is not available for users who are not staff or TL4", async function (assert) {
|
||||||
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
|
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
|
||||||
await visit("/latest");
|
await visit("/latest");
|
||||||
assert.notOk(
|
assert
|
||||||
exists(".button.bulk-select"),
|
.dom(".button.bulk-select")
|
||||||
"non-staff and < TL4 users cannot bulk select"
|
.doesNotExist("non-staff and < TL4 users cannot bulk select");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("TL4 users can bulk select", async function (assert) {
|
test("TL4 users can bulk select", async function (assert) {
|
||||||
@ -183,86 +181,87 @@ acceptance("Topic - Bulk Actions", function (needs) {
|
|||||||
|
|
||||||
await click(queryAll("input.bulk-select")[0]);
|
await click(queryAll("input.bulk-select")[0]);
|
||||||
await click(queryAll("input.bulk-select")[1]);
|
await click(queryAll("input.bulk-select")[1]);
|
||||||
|
|
||||||
await click(".bulk-select-actions");
|
await click(".bulk-select-actions");
|
||||||
assert.ok(
|
|
||||||
query("#discourse-modal-title").innerHTML.includes(
|
|
||||||
I18n.t("topics.bulk.actions")
|
|
||||||
),
|
|
||||||
"it opens bulk-select modal"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom("#discourse-modal-title")
|
||||||
I18n.t("topics.bulk.change_category")
|
.hasText(I18n.t("topics.bulk.actions"), "it opens bulk-select modal");
|
||||||
),
|
|
||||||
"it shows an option to change category"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.close_topics")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.change_category"),
|
||||||
"it shows an option to close topics"
|
"it shows an option to change category"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.archive_topics")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.close_topics"),
|
||||||
"it shows an option to archive topics"
|
"it shows an option to close topics"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.notification_level")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.archive_topics"),
|
||||||
"it shows an option to update notification level"
|
"it shows an option to archive topics"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.notOk(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.defer")),
|
.dom(".bulk-buttons")
|
||||||
"it does not show an option to reset read"
|
.includesText(
|
||||||
);
|
I18n.t("topics.bulk.notification_level"),
|
||||||
|
"it shows an option to update notification level"
|
||||||
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.unlist_topics")
|
.doesNotIncludeText(
|
||||||
),
|
I18n.t("topics.bulk.defer"),
|
||||||
"it shows an option to unlist topics"
|
"it does not show an option to reset read"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.reset_bump_dates")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.unlist_topics"),
|
||||||
"it shows an option to reset bump dates"
|
"it shows an option to unlist topics"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.change_tags")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.reset_bump_dates"),
|
||||||
"it shows an option to replace tags"
|
"it shows an option to reset bump dates"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.append_tags")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.change_tags"),
|
||||||
"it shows an option to append tags"
|
"it shows an option to replace tags"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(
|
.dom(".bulk-buttons")
|
||||||
I18n.t("topics.bulk.remove_tags")
|
.includesText(
|
||||||
),
|
I18n.t("topics.bulk.append_tags"),
|
||||||
"it shows an option to remove all tags"
|
"it shows an option to append tags"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.notOk(
|
assert
|
||||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.delete")),
|
.dom(".bulk-buttons")
|
||||||
"it does not show an option to delete topics"
|
.includesText(
|
||||||
);
|
I18n.t("topics.bulk.remove_tags"),
|
||||||
|
"it shows an option to remove all tags"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".bulk-buttons")
|
||||||
|
.doesNotIncludeText(
|
||||||
|
I18n.t("topics.bulk.delete"),
|
||||||
|
"it does not show an option to delete topics"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -87,6 +87,7 @@ import { reset as resetLinkLookup } from "discourse/lib/link-lookup";
|
|||||||
import { resetMentions } from "discourse/lib/link-mentions";
|
import { resetMentions } from "discourse/lib/link-mentions";
|
||||||
import { resetModelTransformers } from "discourse/lib/model-transformers";
|
import { resetModelTransformers } from "discourse/lib/model-transformers";
|
||||||
import { cleanupTemporaryModuleRegistrations } from "./temporary-module-helper";
|
import { cleanupTemporaryModuleRegistrations } from "./temporary-module-helper";
|
||||||
|
import { clearBulkButtons } from "discourse/controllers/topic-bulk-actions";
|
||||||
|
|
||||||
export function currentUser() {
|
export function currentUser() {
|
||||||
return User.create(sessionFixtures["/session/current.json"].current_user);
|
return User.create(sessionFixtures["/session/current.json"].current_user);
|
||||||
@ -223,6 +224,7 @@ export function testCleanup(container, app) {
|
|||||||
resetMentions();
|
resetMentions();
|
||||||
cleanupTemporaryModuleRegistrations();
|
cleanupTemporaryModuleRegistrations();
|
||||||
cleanupCssGeneratorTags();
|
cleanupCssGeneratorTags();
|
||||||
|
clearBulkButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupCssGeneratorTags() {
|
function cleanupCssGeneratorTags() {
|
||||||
|
@ -7,6 +7,12 @@ in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.7.1] - 2023-07-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Adds `addBulkActionButton` which adds actions to the Bulk Topic modal
|
||||||
|
|
||||||
## [1.7.0] - 2023-07-17
|
## [1.7.0] - 2023-07-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
Loading…
Reference in New Issue
Block a user