FEATURE: anonymous sidebar categories and tags (#18038)

Default sidebar tags for not authenticated users can be defined in admin panel. Otherwise, top 5 categories and tags are taken.

Optionally, if categories are set up in permanent order, then the first 5 categories are taken.
This commit is contained in:
Krzysztof Kotlarek 2022-08-23 18:20:46 +10:00 committed by GitHub
parent 1d1a7db182
commit 2d58996a3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 292 additions and 21 deletions

View File

@ -0,0 +1,22 @@
<Sidebar::Section
@sectionName="categories"
@headerLinkText={{i18n "sidebar.sections.categories.header_link_text"}} >
{{#each this.sectionLinks as |sectionLink|}}
<Sidebar::SectionLink
@route={{sectionLink.route}}
@title={{sectionLink.title}}
@content={{sectionLink.text}}
@currentWhen={{sectionLink.currentWhen}}
@model={{sectionLink.model}}
@prefixType={{sectionLink.prefixType}}
@prefixValue={{sectionLink.prefixValue}}
@prefixColor={{sectionLink.prefixColor}} >
</Sidebar::SectionLink>
{{/each}}
<Sidebar::SectionLink
@linkName="more-categories"
@content={{i18n "sidebar.more"}}
@route="discovery.categories"
/>
</Sidebar::Section>

View File

@ -0,0 +1,36 @@
import { cached } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import CategorySectionLink from "discourse/lib/sidebar/user/categories-section/category-section-link";
export default class SidebarAnonymousCategoriesSection extends Component {
@service topicTrackingState;
@service site;
@service siteSettings;
@cached
get sectionLinks() {
let categories = this.site.categoriesList;
if (this.siteSettings.default_sidebar_categories) {
const defaultCategoryIds = this.siteSettings.default_sidebar_categories
.split("|")
.map((categoryId) => parseInt(categoryId, 10));
categories = categories.filter((category) =>
defaultCategoryIds.includes(category.id)
);
} else {
categories = categories
.filter((category) => !category.parent_category_id)
.slice(0, 5);
}
return categories.map((category) => {
return new CategorySectionLink({
category,
topicTrackingState: this.topicTrackingState,
});
});
}
}

View File

@ -1,3 +1,6 @@
<div class="sidebar-sections sidebar-sections-anonymous">
{{!-- add sections for anonymous user --}}
<Sidebar::Anonymous::CategoriesSection />
{{#if this.siteSettings.tagging_enabled}}
<Sidebar::Anonymous::TagsSection />
{{/if}}
</div>

View File

@ -1,3 +1,6 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class SidebarAnonymousSections extends Component {}
export default class SidebarAnonymousSections extends Component {
@service siteSettings;
}

View File

@ -0,0 +1,18 @@
<Sidebar::Section
@sectionName="tags"
@headerLinkText={{i18n "sidebar.sections.tags.header_link_text"}} >
{{#each this.sectionLinks as |sectionLink|}}
<Sidebar::SectionLink
@route={{sectionLink.route}}
@content={{sectionLink.text}}
@currentWhen={{sectionLink.currentWhen}}
@models={{sectionLink.models}} >
</Sidebar::SectionLink>
{{/each}}
<Sidebar::SectionLink
@linkName="more-tags"
@content={{i18n "sidebar.more"}}
@route="tags"
/>
</Sidebar::Section>

View File

@ -0,0 +1,27 @@
import { cached } from "@glimmer/tracking";
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import TagSectionLink from "discourse/lib/sidebar/user/tags-section/tag-section-link";
export default class SidebarAnonymousTagsSection extends Component {
@service router;
@service topicTrackingState;
@service site;
@cached
get sectionLinks() {
let tags;
if (this.site.anonymous_default_sidebar_tags) {
tags = this.site.anonymous_default_sidebar_tags;
} else {
tags = this.site.top_tags.slice(0, 5);
}
return tags.map((tagName) => {
return new TagSectionLink({
tagName,
topicTrackingState: this.topicTrackingState,
});
});
}
}

View File

@ -38,14 +38,14 @@ export default class SidebarUserTagsSection extends Component {
if (tag.pm_only) {
links.push(
new PMTagSectionLink({
tag,
tagName: tag.name,
currentUser: this.currentUser,
})
);
} else {
links.push(
new TagSectionLink({
tag,
tagName: tag.name,
topicTrackingState: this.topicTrackingState,
})
);

View File

@ -1,15 +1,15 @@
export default class PMTagSectionLink {
constructor({ tag, currentUser }) {
this.tag = tag;
constructor({ tagName, currentUser }) {
this.tagName = tagName;
this.currentUser = currentUser;
}
get name() {
return this.tag.name;
return this.tagName;
}
get models() {
return [this.currentUser, this.tag.name];
return [this.currentUser, this.tagName];
}
get route() {
@ -17,6 +17,6 @@ export default class PMTagSectionLink {
}
get text() {
return this.tag.name;
return this.tagName;
}
}

View File

@ -8,8 +8,8 @@ export default class TagSectionLink {
@tracked totalUnread = 0;
@tracked totalNew = 0;
constructor({ tag, topicTrackingState }) {
this.tagName = tag.name;
constructor({ tagName, topicTrackingState }) {
this.tagName = tagName;
this.topicTrackingState = topicTrackingState;
this.refreshCounts();
}

View File

@ -0,0 +1,53 @@
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import {
acceptance,
exists,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
acceptance("Sidebar - Anonymous Categories Section", function (needs) {
needs.settings({
enable_experimental_sidebar_hamburger: true,
enable_sidebar: true,
suppress_uncategorized_badge: false,
});
test("category section links", async function (assert) {
await visit("/");
const categories = queryAll(
".sidebar-section-categories .sidebar-section-link-wrapper"
);
assert.strictEqual(categories.length, 6);
assert.strictEqual(categories[0].textContent.trim(), "support");
assert.strictEqual(categories[1].textContent.trim(), "bug");
assert.strictEqual(categories[2].textContent.trim(), "feature");
assert.strictEqual(categories[3].textContent.trim(), "dev");
assert.strictEqual(categories[4].textContent.trim(), "ux");
assert.ok(
exists("a.sidebar-section-link-more-categories"),
"more link is visible"
);
});
test("default sidebar categories", async function (assert) {
this.siteSettings.default_sidebar_categories = "3|13|1";
await visit("/");
const categories = queryAll(
".sidebar-section-categories .sidebar-section-link-wrapper"
);
assert.strictEqual(categories.length, 4);
assert.strictEqual(categories[0].textContent.trim(), "bug");
assert.strictEqual(categories[1].textContent.trim(), "meta");
assert.strictEqual(categories[2].textContent.trim(), "blog");
assert.ok(
exists("a.sidebar-section-link-more-categories"),
"more link is visible"
);
});
});

View File

@ -0,0 +1,89 @@
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import {
acceptance,
exists,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
acceptance("Sidebar - Anonymous Tags Section", function (needs) {
needs.settings({
enable_experimental_sidebar_hamburger: true,
enable_sidebar: true,
suppress_uncategorized_badge: false,
tagging_enabled: true,
});
needs.site({
top_tags: ["design", "development", "fun"],
});
test("tag section links", async function (assert) {
await visit("/");
const categories = queryAll(
".sidebar-section-tags .sidebar-section-link-wrapper"
);
assert.strictEqual(categories.length, 4);
assert.strictEqual(categories[0].textContent.trim(), "design");
assert.strictEqual(categories[1].textContent.trim(), "development");
assert.strictEqual(categories[2].textContent.trim(), "fun");
assert.ok(
exists("a.sidebar-section-link-more-tags"),
"more link is visible"
);
});
});
acceptance("Sidebar - Anonymous Tags Section - default tags", function (needs) {
needs.settings({
enable_experimental_sidebar_hamburger: true,
enable_sidebar: true,
suppress_uncategorized_badge: false,
tagging_enabled: true,
});
needs.site({
top_tags: ["design", "development", "fun"],
anonymous_default_sidebar_tags: ["random", "meta"],
});
test("tag section links", async function (assert) {
await visit("/");
const categories = queryAll(
".sidebar-section-tags .sidebar-section-link-wrapper"
);
assert.strictEqual(categories.length, 3);
assert.strictEqual(categories[0].textContent.trim(), "random");
assert.strictEqual(categories[1].textContent.trim(), "meta");
assert.ok(
exists("a.sidebar-section-link-more-tags"),
"more link is visible"
);
});
});
acceptance(
"Sidebar - Anonymous Tags Section - Tagging disabled",
function (needs) {
needs.settings({
enable_experimental_sidebar_hamburger: true,
enable_sidebar: true,
suppress_uncategorized_badge: false,
tagging_enabled: false,
});
needs.site({
top_tags: ["design", "development", "fun"],
});
test("tag section links", async function (assert) {
await visit("/");
assert.ok(!exists(".sidebar-section-tags"), "section is not visible");
});
}
);

View File

@ -1670,6 +1670,15 @@ class User < ActiveRecord::Base
categories_ids
end
def sidebar_tags
return custom_sidebar_tags if custom_sidebar_tags.present?
if SiteSetting.default_sidebar_tags.present?
tag_names = SiteSetting.default_sidebar_tags.split("|") - DiscourseTagging.hidden_tag_names(guardian)
return Tag.where(name: tag_names)
end
Tag.none
end
protected
def badge_grant
@ -1962,15 +1971,6 @@ class User < ActiveRecord::Base
end
end
def sidebar_tags
return custom_sidebar_tags if custom_sidebar_tags.present?
if SiteSetting.default_sidebar_tags.present?
tag_names = SiteSetting.default_sidebar_tags.split("|") - DiscourseTagging.hidden_tag_names(guardian)
return Tag.where(name: tag_names)
end
[]
end
def self.ensure_consistency!
DB.exec <<~SQL
UPDATE users

View File

@ -35,7 +35,8 @@ class SiteSerializer < ApplicationSerializer
:categories,
:markdown_additional_options,
:displayed_about_plugin_stat_groups,
:show_welcome_topic_banner
:show_welcome_topic_banner,
:anonymous_default_sidebar_tags
)
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
@ -218,6 +219,14 @@ class SiteSerializer < ApplicationSerializer
Site.show_welcome_topic_banner?(scope)
end
def anonymous_default_sidebar_tags
User.new.sidebar_tags.pluck(:name)
end
def include_anonymous_default_sidebar_tags?
SiteSetting.default_sidebar_tags.present?
end
private
def ordered_flags(flags)

View File

@ -124,4 +124,15 @@ RSpec.describe SiteSerializer do
serialized = described_class.new(Site.new(admin_guardian), scope: admin_guardian, root: false).as_json
expect(serialized[:show_welcome_topic_banner]).to eq(true)
end
it 'includes anonymous_default_sidebar_tags' do
Fabricate(:tag, name: "dev")
Fabricate(:tag, name: "random")
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_default_sidebar_tags]).to eq(nil)
SiteSetting.default_sidebar_tags = "dev|random"
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_default_sidebar_tags]).to eq(["dev", "random"])
end
end