Skip to content

Commit e63c903

Browse files
authored
FEATURE: include tag sidebars, migrate to objects setting (#22)
1 parent 41ab679 commit e63c903

File tree

9 files changed

+272
-80
lines changed

9 files changed

+272
-80
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
module.exports = require("@discourse/lint-configs/eslint-theme");
1+
module.exports = require("@discourse/lint-configs/eslint-theme");

README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# discourse-category-sidebars
1+
# Discourse Topic List Sidebars
22

3-
This theme component takes a topic and applies it as a sidebar for a category's topic list. These sidebars are only visible when the browser is 767px or wider (most tablets and monitors).
3+
This theme component takes a topic and applies it as a sidebar for a category or tag's topic list. These sidebars are only visible when the browser is 767px or wider (most tablets and monitors).
44

55
![image](https://user-images.githubusercontent.com/5862206/214492836-6dcb5dde-d0cf-4ee8-9811-2cf135b95c7b.png)
66

@@ -10,27 +10,23 @@ This theme component takes a topic and applies it as a sidebar for a category's
1010

1111
## What can I do with this theme component?
1212

13-
- Choose a topic and display its content as a sidebar for a category.
13+
- Choose a topic and display its content as a sidebar for a category or tag.
1414

15-
- Set a sidebar to be displayed on the /latest, /new, /unread, and /top pages by using `all` as the category name in your settings.
15+
- Set a sidebar to be displayed on the /latest, /new, /unread, and /top pages by using `all` as the sidebar name in your settings.
1616

1717
- Choose for the sidebars to appear on the left or the right of the topic list.
1818

1919
- By default a category's sidebar will also display for all its subcategories unless a subcategory has its own sidebar defined (you can disable this by unchecking the `inherit parent sidebar` setting).
2020

21-
## How do I configure it?
22-
23-
Simply insert the category slug (from the category url, e.g. [example.com/c/](#)**staff**) and the id of the topic (e.g. [example.com/t/example-topic/](#)**57**)
24-
25-
![image](https://user-images.githubusercontent.com/5862206/214492886-e5c35808-6c82-4ade-8eed-062c70851d7f.png)
21+
---
2622

2723
I recommend creating sidebar topics in their respective categories, closing the topic so there are no replies, and unlisting it (so it doesn't appear in the topic list).
2824

2925
Note that you can not use a topic in a private category as a sidebar in a public category (you can, but users without access to that private topic will just see a blank sidebar!).
3026

3127
## Custom CSS
3228

33-
Each category sidebar is wrapped with a class that contains the category slug, so for the staff category that would be `.category-sidebar-staff`. You can use these classes to style the individual sidebars.
29+
Each sidebar is wrapped with a data attribute that contains the category slug or tag name, so for the staff category that would be `[data-category-sidebar="staff"]` for a tag named "important" it would be `[data-tag-sidebar="important"]`. You can use these attributes to style the individual sidebars.
3430

3531
The body tag on pages with sidebars also has a class added so you can use `body.custom-sidebar` to apply styles on all pages that have a sidebar.
3632

about.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "discourse-category-sidebars",
3-
"license_url": "https://github.com/awesomerobot/discourse-category-sidebars/blob/master/LICENSE",
4-
"about_url": "https://github.com/awesomerobot/discourse-category-sidebars/blob/master/README.md",
2+
"name": "Discourse Topic List Sidebars",
3+
"license_url": "https://github.com/discourse/discourse-category-sidebars/blob/master/LICENSE",
4+
"about_url": "https://github.com/discourse/discourse-category-sidebars/blob/master/README.md",
55
"component": true
66
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Component from "@glimmer/component";
2-
import { tracked } from "@glimmer/tracking";
2+
import { cached, tracked } from "@glimmer/tracking";
33
import { concat } from "@ember/helper";
44
import { action } from "@ember/object";
55
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
@@ -9,49 +9,29 @@ import { htmlSafe } from "@ember/template";
99
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
1010
import bodyClass from "discourse/helpers/body-class";
1111
import { ajax } from "discourse/lib/ajax";
12-
import Category from "discourse/models/category";
1312

14-
export default class CategorySidebar extends Component {
13+
export default class TopicListSidebar extends Component {
1514
@service router;
1615
@service siteSettings;
1716
@service site;
17+
1818
@tracked sidebarContent;
1919
@tracked loading = true;
2020

21-
<template>
22-
{{#if this.matchedSetting}}
23-
{{bodyClass "custom-sidebar"}}
24-
{{bodyClass (concat "sidebar-" settings.sidebar_side)}}
25-
<div
26-
class="category-sidebar"
27-
{{didInsert this.fetchPostContent}}
28-
{{didUpdate this.fetchPostContent this.category}}
29-
>
30-
<div class="sticky-sidebar">
31-
<div
32-
class="category-sidebar-contents"
33-
data-category-sidebar={{this.category.slug}}
34-
>
35-
<div class="cooked">
36-
{{#unless this.loading}}
37-
{{htmlSafe this.sidebarContent}}
38-
{{/unless}}
39-
<ConditionalLoadingSpinner @condition={{this.loading}} />
40-
</div>
41-
</div>
42-
</div>
43-
</div>
44-
{{/if}}
45-
</template>
21+
findName(name) {
22+
return settings.sidebars.filter((setting) => setting.name === name);
23+
}
4624

47-
get parsedSetting() {
48-
return settings.setup.split("|").reduce((result, setting) => {
49-
const [category, value] = setting
50-
.split(",")
51-
.map((postID) => postID.trim());
52-
result[category] = { post: value };
53-
return result;
54-
}, {});
25+
findCategory(categoryId) {
26+
return settings.sidebars.filter((setting) =>
27+
setting.category?.includes(categoryId)
28+
);
29+
}
30+
31+
findTag(tagName) {
32+
return settings.sidebars.filter((setting) =>
33+
setting.tag?.includes(tagName)
34+
);
5535
}
5636

5737
get isTopRoute() {
@@ -73,53 +53,96 @@ export default class CategorySidebar extends Component {
7353
return this.router?.currentRoute?.params?.category_slug_path_with_id;
7454
}
7555

76-
get category() {
77-
return this.categorySlugPathWithID
78-
? Category.findBySlugPathWithID(this.categorySlugPathWithID)
79-
: null;
56+
get tagId() {
57+
return this.router?.currentRoute?.params?.tag_id;
8058
}
8159

8260
get matchedSetting() {
83-
if (this.parsedSetting["all"] && this.isTopRoute) {
84-
// if this is a top_menu route, use the "all" setting
85-
return this.parsedSetting["all"];
86-
} else if (this.categorySlugPathWithID) {
87-
const categorySlug = this.category.slug;
88-
const parentCategorySlug = this.category.parentCategory?.slug;
89-
90-
// if there's a setting for this category, use it
91-
if (categorySlug && this.parsedSetting[categorySlug]) {
92-
return this.parsedSetting[categorySlug];
93-
}
61+
const categoryId = this.category?.id;
62+
const parentCategoryId = this.category?.parentCategory?.id;
63+
const tagId = this.tagId;
64+
65+
let result = null;
66+
67+
if (this.findName("all") && this.isTopRoute) {
68+
result = this.findName("all");
69+
}
9470

95-
// if there's not a setting for this category
96-
// check the parent, and maybe use that
71+
if (categoryId) {
9772
if (
9873
settings.inherit_parent_sidebar &&
99-
parentCategorySlug &&
100-
this.parsedSetting[parentCategorySlug]
74+
parentCategoryId &&
75+
this.findCategory(parentCategoryId).length
10176
) {
102-
return this.parsedSetting[parentCategorySlug];
77+
result = this.findCategory(parentCategoryId);
10378
}
79+
if (this.findCategory(categoryId).length) {
80+
result = this.findCategory(categoryId);
81+
}
82+
}
83+
84+
if (tagId) {
85+
result = this.findTag(tagId);
10486
}
87+
88+
return result;
89+
}
90+
91+
@cached
92+
get category() {
93+
return (
94+
this.router.currentRoute.attributes.category ||
95+
this.topic?.get("category")
96+
);
10597
}
10698

10799
@action
108100
async fetchPostContent() {
109101
this.loading = true;
110102

103+
// if multiple, use the last matching setting
104+
const [lastSetting] = [...this.matchedSetting].reverse();
105+
const topicId = lastSetting.topic_id;
106+
111107
try {
112108
if (this.matchedSetting) {
113-
const response = await ajax(`/t/${this.matchedSetting.post}.json`);
109+
const response = await ajax(`/t/${topicId}.json`);
114110
this.sidebarContent = response.post_stream.posts[0].cooked;
115111
}
116112
} catch (error) {
117113
// eslint-disable-next-line no-console
118-
console.error("Error fetching post for category sidebar:", error);
114+
console.error("Error fetching post for sidebar:", error);
119115
} finally {
120116
this.loading = false;
121117
}
122118

123119
return this.sidebarContent;
124120
}
121+
122+
<template>
123+
{{#if this.matchedSetting}}
124+
{{bodyClass "custom-sidebar"}}
125+
{{bodyClass (concat "sidebar-" settings.sidebar_side)}}
126+
<div
127+
class="category-sidebar"
128+
{{didInsert this.fetchPostContent}}
129+
{{didUpdate this.fetchPostContent this.category}}
130+
>
131+
<div class="sticky-sidebar">
132+
<div
133+
class="category-sidebar-contents"
134+
data-category-sidebar={{this.category.slug}}
135+
data-tag-sidebar={{this.tagId}}
136+
>
137+
<div class="cooked">
138+
{{#unless this.loading}}
139+
{{htmlSafe this.sidebarContent}}
140+
{{/unless}}
141+
<ConditionalLoadingSpinner @condition={{this.loading}} />
142+
</div>
143+
</div>
144+
</div>
145+
</div>
146+
{{/if}}
147+
</template>
125148
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{{#unless site.mobileView}}
2-
<CategorySidebar />
2+
<TopicListSidebar />
33
{{/unless}}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default function migrate(settings, helpers) {
2+
const oldSetup = settings.get("setup");
3+
4+
if (oldSetup) {
5+
const newSidebars = oldSetup
6+
.split("|")
7+
.map((sidebar) => {
8+
const [categorySlug, topicId] = sidebar
9+
.split(",")
10+
.map((value) => value.trim());
11+
const sanitizedCategorySlug =
12+
categorySlug.replace(/-/g, " ") || "sidebar";
13+
const parsedTopicId = parseInt(topicId, 10) || 0;
14+
15+
let categoryId = helpers.getCategoryIdByName(sanitizedCategorySlug);
16+
17+
return {
18+
name:
19+
sanitizedCategorySlug.toLowerCase() === "all"
20+
? "all"
21+
: sanitizedCategorySlug,
22+
category: categoryId ? [categoryId] : [],
23+
tag: [],
24+
topic_id: parsedTopicId,
25+
};
26+
})
27+
.filter((item) => item !== null);
28+
29+
settings.set("sidebars", newSidebars);
30+
settings.delete("setup");
31+
}
32+
33+
return settings;
34+
}

settings.yml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
1-
setup:
2-
type: list
3-
default: "staff, 3"
1+
sidebars:
2+
type: objects
3+
default: []
4+
schema:
5+
name: "sidebar"
6+
identifier: name
7+
properties:
8+
name:
9+
type: string
10+
required: true
11+
category:
12+
type: categories
13+
tag:
14+
type: tags
15+
topic_id:
16+
type: integer
17+
required: true
418

519
sidebar_side:
620
default: right

test/acceptance/category-sidebars-test.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,27 @@ import { acceptance } from "discourse/tests/helpers/qunit-helpers";
44

55
acceptance("CategorySidebar - General", function () {
66
test("Sidebar appears based on matching setting", async function (assert) {
7-
settings.setup = "bug, 280";
7+
settings.sidebars = [
8+
{ category: [1], name: "bug", tag: [], topic_id: 280 },
9+
];
810

911
await visit("/c/bug");
1012

1113
assert.dom(".category-sidebar").exists("the sidebar should appear");
1214
});
1315

1416
test("Sidebar appears based on all setting", async function (assert) {
15-
settings.setup = "all, 280";
17+
settings.sidebars = [{ category: [], name: "all", tag: [], topic_id: 280 }];
1618

1719
await visit("/latest");
1820

1921
assert.dom(".category-sidebar").exists("the sidebar should appear");
2022
});
2123

2224
test("Sidebar does not appear when no matching setting", async function (assert) {
23-
settings.setup = "foo, 280";
25+
settings.sidebars = [
26+
{ category: [2], name: "foo", tag: [], topic_id: 280 },
27+
];
2428

2529
await visit("/c/bug");
2630

@@ -30,7 +34,9 @@ acceptance("CategorySidebar - General", function () {
3034
});
3135

3236
test("Sidebar content is displayed", async function (assert) {
33-
settings.setup = "bug, 280";
37+
settings.sidebars = [
38+
{ category: [1], name: "bug", tag: [], topic_id: 280 },
39+
];
3440

3541
await visit("/c/bug");
3642

@@ -40,4 +46,14 @@ acceptance("CategorySidebar - General", function () {
4046

4147
assert.dom(".cooked").hasText(/German/, "the sidebar should have content");
4248
});
49+
50+
test("Sidebar appears based on tag setting", async function (assert) {
51+
settings.sidebars = [
52+
{ category: [], name: "sidebar", tag: ["important"], topic_id: 280 },
53+
];
54+
55+
await visit("/tag/important");
56+
57+
assert.dom(".category-sidebar").exists("the sidebar should appear");
58+
});
4359
});

0 commit comments

Comments
 (0)