diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/custom-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/common/custom-section.hbs index f42bc7fd71f..b0e25bf2288 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/common/custom-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/common/custom-section.hbs @@ -7,51 +7,50 @@ @class={{this.section.dragCss}} > {{#each this.section.links as |link|}} - {{#if link.shouldDisplay}} - {{#if link.external}} - + {{else}} + - {{else}} - - {{/if}} + ) + )}} + /> {{/if}} {{/each}} diff --git a/app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs b/app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs index d23371fbdf6..a3cf4c2ece1 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs @@ -1,15 +1,29 @@ - \ No newline at end of file +{{#if @sectionLink.external}} + +{{else}} + +{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.hbs b/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.hbs new file mode 100644 index 00000000000..923dd7cdf24 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.hbs @@ -0,0 +1,72 @@ +
+
+ {{d-icon "grip-lines"}} +
+
+ + {{#if @link.invalidIconMessage}} +
+ {{@link.invalidIconMessage}} +
+ {{/if}} +
+
+ + {{#if @link.invalidNameMessage}} +
+ {{@link.invalidNameMessage}} +
+ {{/if}} +
+
+ + {{#if @link.invalidValueMessage}} +
+ {{@link.invalidValueMessage}} +
+ {{/if}} +
+ +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.js b/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.js new file mode 100644 index 00000000000..106d2eba74e --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/sidebar/section-form-link.js @@ -0,0 +1,68 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { tracked } from "@glimmer/tracking"; + +export default class SectionFormLink extends Component { + @tracked dragCssClass; + + dragCount = 0; + + isAboveElement(event) { + event.preventDefault(); + const target = event.currentTarget; + const domRect = target.getBoundingClientRect(); + return event.offsetY < domRect.height / 2; + } + + @action + dragHasStarted(event) { + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("linkId", this.args.link.objectId); + this.dragCssClass = "dragging"; + } + + @action + dragOver(event) { + event.preventDefault(); + if (!this.dragCssClass) { + if (this.isAboveElement(event)) { + this.dragCssClass = "drag-above"; + } else { + this.dragCssClass = "drag-below"; + } + } + } + @action + dragEnter() { + this.dragCount++; + } + + @action + dragLeave() { + this.dragCount--; + if ( + this.dragCount === 0 && + (this.dragCssClass === "drag-above" || this.dragCssClass === "drag-below") + ) { + this.dragCssClass = null; + } + } + + @action + dropItem(event) { + event.stopPropagation(); + this.dragCounter = 0; + this.args.reorderCallback( + parseInt(event.dataTransfer.getData("linkId"), 10), + this.args.link, + this.isAboveElement(event) + ); + this.dragCssClass = null; + } + + @action + dragEnd() { + this.dragCounter = 0; + this.dragCssClass = null; + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/sidebar-section-form.js b/app/assets/javascripts/discourse/app/controllers/sidebar-section-form.js index 49ef501c9f1..42a95868f44 100644 --- a/app/assets/javascripts/discourse/app/controllers/sidebar-section-form.js +++ b/app/assets/javascripts/discourse/app/controllers/sidebar-section-form.js @@ -9,6 +9,7 @@ import { sanitize } from "discourse/lib/text"; import { tracked } from "@glimmer/tracking"; import { A } from "@ember/array"; import { SIDEBAR_SECTION, SIDEBAR_URL } from "discourse/lib/constants"; +import { bind } from "discourse-common/utils/decorators"; const FULL_RELOAD_LINKS_REGEX = [ /^\/my\/[a-z_\-\/]+$/, @@ -19,17 +20,30 @@ const FULL_RELOAD_LINKS_REGEX = [ class Section { @tracked title; @tracked links; + @tracked secondaryLinks; - constructor({ title, links, id, publicSection }) { + constructor({ + title, + links, + secondaryLinks, + id, + publicSection, + sectionType, + }) { this.title = title; this.public = publicSection; + this.sectionType = sectionType; this.links = links; + this.secondaryLinks = secondaryLinks; this.id = id; } get valid() { + const allLinks = this.links + .filter((link) => !link._destroy) + .concat(this.secondaryLinks?.filter((link) => !link._destroy) || []); const validLinks = - this.links.length > 0 && this.links.every((link) => link.valid); + allLinks.length > 0 && allLinks.every((link) => link.valid); return this.validTitle && validLinks; } @@ -70,7 +84,7 @@ class SectionLink { @tracked value; @tracked _destroy; - constructor({ router, icon, name, value, id }) { + constructor({ router, icon, name, value, id, objectId, segment }) { this.router = router; this.icon = icon || "link"; this.name = name; @@ -78,6 +92,8 @@ class SectionLink { this.id = id; this.httpHost = "http://" + window.location.host; this.httpsHost = "https://" + window.location.host; + this.objectId = objectId; + this.segment = segment; } get path() { @@ -165,6 +181,10 @@ class SectionLink { ); } + get isPrimary() { + return this.segment === "primary"; + } + get #blankIcon() { return isEmpty(this.icon); } @@ -221,6 +241,7 @@ export default Controller.extend(ModalFunctionality, { flashText: null, flashClass: null, }); + this.nextObjectId = 0; this.model = this.initModel(); }, @@ -233,27 +254,48 @@ export default Controller.extend(ModalFunctionality, { return new Section({ title: this.model.title, publicSection: this.model.public, - links: A( - this.model.links.map( - (link) => - new SectionLink({ - router: this.router, - icon: link.icon, - name: link.name, - value: link.value, - id: link.id, - }) - ) - ), + sectionType: this.model.section_type, + links: this.model.links.reduce((acc, link) => { + if (link.segment === "primary") { + this.nextObjectId++; + acc.push(this.initLink(link)); + } + return acc; + }, A()), + secondaryLinks: this.model.links.reduce((acc, link) => { + if (link.segment === "secondary") { + this.nextObjectId++; + acc.push(this.initLink(link)); + } + return acc; + }, A()), id: this.model.id, }); } else { return new Section({ - links: A([new SectionLink({ router: this.router })]), + links: A([ + new SectionLink({ + router: this.router, + objectId: this.nextObjectId, + segment: "primary", + }), + ]), }); } }, + initLink(link) { + return new SectionLink({ + router: this.router, + icon: link.icon, + name: link.name, + value: link.value, + id: link.id, + objectId: this.nextObjectId, + segment: link.segment, + }); + }, + create() { return ajax(`/sidebar_sections`, { type: "POST", @@ -294,15 +336,18 @@ export default Controller.extend(ModalFunctionality, { data: JSON.stringify({ title: this.model.title, public: this.model.public, - links: this.model.links.map((link) => { - return { - id: link.id, - icon: link.icon, - name: link.name, - value: link.path, - _destroy: link._destroy, - }; - }), + links: this.model.links + .concat(this.model?.secondaryLinks || []) + .map((link) => { + return { + id: link.id, + icon: link.icon, + name: link.name, + value: link.path, + segment: link.segment, + _destroy: link._destroy, + }; + }), }), }) .then((data) => { @@ -329,23 +374,112 @@ export default Controller.extend(ModalFunctionality, { return this.model.links.filter((link) => !link._destroy); }, + get activeSecondaryLinks() { + return this.model.secondaryLinks?.filter((link) => !link._destroy); + }, + get header() { return this.model.id ? "sidebar.sections.custom.edit" : "sidebar.sections.custom.add"; }, + @bind + reorder(linkFromId, linkTo, above) { + if (linkFromId === linkTo.objectId) { + return; + } + let linkFrom = this.model.links.find( + (link) => link.objectId === linkFromId + ); + if (!linkFrom) { + linkFrom = this.model.secondaryLinks.find( + (link) => link.objectId === linkFromId + ); + } + + if (linkFrom.isPrimary) { + this.model.links.removeObject(linkFrom); + } else { + this.model.secondaryLinks?.removeObject(linkFrom); + } + + if (linkTo.isPrimary) { + const toPosition = this.model.links.indexOf(linkTo); + linkFrom.segment = "primary"; + this.model.links.insertAt(above ? toPosition : toPosition + 1, linkFrom); + } else { + linkFrom.segment = "secondary"; + const toPosition = this.model.secondaryLinks.indexOf(linkTo); + this.model.secondaryLinks.insertAt( + above ? toPosition : toPosition + 1, + linkFrom + ); + } + }, + + get canDelete() { + return this.model.id && !this.model.sectionType; + }, + + @bind + deleteLink(link) { + if (link.id) { + link._destroy = "1"; + } else { + if (link.isPrimary) { + this.model.links.removeObject(link); + } else { + this.model.secondaryLinks.removeObject(link); + } + } + }, + actions: { addLink() { - this.model.links.pushObject(new SectionLink({ router: this.router })); + this.nextObjectId = this.nextObjectId + 1; + this.model.links.pushObject( + new SectionLink({ + router: this.router, + objectId: this.nextObjectId, + segment: "primary", + }) + ); }, - deleteLink(link) { - if (link.id) { - link._destroy = "1"; - } else { - this.model.links.removeObject(link); - } + addSecondaryLink() { + this.nextObjectId = this.nextObjectId + 1; + this.model.secondaryLinks.pushObject( + new SectionLink({ + router: this.router, + objectId: this.nextObjectId, + segment: "secondary", + }) + ); + }, + + resetToDefault() { + return this.dialog.yesNoConfirm({ + message: I18n.t("sidebar.sections.custom.reset_confirm"), + didConfirm: () => { + return ajax(`/sidebar_sections/reset/${this.model.id}`, { + type: "PUT", + }) + .then((data) => { + this.currentUser.sidebar_sections.shiftObject(); + this.currentUser.sidebar_sections.unshiftObject( + data["sidebar_section"] + ); + this.send("closeModal"); + }) + .catch((e) => + this.setProperties({ + flashText: sanitize(extractError(e)), + flashClass: "error", + }) + ); + }, + }); }, save() { diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/base-community-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/base-community-section-link.js index e7dc1603fdb..656fdd32174 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/base-community-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/base-community-section-link.js @@ -9,6 +9,8 @@ export default class BaseCommunitySectionLink { router, siteSettings, inMoreDrawer, + overridenName, + overridenIcon, } = {}) { this.router = router; this.topicTrackingState = topicTrackingState; @@ -16,6 +18,8 @@ export default class BaseCommunitySectionLink { this.appEvents = appEvents; this.siteSettings = siteSettings; this.inMoreDrawer = inMoreDrawer; + this.overridenName = overridenName; + this.overridenIcon = overridenIcon; } /** @@ -105,10 +109,17 @@ export default class BaseCommunitySectionLink { /** * @returns {string} The name of the fontawesome icon to be displayed before the link. Defaults to "link". */ - get prefixValue() { + get defaultPrefixValue() { return "link"; } + /** + * @returns {string} The name of the fontawesome icon to be displayed before the link. + */ + get prefixValue() { + return this.overridenIcon || this.defaultPrefixValue; + } + _notImplemented() { throw "not implemented"; } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/about-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/about-section-link.js index 46c38aa4e14..5b155fe0f2a 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/about-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/about-section-link.js @@ -16,10 +16,13 @@ export default class AboutSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.about.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } - get prefixValue() { + get defaultPrefixValue() { return "info-circle"; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/badges-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/badges-section-link.js index 6904b2cd8c0..139bca4fc2a 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/badges-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/badges-section-link.js @@ -16,14 +16,17 @@ export default class BadgesSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.badges.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } get shouldDisplay() { return this.siteSettings.enable_badges; } - get prefixValue() { + get defaultPrefixValue() { return "certificate"; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js index 35ca0858c5d..da7dce35350 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js @@ -44,7 +44,10 @@ export default class EverythingSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.everything.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } get currentWhen() { @@ -92,7 +95,7 @@ export default class EverythingSectionLink extends BaseSectionLink { return "discovery.latest"; } - get prefixValue() { + get defaultPrefixValue() { return "layer-group"; } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/faq-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/faq-section-link.js index 37a1136c0cd..38e83c58568 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/faq-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/faq-section-link.js @@ -20,10 +20,13 @@ export default class FAQSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.faq.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } - get prefixValue() { + get defaultPrefixValue() { return "question-circle"; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/groups-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/groups-section-link.js index f2d6fe78e8f..667d77dc67a 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/groups-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/groups-section-link.js @@ -16,14 +16,17 @@ export default class GroupsSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.groups.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } get shouldDisplay() { return this.siteSettings.enable_group_directory; } - get prefixValue() { + get defaultPrefixValue() { return "user-friends"; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/users-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/users-section-link.js index fafbf071245..02b979fea92 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/users-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/users-section-link.js @@ -16,7 +16,10 @@ export default class UsersSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.users.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } get shouldDisplay() { @@ -26,7 +29,7 @@ export default class UsersSectionLink extends BaseSectionLink { ); } - get prefixValue() { + get defaultPrefixValue() { return "users"; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/community-section.js b/app/assets/javascripts/discourse/app/lib/sidebar/community-section.js index 97e653ca46d..20fae4dba05 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/community-section.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/community-section.js @@ -20,6 +20,7 @@ import { customSectionLinks, secondaryCustomSectionLinks, } from "discourse/lib/sidebar/custom-community-section-links"; +import showModal from "discourse/lib/show-modal"; const LINKS_IN_BOTH_SEGMENTS = ["/review"]; @@ -111,13 +112,23 @@ export default class CommunitySection { const sectionLinkClass = SPECIAL_LINKS_MAP[link.value]; if (sectionLinkClass) { - return this.#initializeSectionLink(sectionLinkClass, inMoreDrawer); + return this.#initializeSectionLink( + sectionLinkClass, + inMoreDrawer, + link.name, + link.scon + ); } else { return new SectionLink(link, this, this.router); } } - #initializeSectionLink(sectionLinkClass, inMoreDrawer) { + #initializeSectionLink( + sectionLinkClass, + inMoreDrawer, + overridenName, + overridenIcon + ) { if (this.router.isDestroying) { return; } @@ -128,28 +139,48 @@ export default class CommunitySection { router: this.router, siteSettings: this.siteSettings, inMoreDrawer, + overridenName, + overridenIcon, }); } get decoratedTitle() { return I18n.t( - `sidebar.sections.${this.section.title.toLowerCase()}.header_link_text` + `sidebar.sections.${this.section.title.toLowerCase()}.header_link_text`, + { defaultValue: this.section.title } ); } get headerActions() { + if (this.currentUser?.admin) { + return [ + { + action: this.editSection, + title: I18n.t( + "sidebar.sections.community.header_action_edit_section_title" + ), + }, + ]; + } if (this.currentUser) { return [ { action: this.composeTopic, - title: I18n.t("sidebar.sections.community.header_action_title"), + title: I18n.t( + "sidebar.sections.community.header_action_create_topic_title" + ), }, ]; } } get headerActionIcon() { - return "plus"; + return this.currentUser?.admin ? "pencil-alt" : "plus"; + } + + @action + editSection() { + showModal("sidebar-section-form", { model: this.section }); } @action diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-section-link.js index 850a7c48d6b..86aa606f93d 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-section-link.js @@ -16,14 +16,17 @@ export default class AdminSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.admin.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } get shouldDisplay() { - return this.currentUser?.staff; + return !!this.currentUser?.staff; } - get prefixValue() { + get defaultPrefixValue() { return "wrench"; } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js index 94debfda7d2..4c7a54f20ee 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/my-posts-section-link.js @@ -71,7 +71,12 @@ export default class MyPostsSectionLink extends BaseSectionLink { if (this._hasDraft && this.currentUser?.new_new_view_enabled) { return I18n.t("sidebar.sections.community.links.my_posts.content_drafts"); } else { - return I18n.t("sidebar.sections.community.links.my_posts.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName + .toLowerCase() + .replace(" ", "/")}.content`, + { defaultValue: this.overridenName } + ); } } @@ -90,7 +95,7 @@ export default class MyPostsSectionLink extends BaseSectionLink { return this.draftCount > 0; } - get prefixValue() { + get defaultPrefixValue() { if (this._hasDraft && this.currentUser?.new_new_view_enabled) { return "pencil-alt"; } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/review-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/review-section-link.js index 1ac7d1dabff..f9a66aa4885 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/review-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/review-section-link.js @@ -53,7 +53,10 @@ export default class ReviewSectionLink extends BaseSectionLink { } get text() { - return I18n.t("sidebar.sections.community.links.review.content"); + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); } get shouldDisplay() { @@ -70,7 +73,7 @@ export default class ReviewSectionLink extends BaseSectionLink { } } - get prefixValue() { + get defaultPrefixValue() { return "flag"; } } diff --git a/app/assets/javascripts/discourse/app/templates/modal/sidebar-section-form.hbs b/app/assets/javascripts/discourse/app/templates/modal/sidebar-section-form.hbs index 831629cc97b..b7adff24ef8 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/sidebar-section-form.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/sidebar-section-form.hbs @@ -22,67 +22,23 @@ {{/if}} - {{#each this.activeLinks as |link|}} -
-
- - - {{#if link.invalidIconMessage}} -
- {{link.invalidIconMessage}} -
- {{/if}} -
-
- - - {{#if link.invalidNameMessage}} -
- {{link.invalidNameMessage}} -
- {{/if}} -
-
- - - {{#if link.invalidValueMessage}} -
- {{link.invalidValueMessage}} -
- {{/if}} -
- +
+ + + +
+ {{#each this.activeLinks as |link|}} + {{/each}} - {{#if this.currentUser.staff}} -
+ {{#if this.model.sectionType}} +
+

{{i18n "sidebar.sections.custom.more_menu"}}

+ {{#each this.activeSecondaryLinks as |link|}} + + {{/each}} + + + {{#if this.model.sectionType}} + + {{/if}} + {{/if}} + {{#if (and this.currentUser.staff (not this.model.sectionType))}} +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js index cd32940cfc9..17734586c44 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js @@ -25,6 +25,7 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) { tracked_tags: ["tag1"], watched_tags: ["tag2"], watching_first_post_tags: ["tag3"], + admin: false, }); needs.settings({ diff --git a/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js index cd2713b541c..01d015a253d 100644 --- a/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js @@ -75,7 +75,7 @@ export default { }, { id: 331, - name: "Info", + name: "About", value: "/about", icon: "info-circle", external: false, diff --git a/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js index c7c05895436..86225054781 100644 --- a/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js @@ -723,7 +723,7 @@ export default { }, { id: 331, - name: "Info", + name: "About", value: "/about", icon: "info-circle", external: false, diff --git a/app/assets/stylesheets/common/base/sidebar-custom-section.scss b/app/assets/stylesheets/common/base/sidebar-custom-section.scss index 998ed4e4f7c..d574e47f47f 100644 --- a/app/assets/stylesheets/common/base/sidebar-custom-section.scss +++ b/app/assets/stylesheets/common/base/sidebar-custom-section.scss @@ -16,6 +16,10 @@ .sidebar-section-link-prefix.icon { cursor: move; } + .sidebar-section[data-section-name="community"] + .sidebar-section-link-prefix.icon { + cursor: pointer; + } a { -webkit-touch-callout: none !important; diff --git a/app/assets/stylesheets/common/base/sidebar.scss b/app/assets/stylesheets/common/base/sidebar.scss index df39a9eb167..f36a70e00e0 100644 --- a/app/assets/stylesheets/common/base/sidebar.scss +++ b/app/assets/stylesheets/common/base/sidebar.scss @@ -106,8 +106,21 @@ } } } - .sidebar-section-form-modal { + .draggable { + cursor: move; + align-self: center; + margin-left: auto; + margin-right: auto; + -webkit-user-drag: element; + -khtml-user-drag: element; + -moz-user-drag: element; + -o-user-drag: element; + user-drag: element; + } + .dragging { + opacity: 0.4; + } .modal-inner-container { width: var(--modal-max-width); } @@ -122,17 +135,52 @@ } .row-wrapper { display: grid; - grid-template-columns: auto auto auto 2em; + grid-template-columns: 25px 60px auto auto 2em; gap: 1em; - margin-top: 1em; + padding: 0.5em 1px; + -webkit-user-drag: none; + -khtml-user-drag: none; + -moz-user-drag: none; + -o-user-drag: none; + user-drag: none; + cursor: default; + + &.header { + padding-bottom: 0; + padding-top: 1em; + label { + margin-bottom: 0; + } + .link-url { + margin-left: -1em; + } + } + + &.drag-above { + border-top: 1px dotted #666; + margin-top: -1px; + } + &.drag-below { + border-bottom: 1px dotted #666; + padding-bottom: calc(0.5em - 1px); + } + .link-icon { + grid-column: 1 / span 2; + padding-left: calc(25px + 1em); + } + &.mark-public-wrapper { + label { + grid-column: 1 / -1; + } + } } .delete-link { height: 1em; - align-self: end; - margin-bottom: 0.75em; + align-self: center; margin-right: 1em; } - .btn-flat.add-link { + .btn-flat.add-link, + .btn-flat.reset-link { margin-top: 1em; margin-left: -0.65em; &:active, @@ -148,6 +196,9 @@ color: var(--tertiary-hover); } } + .btn-flat.reset-link { + float: right; + } .modal-footer { display: flex; justify-content: space-between; @@ -156,4 +207,13 @@ margin-right: 0; } } + .select-kit.multi-select .multi-select-header .formatted-selection { + display: none; + } + .modal-inner-container .select-kit { + width: 60px; + } + .select-kit.is-expanded .select-kit-body { + width: 220px !important; + } } diff --git a/app/controllers/sidebar_sections_controller.rb b/app/controllers/sidebar_sections_controller.rb index a9eea34f3dd..a61af92b7a8 100644 --- a/app/controllers/sidebar_sections_controller.rb +++ b/app/controllers/sidebar_sections_controller.rb @@ -59,7 +59,7 @@ class SidebarSectionsController < ApplicationController Site.clear_anon_cache! end - render_serialized(sidebar_section, SidebarSectionSerializer) + render_serialized(sidebar_section.reload, SidebarSectionSerializer) rescue ActiveRecord::RecordInvalid => e render_json_error(e.record.errors.full_messages.first) rescue Discourse::InvalidAccess diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2cbc4c766bd..581b6d68204 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4413,10 +4413,13 @@ en: save: "Save" delete: "Delete" delete_confirm: "Are you sure you want to delete this section?" + reset_confirm: "Are you sure you want to reset this section to default?" public: "Make this section public and visible to everyone" + more_menu: "More menu" links: add: "Add another link" delete: "Delete link" + reset: "Reset to default" icon: label: "Icon" validation: @@ -4473,7 +4476,8 @@ en: configure_defaults: "Configure defaults" community: header_link_text: "Community" - header_action_title: "Create a topic" + header_action_create_topic_title: "Create a topic" + header_action_edit_section_title: "Edit Community section" links: about: content: "About" diff --git a/spec/system/custom_sidebar_sections_spec.rb b/spec/system/custom_sidebar_sections_spec.rb index 33f69c55e39..b2f5356e12b 100644 --- a/spec/system/custom_sidebar_sections_spec.rb +++ b/spec/system/custom_sidebar_sections_spec.rb @@ -114,25 +114,17 @@ describe "Custom sidebar sections", type: :system, js: true do sign_in user visit("/latest") - within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(1)") do - expect(sidebar).to have_section_link("Sidebar Tags") - end - - within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(2)") do - expect(sidebar).to have_section_link("Sidebar Categories") - end + expect(sidebar.primary_section_links("my-section")).to eq( + ["Sidebar Tags", "Sidebar Categories"], + ) tags_link = find(".sidebar-section-link[data-link-name='Sidebar Tags']") categories_link = find(".sidebar-section-link[data-link-name='Sidebar Categories']") tags_link.drag_to(categories_link, html5: true, delay: 0.4) - within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(1)") do - expect(sidebar).to have_section_link("Sidebar Categories") - end - - within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(2)") do - expect(sidebar).to have_section_link("Sidebar Tags") - end + expect(sidebar.primary_section_links("my-section")).to eq( + ["Sidebar Categories", "Sidebar Tags"], + ) end it "does not allow the user to edit public section" do @@ -201,6 +193,29 @@ describe "Custom sidebar sections", type: :system, js: true do expect(sidebar).to have_no_section("Edited public section") end + it "allows admin to edit community section and reset to default" do + sign_in admin + visit("/latest") + + sidebar.edit_custom_section("Community") + section_modal.fill_name("Edited community section") + section_modal.everything_link.drag_to(section_modal.review_link, delay: 0.4) + section_modal.save + + expect(sidebar).to have_section("Edited community section") + expect(sidebar.primary_section_links("edited-community-section")).to eq( + ["My Posts", "Everything", "Admin", "More"], + ) + + sidebar.edit_custom_section("Edited community section") + section_modal.reset + + expect(sidebar).to have_section("Community") + expect(sidebar.primary_section_links("community")).to eq( + ["Everything", "My Posts", "Admin", "More"], + ) + end + it "shows anonymous public sections" do sidebar_section = Fabricate(:sidebar_section, title: "Public section", public: true) sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags") diff --git a/spec/system/page_objects/components/sidebar.rb b/spec/system/page_objects/components/sidebar.rb index 87e2056030d..a8b944ba8ef 100644 --- a/spec/system/page_objects/components/sidebar.rb +++ b/spec/system/page_objects/components/sidebar.rb @@ -63,6 +63,10 @@ module PageObjects find(SIDEBAR_WRAPPER_SELECTOR).has_no_button?(name) end + def primary_section_links(slug) + all("[data-section-name='#{slug}'] .sidebar-section-link-wrapper").map(&:text) + end + private def section_link_present?(name, href: nil, active: false, present:) diff --git a/spec/system/page_objects/components/sidebar_header_dropdown.rb b/spec/system/page_objects/components/sidebar_header_dropdown.rb index d07cbc669c4..e855bcbcbb9 100644 --- a/spec/system/page_objects/components/sidebar_header_dropdown.rb +++ b/spec/system/page_objects/components/sidebar_header_dropdown.rb @@ -25,7 +25,7 @@ module PageObjects def click_community_header_button page.click_button( - I18n.t("js.sidebar.sections.community.header_action_title"), + I18n.t("js.sidebar.sections.community.header_action_create_topic_title"), class: "sidebar-section-header-button", ) end diff --git a/spec/system/page_objects/modals/sidebar_section_form.rb b/spec/system/page_objects/modals/sidebar_section_form.rb index 6b789a7f9a7..37100fd5a07 100644 --- a/spec/system/page_objects/modals/sidebar_section_form.rb +++ b/spec/system/page_objects/modals/sidebar_section_form.rb @@ -28,6 +28,11 @@ module PageObjects find(".dialog-container .btn-primary").click end + def reset + find(".reset-link").click + find(".dialog-footer .btn-primary").click + end + def save find("#save-section").click end @@ -39,9 +44,18 @@ module PageObjects def has_disabled_save? find_button("Save", disabled: true) end + def has_enabled_save? find_button("Save", disabled: false) end + + def everything_link + find(".draggable[data-link-name='Everything']") + end + + def review_link + find(".draggable[data-link-name='Review']") + end end end end