diff --git a/app/assets/javascripts/discourse/app/helpers/category-link.js b/app/assets/javascripts/discourse/app/helpers/category-link.js index 63e8bc695df..b26560a0081 100644 --- a/app/assets/javascripts/discourse/app/helpers/category-link.js +++ b/app/assets/javascripts/discourse/app/helpers/category-link.js @@ -54,8 +54,11 @@ export function categoryBadgeHTML(category, opts) { const depth = (opts.depth || 1) + 1; if (opts.recursive && depth <= siteSettings.max_category_nesting) { const parentCategory = Category.findById(category.parent_category_id); + const lastSubcategory = !opts.depth; opts.depth = depth; - return categoryBadgeHTML(parentCategory, opts) + _renderer(category, opts); + const parentBadges = categoryBadgeHTML(parentCategory, opts); + opts.lastSubcategory = lastSubcategory; + return parentBadges + _renderer(category, opts); } return _renderer(category, opts); @@ -183,5 +186,11 @@ function defaultCategoryLinkRenderer(category, opts) { if (opts.topicCount && categoryStyle === "box") { afterBadgeWrapper += buildTopicCount(opts.topicCount); } + if (opts.plusSubcategories && opts.lastSubcategory) { + afterBadgeWrapper += `${I18n.t( + "category_row.plus_subcategories", + { count: opts.plusSubcategories } + )}`; + } return `<${tagName} class="badge-wrapper ${extraClasses}" ${href}>${html}${afterBadgeWrapper}`; } diff --git a/app/assets/javascripts/select-kit/addon/components/category-selector.js b/app/assets/javascripts/select-kit/addon/components/category-selector.js index 2c2245aa3f1..69f157fe419 100644 --- a/app/assets/javascripts/select-kit/addon/components/category-selector.js +++ b/app/assets/javascripts/select-kit/addon/components/category-selector.js @@ -1,4 +1,6 @@ -import { get, computed } from "@ember/object"; +import I18n from "I18n"; +import { categoryBadgeHTML } from "discourse/helpers/category-link"; +import EmberObject, { get, computed } from "@ember/object"; import { mapBy } from "@ember/object/computed"; import { makeArray } from "discourse-common/lib/helpers"; import MultiSelectComponent from "select-kit/components/multi-select"; @@ -52,6 +54,57 @@ export default MultiSelectComponent.extend({ return "category-row"; }, + search(filter) { + const result = this._super(filter); + if (result.length === 1) { + const subcategoryIds = new Set([result[0].id]); + for (let i = 0; i < this.siteSettings.max_category_nesting; ++i) { + subcategoryIds.forEach((categoryId) => { + this.content.forEach((category) => { + if (category.parent_category_id === categoryId) { + subcategoryIds.add(category.id); + } + }); + }); + } + + if (subcategoryIds.size > 1) { + result.push( + EmberObject.create({ + multicategory: [...subcategoryIds], + category: result[0], + title: I18n.t("category_row.plus_subcategories_title", { + name: result[0].name, + count: subcategoryIds.size - 1, + }), + label: categoryBadgeHTML(result[0], { + link: false, + recursive: true, + plusSubcategories: subcategoryIds.size - 1, + }).htmlSafe(), + }) + ); + } + } + + return result; + }, + + select(value, item) { + if (item.multicategory) { + const items = item.multicategory.map((id) => + Category.findById(parseInt(id, 10)) + ); + + const newValues = makeArray(this.value).concat(items.map((i) => i.id)); + const newContent = makeArray(this.selectedContent).concat(items); + + this.selectKit.change(newValues, newContent); + } else { + this._super(value, item); + } + }, + actions: { onChange(values) { this.attrs.onChange( diff --git a/app/assets/stylesheets/common/select-kit/category-selector.scss b/app/assets/stylesheets/common/select-kit/category-selector.scss index 972be8bae4a..75269ade54b 100644 --- a/app/assets/stylesheets/common/select-kit/category-selector.scss +++ b/app/assets/stylesheets/common/select-kit/category-selector.scss @@ -16,6 +16,10 @@ .topic-count { margin-left: 0.25em; } + + .plus-subcategories { + font-size: $font-down-2; + } } } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4db2215f327..b12f6774ba7 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1763,6 +1763,12 @@ en: topic_count: one: "%{count} topic in this category" other: "%{count} topics in this category" + plus_subcategories_title: + one: "%{name} and one subcategory" + other: "%{name} and %{count} subcategories" + plus_subcategories: + one: "+ %{count} subcategory" + other: "+ %{count} subcategories" select_kit: default_header_text: Select...