FIX: Show "no category" in category-chooser (#25917)

CategoryChooser component usually displays just categories, but
sometimes it can show two none values: a "no category" or Uncategorized.
This commit makes sure that these are rendered correctly.

The problem was that the "none" item was automatically inserted in the
list of options, but that should not always happen. Toggling option
`autoInsertNoneItem` requires setting `none` too.
This commit is contained in:
Bianca Nenciu
2024-02-29 13:48:20 +02:00
committed by GitHub
parent 0bb492c6b6
commit e74a9efee1
8 changed files with 97 additions and 18 deletions

View File

@@ -19,13 +19,13 @@
<label>{{i18n "category.parent"}}</label>
<CategoryChooser
@value={{this.category.parent_category_id}}
@categories={{this.parentCategories}}
@allowSubCategories={{true}}
@allowRestrictedCategories={{true}}
@onChange={{action (mut this.category.parent_category_id)}}
@options={{hash
allowUncategorized=false
excludeCategoryId=this.category.id
autoInsertNoneItem=true
none=true
}}
/>

View File

@@ -176,6 +176,25 @@ export default class Category extends RestModel {
return category;
}
static async asyncFindBySlugPath(slugPath, opts = {}) {
const data = { slug_path: slugPath };
if (opts.includePermissions) {
data.include_permissions = true;
}
const result = await ajax("/categories/find", { data });
const categories = result["categories"].map((category) => {
category = Site.current().updateCategory(category);
if (opts.includePermissions) {
category.setupGroupsAndPermissions();
}
return category;
});
return categories[categories.length - 1];
}
static async asyncFindBySlugPathWithID(slugPathWithID) {
const result = await ajax("/categories/find", {
data: { slug_path_with_id: slugPathWithID },

View File

@@ -7,11 +7,9 @@ export default DiscourseRoute.extend({
router: service(),
model(params) {
return Category.reloadCategoryWithPermissions(
params,
this.store,
this.site
);
return this.site.lazy_load_categories
? Category.asyncFindBySlugPath(params.slug, { includePermissions: true })
: Category.reloadCategoryWithPermissions(params, this.store, this.site);
},
afterModel(model) {

View File

@@ -145,6 +145,36 @@ acceptance("Category Edit", function (needs) {
assert.deepEqual(removePayload.allowed_tag_groups, []);
});
test("Editing parent category (disabled Uncategorized)", async function (assert) {
this.siteSettings.allow_uncategorized_topics = false;
await visit("/c/bug/edit");
const categoryChooser = selectKit(".category-chooser");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(6);
await categoryChooser.expand();
const names = [...categoryChooser.rows()].map((row) => row.dataset.name);
assert.ok(names.includes("(no category)"));
assert.notOk(names.includes("Uncategorized"));
});
test("Editing parent category (enabled Uncategorized)", async function (assert) {
this.siteSettings.allow_uncategorized_topics = true;
await visit("/c/bug/edit");
const categoryChooser = selectKit(".category-chooser");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(6);
await categoryChooser.expand();
const names = [...categoryChooser.rows()].map((row) => row.dataset.name);
assert.ok(names.includes("(no category)"));
assert.notOk(names.includes("Uncategorized"));
});
test("Index Route", async function (assert) {
await visit("/c/bug/edit");
assert.strictEqual(

View File

@@ -12,12 +12,13 @@ import ComboBoxComponent from "select-kit/components/combo-box";
export default ComboBoxComponent.extend({
pluginApiIdentifiers: ["category-chooser"],
classNames: ["category-chooser"],
allowUncategorizedTopics: setting("allow_uncategorized_topics"),
allowUncategorized: setting("allow_uncategorized_topics"),
fixedCategoryPositionsOnCreate: setting("fixed_category_positions_on_create"),
selectKitOptions: {
filterable: true,
allowUncategorized: false,
allowUncategorized: "allowUncategorized",
autoInsertNoneItem: false,
allowSubCategories: true,
permissionType: PermissionType.FULL,
excludeCategoryId: null,
@@ -30,6 +31,7 @@ export default ComboBoxComponent.extend({
if (
this.site.lazy_load_categories &&
this.value &&
!Category.hasAsyncFoundAll([this.value])
) {
// eslint-disable-next-line no-console
@@ -54,10 +56,7 @@ export default ComboBoxComponent.extend({
I18n.t(isString ? this.selectKit.options.none : "category.none")
)
);
} else if (
this.allowUncategorizedTopics ||
this.selectKit.options.allowUncategorized
) {
} else if (this.selectKit.options.allowUncategorized) {
return Category.findUncategorized();
} else {
const defaultCategoryId = parseInt(
@@ -94,8 +93,10 @@ export default ComboBoxComponent.extend({
search(filter) {
if (this.site.lazy_load_categories) {
return Category.asyncSearch(this._normalize(filter), {
scopedCategoryId: this.selectKit.options?.scopedCategoryId,
prioritizedCategoryId: this.selectKit.options?.prioritizedCategoryId,
includeUncategorized: this.selectKit.options.allowUncategorized,
rejectCategoryIds: [this.selectKit.options.excludeCategoryId],
scopedCategoryId: this.selectKit.options.scopedCategoryId,
prioritizedCategoryId: this.selectKit.options.prioritizedCategoryId,
});
}

View File

@@ -24,6 +24,7 @@ export default ComboBoxComponent.extend({
navigateToEdit: false,
editingCategory: false,
editingCategoryTab: null,
allowUncategorized: setting("allow_uncategorized_topics"),
selectKitOptions: {
filterable: true,
@@ -40,7 +41,7 @@ export default ComboBoxComponent.extend({
displayCategoryDescription: "displayCategoryDescription",
headerComponent: "category-drop/category-drop-header",
parentCategory: false,
allowUncategorized: setting("allow_uncategorized_topics"),
allowUncategorized: "allowUncategorized",
},
modifyComponentForRow() {

View File

@@ -303,9 +303,17 @@ class CategoriesController < ApplicationController
def find
categories = []
serializer = params[:include_permissions] ? CategorySerializer : SiteCategorySerializer
if params[:ids].present?
categories = Category.secured(guardian).where(id: params[:ids])
elsif params[:slug_path].present?
category = Category.find_by_slug_path(params[:slug_path].split("/"))
raise Discourse::NotFound if category.blank?
guardian.ensure_can_see!(category)
ancestors = Category.secured(guardian).with_ancestors(category.id).where.not(id: category.id)
categories = [*ancestors, category]
elsif params[:slug_path_with_id].present?
category = Category.find_by_slug_path_with_id(params[:slug_path_with_id])
raise Discourse::NotFound if category.blank?
@@ -319,7 +327,7 @@ class CategoriesController < ApplicationController
Category.preload_user_fields!(guardian, categories)
render_serialized(categories, SiteCategorySerializer, root: :categories, scope: guardian)
render_serialized(categories, serializer, root: :categories, scope: guardian)
end
def search
@@ -333,8 +341,12 @@ class CategoriesController < ApplicationController
true
end
)
select_category_ids = params[:select_category_ids].presence
reject_category_ids = params[:reject_category_ids].presence
if params[:select_category_ids].is_a?(Array)
select_category_ids = params[:select_category_ids].map(&:presence)
end
if params[:reject_category_ids].is_a?(Array)
reject_category_ids = params[:reject_category_ids].map(&:presence)
end
include_subcategories =
if params[:include_subcategories].present?
ActiveModel::Type::Boolean.new.cast(params[:include_subcategories])