FEATURE: Add modal for editing tags in navigation menu (#22214)

What does this change do?

This change is a first pass for adding a modal used to edit tags that appears in
the navigation menu. As the feature is being worked on in phases, it is
currently hidden behind the `new_edit_sidebar_categories_tags_interface_groups` site setting.

The following features will be worked on in future commits:

1. Input filter to filter through the tgas
2. Button to reset tag selection to default navigation menu tags site
   settings
3. Button to deselect all current selection
This commit is contained in:
Alan Guo Xiang Tan 2023-06-21 09:09:56 +08:00 committed by GitHub
parent 1987fce018
commit 08d8bd9f43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 272 additions and 4 deletions

View File

@ -84,7 +84,7 @@
<div class="modal-footer sidebar-categories-form__modal-footer">
<DButton
@class="btn-primary sidebar-categories-form__save-button"
@label="sidebar.categories_form_modal.save"
@label="save"
@disabled={{this.saving}}
@action={{this.save}}
/>

View File

@ -0,0 +1,42 @@
<DModalBody
@title="sidebar.tags_form_modal.title"
@class="sidebar-tags-form-modal"
>
<form class="sidebar-tags-form">
{{#each this.tags as |tag|}}
<div class="sidebar-tags-form__tag" data-tag-name={{tag.name}}>
<Input
id={{concat "sidebar-tags-form__input--" tag.name}}
class="sidebar-tags-form__input"
@type="checkbox"
@checked={{includes this.selectedTags tag.name}}
{{on "click" (action "toggleTag" tag.name)}}
/>
<label
class="sidebar-tags-form__tag-label"
for={{concat "sidebar-tags-form__input--" tag.name}}
>
<p>
<span class="sidebar-tags-form__tag-label-name">
{{tag.name}}
</span>
<span class="sidebar-tags-form__tag-label-count">
({{tag.count}})
</span>
</p>
</label>
</div>
{{/each}}
</form>
</DModalBody>
<div class="modal-footer">
<DButton
@class="btn-primary sidebar-tags-form__save-button"
@label="save"
@disabled={{this.saving}}
@action={{this.save}}
/>
</div>

View File

@ -0,0 +1,63 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class extends Component {
@service currentUser;
@service store;
@tracked tags = [];
@tracked selectedTags = [...this.currentUser.sidebarTagNames];
constructor() {
super(...arguments);
this.#loadTags();
}
async #loadTags() {
// This is loading all tags upfront and there is no pagination for it. However, this is what we are doing for the
// `/tags` route as well so we have decided to kick this can of worms down the road for now.
await this.store
.findAll("tag")
.then((tags) => {
this.tags = tags.content.sort((a, b) => {
return a.name.localeCompare(b.name);
});
})
.catch((error) => {
popupAjaxError(error);
});
}
@action
toggleTag(tag) {
if (this.selectedTags.includes(tag)) {
this.selectedTags.removeObject(tag);
} else {
this.selectedTags.addObject(tag);
}
}
@action
save() {
this.saving = true;
const initialSidebarTags = this.currentUser.sidebar_tags;
this.currentUser.set("sidebar_tag_names", this.selectedTags);
this.currentUser
.save(["sidebar_tag_names"])
.then((result) => {
this.currentUser.set("sidebar_tags", result.user.sidebar_tags);
this.args.closeModal();
})
.catch((error) => {
this.currentUser.set("sidebar_tags", initialSidebarTags);
popupAjaxError(error);
})
.finally(() => {
this.saving = false;
});
}
}

View File

@ -6,6 +6,7 @@ import SidebarCommonTagsSection from "discourse/components/sidebar/common/tags-s
import TagSectionLink from "discourse/lib/sidebar/user/tags-section/tag-section-link";
import PMTagSectionLink from "discourse/lib/sidebar/user/tags-section/pm-tag-section-link";
import { hasDefaultSidebarTags } from "discourse/lib/sidebar/helpers";
import showModal from "discourse/lib/show-modal";
export default class SidebarUserTagsSection extends SidebarCommonTagsSection {
@service router;
@ -79,6 +80,12 @@ export default class SidebarUserTagsSection extends SidebarCommonTagsSection {
@action
editTracked() {
this.router.transitionTo("preferences.navigation-menu", this.currentUser);
if (
this.currentUser.new_edit_sidebar_categories_tags_interface_groups_enabled
) {
showModal("sidebar-tags-form");
} else {
this.router.transitionTo("preferences.navigation-menu", this.currentUser);
}
}
}

View File

@ -0,0 +1,6 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class SidebarTagsForm extends Controller.extend(
ModalFunctionality
) {}

View File

@ -0,0 +1 @@
<Sidebar::TagsFormModal @closeModal={{(action "closeModal")}} />

View File

@ -134,7 +134,7 @@
}
&:not(.history-modal) {
.modal-body:not(.reorder-categories):not(.poll-ui-builder):not(.poll-breakdown):not(.sidebar-categories-form__modal-body) {
.modal-body:not(.reorder-categories):not(.poll-ui-builder):not(.poll-breakdown):not(.sidebar-categories-form-modal):not(.sidebar-tags-form-modal) {
max-height: 80vh !important;
@media screen and (max-height: 500px) {
max-height: 65vh !important;

View File

@ -29,6 +29,7 @@
@import "share-and-invite-modal";
@import "download-calendar";
@import "sidebar-categories-form";
@import "sidebar-tags-form";
@import "svg";
@import "tap-tile";
@import "time-input";

View File

@ -0,0 +1,34 @@
.sidebar-tags-form-modal {
.modal-body {
min-height: 30vh;
}
}
.sidebar-tags-form {
display: flex;
flex-wrap: wrap;
.sidebar-tags-form__tag {
flex-basis: 30%;
display: flex;
flex-direction: row;
align-items: center;
padding: 0.5em 0.25em;
.sidebar-tags-form__tag-label {
margin-bottom: 0;
p {
margin: 0;
}
}
.sidebar-tags-form__input {
margin-top: 0;
}
}
.sidebar-tags-form__tag-label-count {
color: var(--primary-medium);
}
}

View File

@ -1,4 +1,5 @@
@import "sidebar-categories-form";
@import "sidebar-tags-form";
@import "user-card";
@import "user-info";
@import "user-stream-item";

View File

@ -0,0 +1,5 @@
.sidebar-tags-form-modal {
.modal-inner-container {
min-width: var(--modal-max-width);
}
}

View File

@ -1,4 +1,5 @@
@import "sidebar-categories-form";
@import "sidebar-tags-form";
@import "topic-footer-mobile-dropdown";
@import "user-card";
@import "user-stream-item";

View File

@ -0,0 +1,5 @@
.sidebar-tags-form {
.sidebar-tags-form__tag {
flex-basis: 100%;
}
}

View File

@ -4390,7 +4390,6 @@ en:
all_categories: "All categories"
all_tags: "All tags"
categories_form_modal:
save: "Save"
title: "Edit categories navigation"
subtitle:
button_text: "Deselect all"
@ -4398,6 +4397,8 @@ en:
filter_placeholder: "Filter categories"
no_categories: "There are no categories matching the given term."
reset_to_defaults: "Reset to defaults"
tags_form_modal:
title: "Edit tags navigation"
sections:
custom:

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
RSpec.describe "Editing sidebar tags navigation", type: :system do
fab!(:user) { Fabricate(:user) }
fab!(:group) { Fabricate(:group).tap { |g| g.add(user) } }
fab!(:tag) { Fabricate(:tag, name: "tag", public_topic_count: 1, staff_topic_count: 1) }
fab!(:tag2) { Fabricate(:tag, name: "tag2", public_topic_count: 2, staff_topic_count: 2) }
fab!(:tag3) { Fabricate(:tag, name: "tag3", public_topic_count: 3, staff_topic_count: 3) }
let(:sidebar) { PageObjects::Components::Sidebar.new }
before do
SiteSetting.new_edit_sidebar_categories_tags_interface_groups = group.name
sign_in(user)
end
it "allows a user to edit the sidebar categories navigation" do
visit "/latest"
expect(sidebar).to have_tags_section
expect(sidebar).to have_no_section_link(tag.name)
expect(sidebar).to have_no_section_link(tag2.name)
expect(sidebar).to have_no_section_link(tag3.name)
modal = sidebar.click_edit_tags_button
expect(modal).to have_right_title(I18n.t("js.sidebar.tags_form_modal.title"))
expect(modal).to have_tag_checkboxes([tag, tag2, tag3])
modal.toggle_tag_checkbox(tag).toggle_tag_checkbox(tag2).save
expect(modal).to be_closed
expect(sidebar).to have_section_link(tag.name)
expect(sidebar).to have_section_link(tag2.name)
expect(sidebar).to have_no_section_link(tag3.name)
visit "/latest"
expect(sidebar).to have_section_link(tag.name)
expect(sidebar).to have_section_link(tag2.name)
expect(sidebar).to have_no_section_link(tag3.name)
modal = sidebar.click_edit_tags_button
modal.toggle_tag_checkbox(tag2).save
expect(modal).to be_closed
expect(sidebar).to have_section_link(tag.name)
expect(sidebar).to have_no_section_link(tag2.name)
expect(sidebar).to have_no_section_link(tag3.name)
end
end

View File

@ -31,6 +31,14 @@ module PageObjects
PageObjects::Modals::SidebarEditCategories.new
end
def click_edit_tags_button
within(".sidebar-section[data-section-name='tags']") do
click_button(class: "sidebar-section-header-button", visible: false)
end
PageObjects::Modals::SidebarEditTags.new
end
def edit_custom_section(name)
find(".sidebar-section[data-section-name='#{name.parameterize}']").hover
@ -71,6 +79,10 @@ module PageObjects
has_section?("Categories")
end
def has_tags_section?
has_section?("Tags")
end
def has_no_section?(name)
find(SIDEBAR_WRAPPER_SELECTOR).has_no_button?(name)
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module PageObjects
module Modals
class SidebarEditTags < PageObjects::Modals::Base
def closed?
has_no_css?(".sidebar-tags-form-modal")
end
def has_right_title?(title)
has_css?(".sidebar-tags-form-modal #discourse-modal-title", text: title)
end
def has_tag_checkboxes?(tags)
tag_names = tags.map(&:name)
has_css?(".sidebar-tags-form-modal .sidebar-tags-form__tag", count: tag_names.length) &&
all(".sidebar-tags-form-modal .sidebar-tags-form__tag").all? do |row|
tag_names.include?(row["data-tag-name"].to_s)
end
end
def toggle_tag_checkbox(tag)
find(
".sidebar-tags-form-modal .sidebar-tags-form__tag[data-tag-name='#{tag.name}'] .sidebar-tags-form__input",
).click
self
end
def save
find(".sidebar-tags-form-modal .sidebar-tags-form__save-button").click
self
end
end
end
end