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
|
||||
@disabled={{this.disabled}}
|
||||
@action={{action "changeNotificationLevel"}}
|
||||
@action={{this.changeNotificationLevel}}
|
||||
@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 { tracked } from "@glimmer/tracking";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { Promise } from "rsvp";
|
||||
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 = [];
|
||||
|
||||
const alwaysTrue = () => true;
|
||||
|
||||
function identity() {}
|
||||
|
||||
function addBulkButton(action, key, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const btn = {
|
||||
action,
|
||||
label: `topics.bulk.${key}`,
|
||||
export function _addBulkButton(opts) {
|
||||
_customButtons.push({
|
||||
label: opts.label,
|
||||
icon: opts.icon,
|
||||
buttonVisible: opts.buttonVisible || alwaysTrue,
|
||||
enabledSetting: opts.enabledSetting,
|
||||
class: opts.class,
|
||||
};
|
||||
|
||||
_buttons.push(btn);
|
||||
visible: opts.visible,
|
||||
action: opts.action,
|
||||
});
|
||||
}
|
||||
|
||||
// Default buttons
|
||||
addBulkButton("showChangeCategory", "change_category", {
|
||||
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;
|
||||
},
|
||||
});
|
||||
export function clearBulkButtons() {
|
||||
_customButtons.length = 0;
|
||||
}
|
||||
|
||||
// Modal for performing bulk actions on topics
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
userPrivateMessages: controller("user-private-messages"),
|
||||
dialog: service(),
|
||||
tags: null,
|
||||
emptyTags: empty("tags"),
|
||||
categoryId: alias("model.category.id"),
|
||||
processedTopicCount: 0,
|
||||
isGroup: alias("userPrivateMessages.isGroup"),
|
||||
groupFilter: alias("userPrivateMessages.groupFilter"),
|
||||
export default class TopicBulkActions extends Controller.extend(
|
||||
ModalFunctionality
|
||||
) {
|
||||
@service currentUser;
|
||||
@service siteSettings;
|
||||
@service dialog;
|
||||
@controller("user-private-messages") userPrivateMessages;
|
||||
|
||||
@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() {
|
||||
const topics = this.get("model.topics");
|
||||
this.set(
|
||||
"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");
|
||||
},
|
||||
this.modal.set("modalClass", "topic-bulk-actions-modal small");
|
||||
this.activeComponent = null;
|
||||
}
|
||||
|
||||
perform(operation) {
|
||||
this.set("processedTopicCount", 0);
|
||||
if (this.get("model.topics").length > 20) {
|
||||
this.send("changeBulkTemplate", "modal/bulk-progress");
|
||||
async perform(operation) {
|
||||
this.loading = true;
|
||||
|
||||
if (this.model.topics.length > 20) {
|
||||
this.showProgress = true;
|
||||
}
|
||||
|
||||
this.set("loading", true);
|
||||
|
||||
return this._processChunks(operation)
|
||||
.catch(() => {
|
||||
this.dialog.alert(I18n.t("generic_error"));
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("loading", false);
|
||||
});
|
||||
},
|
||||
try {
|
||||
return this._processChunks(operation);
|
||||
} catch {
|
||||
this.dialog.alert(I18n.t("generic_error"));
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.processedTopicCount = 0;
|
||||
this.showProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
_generateTopicChunks(allTopics) {
|
||||
let startIndex = 0;
|
||||
|
@ -165,168 +233,70 @@ export default Controller.extend(ModalFunctionality, {
|
|||
const chunks = [];
|
||||
|
||||
while (startIndex < allTopics.length) {
|
||||
let topics = allTopics.slice(startIndex, startIndex + chunkSize);
|
||||
const topics = allTopics.slice(startIndex, startIndex + chunkSize);
|
||||
chunks.push(topics);
|
||||
startIndex += chunkSize;
|
||||
}
|
||||
|
||||
return chunks;
|
||||
},
|
||||
}
|
||||
|
||||
_processChunks(operation) {
|
||||
const allTopics = this.get("model.topics");
|
||||
const allTopics = this.model.topics;
|
||||
const topicChunks = this._generateTopicChunks(allTopics);
|
||||
const topicIds = [];
|
||||
|
||||
const tasks = topicChunks.map((topics) => () => {
|
||||
return Topic.bulkOperation(topics, operation).then((result) => {
|
||||
this.set(
|
||||
"processedTopicCount",
|
||||
this.get("processedTopicCount") + topics.length
|
||||
);
|
||||
return result;
|
||||
});
|
||||
const tasks = topicChunks.map((topics) => async () => {
|
||||
const result = await Topic.bulkOperation(topics, operation);
|
||||
this.processedTopicCount = this.processedTopicCount + topics.length;
|
||||
return result;
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const resolveNextTask = () => {
|
||||
const resolveNextTask = async () => {
|
||||
if (tasks.length === 0) {
|
||||
const topics = topicIds.map((id) => allTopics.findBy("id", id));
|
||||
return resolve(topics);
|
||||
}
|
||||
|
||||
tasks
|
||||
.shift()()
|
||||
.then((result) => {
|
||||
if (result && result.topic_ids) {
|
||||
topicIds.push(...result.topic_ids);
|
||||
}
|
||||
resolveNextTask();
|
||||
})
|
||||
.catch(reject);
|
||||
const task = tasks.shift();
|
||||
|
||||
try {
|
||||
const result = await task();
|
||||
if (result?.topic_ids) {
|
||||
topicIds.push(...result.topic_ids);
|
||||
}
|
||||
resolveNextTask();
|
||||
} catch {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
|
||||
resolveNextTask();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
forEachPerformed(operation, cb) {
|
||||
this.perform(operation).then((topics) => {
|
||||
if (topics) {
|
||||
topics.forEach(cb);
|
||||
(this.refreshClosure || identity)();
|
||||
this.send("closeModal");
|
||||
}
|
||||
});
|
||||
},
|
||||
@action
|
||||
setComponent(component) {
|
||||
this.activeComponent = component;
|
||||
}
|
||||
|
||||
performAndRefresh(operation) {
|
||||
return this.perform(operation).then(() => {
|
||||
(this.refreshClosure || identity)();
|
||||
@action
|
||||
async forEachPerformed(operation, cb) {
|
||||
const topics = await this.perform(operation);
|
||||
|
||||
if (topics) {
|
||||
topics.forEach(cb);
|
||||
this.refreshClosure?.();
|
||||
this.send("closeModal");
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
actions: {
|
||||
showTagTopics() {
|
||||
this.set("tags", "");
|
||||
this.set("action", "changeTags");
|
||||
this.set("label", "change_tags");
|
||||
this.set("title", "choose_new_tags");
|
||||
this.send("changeBulkTemplate", "bulk-tag");
|
||||
},
|
||||
@action
|
||||
async performAndRefresh(operation) {
|
||||
await this.perform(operation);
|
||||
|
||||
changeTags() {
|
||||
this.performAndRefresh({ type: "change_tags", tags: this.tags });
|
||||
},
|
||||
|
||||
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 };
|
||||
this.refreshClosure?.();
|
||||
this.send("closeModal");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,12 +120,13 @@ import { registerModelTransformer } from "discourse/lib/model-transformers";
|
|||
import { registerCustomUserNavMessagesDropdownRow } from "discourse/controllers/user-private-messages";
|
||||
import { registerFullPageSearchType } from "discourse/controllers/full-page-search";
|
||||
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
|
||||
// based on Semantic Versioning 2.0.0. Please update the changelog at
|
||||
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||
// 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.
|
||||
function canModify(klass, type, resolverName, changes) {
|
||||
|
@ -1962,15 +1963,15 @@ class PluginApi {
|
|||
* })
|
||||
* ```
|
||||
*
|
||||
* @params {Object} arg - An object
|
||||
* @params {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".
|
||||
* @params {string} arg.prefixValue - The value of the prefix to use.
|
||||
* @param {Object} arg - An object
|
||||
* @param {string} arg.categoryId - The id of the category
|
||||
* @param {string} arg.prefixType - The type of prefix to use. Can be "icon", "image", "text" or "span".
|
||||
* @param {string} arg.prefixValue - The value of the prefix to use.
|
||||
* For "icon", pass in the name of a FontAwesome 5 icon.
|
||||
* For "image", pass in the src of the image.
|
||||
* For "text", pass in the text to display.
|
||||
* 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({
|
||||
categoryId,
|
||||
|
@ -2000,10 +2001,10 @@ class PluginApi {
|
|||
* });
|
||||
* ```
|
||||
*
|
||||
* @params {Object} arg - An object
|
||||
* @params {string} arg.tagName - The name of the tag
|
||||
* @params {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 {Object} arg - An object
|
||||
* @param {string} arg.tagName - The name of the tag
|
||||
* @param {string} arg.prefixValue - The name of a FontAwesome 5 icon.
|
||||
* @param {string} arg.prefixColor - The color represented using hexadecimal to use for the prefix. Example: "#FF0000" or "#FFF".
|
||||
*/
|
||||
registerCustomTagSectionLinkPrefixIcon({
|
||||
tagName,
|
||||
|
@ -2279,6 +2280,49 @@ class PluginApi {
|
|||
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
|
||||
|
|
|
@ -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>
|
||||
<p>{{html-safe
|
||||
(i18n "topics.bulk.selected" count=this.model.topics.length)
|
||||
}}</p>
|
||||
{{outlet "bulkOutlet"}}
|
||||
<p>
|
||||
{{html-safe (i18n "topics.bulk.selected" count=this.model.topics.length)}}
|
||||
</p>
|
||||
|
||||
{{#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>
|
|
@ -1,9 +1,7 @@
|
|||
import {
|
||||
acceptance,
|
||||
count,
|
||||
exists,
|
||||
invisible,
|
||||
query,
|
||||
queryAll,
|
||||
updateCurrentUser,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
|
@ -35,85 +33,86 @@ acceptance("Topic - Bulk Actions", function (needs) {
|
|||
|
||||
await click(".bulk-select-actions");
|
||||
|
||||
assert.ok(
|
||||
query("#discourse-modal-title").innerHTML.includes(
|
||||
I18n.t("topics.bulk.actions")
|
||||
),
|
||||
"it opens bulk-select modal"
|
||||
);
|
||||
assert
|
||||
.dom("#discourse-modal-title")
|
||||
.hasText(I18n.t("topics.bulk.actions"), "it opens bulk-select modal");
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.change_category")
|
||||
),
|
||||
"it shows an option to change category"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.change_category"),
|
||||
"it shows an option to change category"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.close_topics")
|
||||
),
|
||||
"it shows an option to close topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.close_topics"),
|
||||
"it shows an option to close topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.archive_topics")
|
||||
),
|
||||
"it shows an option to archive topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.archive_topics"),
|
||||
"it shows an option to archive topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.notification_level")
|
||||
),
|
||||
"it shows an option to update notification level"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.notification_level"),
|
||||
"it shows an option to update notification level"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.defer")),
|
||||
"it shows an option to reset read"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.defer"),
|
||||
"it shows an option to reset read"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.unlist_topics")
|
||||
),
|
||||
"it shows an option to unlist topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.unlist_topics"),
|
||||
"it shows an option to unlist topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.reset_bump_dates")
|
||||
),
|
||||
"it shows an option to reset bump dates"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.reset_bump_dates"),
|
||||
"it shows an option to reset bump dates"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.change_tags")
|
||||
),
|
||||
"it shows an option to replace tags"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.change_tags"),
|
||||
"it shows an option to replace tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.append_tags")
|
||||
),
|
||||
"it shows an option to append tags"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.append_tags"),
|
||||
"it shows an option to append tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.remove_tags")
|
||||
),
|
||||
"it shows an option to remove all tags"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.remove_tags"),
|
||||
"it shows an option to remove all tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.delete")),
|
||||
"it shows an option to delete topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.delete"),
|
||||
"it shows an option to delete topics"
|
||||
);
|
||||
});
|
||||
|
||||
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(".modal-body .delete-topics");
|
||||
|
||||
assert.ok(
|
||||
assert.true(
|
||||
invisible(".topic-bulk-actions-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) {
|
||||
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
|
||||
await visit("/latest");
|
||||
assert.notOk(
|
||||
exists(".button.bulk-select"),
|
||||
"non-staff and < TL4 users cannot bulk select"
|
||||
);
|
||||
assert
|
||||
.dom(".button.bulk-select")
|
||||
.doesNotExist("non-staff and < TL4 users cannot bulk select");
|
||||
});
|
||||
|
||||
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")[1]);
|
||||
|
||||
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(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.change_category")
|
||||
),
|
||||
"it shows an option to change category"
|
||||
);
|
||||
assert
|
||||
.dom("#discourse-modal-title")
|
||||
.hasText(I18n.t("topics.bulk.actions"), "it opens bulk-select modal");
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.close_topics")
|
||||
),
|
||||
"it shows an option to close topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.change_category"),
|
||||
"it shows an option to change category"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.archive_topics")
|
||||
),
|
||||
"it shows an option to archive topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.close_topics"),
|
||||
"it shows an option to close topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.notification_level")
|
||||
),
|
||||
"it shows an option to update notification level"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.archive_topics"),
|
||||
"it shows an option to archive topics"
|
||||
);
|
||||
|
||||
assert.notOk(
|
||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.defer")),
|
||||
"it does not show an option to reset read"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.notification_level"),
|
||||
"it shows an option to update notification level"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.unlist_topics")
|
||||
),
|
||||
"it shows an option to unlist topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.doesNotIncludeText(
|
||||
I18n.t("topics.bulk.defer"),
|
||||
"it does not show an option to reset read"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.reset_bump_dates")
|
||||
),
|
||||
"it shows an option to reset bump dates"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.unlist_topics"),
|
||||
"it shows an option to unlist topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.change_tags")
|
||||
),
|
||||
"it shows an option to replace tags"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.reset_bump_dates"),
|
||||
"it shows an option to reset bump dates"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.append_tags")
|
||||
),
|
||||
"it shows an option to append tags"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.change_tags"),
|
||||
"it shows an option to replace tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.remove_tags")
|
||||
),
|
||||
"it shows an option to remove all tags"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.includesText(
|
||||
I18n.t("topics.bulk.append_tags"),
|
||||
"it shows an option to append tags"
|
||||
);
|
||||
|
||||
assert.notOk(
|
||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.delete")),
|
||||
"it does not show an option to delete topics"
|
||||
);
|
||||
assert
|
||||
.dom(".bulk-buttons")
|
||||
.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 { resetModelTransformers } from "discourse/lib/model-transformers";
|
||||
import { cleanupTemporaryModuleRegistrations } from "./temporary-module-helper";
|
||||
import { clearBulkButtons } from "discourse/controllers/topic-bulk-actions";
|
||||
|
||||
export function currentUser() {
|
||||
return User.create(sessionFixtures["/session/current.json"].current_user);
|
||||
|
@ -223,6 +224,7 @@ export function testCleanup(container, app) {
|
|||
resetMentions();
|
||||
cleanupTemporaryModuleRegistrations();
|
||||
cleanupCssGeneratorTags();
|
||||
clearBulkButtons();
|
||||
}
|
||||
|
||||
function cleanupCssGeneratorTags() {
|
||||
|
|
|
@ -7,6 +7,12 @@ in this file.
|
|||
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).
|
||||
|
||||
## [1.7.1] - 2023-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- Adds `addBulkActionButton` which adds actions to the Bulk Topic modal
|
||||
|
||||
## [1.7.0] - 2023-07-17
|
||||
|
||||
### Added
|
||||
|
|
Loading…
Reference in New Issue