Skip to content

Commit

Permalink
FEATURE: include tag sidebars, migrate to objects setting (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
awesomerobot authored May 29, 2024
1 parent 41ab679 commit e63c903
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require("@discourse/lint-configs/eslint-theme");
module.exports = require("@discourse/lint-configs/eslint-theme");
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# discourse-category-sidebars
# Discourse Topic List Sidebars

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).
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).

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

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

## What can I do with this theme component?

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

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

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

- 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).

## How do I configure it?

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**)

![image](https://user-images.githubusercontent.com/5862206/214492886-e5c35808-6c82-4ade-8eed-062c70851d7f.png)
---

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).

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!).

## Custom CSS

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.
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.

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.

Expand Down
6 changes: 3 additions & 3 deletions about.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "discourse-category-sidebars",
"license_url": "https://github.com/awesomerobot/discourse-category-sidebars/blob/master/LICENSE",
"about_url": "https://github.com/awesomerobot/discourse-category-sidebars/blob/master/README.md",
"name": "Discourse Topic List Sidebars",
"license_url": "https://github.com/discourse/discourse-category-sidebars/blob/master/LICENSE",
"about_url": "https://github.com/discourse/discourse-category-sidebars/blob/master/README.md",
"component": true
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { cached, tracked } from "@glimmer/tracking";
import { concat } from "@ember/helper";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
Expand All @@ -9,49 +9,29 @@ import { htmlSafe } from "@ember/template";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import bodyClass from "discourse/helpers/body-class";
import { ajax } from "discourse/lib/ajax";
import Category from "discourse/models/category";

export default class CategorySidebar extends Component {
export default class TopicListSidebar extends Component {
@service router;
@service siteSettings;
@service site;

@tracked sidebarContent;
@tracked loading = true;

<template>
{{#if this.matchedSetting}}
{{bodyClass "custom-sidebar"}}
{{bodyClass (concat "sidebar-" settings.sidebar_side)}}
<div
class="category-sidebar"
{{didInsert this.fetchPostContent}}
{{didUpdate this.fetchPostContent this.category}}
>
<div class="sticky-sidebar">
<div
class="category-sidebar-contents"
data-category-sidebar={{this.category.slug}}
>
<div class="cooked">
{{#unless this.loading}}
{{htmlSafe this.sidebarContent}}
{{/unless}}
<ConditionalLoadingSpinner @condition={{this.loading}} />
</div>
</div>
</div>
</div>
{{/if}}
</template>
findName(name) {
return settings.sidebars.filter((setting) => setting.name === name);
}

get parsedSetting() {
return settings.setup.split("|").reduce((result, setting) => {
const [category, value] = setting
.split(",")
.map((postID) => postID.trim());
result[category] = { post: value };
return result;
}, {});
findCategory(categoryId) {
return settings.sidebars.filter((setting) =>
setting.category?.includes(categoryId)
);
}

findTag(tagName) {
return settings.sidebars.filter((setting) =>
setting.tag?.includes(tagName)
);
}

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

get category() {
return this.categorySlugPathWithID
? Category.findBySlugPathWithID(this.categorySlugPathWithID)
: null;
get tagId() {
return this.router?.currentRoute?.params?.tag_id;
}

get matchedSetting() {
if (this.parsedSetting["all"] && this.isTopRoute) {
// if this is a top_menu route, use the "all" setting
return this.parsedSetting["all"];
} else if (this.categorySlugPathWithID) {
const categorySlug = this.category.slug;
const parentCategorySlug = this.category.parentCategory?.slug;

// if there's a setting for this category, use it
if (categorySlug && this.parsedSetting[categorySlug]) {
return this.parsedSetting[categorySlug];
}
const categoryId = this.category?.id;
const parentCategoryId = this.category?.parentCategory?.id;
const tagId = this.tagId;

let result = null;

if (this.findName("all") && this.isTopRoute) {
result = this.findName("all");
}

// if there's not a setting for this category
// check the parent, and maybe use that
if (categoryId) {
if (
settings.inherit_parent_sidebar &&
parentCategorySlug &&
this.parsedSetting[parentCategorySlug]
parentCategoryId &&
this.findCategory(parentCategoryId).length
) {
return this.parsedSetting[parentCategorySlug];
result = this.findCategory(parentCategoryId);
}
if (this.findCategory(categoryId).length) {
result = this.findCategory(categoryId);
}
}

if (tagId) {
result = this.findTag(tagId);
}

return result;
}

@cached
get category() {
return (
this.router.currentRoute.attributes.category ||
this.topic?.get("category")
);
}

@action
async fetchPostContent() {
this.loading = true;

// if multiple, use the last matching setting
const [lastSetting] = [...this.matchedSetting].reverse();
const topicId = lastSetting.topic_id;

try {
if (this.matchedSetting) {
const response = await ajax(`/t/${this.matchedSetting.post}.json`);
const response = await ajax(`/t/${topicId}.json`);
this.sidebarContent = response.post_stream.posts[0].cooked;
}
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error fetching post for category sidebar:", error);
console.error("Error fetching post for sidebar:", error);
} finally {
this.loading = false;
}

return this.sidebarContent;
}

<template>
{{#if this.matchedSetting}}
{{bodyClass "custom-sidebar"}}
{{bodyClass (concat "sidebar-" settings.sidebar_side)}}
<div
class="category-sidebar"
{{didInsert this.fetchPostContent}}
{{didUpdate this.fetchPostContent this.category}}
>
<div class="sticky-sidebar">
<div
class="category-sidebar-contents"
data-category-sidebar={{this.category.slug}}
data-tag-sidebar={{this.tagId}}
>
<div class="cooked">
{{#unless this.loading}}
{{htmlSafe this.sidebarContent}}
{{/unless}}
<ConditionalLoadingSpinner @condition={{this.loading}} />
</div>
</div>
</div>
</div>
{{/if}}
</template>
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{{#unless site.mobileView}}
<CategorySidebar />
<TopicListSidebar />
{{/unless}}
34 changes: 34 additions & 0 deletions migrations/settings/0001-migrate-to-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export default function migrate(settings, helpers) {
const oldSetup = settings.get("setup");

if (oldSetup) {
const newSidebars = oldSetup
.split("|")
.map((sidebar) => {
const [categorySlug, topicId] = sidebar
.split(",")
.map((value) => value.trim());
const sanitizedCategorySlug =
categorySlug.replace(/-/g, " ") || "sidebar";
const parsedTopicId = parseInt(topicId, 10) || 0;

let categoryId = helpers.getCategoryIdByName(sanitizedCategorySlug);

return {
name:
sanitizedCategorySlug.toLowerCase() === "all"
? "all"
: sanitizedCategorySlug,
category: categoryId ? [categoryId] : [],
tag: [],
topic_id: parsedTopicId,
};
})
.filter((item) => item !== null);

settings.set("sidebars", newSidebars);
settings.delete("setup");
}

return settings;
}
20 changes: 17 additions & 3 deletions settings.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
setup:
type: list
default: "staff, 3"
sidebars:
type: objects
default: []
schema:
name: "sidebar"
identifier: name
properties:
name:
type: string
required: true
category:
type: categories
tag:
type: tags
topic_id:
type: integer
required: true

sidebar_side:
default: right
Expand Down
24 changes: 20 additions & 4 deletions test/acceptance/category-sidebars-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@ import { acceptance } from "discourse/tests/helpers/qunit-helpers";

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

await visit("/c/bug");

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

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

await visit("/latest");

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

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

await visit("/c/bug");

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

test("Sidebar content is displayed", async function (assert) {
settings.setup = "bug, 280";
settings.sidebars = [
{ category: [1], name: "bug", tag: [], topic_id: 280 },
];

await visit("/c/bug");

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

assert.dom(".cooked").hasText(/German/, "the sidebar should have content");
});

test("Sidebar appears based on tag setting", async function (assert) {
settings.sidebars = [
{ category: [], name: "sidebar", tag: ["important"], topic_id: 280 },
];

await visit("/tag/important");

assert.dom(".category-sidebar").exists("the sidebar should appear");
});
});
Loading

0 comments on commit e63c903

Please sign in to comment.