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