FEATURE: Introduce an optimized style for category page (#29239)
The new style is called `categories_only_optimized` and it is designed to show only the parent categories, without any subcategories. This works best for communities with many categories (over a thousand).
This commit is contained in:
parent
cb5e0d358d
commit
d37a0d401c
|
@ -0,0 +1,83 @@
|
|||
<PluginOutlet
|
||||
@name="categories-only-wrapper"
|
||||
@outletArgs={{hash categories=this.categories}}
|
||||
>
|
||||
{{#if this.categories}}
|
||||
{{#if this.filteredCategories}}
|
||||
<table
|
||||
class="category-list optimized {{if this.showTopics 'with-topics'}}"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="category"><span
|
||||
role="heading"
|
||||
aria-level="2"
|
||||
id="categories-only-category"
|
||||
>{{i18n "categories.category"}}</span></th>
|
||||
<th class="topics">{{i18n "categories.topics"}}</th>
|
||||
{{#if this.showTopics}}
|
||||
<th class="latest">{{i18n "categories.latest"}}</th>
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody aria-labelledby="categories-only-category">
|
||||
{{#each this.categories as |category|}}
|
||||
<ParentCategoryRowOptimized
|
||||
@category={{category}}
|
||||
@showTopics={{this.showTopics}}
|
||||
/>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.mutedCategories}}
|
||||
<div class="muted-categories">
|
||||
<a
|
||||
href
|
||||
class="muted-categories-link"
|
||||
{{on "click" this.toggleShowMuted}}
|
||||
>
|
||||
<h3 class="muted-categories-heading">{{i18n "categories.muted"}}</h3>
|
||||
{{#if this.mutedToggleIcon}}
|
||||
{{d-icon this.mutedToggleIcon}}
|
||||
{{/if}}
|
||||
</a>
|
||||
<table
|
||||
class="category-list optimized
|
||||
{{if this.showTopics 'with-topics'}}
|
||||
{{unless this.showMutedCategories 'hidden'}}"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="category"><span
|
||||
role="heading"
|
||||
aria-level="2"
|
||||
id="categories-only-category-muted"
|
||||
>{{i18n "categories.category"}}</span></th>
|
||||
<th class="topics">{{i18n "categories.topics"}}</th>
|
||||
{{#if this.showTopics}}
|
||||
<th class="latest">{{i18n "categories.latest"}}</th>
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody aria-labelledby="categories-only-category-muted">
|
||||
{{#each this.categories as |category|}}
|
||||
<ParentCategoryRowOptimized
|
||||
@category={{category}}
|
||||
@showTopics={{this.showTopics}}
|
||||
@listType="muted"
|
||||
/>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</PluginOutlet>
|
||||
|
||||
<PluginOutlet
|
||||
@name="below-categories-only"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash categories=this.categories showTopics=this.showTopics}}
|
||||
/>
|
|
@ -0,0 +1,56 @@
|
|||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
@tagName("")
|
||||
export default class CategoriesOnlyOptimized extends Component {
|
||||
showMuted = false;
|
||||
|
||||
@discourseComputed("showMutedCategories", "filteredCategories.length")
|
||||
mutedToggleIcon(showMutedCategories, filteredCategoriesLength) {
|
||||
if (filteredCategoriesLength === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showMutedCategories) {
|
||||
return "minus";
|
||||
}
|
||||
|
||||
return "plus";
|
||||
}
|
||||
|
||||
@discourseComputed("showMuted", "filteredCategories.length")
|
||||
showMutedCategories(showMuted, filteredCategoriesLength) {
|
||||
return showMuted || filteredCategoriesLength === 0;
|
||||
}
|
||||
|
||||
@discourseComputed("categories", "categories.length")
|
||||
filteredCategories(categories, categoriesLength) {
|
||||
if (!categories || categoriesLength === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return categories.filter((cat) => !cat.isHidden);
|
||||
}
|
||||
|
||||
@discourseComputed("categories", "categories.length")
|
||||
mutedCategories(categories, categoriesLength) {
|
||||
if (!categories || categoriesLength === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// hide in single category pages
|
||||
if (categories.firstObject.parent_category_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return categories.filterBy("hasMuted");
|
||||
}
|
||||
|
||||
@action
|
||||
toggleShowMuted(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("showMuted");
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import CategoriesAndTopTopics from "discourse/components/categories-and-top-topi
|
|||
import CategoriesBoxes from "discourse/components/categories-boxes";
|
||||
import CategoriesBoxesWithTopics from "discourse/components/categories-boxes-with-topics";
|
||||
import CategoriesOnly from "discourse/components/categories-only";
|
||||
import CategoriesOnlyOptimized from "discourse/components/categories-only-optimized";
|
||||
import CategoriesWithFeaturedTopics from "discourse/components/categories-with-featured-topics";
|
||||
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
||||
import LoadMore from "discourse/components/load-more";
|
||||
|
@ -15,6 +16,7 @@ import SubcategoriesWithFeaturedTopics from "discourse/components/subcategories-
|
|||
const mobileCompatibleViews = [
|
||||
"categories_with_featured_topics",
|
||||
"subcategories_with_featured_topics",
|
||||
"categories_only_optimized",
|
||||
];
|
||||
|
||||
const subcategoryComponents = {
|
||||
|
@ -31,6 +33,7 @@ const globalComponents = {
|
|||
categories_boxes_with_topics: CategoriesBoxesWithTopics,
|
||||
categories_boxes: CategoriesBoxes,
|
||||
categories_only: CategoriesOnly,
|
||||
categories_only_optimized: CategoriesOnlyOptimized,
|
||||
categories_with_featured_topics: CategoriesWithFeaturedTopics,
|
||||
subcategories_with_featured_topics: SubcategoriesWithFeaturedTopics,
|
||||
};
|
||||
|
@ -81,7 +84,12 @@ export default class CategoriesDisplay extends Component {
|
|||
}
|
||||
|
||||
get canLoadMore() {
|
||||
return this.site.lazy_load_categories && this.args.loadMore;
|
||||
return (
|
||||
this.args.loadMore &&
|
||||
(this.site.lazy_load_categories ||
|
||||
this.siteSettings.desktop_category_page_style ===
|
||||
"categories_only_optimized")
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
{{#unless this.isHidden}}
|
||||
<PluginOutlet
|
||||
@name="category-list-above-each-category"
|
||||
@outletArgs={{hash category=this.category}}
|
||||
/>
|
||||
<tr
|
||||
data-category-id={{this.category.id}}
|
||||
data-notification-level={{this.category.notificationLevelString}}
|
||||
class="{{if
|
||||
this.category.description_excerpt
|
||||
'has-description'
|
||||
'no-description'
|
||||
}}
|
||||
{{if this.category.uploaded_logo.url 'has-logo' 'no-logo'}}"
|
||||
>
|
||||
<td
|
||||
class="category {{if this.isMuted 'muted'}}"
|
||||
style={{category-color-variable this.category.color}}
|
||||
>
|
||||
<CategoryTitleLink @category={{this.category}} />
|
||||
<PluginOutlet
|
||||
@name="below-category-title-link"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash category=this.category}}
|
||||
/>
|
||||
{{#if this.category.description_excerpt}}
|
||||
<div class="category-description">
|
||||
{{dir-span this.category.description_excerpt htmlSafe="true"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
<td class="topics">
|
||||
<div title={{this.category.statTitle}}>{{html-safe
|
||||
this.category.stat
|
||||
}}</div>
|
||||
<CategoryUnread
|
||||
@category={{this.category}}
|
||||
@tagName="div"
|
||||
@unreadTopicsCount={{this.unreadTopicsCount}}
|
||||
@newTopicsCount={{this.newTopicsCount}}
|
||||
class="unread-new"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{{/unless}}
|
|
@ -0,0 +1,3 @@
|
|||
import CategoryListItem from "discourse/components/category-list-item";
|
||||
|
||||
export default class ParentCategoryRowOptimized extends CategoryListItem {}
|
|
@ -0,0 +1,36 @@
|
|||
{{#if this.categories}}
|
||||
{{#if this.filteredCategories}}
|
||||
<div class="category-list optimized {{if this.showTopics 'with-topics'}}">
|
||||
{{#each this.filteredCategories as |c|}}
|
||||
<ParentCategoryRowOptimized
|
||||
@category={{c}}
|
||||
@showTopics={{this.showTopics}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.mutedCategories}}
|
||||
<div class="muted-categories">
|
||||
<a href class="muted-categories-link" {{on "click" this.toggleShowMuted}}>
|
||||
<h3 class="muted-categories-heading">{{i18n "categories.muted"}}</h3>
|
||||
{{#if this.mutedToggleIcon}}
|
||||
{{d-icon this.mutedToggleIcon}}
|
||||
{{/if}}
|
||||
</a>
|
||||
<div
|
||||
class="category-list optimized
|
||||
{{if this.showTopics 'with-topics'}}
|
||||
{{unless this.showMutedCategories 'hidden'}}"
|
||||
>
|
||||
{{#each this.mutedCategories as |c|}}
|
||||
<ParentCategoryRowOptimized
|
||||
@category={{c}}
|
||||
@showTopics={{this.showTopics}}
|
||||
@listType="muted"
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
|
@ -0,0 +1,29 @@
|
|||
{{#unless this.isHidden}}
|
||||
<PluginOutlet
|
||||
@name="category-list-above-each-category"
|
||||
@outletArgs={{hash category=this.category}}
|
||||
/>
|
||||
<div
|
||||
data-category-id={{this.category.id}}
|
||||
data-notification-level={{this.category.notificationLevelString}}
|
||||
style={{border-color this.category.color}}
|
||||
class="category-list-item category {{if this.isMuted 'muted'}}"
|
||||
>
|
||||
<table class="topic-list">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="main-link">
|
||||
<CategoryTitleLink @category={{this.category}} />
|
||||
</th>
|
||||
</tr>
|
||||
{{#if this.category.description_excerpt}}
|
||||
<tr class="category-description">
|
||||
<td colspan="3">
|
||||
{{html-safe this.category.description_excerpt}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/unless}}
|
|
@ -144,6 +144,16 @@
|
|||
border-left: 6px solid;
|
||||
}
|
||||
}
|
||||
|
||||
&.optimized {
|
||||
tr {
|
||||
border: none;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 10px 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-list-item.category {
|
||||
|
|
|
@ -140,7 +140,8 @@ class CategoryList
|
|||
end
|
||||
|
||||
page = [1, @options[:page].to_i].max
|
||||
if @guardian.can_lazy_load_categories?
|
||||
if SiteSetting.desktop_category_page_style == "categories_only_optimized" ||
|
||||
@guardian.can_lazy_load_categories?
|
||||
query = query.limit(CATEGORIES_PER_PAGE).offset((page - 1) * CATEGORIES_PER_PAGE)
|
||||
elsif page > 1
|
||||
# Pagination is supported only when lazy load is enabled. If it is not,
|
||||
|
|
|
@ -10,6 +10,7 @@ class CategoryPageStyle < EnumSiteSetting
|
|||
def self.values
|
||||
@values ||= [
|
||||
{ name: "category_page_style.categories_only", value: "categories_only" },
|
||||
{ name: "category_page_style.categories_only_optimized", value: "categories_only_optimized" },
|
||||
{
|
||||
name: "category_page_style.categories_with_featured_topics",
|
||||
value: "categories_with_featured_topics",
|
||||
|
|
|
@ -2513,6 +2513,7 @@ en:
|
|||
|
||||
category_page_style:
|
||||
categories_only: "Categories Only"
|
||||
categories_only_optimized: "Categories Only (Optimized)"
|
||||
categories_with_featured_topics: "Categories with Featured Topics"
|
||||
categories_and_latest_topics: "Categories and Latest Topics"
|
||||
categories_and_latest_topics_created_date: "Categories and Latest Topics (sort by topic created date)"
|
||||
|
|
|
@ -416,7 +416,19 @@ RSpec.describe CategoriesController do
|
|||
expect(response.parsed_body["category_list"]["categories"].count).to eq(2)
|
||||
end
|
||||
|
||||
it "does not paginate results when lazy_load_categories is disabled" do
|
||||
it "paginates results wihen desktop_category_page_style is categories_only_optimized" do
|
||||
SiteSetting.desktop_category_page_style = "categories_only_optimized"
|
||||
|
||||
stub_const(CategoryList, "CATEGORIES_PER_PAGE", 2) { get "/categories.json?page=1" }
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["category_list"]["categories"].count).to eq(2)
|
||||
|
||||
stub_const(CategoryList, "CATEGORIES_PER_PAGE", 2) { get "/categories.json?page=2" }
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["category_list"]["categories"].count).to eq(2)
|
||||
end
|
||||
|
||||
it "does not paginate results by default" do
|
||||
stub_const(CategoryList, "CATEGORIES_PER_PAGE", 2) { get "/categories.json?page=1" }
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["category_list"]["categories"].count).to eq(4)
|
||||
|
|
Loading…
Reference in New Issue