diff --git a/app/assets/javascripts/discourse/app/components/sidebar/categories-form-modal.hbs b/app/assets/javascripts/discourse/app/components/sidebar/categories-form-modal.hbs
new file mode 100644
index 00000000000..915400a137b
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/categories-form-modal.hbs
@@ -0,0 +1,81 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/categories-form-modal.js b/app/assets/javascripts/discourse/app/components/sidebar/categories-form-modal.js
new file mode 100644
index 00000000000..bfcd7837e35
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/categories-form-modal.js
@@ -0,0 +1,52 @@
+import { action } from "@ember/object";
+import { inject as service } from "@ember/service";
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+export default class extends Component {
+ @service site;
+ @service currentUser;
+
+ @tracked selectedSidebarCategoryIds = [
+ ...this.currentUser.sidebar_category_ids,
+ ];
+
+ @tracked siteCategories = this.site.categoriesList.filter((category) => {
+ return !category.parent_category_id && !category.isUncategorizedCategory;
+ });
+
+ @action
+ toggleCategory(categoryId) {
+ if (this.selectedSidebarCategoryIds.includes(categoryId)) {
+ this.selectedSidebarCategoryIds.removeObject(categoryId);
+ } else {
+ this.selectedSidebarCategoryIds.addObject(categoryId);
+ }
+ }
+
+ @action
+ save() {
+ this.saving = true;
+ const initialSidebarCategoryIds = this.currentUser.sidebar_category_ids;
+
+ this.currentUser.set(
+ "sidebar_category_ids",
+ this.selectedSidebarCategoryIds
+ );
+
+ this.currentUser
+ .save(["sidebar_category_ids"])
+ .then(() => {
+ this.args.closeModal();
+ })
+ .catch((error) => {
+ this.currentUser.set("sidebar_category_ids", initialSidebarCategoryIds);
+ popupAjaxError(error);
+ })
+ .finally(() => {
+ this.saving = false;
+ });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs
index 4793bda95d4..f02dabc5afd 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs
+++ b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.hbs
@@ -33,6 +33,20 @@
data-category-id={{sectionLink.category.id}}
/>
{{/each}}
+ {{else if this.currentUser.new_edit_sidebar_categories_tags_interface_groups_enabled}}
+
{{else}}
`border-color: #${color}; `);
+const validDirections = ["top", "right", "bottom", "left"];
+
+export default htmlHelper((color, direction) => {
+ const borderColor = `#${color}`;
+
+ const borderProperty =
+ direction && validDirections.includes(direction)
+ ? `border-${direction}-color`
+ : "border-color";
+
+ return `${borderProperty}: ${borderColor} `;
+});
diff --git a/app/assets/javascripts/discourse/app/templates/modal/sidebar-categories-form.hbs b/app/assets/javascripts/discourse/app/templates/modal/sidebar-categories-form.hbs
new file mode 100644
index 00000000000..269ac4baa7a
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/templates/modal/sidebar-categories-form.hbs
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss
index f5a3a14717f..1f8bd5299d3 100644
--- a/app/assets/stylesheets/common/components/_index.scss
+++ b/app/assets/stylesheets/common/components/_index.scss
@@ -27,6 +27,7 @@
@import "relative-time-picker";
@import "share-and-invite-modal";
@import "download-calendar";
+@import "sidebar-categories-form";
@import "svg";
@import "tap-tile";
@import "time-input";
diff --git a/app/assets/stylesheets/common/components/sidebar-categories-form.scss b/app/assets/stylesheets/common/components/sidebar-categories-form.scss
new file mode 100644
index 00000000000..2ccfbf408b7
--- /dev/null
+++ b/app/assets/stylesheets/common/components/sidebar-categories-form.scss
@@ -0,0 +1,58 @@
+.sidebar-categories-form {
+ .sidebar-categories-form__row {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--primary-low);
+ border-left: 4px solid;
+ margin-bottom: 1em;
+ padding: 0.75em;
+ }
+
+ .sidebar-categories-form__input {
+ margin-left: auto;
+ margin-right: 0;
+ align-self: baseline;
+ }
+
+ .sidebar-categories-form__category-row {
+ display: flex;
+ flex-direction: row;
+ margin-right: 1em;
+ padding: 0.5em 0;
+ }
+
+ .sidebar-categories-form__category-row:not(:first-child) {
+ border-top: 1px solid var(--primary-low);
+
+ .sidebar-categories-form__input {
+ align-self: center;
+ }
+ }
+
+ .sidebar-categories-form__category-row:nth-child(2) {
+ padding-left: 1em;
+ }
+
+ .sidebar-categories-form__category-row:nth-child(n + 3) {
+ margin-left: 1em;
+ }
+
+ .sidebar-categories-form__category-label {
+ display: flex;
+ flex-direction: column;
+ margin-right: 0.5em;
+ flex-grow: 1;
+ }
+
+ .sidebar-categories-form__category-badge {
+ .category-name {
+ color: var(--primary);
+ font-size: var(--font-up-1);
+ }
+ }
+
+ .sidebar-categories-form__category-description {
+ color: var(--primary-high);
+ font-size: var(--font-down-1);
+ }
+}
diff --git a/app/models/user.rb b/app/models/user.rb
index a5576cc1e4a..fbc8f632fcd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1827,6 +1827,10 @@ class User < ActiveRecord::Base
in_any_groups?(SiteSetting.experimental_new_new_view_groups_map)
end
+ def new_edit_sidebar_categories_tags_interface_groups_enabled?
+ in_any_groups?(SiteSetting.new_edit_sidebar_categories_tags_interface_groups_map)
+ end
+
protected
def badge_grant
diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb
index c24a1690a43..fcfb09a600c 100644
--- a/app/serializers/current_user_serializer.rb
+++ b/app/serializers/current_user_serializer.rb
@@ -68,7 +68,8 @@ class CurrentUserSerializer < BasicUserSerializer
:sidebar_category_ids,
:sidebar_list_destination,
:sidebar_sections,
- :new_new_view_enabled?
+ :new_new_view_enabled?,
+ :new_edit_sidebar_categories_tags_interface_groups_enabled?
delegate :user_stat, to: :object, private: true
delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 2964e59e4d0..653b722f64a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -4421,6 +4421,12 @@ en:
more: "More"
all_categories: "All categories"
all_tags: "All tags"
+ categories_form:
+ save: "Save"
+ title: "Edit categories navigation"
+ filter_input:
+ placeholder: "Filter categories"
+
sections:
custom:
add: "Add custom section"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 1fb0855f63e..24252c72cef 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -2112,6 +2112,12 @@ developer:
experimental_topics_filter:
client: true
default: false
+ new_edit_sidebar_categories_tags_interface_groups:
+ type: group_list
+ list_type: compact
+ default: ""
+ allow_any: false
+ hidden: true
navigation:
navigation_menu:
diff --git a/spec/fabricators/category_fabricator.rb b/spec/fabricators/category_fabricator.rb
index 6932d40ffcc..ee5c5ca961b 100644
--- a/spec/fabricators/category_fabricator.rb
+++ b/spec/fabricators/category_fabricator.rb
@@ -3,6 +3,7 @@
Fabricator(:category) do
name { sequence(:name) { |n| "Amazing Category #{n}" } }
skip_category_definition true
+ color { SecureRandom.hex(3) }
user
end
diff --git a/spec/system/editing_sidebar_categories_navigation_spec.rb b/spec/system/editing_sidebar_categories_navigation_spec.rb
new file mode 100644
index 00000000000..6b5cd01cb24
--- /dev/null
+++ b/spec/system/editing_sidebar_categories_navigation_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+RSpec.describe "Editing sidebar categories navigation", type: :system do
+ fab!(:user) { Fabricate(:user) }
+ fab!(:group) { Fabricate(:group).tap { |g| g.add(user) } }
+ fab!(:category) { Fabricate(:category) }
+ fab!(:category_subcategory) { Fabricate(:category, parent_category_id: category.id) }
+ fab!(:category_subcategory2) { Fabricate(:category, parent_category_id: category.id) }
+ fab!(:category2) { Fabricate(:category) }
+ fab!(:category2_subcategory) { Fabricate(:category, parent_category_id: category2.id) }
+
+ let(:sidebar) { PageObjects::Components::Sidebar.new }
+
+ before do
+ SiteSetting.new_edit_sidebar_categories_tags_interface_groups = group.name
+ SiteSetting.default_sidebar_categories = "#{category.id}|#{category2.id}"
+ sign_in(user)
+ end
+
+ it "allows a user to edit the sidebar categories navigation" do
+ visit "/latest"
+
+ expect(sidebar).to have_categories_section
+
+ modal = sidebar.click_edit_categories_button
+
+ expect(modal).to have_right_title(I18n.t("js.sidebar.categories_form.title"))
+
+ modal
+ .toggle_category_checkbox(category)
+ .toggle_category_checkbox(category_subcategory2)
+ .toggle_category_checkbox(category2)
+ .save
+
+ expect(modal).to be_closed
+ expect(sidebar).to have_section_link(category.name)
+ expect(sidebar).to have_section_link(category_subcategory2.name)
+ expect(sidebar).to have_section_link(category2.name)
+
+ visit "/latest"
+
+ expect(sidebar).to have_categories_section
+ expect(sidebar).to have_section_link(category.name)
+ expect(sidebar).to have_section_link(category_subcategory2.name)
+ expect(sidebar).to have_section_link(category2.name)
+
+ modal = sidebar.click_edit_categories_button
+ modal.toggle_category_checkbox(category_subcategory2).toggle_category_checkbox(category2).save
+
+ expect(modal).to be_closed
+
+ expect(sidebar).to have_section_link(category.name)
+ expect(sidebar).to have_no_section_link(category_subcategory2.name)
+ expect(sidebar).to have_no_section_link(category2.name)
+ end
+end
diff --git a/spec/system/page_objects/components/sidebar.rb b/spec/system/page_objects/components/sidebar.rb
index 2349d48141d..a8685200806 100644
--- a/spec/system/page_objects/components/sidebar.rb
+++ b/spec/system/page_objects/components/sidebar.rb
@@ -23,6 +23,14 @@ module PageObjects
page.has_no_button?(add_section_button_text)
end
+ def click_edit_categories_button
+ within(".sidebar-section[data-section-name='categories']") do
+ click_button(class: "sidebar-section-header-button", visible: false)
+ end
+
+ PageObjects::Modals::SidebarEditCategories.new
+ end
+
def edit_custom_section(name)
find(".sidebar-section[data-section-name='#{name.parameterize}']").hover
@@ -59,6 +67,10 @@ module PageObjects
find(SIDEBAR_WRAPPER_SELECTOR).has_button?(name)
end
+ def has_categories_section?
+ has_section?("Categories")
+ end
+
def has_no_section?(name)
find(SIDEBAR_WRAPPER_SELECTOR).has_no_button?(name)
end
diff --git a/spec/system/page_objects/modals/sidebar_edit_categories.rb b/spec/system/page_objects/modals/sidebar_edit_categories.rb
new file mode 100644
index 00000000000..3493669f769
--- /dev/null
+++ b/spec/system/page_objects/modals/sidebar_edit_categories.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module PageObjects
+ module Modals
+ class SidebarEditCategories < PageObjects::Modals::Base
+ MODAL_SELECTOR = ".sidebar-categories-form-modal"
+
+ def closed?
+ has_no_css?(MODAL_SELECTOR)
+ end
+
+ def has_right_title?(title)
+ has_css?("#{MODAL_SELECTOR} #discourse-modal-title", text: title)
+ end
+
+ def toggle_category_checkbox(category)
+ find(
+ "#{MODAL_SELECTOR} .sidebar-categories-form__category-row[data-category-id='#{category.id}'] .sidebar-categories-form__input",
+ ).click
+
+ self
+ end
+
+ def save
+ find("#{MODAL_SELECTOR} .sidebar-categories-form__save-button").click
+ end
+ end
+ end
+end