FEATURE: Show remaining count in category-drop (#25938)
When "lazy load categories" is enabled, the CategoryDrop component will render at most 15 categories. If there are more categories, a "Show more" link pointing to the categories page will be displayed.
This commit is contained in:
parent
821402d024
commit
e89bdea830
|
@ -400,6 +400,7 @@ export default class Category extends RestModel {
|
|||
categories: result["categories"].map((category) =>
|
||||
Site.current().updateCategory(category)
|
||||
),
|
||||
categoriesCount: result["categories_count"],
|
||||
};
|
||||
} else {
|
||||
return result["categories"].map((category) =>
|
||||
|
|
|
@ -68,7 +68,7 @@ module("Integration | Component | select-kit/category-drop", function (hooks) {
|
|||
const text = this.subject.header().label();
|
||||
assert.strictEqual(
|
||||
text,
|
||||
I18n.t("category.all").toLowerCase(),
|
||||
I18n.t("categories.categories_label"),
|
||||
"it uses the noneLabel"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { inject as service } from "@ember/service";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import {
|
||||
ALL_CATEGORIES_ID,
|
||||
NO_CATEGORIES_ID,
|
||||
} from "select-kit/components/category-drop";
|
||||
|
||||
export default class CategoryDropMoreCollection extends Component {
|
||||
@service site;
|
||||
|
||||
tagName = "";
|
||||
|
||||
get moreCount() {
|
||||
if (!this.args.selectKit.totalCount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const currentCount = this.args.collection.content.filter(
|
||||
(category) =>
|
||||
category.id !== NO_CATEGORIES_ID && category.id !== ALL_CATEGORIES_ID
|
||||
).length;
|
||||
|
||||
return this.args.selectKit.totalCount - currentCount;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.moreCount}}
|
||||
<div class="category-drop-footer">
|
||||
<span>{{i18n
|
||||
"categories.plus_more_count"
|
||||
(hash count=this.moreCount)
|
||||
}}</span>
|
||||
<LinkTo @route="discovery.categories">
|
||||
{{i18n "categories.view_all"}}
|
||||
{{icon "external-link-alt"}}
|
||||
</LinkTo>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -9,18 +9,22 @@ import DiscourseURL, {
|
|||
} from "discourse/lib/url";
|
||||
import Category from "discourse/models/category";
|
||||
import I18n from "discourse-i18n";
|
||||
import CategoryDropMoreCollection from "select-kit/components/category-drop-more-collection";
|
||||
import CategoryRow from "select-kit/components/category-row";
|
||||
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||
import { MAIN_COLLECTION } from "select-kit/components/select-kit";
|
||||
|
||||
export const NO_CATEGORIES_ID = "no-categories";
|
||||
export const ALL_CATEGORIES_ID = "all-categories";
|
||||
|
||||
const MORE_COLLECTION = "MORE_COLLECTION";
|
||||
|
||||
export default ComboBoxComponent.extend({
|
||||
pluginApiIdentifiers: ["category-drop"],
|
||||
classNames: ["category-drop"],
|
||||
value: readOnly("category.id"),
|
||||
content: readOnly("categoriesWithShortcuts.[]"),
|
||||
noCategoriesLabel: I18n.t("categories.no_subcategory"),
|
||||
noCategoriesLabel: I18n.t("categories.no_subcategories"),
|
||||
navigateToEdit: false,
|
||||
editingCategory: false,
|
||||
editingCategoryTab: null,
|
||||
|
@ -44,6 +48,18 @@ export default ComboBoxComponent.extend({
|
|||
allowUncategorized: "allowUncategorized",
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.insertAfterCollection(MAIN_COLLECTION, MORE_COLLECTION);
|
||||
},
|
||||
|
||||
modifyComponentForCollection(collection) {
|
||||
if (collection === MORE_COLLECTION) {
|
||||
return CategoryDropMoreCollection;
|
||||
}
|
||||
},
|
||||
|
||||
modifyComponentForRow() {
|
||||
return CategoryRow;
|
||||
},
|
||||
|
@ -85,6 +101,12 @@ export default ComboBoxComponent.extend({
|
|||
});
|
||||
}
|
||||
|
||||
// If there is a single shortcut, we can have a single "remove filter"
|
||||
// option
|
||||
if (shortcuts.length === 1 && shortcuts[0].id === ALL_CATEGORIES_ID) {
|
||||
shortcuts[0].name = I18n.t("categories.remove_filter");
|
||||
}
|
||||
|
||||
return shortcuts;
|
||||
}
|
||||
),
|
||||
|
@ -96,9 +118,17 @@ export default ComboBoxComponent.extend({
|
|||
|
||||
modifyNoSelection() {
|
||||
if (this.selectKit.options.noSubcategories) {
|
||||
return this.defaultItem(NO_CATEGORIES_ID, this.noCategoriesLabel);
|
||||
return this.defaultItem(
|
||||
NO_CATEGORIES_ID,
|
||||
I18n.t("categories.no_subcategories")
|
||||
);
|
||||
} else {
|
||||
return this.defaultItem(ALL_CATEGORIES_ID, this.allCategoriesLabel);
|
||||
return this.defaultItem(
|
||||
ALL_CATEGORIES_ID,
|
||||
this.selectKit.options.subCategory
|
||||
? I18n.t("categories.subcategories_label")
|
||||
: I18n.t("categories.categories_label")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -149,16 +179,23 @@ export default ComboBoxComponent.extend({
|
|||
parentCategoryId = -1;
|
||||
}
|
||||
|
||||
const results = (
|
||||
await Category.asyncSearch(filter, {
|
||||
const result = await Category.asyncSearch(filter, {
|
||||
parentCategoryId,
|
||||
includeUncategorized: this.siteSettings.allow_uncategorized_topics,
|
||||
includeAncestors: true,
|
||||
limit: 15,
|
||||
})
|
||||
).categories;
|
||||
// Show all categories if possible (up to 18), otherwise show just
|
||||
// first 15 and let CategoryDropMoreCollection show the "show more" link
|
||||
limit: 18,
|
||||
});
|
||||
|
||||
return this.shortcuts.concat(results);
|
||||
const categories =
|
||||
result.categoriesCount > 18
|
||||
? result.categories.slice(0, 15)
|
||||
: result.categories;
|
||||
|
||||
this.selectKit.totalCount = result.categoriesCount;
|
||||
|
||||
return this.shortcuts.concat(categories);
|
||||
}
|
||||
|
||||
const opts = {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { computed } from "@ember/object";
|
||||
import { equal, readOnly } from "@ember/object/computed";
|
||||
import { i18n, setting } from "discourse/lib/computed";
|
||||
import { readOnly } from "@ember/object/computed";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import DiscourseURL, { getCategoryAndTagUrl } from "discourse/lib/url";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import I18n from "discourse-i18n";
|
||||
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||
import FilterForMore from "select-kit/components/filter-for-more";
|
||||
import { MAIN_COLLECTION } from "select-kit/components/select-kit";
|
||||
|
@ -10,7 +11,8 @@ import TagsMixin from "select-kit/mixins/tags";
|
|||
|
||||
export const NO_TAG_ID = "no-tags";
|
||||
export const ALL_TAGS_ID = "all-tags";
|
||||
export const NONE_TAG_ID = "none";
|
||||
|
||||
export const NONE_TAG = "none";
|
||||
|
||||
const MORE_TAGS_COLLECTION = "MORE_TAGS_COLLECTION";
|
||||
|
||||
|
@ -45,8 +47,6 @@ export default ComboBoxComponent.extend(TagsMixin, {
|
|||
autoInsertNoneItem: false,
|
||||
},
|
||||
|
||||
noTagsSelected: equal("tagId", NONE_TAG_ID),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
|
@ -68,21 +68,19 @@ export default ComboBoxComponent.extend(TagsMixin, {
|
|||
},
|
||||
|
||||
modifyNoSelection() {
|
||||
if (this.noTagsSelected) {
|
||||
return this.defaultItem(NO_TAG_ID, this.noTagsLabel);
|
||||
if (this.tagId === NONE_TAG) {
|
||||
return this.defaultItem(NO_TAG_ID, I18n.t("tagging.selector_no_tags"));
|
||||
} else {
|
||||
return this.defaultItem(ALL_TAGS_ID, this.allTagsLabel);
|
||||
return this.defaultItem(ALL_TAGS_ID, I18n.t("tagging.selector_tags"));
|
||||
}
|
||||
},
|
||||
|
||||
modifySelection(content) {
|
||||
if (this.tagId) {
|
||||
if (this.noTagsSelected) {
|
||||
content = this.defaultItem(NO_TAG_ID, this.noTagsLabel);
|
||||
} else {
|
||||
if (this.tagId === NONE_TAG) {
|
||||
content = this.defaultItem(NO_TAG_ID, I18n.t("tagging.selector_no_tags"));
|
||||
} else if (this.tagId) {
|
||||
content = this.defaultItem(this.tagId, this.tagId);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
@ -91,10 +89,6 @@ export default ComboBoxComponent.extend(TagsMixin, {
|
|||
return this.tagId ? `tag-${this.tagId}` : "tag_all";
|
||||
}),
|
||||
|
||||
allTagsLabel: i18n("tagging.selector_all_tags"),
|
||||
|
||||
noTagsLabel: i18n("tagging.selector_no_tags"),
|
||||
|
||||
modifyComponentForRow() {
|
||||
return "tag-row";
|
||||
},
|
||||
|
@ -102,15 +96,24 @@ export default ComboBoxComponent.extend(TagsMixin, {
|
|||
shortcuts: computed("tagId", function () {
|
||||
const shortcuts = [];
|
||||
|
||||
if (this.tagId !== NONE_TAG_ID) {
|
||||
if (this.tagId !== NONE_TAG) {
|
||||
shortcuts.push({
|
||||
id: NO_TAG_ID,
|
||||
name: this.noTagsLabel,
|
||||
name: I18n.t("tagging.selector_no_tags"),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.tagId) {
|
||||
shortcuts.push({ id: ALL_TAGS_ID, name: this.allTagsLabel });
|
||||
shortcuts.push({
|
||||
id: ALL_TAGS_ID,
|
||||
name: I18n.t("tagging.selector_all_tags"),
|
||||
});
|
||||
}
|
||||
|
||||
// If there is a single shortcut, we can have a single "remove filter"
|
||||
// option
|
||||
if (shortcuts.length === 1 && shortcuts[0].id === ALL_TAGS_ID) {
|
||||
shortcuts[0].name = I18n.t("tagging.selector_remove_filter");
|
||||
}
|
||||
|
||||
return shortcuts;
|
||||
|
@ -173,7 +176,7 @@ export default ComboBoxComponent.extend(TagsMixin, {
|
|||
actions: {
|
||||
onChange(tagId, tag) {
|
||||
if (tagId === NO_TAG_ID) {
|
||||
tagId = NONE_TAG_ID;
|
||||
tagId = NONE_TAG;
|
||||
} else if (tagId === ALL_TAGS_ID) {
|
||||
tagId = null;
|
||||
} else if (tag && tag.targetTagId) {
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
{{#if this.isTag}}
|
||||
{{discourse-tag this.rowValue noHref=true count=this.item.count}}
|
||||
{{else}}
|
||||
<span class="name">{{this.item.name}}</span>
|
||||
{{/if}}
|
|
@ -1,5 +1,11 @@
|
|||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
|
||||
|
||||
export default SelectKitRowComponent.extend({
|
||||
classNames: ["tag-row"],
|
||||
|
||||
@discourseComputed("item")
|
||||
isTag(item) {
|
||||
return item.id !== "no-tags" && item.id !== "all-tags";
|
||||
},
|
||||
});
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
}
|
||||
|
||||
.category-drop-header {
|
||||
&[data-value=""] {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
|
||||
&.is-none .selected-name {
|
||||
color: inherit;
|
||||
}
|
||||
|
@ -42,6 +46,22 @@
|
|||
font-size: var(--font-down-1);
|
||||
}
|
||||
}
|
||||
|
||||
.category-drop-footer {
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--primary-low);
|
||||
display: flex;
|
||||
font-size: var(--font-down-1);
|
||||
height: 30px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
a,
|
||||
span {
|
||||
color: var(--primary-high);
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-drop-header {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -392,6 +392,8 @@ class CategoriesController < ApplicationController
|
|||
|
||||
categories = categories.where(parent_category_id: nil) if !include_subcategories
|
||||
|
||||
categories_count = categories.count
|
||||
|
||||
categories = categories.limit(limit || MAX_CATEGORIES_LIMIT)
|
||||
|
||||
Category.preload_user_fields!(guardian, categories)
|
||||
|
@ -409,18 +411,17 @@ class CategoriesController < ApplicationController
|
|||
]
|
||||
end
|
||||
|
||||
response = {
|
||||
categories_count: categories_count,
|
||||
categories: serialize_data(categories, SiteCategorySerializer, scope: guardian),
|
||||
}
|
||||
|
||||
if include_ancestors
|
||||
ancestors = Category.secured(guardian).ancestors_of(categories.map(&:id))
|
||||
|
||||
render_json_dump(
|
||||
{
|
||||
categories: serialize_data(categories, SiteCategorySerializer, scope: guardian),
|
||||
ancestors: serialize_data(ancestors, SiteCategorySerializer, scope: guardian),
|
||||
},
|
||||
)
|
||||
else
|
||||
render_serialized(categories, SiteCategorySerializer, root: :categories, scope: guardian)
|
||||
response[:ancestors] = serialize_data(ancestors, SiteCategorySerializer, scope: guardian)
|
||||
end
|
||||
|
||||
render_json_dump(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1034,9 +1034,13 @@ en:
|
|||
"15": "Drafts"
|
||||
|
||||
categories:
|
||||
all: "all categories"
|
||||
all_subcategories: "all"
|
||||
no_subcategory: "none"
|
||||
categories_label: "categories"
|
||||
subcategories_label: "subcategories"
|
||||
all_subcategories: "all subcategories"
|
||||
no_subcategories: "no subcategories"
|
||||
remove_filter: "remove filter"
|
||||
plus_more_count: "+%{count} more"
|
||||
view_all: "view all"
|
||||
category: "Category"
|
||||
category_list: "Display category list"
|
||||
reorder:
|
||||
|
@ -4353,8 +4357,10 @@ en:
|
|||
tagging:
|
||||
all_tags: "All tags"
|
||||
other_tags: "Other Tags"
|
||||
selector_tags: "tags"
|
||||
selector_all_tags: "all tags"
|
||||
selector_no_tags: "no tags"
|
||||
selector_remove_filter: "remove filter"
|
||||
tags: "Tags"
|
||||
choose_for_topic: "optional tags"
|
||||
choose_for_topic_required:
|
||||
|
|
|
@ -61,7 +61,7 @@ describe "Navigating with breadcrumbs", type: :system do
|
|||
expect(discovery.topic_list).to have_topic(c1_topic_tagged)
|
||||
expect(discovery.topic_list).to have_topics(count: 2)
|
||||
|
||||
expect(discovery.tag_drop).to have_selected_name("all tags")
|
||||
expect(discovery.tag_drop).to have_selected_name("tags")
|
||||
discovery.tag_drop.select_row_by_value(tag.name)
|
||||
|
||||
expect(discovery.topic_list).to have_topics(count: 1)
|
||||
|
@ -74,8 +74,8 @@ describe "Navigating with breadcrumbs", type: :system do
|
|||
expect(discovery.topic_list).to have_topic(c3_topic_tagged)
|
||||
expect(discovery.topic_list).to have_topics(count: 2)
|
||||
|
||||
expect(discovery.subcategory_drop).to have_selected_name("none")
|
||||
expect(discovery.tag_drop).to have_selected_name("all tags")
|
||||
expect(discovery.subcategory_drop).to have_selected_name("no subcategories")
|
||||
expect(discovery.tag_drop).to have_selected_name("tags")
|
||||
discovery.tag_drop.select_row_by_value(tag.name)
|
||||
|
||||
expect(page).to have_current_path(
|
||||
|
|
Loading…
Reference in New Issue