DEV: move sidebar community section to database (#21166)

* DEV: move sidebar community section to database

Before, community section was hard-coded. In the future, we are planning to allow admins to edit it. Therefore, it has to be moved to database to `custom_sections` table.

Few steps and simplifications has to be made:
- custom section was hidden behind `enable_custom_sidebar_sections` feature flag. It has to be deleted so all forums, see community section;
- migration to add `section_type` column to sidebar section to show it is a special type;
- migration to add `segment` column to sidebar links to determine if link should be displayed in primary section or in more section;
- simplify more section to have one level only (secondary section links are merged);
- ensure that links like `everything` are correctly tracking state;
- make user an anonymous links position consistence. For example, from now on `faq` link for user and anonymous is visible in more tab;
- delete old community-section template.
This commit is contained in:
Krzysztof Kotlarek 2023-05-04 12:14:09 +10:00 committed by GitHub
parent afc1611be7
commit 709fa24558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 705 additions and 502 deletions

View File

@ -1,36 +0,0 @@
import SidebarCommonCommunitySection from "discourse/components/sidebar/common/community-section";
import EverythingSectionLink from "discourse/lib/sidebar/common/community-section/everything-section-link";
import AboutSectionLink from "discourse/lib/sidebar/common/community-section/about-section-link";
import FAQSectionLink from "discourse/lib/sidebar/common/community-section/faq-section-link";
import GroupsSectionLink from "discourse/lib/sidebar/common/community-section/groups-section-link";
import UsersSectionLink from "discourse/lib/sidebar/common/community-section/users-section-link";
import BadgesSectionLink from "discourse/lib/sidebar/common/community-section/badges-section-link";
export default class SidebarAnonymousCommunitySection extends SidebarCommonCommunitySection {
get defaultMainSectionLinks() {
const defaultLinks = [
EverythingSectionLink,
UsersSectionLink,
FAQSectionLink,
];
defaultLinks.splice(
this.displayShortSiteDescription ? 0 : 2,
0,
AboutSectionLink
);
return defaultLinks;
}
get displayShortSiteDescription() {
return (
!this.currentUser &&
(this.siteSettings.short_site_description || "").length > 0
);
}
get defaultMoreSectionLinks() {
return [GroupsSectionLink, BadgesSectionLink];
}
}

View File

@ -5,27 +5,49 @@
@headerLinkText={{section.decoratedTitle}} @headerLinkText={{section.decoratedTitle}}
@collapsable={{true}} @collapsable={{true}}
> >
{{#if section.displayShortSiteDescription}}
<Sidebar::SectionMessage>
{{section.siteSettings.short_site_description}}
</Sidebar::SectionMessage>
{{/if}}
{{#each section.links as |link|}} {{#each section.links as |link|}}
{{#if link.external}} {{#if link.shouldDisplay}}
<Sidebar::SectionLink {{#if link.external}}
@linkName={{link.name}} <Sidebar::SectionLink
@content={{replace-emoji link.name}} @linkName={{link.name}}
@prefixType="icon" @content={{replace-emoji link.text}}
@prefixValue={{link.icon}} @prefixType="icon"
@href={{link.value}} @prefixValue={{link.prefixValue}}
/> @href={{link.value}}
{{else}} />
<Sidebar::SectionLink {{else}}
@linkName={{link.name}} <Sidebar::SectionLink
@route={{link.route}} @shouldDisplay={{link.shouldDisplay}}
@models={{link.models}} @href={{link.href}}
@query={{link.query}} @title={{link.title}}
@content={{replace-emoji link.name}} @currentWhen={{link.currentWhen}}
@prefixType="icon" @badgeText={{link.badgeText}}
@prefixValue={{link.icon}} @suffixCSSClass={{link.suffixCSSClass}}
/> @suffixValue={{link.suffixValue}}
@suffixType={{link.suffixType}}
@linkName={{link.name}}
@route={{link.route}}
@model={{link.model}}
@models={{link.models}}
@query={{link.query}}
@content={{replace-emoji link.text}}
@prefixType="icon"
@prefixValue={{link.prefixValue}}
/>
{{/if}}
{{/if}} {{/if}}
{{/each}} {{/each}}
{{#if section.moreLinks}}
<Sidebar::MoreSectionLinks @sectionLinks={{section.moreLinks}} />
{{/if}}
</Sidebar::Section> </Sidebar::Section>
{{/each}} {{/each}}
</div> </div>

View File

@ -1,16 +1,29 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import Section from "discourse/components/sidebar/user/section"; import Section from "discourse/lib/sidebar/section";
import CommunitySection from "discourse/lib/sidebar/community-section";
export default class SidebarAnonymousCustomSections extends Component { export default class SidebarAnonymousCustomSections extends Component {
@service router; @service router;
@service site; @service site;
@service siteSettings;
get sections() { get sections() {
return this.site.anonymous_sidebar_sections?.map((section) => { return this.site.anonymous_sidebar_sections?.map((section) => {
return new Section({ let klass;
switch (section.section_type) {
case "community":
klass = CommunitySection;
break;
default:
klass = Section;
}
return new klass({
section, section,
currentUser: this.currentUser,
router: this.router, router: this.router,
siteSettings: this.siteSettings,
}); });
}); });
} }

View File

@ -1,5 +1,4 @@
<div class="sidebar-sections sidebar-sections-anonymous"> <div class="sidebar-sections sidebar-sections-anonymous">
<Sidebar::Anonymous::CommunitySection @collapsable={{@collapsableSections}} />
<Sidebar::Anonymous::CustomSections /> <Sidebar::Anonymous::CustomSections />
<Sidebar::Anonymous::CategoriesSection <Sidebar::Anonymous::CategoriesSection
@collapsable={{@collapsableSections}} @collapsable={{@collapsableSections}}

View File

@ -1,49 +0,0 @@
<Sidebar::Section
@sectionName="community"
@headerLinkText={{i18n "sidebar.sections.community.header_link_text"}}
@headerActionsIcon={{this.headerActionsIcon}}
@headerActions={{this.headerActions}}
@collapsable={{@collapsable}}
>
{{#if this.displayShortSiteDescription}}
<Sidebar::SectionMessage>
{{this.siteSettings.short_site_description}}
</Sidebar::SectionMessage>
{{/if}}
{{#each this.sectionLinks as |sectionLink|}}
<Sidebar::SectionLink
@shouldDisplay={{sectionLink.shouldDisplay}}
@linkName={{sectionLink.name}}
@href={{sectionLink.href}}
@route={{sectionLink.route}}
@query={{sectionLink.query}}
@title={{sectionLink.title}}
@content={{sectionLink.text}}
@currentWhen={{sectionLink.currentWhen}}
@badgeText={{sectionLink.badgeText}}
@model={{sectionLink.model}}
@models={{sectionLink.models}}
@prefixType={{sectionLink.prefixType}}
@prefixValue={{sectionLink.prefixValue}}
@suffixCSSClass={{sectionLink.suffixCSSClass}}
@suffixValue={{sectionLink.suffixValue}}
@suffixType={{sectionLink.suffixType}}
/>
{{/each}}
{{#if this.isDesktopDropdownMode}}
{{#each this.moreSectionLinks as |sectionLink|}}
<Sidebar::MoreSectionLink @sectionLink={{sectionLink}} />
{{/each}}
{{#each this.moreSecondarySectionLinks as |sectionLink|}}
<Sidebar::MoreSectionLink @sectionLink={{sectionLink}} />
{{/each}}
{{else}}
<Sidebar::MoreSectionLinks
@sectionLinks={{this.moreSectionLinks}}
@secondarySectionLinks={{this.moreSecondarySectionLinks}}
/>
{{/if}}
</Sidebar::Section>

View File

@ -1,104 +0,0 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import {
customSectionLinks,
secondaryCustomSectionLinks,
} from "discourse/lib/sidebar/custom-community-section-links";
export default class SidebarCommunitySection extends Component {
@service router;
@service topicTrackingState;
@service currentUser;
@service appEvents;
@service site;
@service siteSettings;
@tracked sectionLinks;
@tracked moreSectionLinks;
@tracked moreSecondarySectionLinks;
callbackId;
headerActionsIcon;
headerActions;
constructor() {
super(...arguments);
this.moreSectionLinks = this.#initializeSectionLinks(
[...this.defaultMoreSectionLinks, ...customSectionLinks],
{ inMoreDrawer: true }
);
this.moreSecondarySectionLinks = this.#initializeSectionLinks(
[
...this.defaultMoreSecondarySectionLinks,
...secondaryCustomSectionLinks,
],
{ inMoreDrawer: true }
);
this.sectionLinks = this.#initializeSectionLinks(
this.defaultMainSectionLinks,
{ inMoreDrawer: false }
);
this.callbackId = this.topicTrackingState.onStateChange(() => {
this.sectionLinks.forEach((sectionLink) => {
sectionLink.onTopicTrackingStateChange();
});
});
}
willDestroy() {
[
...this.sectionLinks,
...this.moreSectionLinks,
...this.moreSecondarySectionLinks,
].forEach((sectionLink) => {
sectionLink.teardown?.();
});
this.topicTrackingState.offStateChange(this.callbackId);
}
// Override in child
get defaultMainSectionLinks() {
return [];
}
// Override in child
get defaultMoreSectionLinks() {
return [];
}
// Override in child
get defaultMoreSecondarySectionLinks() {
return [];
}
get isDesktopDropdownMode() {
const headerDropdownMode =
this.siteSettings.navigation_menu === "header dropdown";
return !this.site.mobileView && headerDropdownMode;
}
#initializeSectionLinks(sectionLinkClasses, { inMoreDrawer } = {}) {
return sectionLinkClasses.map((sectionLinkClass) => {
return this.#initializeSectionLink(sectionLinkClass, inMoreDrawer);
});
}
#initializeSectionLink(sectionLinkClass, inMoreDrawer) {
return new sectionLinkClass({
topicTrackingState: this.topicTrackingState,
currentUser: this.currentUser,
appEvents: this.appEvents,
router: this.router,
siteSettings: this.siteSettings,
inMoreDrawer,
});
}
}

View File

@ -3,14 +3,12 @@
<div class="sidebar-footer-actions"> <div class="sidebar-footer-actions">
<PluginOutlet @name="sidebar-footer-actions" /> <PluginOutlet @name="sidebar-footer-actions" />
{{#if this.currentUser.custom_sidebar_sections_enabled}} <DButton
<DButton @icon="plus"
@icon="plus" @action={{action this.addSection}}
@action={{action this.addSection}} @class="btn-flat add-section"
@class="btn-flat add-section" @title="sidebar.sections.custom.add"
@title="sidebar.sections.custom.add" />
/>
{{/if}}
{{#if {{#if
(or (or

View File

@ -27,14 +27,6 @@
<Sidebar::MoreSectionLink @sectionLink={{sectionLink}} /> <Sidebar::MoreSectionLink @sectionLink={{sectionLink}} />
{{/each}} {{/each}}
</div> </div>
{{#if (gt this.secondarySectionLinks.length 0)}}
<div class="sidebar-more-section-links-details-content-secondary">
{{#each this.secondarySectionLinks as |sectionLink|}}
<Sidebar::MoreSectionLink @sectionLink={{sectionLink}} />
{{/each}}
</div>
{{/if}}
</div> </div>
</div> </div>
{{/if}} {{/if}}

View File

@ -12,8 +12,6 @@ export default class SidebarMoreSectionLinks extends Component {
@tracked shouldDisplaySectionLinks = false; @tracked shouldDisplaySectionLinks = false;
@tracked activeSectionLink; @tracked activeSectionLink;
#allLinks = [...this.args.sectionLinks, ...this.args.secondarySectionLinks];
constructor() { constructor() {
super(...arguments); super(...arguments);
this.#setActiveSectionLink(); this.#setActiveSectionLink();
@ -92,7 +90,7 @@ export default class SidebarMoreSectionLinks extends Component {
} }
#setActiveSectionLink() { #setActiveSectionLink() {
const activeSectionLink = this.#allLinks.find((sectionLink) => { const activeSectionLink = this.args.sectionLinks.find((sectionLink) => {
const args = [sectionLink.route]; const args = [sectionLink.route];
if (sectionLink.model) { if (sectionLink.model) {

View File

@ -1,77 +0,0 @@
import I18n from "I18n";
import Composer from "discourse/models/composer";
import { getOwner } from "discourse-common/lib/get-owner";
import PermissionType from "discourse/models/permission-type";
import EverythingSectionLink from "discourse/lib/sidebar/common/community-section/everything-section-link";
import MyPostsSectionLink from "discourse/lib/sidebar/user/community-section/my-posts-section-link";
import GroupsSectionLink from "discourse/lib/sidebar/common/community-section/groups-section-link";
import UsersSectionLink from "discourse/lib/sidebar/common/community-section/users-section-link";
import AboutSectionLink from "discourse/lib/sidebar/common/community-section/about-section-link";
import FAQSectionLink from "discourse/lib/sidebar/common/community-section/faq-section-link";
import AdminSectionLink from "discourse/lib/sidebar/user/community-section/admin-section-link";
import BadgesSectionLink from "discourse/lib/sidebar/common/community-section/badges-section-link";
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
import SidebarCommonCommunitySection from "discourse/components/sidebar/common/community-section";
import { action } from "@ember/object";
import { next } from "@ember/runloop";
import { inject as service } from "@ember/service";
export default class SidebarUserCommunitySection extends SidebarCommonCommunitySection {
@service composer;
constructor() {
super(...arguments);
this.headerActionsIcon = "plus";
this.headerActions = [
{
action: this.composeTopic,
title: I18n.t("sidebar.sections.community.header_action_title"),
},
];
}
get defaultMainSectionLinks() {
return [
EverythingSectionLink,
MyPostsSectionLink,
AdminSectionLink,
ReviewSectionLink,
];
}
get defaultMoreSectionLinks() {
return [
GroupsSectionLink,
UsersSectionLink,
BadgesSectionLink,
ReviewSectionLink,
];
}
get defaultMoreSecondarySectionLinks() {
return [AboutSectionLink, FAQSectionLink];
}
@action
composeTopic() {
const composerArgs = {
action: Composer.CREATE_TOPIC,
draftKey: Composer.NEW_TOPIC_KEY,
};
const controller = getOwner(this).lookup("controller:navigation/category");
const category = controller.category;
if (category && category.permission === PermissionType.FULL) {
composerArgs.categoryId = category.id;
}
next(() => {
this.composer.open(composerArgs);
});
}
}

View File

@ -5,42 +5,67 @@
@headerLinkText={{section.decoratedTitle}} @headerLinkText={{section.decoratedTitle}}
@collapsable={{true}} @collapsable={{true}}
@headerActions={{section.headerActions}} @headerActions={{section.headerActions}}
@headerActionsIcon="pencil-alt" @headerActionsIcon={{section.headerActionIcon}}
@class={{section.dragCss}} @class={{section.dragCss}}
> >
{{#each section.links as |link|}} {{#each section.links as |link|}}
{{#if link.external}} {{#if link.shouldDisplay}}
<Sidebar::SectionLink {{#if link.external}}
@linkName={{link.name}} <Sidebar::SectionLink
@content={{replace-emoji link.name}} @linkName={{link.name}}
@prefixType="icon" @content={{replace-emoji link.text}}
@prefixValue={{link.icon}} @prefixType="icon"
@href={{link.value}} @prefixValue={{link.prefixValue}}
@class={{link.linkDragCss}} @href={{link.value}}
{{draggable @class={{link.linkDragCss}}
didStartDrag=link.didStartDrag {{draggable
didEndDrag=link.didEndDrag didStartDrag=link.didStartDrag
dragMove=link.dragMove didEndDrag=link.didEndDrag
}} dragMove=link.dragMove
/> }}
{{else}} />
<Sidebar::SectionLink {{else}}
@linkName={{link.name}} <Sidebar::SectionLink
@route={{link.route}} @shouldDisplay={{link.shouldDisplay}}
@models={{link.models}} @href={{link.href}}
@query={{link.query}} @title={{link.title}}
@content={{replace-emoji link.name}} @linkName={{link.name}}
@prefixType="icon" @route={{link.route}}
@prefixValue={{link.icon}} @model={{link.model}}
@class={{link.linkDragCss}} @models={{link.models}}
{{draggable @query={{link.query}}
didStartDrag=link.didStartDrag @content={{replace-emoji link.text}}
didEndDrag=link.didEndDrag @badgeText={{link.badgeText}}
dragMove=link.dragMove @prefixType="icon"
}} @prefixValue={{link.prefixValue}}
/> @suffixCSSClass={{link.suffixCSSClass}}
@suffixValue={{link.suffixValue}}
@suffixType={{link.suffixType}}
@currentWhen={{link.currentWhen}}
@class={{link.linkDragCss}}
{{(if
link.didStartDrag
(modifier
"draggable"
didStartDrag=link.didStartDrag
didEndDrag=link.didEndDrag
dragMove=link.dragMove
)
)}}
/>
{{/if}}
{{/if}} {{/if}}
{{/each}} {{/each}}
{{#if this.isDesktopDropdownMode}}
{{#each section.moreLinks as |sectionLink|}}
<Sidebar::MoreSectionLink @sectionLink={{sectionLink}} />
{{/each}}
{{else}}
{{#if section.moreLinks}}
<Sidebar::MoreSectionLinks @sectionLinks={{section.moreLinks}} />
{{/if}}
{{/if}}
</Sidebar::Section> </Sidebar::Section>
{{/each}} {{/each}}
</div> </div>

View File

@ -2,12 +2,18 @@ import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import Section from "discourse/components/sidebar/user/section"; import { cached } from "@glimmer/tracking";
import Section from "discourse/lib/sidebar/section";
import CommunitySection from "discourse/lib/sidebar/community-section";
export default class SidebarUserCustomSections extends Component { export default class SidebarUserCustomSections extends Component {
@service currentUser; @service currentUser;
@service router; @service router;
@service messageBus; @service messageBus;
@service appEvents;
@service topicTrackingState;
@service site;
@service siteSettings;
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -16,18 +22,43 @@ export default class SidebarUserCustomSections extends Component {
willDestroy() { willDestroy() {
this.messageBus.unsubscribe("/refresh-sidebar-sections"); this.messageBus.unsubscribe("/refresh-sidebar-sections");
return this.sections.forEach((section) => {
section.teardown?.();
});
} }
@cached
get sections() { get sections() {
return this.currentUser.sidebarSections.map((section) => { return this.currentUser.sidebarSections.map((section) => {
return new Section({ switch (section.section_type) {
section, case "community":
currentUser: this.currentUser, const systemSection = new CommunitySection({
router: this.router, section,
}); currentUser: this.currentUser,
router: this.router,
appEvents: this.appEvents,
topicTrackingState: this.topicTrackingState,
siteSettings: this.siteSettings,
});
return systemSection;
break;
default:
return new Section({
section,
currentUser: this.currentUser,
router: this.router,
});
}
}); });
} }
get isDesktopDropdownMode() {
const headerDropdownMode =
this.siteSettings.navigation_menu === "header dropdown";
return !this.site.mobileView && headerDropdownMode;
}
@bind @bind
_refresh() { _refresh() {
return ajax("/sidebar_sections.json", {}).then((json) => { return ajax("/sidebar_sections.json", {}).then((json) => {

View File

@ -1,8 +1,5 @@
<div class="sidebar-sections"> <div class="sidebar-sections">
<Sidebar::User::CommunitySection @collapsable={{@collapsableSections}} /> <Sidebar::User::CustomSections />
{{#if this.currentUser.custom_sidebar_sections_enabled}}
<Sidebar::User::CustomSections />
{{/if}}
<Sidebar::User::CategoriesSection @collapsable={{@collapsableSections}} /> <Sidebar::User::CategoriesSection @collapsable={{@collapsableSections}} />
{{#if this.currentUser.display_sidebar_tags}} {{#if this.currentUser.display_sidebar_tags}}

View File

@ -0,0 +1,169 @@
import I18n from "I18n";
import SectionLink from "discourse/lib/sidebar/section-link";
import Composer from "discourse/models/composer";
import { getOwner } from "discourse-common/lib/get-owner";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { next } from "@ember/runloop";
import PermissionType from "discourse/models/permission-type";
import EverythingSectionLink from "discourse/lib/sidebar/common/community-section/everything-section-link";
import MyPostsSectionLink from "discourse/lib/sidebar/user/community-section/my-posts-section-link";
import AdminSectionLink from "discourse/lib/sidebar/user/community-section/admin-section-link";
import AboutSectionLink from "discourse/lib/sidebar/common/community-section/about-section-link";
import FAQSectionLink from "discourse/lib/sidebar/common/community-section/faq-section-link";
import UsersSectionLink from "discourse/lib/sidebar/common/community-section/users-section-link";
import GroupsSectionLink from "discourse/lib/sidebar/common/community-section/groups-section-link";
import BadgesSectionLink from "discourse/lib/sidebar/common/community-section/badges-section-link";
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
import {
customSectionLinks,
secondaryCustomSectionLinks,
} from "discourse/lib/sidebar/custom-community-section-links";
const LINKS_IN_BOTH_SEGMENTS = ["/review"];
const SPECIAL_LINKS_MAP = {
"/latest": EverythingSectionLink,
"/new": EverythingSectionLink,
"/about": AboutSectionLink,
"/u": UsersSectionLink,
"/faq": FAQSectionLink,
"/my/activity": MyPostsSectionLink,
"/review": ReviewSectionLink,
"/badges": BadgesSectionLink,
"/admin": AdminSectionLink,
"/g": GroupsSectionLink,
};
export default class CommunitySection {
@tracked links;
@tracked moreLinks;
constructor({
section,
currentUser,
router,
topicTrackingState,
appEvents,
siteSettings,
}) {
this.section = section;
this.router = router;
this.currentUser = currentUser;
this.slug = section.slug;
this.topicTrackingState = topicTrackingState;
this.appEvents = appEvents;
this.siteSettings = siteSettings;
this.section_type = section.section_type;
this.callbackId = this.topicTrackingState?.onStateChange(() => {
this.links.forEach((link) => {
if (link.onTopicTrackingStateChange) {
link.onTopicTrackingStateChange();
}
});
});
this.apiLinks = customSectionLinks
.concat(secondaryCustomSectionLinks)
.map((link) => this.#initializeSectionLink(link, { inMoreDrawer: true }));
this.links = this.section.links
.filter(
(link) =>
link.segment === "primary" ||
LINKS_IN_BOTH_SEGMENTS.includes(link.value)
)
.map((link) => {
return this.#generateLink(link);
})
.filter((link) => link);
this.moreLinks = this.section.links
.filter(
(link) =>
link.segment === "secondary" ||
LINKS_IN_BOTH_SEGMENTS.includes(link.value)
)
.map((link) => {
return this.#generateLink(link, true);
})
.concat(this.apiLinks)
.filter((link) => link);
}
teardown() {
if (this.callbackId) {
this.topicTrackingState.offStateChange(this.callbackId);
}
[...this.links, ...this.moreLinks].forEach((sectionLink) => {
sectionLink.teardown?.();
});
}
#generateLink(link, inMoreDrawer = false) {
const sectionLinkClass = SPECIAL_LINKS_MAP[link.value];
if (sectionLinkClass) {
return this.#initializeSectionLink(sectionLinkClass, inMoreDrawer);
} else {
return new SectionLink(link, this, this.router);
}
}
#initializeSectionLink(sectionLinkClass, inMoreDrawer) {
if (this.router.isDestroying) {
return;
}
return new sectionLinkClass({
topicTrackingState: this.topicTrackingState,
currentUser: this.currentUser,
appEvents: this.appEvents,
router: this.router,
siteSettings: this.siteSettings,
inMoreDrawer,
});
}
get displayShortSiteDescription() {
return !this.currentUser && !!this.siteSettings.short_site_description;
}
get decoratedTitle() {
return I18n.t(
`sidebar.sections.${this.section.title.toLowerCase()}.header_link_text`
);
}
get headerActions() {
if (this.currentUser) {
return [
{
action: this.composeTopic,
title: I18n.t("sidebar.sections.community.header_action_title"),
},
];
}
}
get headerActionIcon() {
return "plus";
}
@action
composeTopic() {
const composerArgs = {
action: Composer.CREATE_TOPIC,
draftKey: Composer.NEW_TOPIC_KEY,
};
const controller = getOwner(this).lookup("controller:navigation/category");
const category = controller.category;
if (category && category.permission === PermissionType.FULL) {
composerArgs.categoryId = category.id;
}
next(() => {
getOwner(this).lookup("controller:composer").open(composerArgs);
});
}
}

View File

@ -11,9 +11,10 @@ export default class SectionLink {
constructor({ external, icon, id, name, value }, section, router) { constructor({ external, icon, id, name, value }, section, router) {
this.external = external; this.external = external;
this.icon = icon; this.prefixValue = icon;
this.id = id; this.id = id;
this.name = name; this.name = name;
this.text = name;
this.value = value; this.value = value;
this.section = section; this.section = section;
@ -25,6 +26,10 @@ export default class SectionLink {
} }
} }
get shouldDisplay() {
return true;
}
@bind @bind
didStartDrag(event) { didStartDrag(event) {
// 0 represents left button of the mouse // 0 represents left button of the mouse

View File

@ -2,7 +2,7 @@ import I18n from "I18n";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { iconHTML } from "discourse-common/lib/icon-library"; import { iconHTML } from "discourse-common/lib/icon-library";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import SectionLink from "discourse/components/sidebar/user/section-link"; import SectionLink from "discourse/lib/sidebar/section-link";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
@ -41,6 +41,10 @@ export default class Section {
} }
} }
get headerActionIcon() {
return "pencil-alt";
}
@bind @bind
disable() { disable() {
this.dragCss = "disabled"; this.dragCss = "disabled";

View File

@ -7,13 +7,19 @@ import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sideb
const USER_DRAFTS_CHANGED_EVENT = "user-drafts:changed"; const USER_DRAFTS_CHANGED_EVENT = "user-drafts:changed";
export default class MyPostsSectionLink extends BaseSectionLink { export default class MyPostsSectionLink extends BaseSectionLink {
@tracked draftCount = this.currentUser.draft_count; @tracked draftCount = this.currentUser?.draft_count;
@tracked hideCount = @tracked hideCount =
this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION;
constructor() { constructor() {
super(...arguments); super(...arguments);
this.appEvents.on(USER_DRAFTS_CHANGED_EVENT, this, this._updateDraftCount); if (this.shouldDisplay) {
this.appEvents.on(
USER_DRAFTS_CHANGED_EVENT,
this,
this._updateDraftCount
);
}
} }
teardown() { teardown() {
@ -97,4 +103,8 @@ export default class MyPostsSectionLink extends BaseSectionLink {
return "circle"; return "circle";
} }
} }
get shouldDisplay() {
return this.currentUser;
}
} }

View File

@ -12,19 +12,26 @@ export default class ReviewSectionLink extends BaseSectionLink {
super(...arguments); super(...arguments);
this._refreshCanDisplay(); this._refreshCanDisplay();
this.appEvents.on("user-reviewable-count:changed", this._refreshCanDisplay); if (this.shouldDisplay) {
this.appEvents.on(
"user-reviewable-count:changed",
this._refreshCanDisplay
);
}
} }
teardown() { teardown() {
this.appEvents.off( if (this.shouldDisplay) {
"user-reviewable-count:changed", this.appEvents.off(
this._refreshCanDisplay "user-reviewable-count:changed",
); this._refreshCanDisplay
);
}
} }
@bind @bind
_refreshCanDisplay() { _refreshCanDisplay() {
if (!this.currentUser.can_review) { if (!this.currentUser?.can_review) {
this.canDisplay = false; this.canDisplay = false;
return; return;
} }

View File

@ -8,9 +8,13 @@ acceptance("Opengraph Tag Updater", function (needs) {
return helper.response({}); return helper.response({});
}); });
}); });
needs.site({});
test("updates OG title and URL", async function (assert) { test("updates OG title and URL", async function (assert) {
await visit("/"); await visit("/");
await click(
".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-summary"
);
await click("a[href='/about']"); await click("a[href='/about']");
assert.strictEqual( assert.strictEqual(

View File

@ -15,6 +15,7 @@ acceptance("Sidebar - Anonymous user - Community Section", function (needs) {
navigation_menu: "sidebar", navigation_menu: "sidebar",
faq_url: "https://discourse.org", faq_url: "https://discourse.org",
}); });
needs.site({});
test("display short site description site setting when it is set", async function (assert) { test("display short site description site setting when it is set", async function (assert) {
this.siteSettings.short_site_description = this.siteSettings.short_site_description =
@ -29,19 +30,9 @@ acceptance("Sidebar - Anonymous user - Community Section", function (needs) {
this.siteSettings.short_site_description, this.siteSettings.short_site_description,
"displays the short site description under the community section" "displays the short site description under the community section"
); );
const sectionLinks = queryAll(
".sidebar-section[data-section-name='community'] .sidebar-section-link"
);
assert.strictEqual(
sectionLinks[0].textContent.trim(),
I18n.t("sidebar.sections.community.links.about.content"),
"displays the about section link first"
);
}); });
test("everything, users, about and FAQ section links are shown by default ", async function (assert) { test("everything section link is shown by default ", async function (assert) {
await visit("/"); await visit("/");
const sectionLinks = queryAll( const sectionLinks = queryAll(
@ -53,24 +44,6 @@ acceptance("Sidebar - Anonymous user - Community Section", function (needs) {
I18n.t("sidebar.sections.community.links.everything.content"), I18n.t("sidebar.sections.community.links.everything.content"),
"displays the everything section link first" "displays the everything section link first"
); );
assert.strictEqual(
sectionLinks[1].textContent.trim(),
I18n.t("sidebar.sections.community.links.users.content"),
"displays the users section link second"
);
assert.strictEqual(
sectionLinks[2].textContent.trim(),
I18n.t("sidebar.sections.community.links.about.content"),
"displays the about section link third"
);
assert.strictEqual(
sectionLinks[3].textContent.trim(),
I18n.t("sidebar.sections.community.links.faq.content"),
"displays the FAQ section link last"
);
}); });
test("users section link is not shown when hide_user_profiles_from_public site setting is enabled", async function (assert) { test("users section link is not shown when hide_user_profiles_from_public site setting is enabled", async function (assert) {
@ -86,7 +59,7 @@ acceptance("Sidebar - Anonymous user - Community Section", function (needs) {
); );
}); });
test("groups and badges section links are shown in more...", async function (assert) { test("users, about, faq, groups and badges section links are shown in more...", async function (assert) {
await visit("/"); await visit("/");
await click( await click(
@ -99,12 +72,30 @@ acceptance("Sidebar - Anonymous user - Community Section", function (needs) {
assert.strictEqual( assert.strictEqual(
sectionLinks[0].textContent.trim(), sectionLinks[0].textContent.trim(),
I18n.t("sidebar.sections.community.links.users.content"),
"displays the users section link second"
);
assert.strictEqual(
sectionLinks[1].textContent.trim(),
I18n.t("sidebar.sections.community.links.about.content"),
"displays the about section link third"
);
assert.strictEqual(
sectionLinks[2].textContent.trim(),
I18n.t("sidebar.sections.community.links.faq.content"),
"displays the FAQ section link last"
);
assert.strictEqual(
sectionLinks[3].textContent.trim(),
I18n.t("sidebar.sections.community.links.groups.content"), I18n.t("sidebar.sections.community.links.groups.content"),
"displays the groups section link first" "displays the groups section link first"
); );
assert.strictEqual( assert.strictEqual(
sectionLinks[1].textContent.trim(), sectionLinks[4].textContent.trim(),
I18n.t("sidebar.sections.community.links.badges.content"), I18n.t("sidebar.sections.community.links.badges.content"),
"displays the badges section link second" "displays the badges section link second"
); );

View File

@ -15,7 +15,7 @@ import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sideb
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
acceptance("Sidebar - Plugin API", function (needs) { acceptance("Sidebar - Plugin API", function (needs) {
needs.user(); needs.user({});
needs.settings({ needs.settings({
navigation_menu: "sidebar", navigation_menu: "sidebar",
@ -439,7 +439,7 @@ acceptance("Sidebar - Plugin API", function (needs) {
); );
const myCustomTopSectionLink = query( const myCustomTopSectionLink = query(
".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-content-secondary .sidebar-section-link[data-link-name='my-custom-top']" ".sidebar-section[data-section-name='community'] .sidebar-more-section-links-details-content-main .sidebar-section-link[data-link-name='my-custom-top']"
); );
assert.ok( assert.ok(

View File

@ -72,7 +72,7 @@ acceptance(
acceptance( acceptance(
"Sidebar - Experimental sidebar and hamburger setting enabled - Sidebar enabled", "Sidebar - Experimental sidebar and hamburger setting enabled - Sidebar enabled",
function (needs) { function (needs) {
needs.user(); needs.user({});
needs.settings({ needs.settings({
navigation_menu: "sidebar", navigation_menu: "sidebar",

View File

@ -50,6 +50,88 @@ export default {
skip_new_user_tips: false, skip_new_user_tips: false,
should_be_redirected_to_top: false, should_be_redirected_to_top: false,
}, },
sidebar_sections: [
{
id: 111,
title: "Community",
section_type: "community",
slug: "community",
links: [
{
id: 329,
name: "Everything",
value: "/latest",
icon: "layer-group",
external: false,
segment: "primary",
},
{
id: 330,
name: "Users",
value: "/u",
icon: "users",
external: false,
segment: "secondary",
},
{
id: 331,
name: "Info",
value: "/about",
icon: "info-circle",
external: false,
segment: "secondary",
},
{
id: 332,
name: "Faq",
value: "/faq",
icon: "question-circle",
external: false,
segment: "secondary",
},
{
id: 333,
name: "My Posts",
value: "/my/activity",
icon: "user",
external: false,
segment: "primary",
},
{
id: 334,
name: "Review",
value: "/review",
icon: "flag",
external: false,
segment: "secondary",
},
{
id: 335,
name: "Admin",
value: "/admin",
icon: "wrench",
external: false,
segment: "primary",
},
{
id: 336,
name: "Groups",
value: "/g",
icon: "user-friends",
external: false,
segment: "secondary",
},
{
id: 337,
name: "Badges",
value: "/badges",
icon: "certificate",
external: false,
segment: "secondary",
},
],
},
]
}, },
}, },
}; };

View File

@ -699,7 +699,90 @@ export default {
], ],
displayed_about_plugin_stat_groups: ["chat_messages"], displayed_about_plugin_stat_groups: ["chat_messages"],
hashtag_configurations: { "topic-composer": ["category", "tag"] }, hashtag_configurations: { "topic-composer": ["category", "tag"] },
hashtag_icons: ["folder", "tag"] hashtag_icons: ["folder", "tag"],
anonymous_sidebar_sections: [
{
id: 111,
title: "Community",
links: [
{
id: 329,
name: "Everything",
value: "/latest",
icon: "layer-group",
external: false,
segment: "primary",
},
{
id: 330,
name: "Users",
value: "/u",
icon: "users",
external: false,
segment: "secondary",
},
{
id: 331,
name: "Info",
value: "/about",
icon: "info-circle",
external: false,
segment: "secondary",
},
{
id: 332,
name: "Faq",
value: "/faq",
icon: "question-circle",
external: false,
segment: "secondary",
},
{
id: 333,
name: "My Posts",
value: "/my/activity",
icon: "user",
external: false,
segment: "primary",
},
{
id: 334,
name: "Review",
value: "/review",
icon: "flag",
external: false,
segment: "secondary",
},
{
id: 335,
name: "Admin",
value: "/admin",
icon: "wrench",
external: false,
segment: "primary",
},
{
id: 336,
name: "Groups",
value: "/g",
icon: "user-friends",
external: false,
segment: "secondary",
},
{
id: 337,
name: "Badges",
value: "/badges",
icon: "certificate",
external: false,
segment: "secondary",
},
],
slug: "community",
public: true,
section_type: "community",
},
],
}, },
}, },
}; };

View File

@ -47,8 +47,4 @@
padding: 0.33rem calc(var(--d-sidebar-row-horizontal-padding) / 3); padding: 0.33rem calc(var(--d-sidebar-row-horizontal-padding) / 3);
} }
} }
.sidebar-more-section-links-details-content-secondary {
border-top: 1.5px solid var(--primary-low);
}
} }

View File

@ -2,7 +2,6 @@
class SidebarSectionsController < ApplicationController class SidebarSectionsController < ApplicationController
requires_login requires_login
before_action :check_if_member_of_group
before_action :check_access_if_public before_action :check_access_if_public
def index def index
@ -20,11 +19,7 @@ class SidebarSectionsController < ApplicationController
if sidebar_section.public? if sidebar_section.public?
StaffActionLogger.new(current_user).log_create_public_sidebar_section(sidebar_section) StaffActionLogger.new(current_user).log_create_public_sidebar_section(sidebar_section)
MessageBus.publish( MessageBus.publish("/refresh-sidebar-sections", nil)
"/refresh-sidebar-sections",
nil,
group_ids: SiteSetting.enable_custom_sidebar_sections_map,
)
Site.clear_anon_cache! Site.clear_anon_cache!
end end
@ -44,11 +39,7 @@ class SidebarSectionsController < ApplicationController
if sidebar_section.public? if sidebar_section.public?
StaffActionLogger.new(current_user).log_update_public_sidebar_section(sidebar_section) StaffActionLogger.new(current_user).log_update_public_sidebar_section(sidebar_section)
MessageBus.publish( MessageBus.publish("/refresh-sidebar-sections", nil)
"/refresh-sidebar-sections",
nil,
group_ids: SiteSetting.enable_custom_sidebar_sections_map,
)
Site.clear_anon_cache! Site.clear_anon_cache!
end end
@ -86,11 +77,7 @@ class SidebarSectionsController < ApplicationController
if sidebar_section.public? if sidebar_section.public?
StaffActionLogger.new(current_user).log_destroy_public_sidebar_section(sidebar_section) StaffActionLogger.new(current_user).log_destroy_public_sidebar_section(sidebar_section)
MessageBus.publish( MessageBus.publish("/refresh-sidebar-sections", nil)
"/refresh-sidebar-sections",
nil,
group_ids: SiteSetting.enable_custom_sidebar_sections_map,
)
end end
render json: SidebarSectionSerializer.new(sidebar_section) render json: SidebarSectionSerializer.new(sidebar_section)
rescue Discourse::InvalidAccess rescue Discourse::InvalidAccess
@ -111,14 +98,6 @@ class SidebarSectionsController < ApplicationController
params.permit(:sidebar_section_id, links_order: []) params.permit(:sidebar_section_id, links_order: [])
end end
def check_if_member_of_group
### TODO remove when enable_custom_sidebar_sections SiteSetting is removed
if !SiteSetting.enable_custom_sidebar_sections.present? ||
!current_user.in_any_groups?(SiteSetting.enable_custom_sidebar_sections_map)
raise Discourse::InvalidAccess
end
end
private private
def check_access_if_public def check_access_if_public

View File

@ -24,6 +24,7 @@ class SidebarSection < ActiveRecord::Base
} }
scope :public_sections, -> { where("public") } scope :public_sections, -> { where("public") }
enum :section_type, { community: 0 }, scopes: false, suffix: true
private private
@ -36,14 +37,16 @@ end
# #
# Table name: sidebar_sections # Table name: sidebar_sections
# #
# id :bigint not null, primary key # id :bigint not null, primary key
# user_id :integer not null # user_id :integer not null
# title :string(30) not null # title :string(30) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# public :boolean default(FALSE), not null # public :boolean default(FALSE), not null
# section_type :integer
# #
# Indexes # Indexes
# #
# index_sidebar_sections_on_section_type (section_type) UNIQUE
# index_sidebar_sections_on_user_id_and_title (user_id,title) UNIQUE # index_sidebar_sections_on_user_id_and_title (user_id,title) UNIQUE
# #

View File

@ -5,6 +5,17 @@ class SidebarUrl < ActiveRecord::Base
MAX_ICON_LENGTH = 40 MAX_ICON_LENGTH = 40
MAX_NAME_LENGTH = 80 MAX_NAME_LENGTH = 80
MAX_VALUE_LENGTH = 200 MAX_VALUE_LENGTH = 200
COMMUNITY_SECTION_LINKS = [
{ name: "Everything", path: "/latest", icon: "layer-group", segment: "primary" },
{ name: "My Posts", path: "/my/activity", icon: "user", segment: "primary" },
{ name: "Review", path: "/review", icon: "flag", segment: "primary" },
{ name: "Admin", path: "/admin", icon: "wrench", segment: "primary" },
{ name: "Users", path: "/u", icon: "users", segment: "secondary" },
{ name: "About", path: "/about", icon: "info-circle", segment: "secondary" },
{ name: "FAQ", path: "/faq", icon: "question-circle", segment: "secondary" },
{ name: "Groups", path: "/g", icon: "user-friends", segment: "secondary" },
{ name: "Badges", path: "/badges", icon: "certificate", segment: "secondary" },
]
validates :icon, presence: true, length: { maximum: MAX_ICON_LENGTH } validates :icon, presence: true, length: { maximum: MAX_ICON_LENGTH }
validates :name, presence: true, length: { maximum: MAX_NAME_LENGTH } validates :name, presence: true, length: { maximum: MAX_NAME_LENGTH }
@ -14,6 +25,8 @@ class SidebarUrl < ActiveRecord::Base
before_validation :remove_internal_hostname, :set_external before_validation :remove_internal_hostname, :set_external
enum :segment, { primary: 0, secondary: 1 }, scopes: false, suffix: true
def path_validator def path_validator
return true if !external? return true if !external?
raise ActionController::RoutingError.new("Not Found") if value !~ Discourse::Utils::URI_REGEXP raise ActionController::RoutingError.new("Not Found") if value !~ Discourse::Utils::URI_REGEXP
@ -48,4 +61,5 @@ end
# updated_at :datetime not null # updated_at :datetime not null
# icon :string(40) not null # icon :string(40) not null
# external :boolean default(FALSE), not null # external :boolean default(FALSE), not null
# segment :integer default("primary"), not null
# #

View File

@ -69,7 +69,6 @@ class CurrentUserSerializer < BasicUserSerializer
:sidebar_category_ids, :sidebar_category_ids,
:sidebar_list_destination, :sidebar_list_destination,
:sidebar_sections, :sidebar_sections,
:custom_sidebar_sections_enabled,
:new_new_view_enabled? :new_new_view_enabled?
delegate :user_stat, to: :object, private: true delegate :user_stat, to: :object, private: true
@ -82,7 +81,7 @@ class CurrentUserSerializer < BasicUserSerializer
.public_sections .public_sections
.or(SidebarSection.where(user_id: object.id)) .or(SidebarSection.where(user_id: object.id))
.includes(sidebar_section_links: :linkable) .includes(sidebar_section_links: :linkable)
.order("(public IS TRUE) DESC") .order("(section_type IS NOT NULL) DESC, (public IS TRUE) DESC")
.map { |section| SidebarSectionSerializer.new(section, root: false) } .map { |section| SidebarSectionSerializer.new(section, root: false) }
end end
@ -301,12 +300,4 @@ class CurrentUserSerializer < BasicUserSerializer
def include_new_personal_messages_notifications_count? def include_new_personal_messages_notifications_count?
redesigned_user_menu_enabled redesigned_user_menu_enabled
end end
def custom_sidebar_sections_enabled
if SiteSetting.enable_custom_sidebar_sections.present?
object.in_any_groups?(SiteSetting.enable_custom_sidebar_sections_map)
else
false
end
end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class SidebarSectionSerializer < ApplicationSerializer class SidebarSectionSerializer < ApplicationSerializer
attributes :id, :title, :links, :slug, :public attributes :id, :title, :links, :slug, :public, :section_type
def links def links
object.sidebar_section_links.map { |link| SidebarUrlSerializer.new(link.linkable, root: false) } object.sidebar_section_links.map { |link| SidebarUrlSerializer.new(link.linkable, root: false) }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class SidebarUrlSerializer < ApplicationSerializer class SidebarUrlSerializer < ApplicationSerializer
attributes :id, :name, :value, :icon, :external attributes :id, :name, :value, :icon, :external, :segment
def external def external
object.external? || object.full_reload? object.external? || object.full_reload?

View File

@ -266,6 +266,7 @@ class SiteSerializer < ApplicationSerializer
SidebarSection SidebarSection
.public_sections .public_sections
.includes(sidebar_section_links: :linkable) .includes(sidebar_section_links: :linkable)
.order("(section_type IS NOT NULL) DESC, (public IS TRUE) DESC")
.map { |section| SidebarSectionSerializer.new(section, root: false) } .map { |section| SidebarSectionSerializer.new(section, root: false) }
end end

View File

@ -907,7 +907,6 @@ bg:
default_categories_muted: "Списък с категории, които са заглушени, по подразбиране." default_categories_muted: "Списък с категории, които са заглушени, по подразбиране."
create_post_for_category_and_tag_changes: "Създайте малка публикация за действие, когато категорията или етикетите на тема се променят" create_post_for_category_and_tag_changes: "Създайте малка публикация за действие, когато категорията или етикетите на тема се променят"
experimental_new_new_view_groups: "ЕКСПЕРИМЕНТАЛНО: Активирайте нов списък с теми, който комбинира непрочетени и нови теми и направете връзката „Всичко“ в страничната лента връзка към него." experimental_new_new_view_groups: "ЕКСПЕРИМЕНТАЛНО: Активирайте нов списък с теми, който комбинира непрочетени и нови теми и направете връзката „Всичко“ в страничната лента връзка към него."
enable_custom_sidebar_sections: "ЕКСПЕРИМЕНТАЛНО: Активирайте персонализирани раздели в страничната лента"
errors: errors:
invalid_email: "Невалиден имейл адрес." invalid_email: "Невалиден имейл адрес."
invalid_username: "Няма потребител с такова потребителско име." invalid_username: "Няма потребител с такова потребителско име."

View File

@ -2162,7 +2162,6 @@ de:
enable_new_notifications_menu: "Aktiviert das neue Benachrichtigungsmenü. Das Deaktivieren dieser Einstellung ist veraltet. Das neue Benachrichtigungsmenü wird immer für Optionen im Navigationsmenü verwendet, die nicht aus den älteren Versionen stammen. <a href='https://meta.discourse.org/t/260358'>Erfahren Sie mehr</a>" enable_new_notifications_menu: "Aktiviert das neue Benachrichtigungsmenü. Das Deaktivieren dieser Einstellung ist veraltet. Das neue Benachrichtigungsmenü wird immer für Optionen im Navigationsmenü verwendet, die nicht aus den älteren Versionen stammen. <a href='https://meta.discourse.org/t/260358'>Erfahren Sie mehr</a>"
enable_experimental_hashtag_autocomplete: "EXPERIMENTELL: Das neue #hashtag-Autovervollständigungssystem für Kategorien und Schlagwörter, das das ausgewählte Element anders darstellt und eine bessere Suche aufweist, verwenden" enable_experimental_hashtag_autocomplete: "EXPERIMENTELL: Das neue #hashtag-Autovervollständigungssystem für Kategorien und Schlagwörter, das das ausgewählte Element anders darstellt und eine bessere Suche aufweist, verwenden"
experimental_new_new_view_groups: "EXPERIMENTELL: Aktiviere eine neue Themenliste, die ungelesene und neue Themen kombiniert, und verlinke den Link „Alles“ in der Seitenleiste darauf." experimental_new_new_view_groups: "EXPERIMENTELL: Aktiviere eine neue Themenliste, die ungelesene und neue Themen kombiniert, und verlinke den Link „Alles“ in der Seitenleiste darauf."
enable_custom_sidebar_sections: "EXPERIMENTELL: Aktiviere benutzerdefinierte Seitenleistenabschnitte"
errors: errors:
invalid_css_color: "Ungültige Farbe. Gib einen Farbnamen oder einen Hexadezimalwert ein." invalid_css_color: "Ungültige Farbe. Gib einen Farbnamen oder einen Hexadezimalwert ein."
invalid_email: "Ungültige E-Mail-Adresse." invalid_email: "Ungültige E-Mail-Adresse."

View File

@ -2269,7 +2269,6 @@ he:
enable_new_notifications_menu: "מפעיל את תפריט ההתראות החדש. השבתת ההגדרה הזאת יצאה מכלל שימוש. תפריט ההתראות החדש תמיד ישמש לבחירות ‚תפריט ניווט’ שאינן מיושנות. <a href='https://meta.discourse.org/t/260358'>מידע נוסף</a>" enable_new_notifications_menu: "מפעיל את תפריט ההתראות החדש. השבתת ההגדרה הזאת יצאה מכלל שימוש. תפריט ההתראות החדש תמיד ישמש לבחירות ‚תפריט ניווט’ שאינן מיושנות. <a href='https://meta.discourse.org/t/260358'>מידע נוסף</a>"
enable_experimental_hashtag_autocomplete: "ניסיוני: ניתן להשתמש במערכת אוטומטית להשלמת #תגיות עבור קטגוריות ותגיות שמעבדת את הפריט הנבחר באופן שונה והחיפוש בה משופר" enable_experimental_hashtag_autocomplete: "ניסיוני: ניתן להשתמש במערכת אוטומטית להשלמת #תגיות עבור קטגוריות ותגיות שמעבדת את הפריט הנבחר באופן שונה והחיפוש בה משופר"
experimental_new_new_view_groups: "ניסיוני: לאפשר רשימת נושאים חדשה המשלבת נושאים שלא נקראו וחדשים ולקשר אליה את „הכול” בסרגל הצד." experimental_new_new_view_groups: "ניסיוני: לאפשר רשימת נושאים חדשה המשלבת נושאים שלא נקראו וחדשים ולקשר אליה את „הכול” בסרגל הצד."
enable_custom_sidebar_sections: "ניסיוני: הפעלת אגפים משלך בסרגל הצד"
errors: errors:
invalid_css_color: "צבע שגוי. נא למלא את שם הצבע או ערך הקסדצימלי." invalid_css_color: "צבע שגוי. נא למלא את שם הצבע או ערך הקסדצימלי."
invalid_email: "כתובת דוא״ל שגויה." invalid_email: "כתובת דוא״ל שגויה."

View File

@ -1034,7 +1034,6 @@ hu:
navigation_menu: "Határozza meg, melyik navigációs menüt használja. Az oldalsáv és a fejléc navigációja a felhasználók által testreszabható. A visszamenőleges kompatibilitás érdekében a régebbi opció is elérhető." navigation_menu: "Határozza meg, melyik navigációs menüt használja. Az oldalsáv és a fejléc navigációja a felhasználók által testreszabható. A visszamenőleges kompatibilitás érdekében a régebbi opció is elérhető."
enable_experimental_hashtag_autocomplete: "KÍSÉRLETI: Használja az új #hashtag automatikus kiegészítési rendszert a kategóriákhoz és címkékhez, amelyek másképp jelenítik meg a kiválasztott elemet, és javítják a keresést" enable_experimental_hashtag_autocomplete: "KÍSÉRLETI: Használja az új #hashtag automatikus kiegészítési rendszert a kategóriákhoz és címkékhez, amelyek másképp jelenítik meg a kiválasztott elemet, és javítják a keresést"
experimental_new_new_view_groups: "Kísérleti: Engedélyezzen egy új témalistát, amely egyesíti az olvasatlan és az új témákat, és az oldalsávban lévő \"Minden\" linket kapcsolja rá." experimental_new_new_view_groups: "Kísérleti: Engedélyezzen egy új témalistát, amely egyesíti az olvasatlan és az új témákat, és az oldalsávban lévő \"Minden\" linket kapcsolja rá."
enable_custom_sidebar_sections: "Kísérleti: Egyéni oldalsáv szekciók engedélyezése"
errors: errors:
invalid_email: "Érvénytelen e-mail cím." invalid_email: "Érvénytelen e-mail cím."
invalid_username: "Nincs ilyen nevű felhasználó." invalid_username: "Nincs ilyen nevű felhasználó."

View File

@ -2162,7 +2162,6 @@ sv:
enable_new_notifications_menu: "Aktiverar den nya aviseringsmenyn. Inaktivering av den här inställningen är föråldrat. Den nya aviseringsmenyn används alltid för icke-äldre \"navigeringsmeny\"-val. <a href='https://meta.discourse.org/t/260358'>Lär dig mer</a>" enable_new_notifications_menu: "Aktiverar den nya aviseringsmenyn. Inaktivering av den här inställningen är föråldrat. Den nya aviseringsmenyn används alltid för icke-äldre \"navigeringsmeny\"-val. <a href='https://meta.discourse.org/t/260358'>Lär dig mer</a>"
enable_experimental_hashtag_autocomplete: "EXPERIMENTELLT: Använd det nya #hashtag-autokompletteringssystemet för kategorier och taggar som renderar det valda objektet annorlunda och har förbättrad sökning" enable_experimental_hashtag_autocomplete: "EXPERIMENTELLT: Använd det nya #hashtag-autokompletteringssystemet för kategorier och taggar som renderar det valda objektet annorlunda och har förbättrad sökning"
experimental_new_new_view_groups: "EXPERIMENTELLT: Aktivera en ny ämneslista som kombinerar olästa och nya ämnen och gör att länken \"Allt\" i sidofältet länkar till den." experimental_new_new_view_groups: "EXPERIMENTELLT: Aktivera en ny ämneslista som kombinerar olästa och nya ämnen och gör att länken \"Allt\" i sidofältet länkar till den."
enable_custom_sidebar_sections: "EXPERIMENTELLT: Aktivera anpassade sidofältssektioner"
errors: errors:
invalid_css_color: "Ogiltig färg. Ange ett färgnamn eller ett hexvärde." invalid_css_color: "Ogiltig färg. Ange ett färgnamn eller ett hexvärde."
invalid_email: "Felaktig e-postadress." invalid_email: "Felaktig e-postadress."

View File

@ -2135,7 +2135,6 @@ tr_TR:
default_sidebar_tags: "Seçilen etiketler varsayılan olarak Kenar Çubuğunun Etiketler bölümünde gösterilir." default_sidebar_tags: "Seçilen etiketler varsayılan olarak Kenar Çubuğunun Etiketler bölümünde gösterilir."
enable_experimental_hashtag_autocomplete: "DENEYSEL: Seçilen ögeyi farklı şekilde işleyen ve aramayı iyileştiren kategoriler ve etiketler için yeni #hashtag otomatik tamamlama sistemini kullanın" enable_experimental_hashtag_autocomplete: "DENEYSEL: Seçilen ögeyi farklı şekilde işleyen ve aramayı iyileştiren kategoriler ve etiketler için yeni #hashtag otomatik tamamlama sistemini kullanın"
experimental_new_new_view_groups: "DENEYSEL: Okunmamış ve yeni konuları birleştiren bir yeni konular listesi etkinleştirin ve kenar çubuğundaki \"Her şey\" bağlantısını buna bağlayın." experimental_new_new_view_groups: "DENEYSEL: Okunmamış ve yeni konuları birleştiren bir yeni konular listesi etkinleştirin ve kenar çubuğundaki \"Her şey\" bağlantısını buna bağlayın."
enable_custom_sidebar_sections: "DENEYSEL: Özel kenar çubuğu bölümlerini etkinleştir"
errors: errors:
invalid_css_color: "Geçersiz renk. Bir renk adı veya hex değeri girin." invalid_css_color: "Geçersiz renk. Bir renk adı veya hex değeri girin."
invalid_email: "Geçersiz e-posta adresi." invalid_email: "Geçersiz e-posta adresi."

View File

@ -2106,7 +2106,6 @@ zh_CN:
enable_new_notifications_menu: "启用新的通知菜单。已不能禁用此设置。新的通知菜单始终用于非传统“导航菜单”选项。 <a href='https://meta.discourse.org/t/260358'>了解更多</a>" enable_new_notifications_menu: "启用新的通知菜单。已不能禁用此设置。新的通知菜单始终用于非传统“导航菜单”选项。 <a href='https://meta.discourse.org/t/260358'>了解更多</a>"
enable_experimental_hashtag_autocomplete: "实验性:对类别和标签使用新的 #hashtag 自动补全系统,以不同方式呈现所选条目,并改进了搜索" enable_experimental_hashtag_autocomplete: "实验性:对类别和标签使用新的 #hashtag 自动补全系统,以不同方式呈现所选条目,并改进了搜索"
experimental_new_new_view_groups: "试验性的:启用一个新的主题列表,将未读和新的主题合并,并使侧边栏中的 \"一切 \"链接指向它。" experimental_new_new_view_groups: "试验性的:启用一个新的主题列表,将未读和新的主题合并,并使侧边栏中的 \"一切 \"链接指向它。"
enable_custom_sidebar_sections: "实验:启用自定义侧边栏部分"
errors: errors:
invalid_css_color: "颜色无效。输入颜色名称或十六进制值。" invalid_css_color: "颜色无效。输入颜色名称或十六进制值。"
invalid_email: "无效的电子邮件地址。" invalid_email: "无效的电子邮件地址。"

View File

@ -2128,13 +2128,6 @@ navigation:
- "unread_new" - "unread_new"
enable_new_notifications_menu: enable_new_notifications_menu:
default: true default: true
enable_custom_sidebar_sections:
client: true
type: group_list
list_type: compact
default: ""
allow_any: false
refresh: true
embedding: embedding:
embed_by_username: embed_by_username:

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSegmentToSidebarUrls < ActiveRecord::Migration[7.0]
def change
add_column :sidebar_urls, :segment, :integer, default: 0, null: false
end
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
class AddSectionTypeToSidebarSections < ActiveRecord::Migration[7.0]
def change
add_column :sidebar_sections, :section_type, :integer
add_index :sidebar_sections, :section_type, unique: true
end
end

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
class InsertCommunityToSidebarSections < ActiveRecord::Migration[7.0]
COMMUNITY_SECTION_LINKS = [
{ name: "Everything", path: "/latest", icon: "layer-group", segment: 0 },
{ name: "My Posts", path: "/my/activity", icon: "user", segment: 0 },
{ name: "Review", path: "/review", icon: "flag", segment: 0 },
{ name: "Admin", path: "/admin", icon: "wrench", segment: 0 },
{ name: "Users", path: "/u", icon: "users", segment: 1 },
{ name: "About", path: "/about", icon: "info-circle", segment: 1 },
{ name: "FAQ", path: "/faq", icon: "question-circle", segment: 1 },
{ name: "Groups", path: "/g", icon: "user-friends", segment: 1 },
{ name: "Badges", path: "/badges", icon: "certificate", segment: 1 },
]
def up
result = DB.query <<~SQL
INSERT INTO sidebar_sections(user_id, title, public, section_type, created_at, updated_at)
VALUES (-1, 'Community', true, 0, now(), now())
RETURNING sidebar_sections.id
SQL
community_section_id = result.last&.id
sidebar_urls =
COMMUNITY_SECTION_LINKS.map do |url_data|
"('#{url_data[:name]}', '#{url_data[:path]}', '#{url_data[:icon]}', '#{url_data[:segment]}', false, now(), now())"
end
result = DB.query <<~SQL
INSERT INTO sidebar_urls(name, value, icon, segment, external, created_at, updated_at)
VALUES #{sidebar_urls.join(",")}
RETURNING sidebar_urls.id
SQL
sidebar_section_links =
result.map.with_index do |url, index|
"(-1, #{url.id}, 'SidebarUrl', #{community_section_id}, #{index}, now(), now())"
end
result = DB.query <<~SQL
INSERT INTO sidebar_section_links(user_id, linkable_id, linkable_type, sidebar_section_id, position, created_at, updated_at)
VALUES #{sidebar_section_links.join(",")}
SQL
end
def down
result = DB.query <<~SQL
DELETE FROM sidebar_sections
WHERE section_type = 0
RETURNING sidebar_sections.id
SQL
community_section_id = result.last&.id
return true if !community_section_id
result = DB.query <<~SQL
DELETE FROM sidebar_section_links
WHERE sidebar_section_id = #{community_section_id}
RETURNING sidebar_section_links.linkable_id
SQL
sidebar_url_ids = result.map(&:linkable_id)
DB.query <<~SQL
DELETE FROM sidebar_urls
WHERE id IN (#{sidebar_url_ids.join(",")})
SQL
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class RemoveEnableCustomSidebarSectionsSetting < ActiveRecord::Migration[7.0]
def up
execute "DELETE FROM site_settings WHERE name = 'enable_custom_sidebar_sections'"
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -51,8 +51,8 @@ RSpec.describe Category do
expect { category_sidebar_section_link.linkable.destroy! }.to change { expect { category_sidebar_section_link.linkable.destroy! }.to change {
SidebarSectionLink.count SidebarSectionLink.count
}.from(3).to(1) }.from(12).to(10)
expect(SidebarSectionLink.first).to eq(tag_sidebar_section_link) expect(SidebarSectionLink.last).to eq(tag_sidebar_section_link)
end end
end end

View File

@ -29,8 +29,8 @@ RSpec.describe Tag do
expect { tag_sidebar_section_link.linkable.destroy! }.to change { expect { tag_sidebar_section_link.linkable.destroy! }.to change {
SidebarSectionLink.count SidebarSectionLink.count
}.from(3).to(1) }.from(12).to(10)
expect(SidebarSectionLink.first).to eq(category_sidebar_section_link) expect(SidebarSectionLink.last).to eq(category_sidebar_section_link)
end end
end end

View File

@ -131,7 +131,7 @@ RSpec.describe User do
it "should not create any sidebar section link records for staged users" do it "should not create any sidebar section link records for staged users" do
user = Fabricate(:user, staged: true) user = Fabricate(:user, staged: true)
expect(SidebarSectionLink.exists?).to eq(false) expect(SidebarSectionLink.exists?(user: user)).to eq(false)
end end
it "should create sidebar section link records when user has been unstaged" do it "should create sidebar section link records when user has been unstaged" do
@ -144,7 +144,7 @@ RSpec.describe User do
it "should not create any sidebar section link records for non human users" do it "should not create any sidebar section link records for non human users" do
user = Fabricate(:user, id: -Time.now.to_i) user = Fabricate(:user, id: -Time.now.to_i)
expect(SidebarSectionLink.exists?).to eq(false) expect(SidebarSectionLink.exists?(user: user)).to eq(false)
end end
it "should not create any tag sidebar section link records when tagging is disabled" do it "should not create any tag sidebar section link records when tagging is disabled" do

View File

@ -4,14 +4,6 @@ RSpec.describe SidebarSectionsController do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:admin) { Fabricate(:admin) } fab!(:admin) { Fabricate(:admin) }
before do
### TODO remove when enable_custom_sidebar_sections SiteSetting is removed
group = Fabricate(:group)
Fabricate(:group_user, group: group, user: user)
Fabricate(:group_user, group: group, user: admin)
SiteSetting.enable_custom_sidebar_sections = group.id.to_s
end
describe "#index" do describe "#index" do
fab!(:sidebar_section) { Fabricate(:sidebar_section, title: "private section", user: user) } fab!(:sidebar_section) { Fabricate(:sidebar_section, title: "private section", user: user) }
fab!(:sidebar_url_1) { Fabricate(:sidebar_url, name: "tags", value: "/tags") } fab!(:sidebar_url_1) { Fabricate(:sidebar_url, name: "tags", value: "/tags") }
@ -29,7 +21,7 @@ RSpec.describe SidebarSectionsController do
get "/sidebar_sections.json" get "/sidebar_sections.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body["sidebar_sections"].map { |section| section["title"] }).to eq( expect(response.parsed_body["sidebar_sections"].map { |section| section["title"] }).to eq(
["public section", "private section"], ["Community", "public section", "private section"],
) )
end end
end end
@ -49,6 +41,8 @@ RSpec.describe SidebarSectionsController do
it "creates custom section for user" do it "creates custom section for user" do
sign_in(user) sign_in(user)
expect(SidebarSection.count).to eq(1)
post "/sidebar_sections.json", post "/sidebar_sections.json",
params: { params: {
title: "custom section", title: "custom section",
@ -66,7 +60,7 @@ RSpec.describe SidebarSectionsController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(SidebarSection.count).to eq(1) expect(SidebarSection.count).to eq(2)
sidebar_section = SidebarSection.last sidebar_section = SidebarSection.last
expect(sidebar_section.title).to eq("custom section") expect(sidebar_section.title).to eq("custom section")

View File

@ -303,11 +303,6 @@ RSpec.describe CurrentUserSerializer do
fab!(:group) { Fabricate(:group) } fab!(:group) { Fabricate(:group) }
fab!(:sidebar_section) { Fabricate(:sidebar_section, user: user) } fab!(:sidebar_section) { Fabricate(:sidebar_section, user: user) }
before do
group.add(user)
SiteSetting.enable_custom_sidebar_sections = group.id
end
it "eager loads sidebar_urls" do it "eager loads sidebar_urls" do
custom_sidebar_section_link_1 = custom_sidebar_section_link_1 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: sidebar_section) Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: sidebar_section)
@ -319,11 +314,9 @@ RSpec.describe CurrentUserSerializer do
track_sql_queries do track_sql_queries do
serialized = described_class.new(user, scope: Guardian.new(user), root: false).as_json serialized = described_class.new(user, scope: Guardian.new(user), root: false).as_json
expect(serialized[:sidebar_sections].map { |sidebar_section| sidebar_section.id }).to eq( expect(serialized[:sidebar_sections].count).to eq(2)
[sidebar_section.id],
)
expect(serialized[:sidebar_sections].first.links.map { |link| link.id }).to eq( expect(serialized[:sidebar_sections].last.links.map { |link| link.id }).to eq(
[custom_sidebar_section_link_1.linkable.id], [custom_sidebar_section_link_1.linkable.id],
) )
end.count end.count
@ -335,11 +328,9 @@ RSpec.describe CurrentUserSerializer do
track_sql_queries do track_sql_queries do
serialized = described_class.new(user, scope: Guardian.new(user), root: false).as_json serialized = described_class.new(user, scope: Guardian.new(user), root: false).as_json
expect(serialized[:sidebar_sections].map { |sidebar_section| sidebar_section.id }).to eq( expect(serialized[:sidebar_sections].count).to eq(2)
[sidebar_section.id],
)
expect(serialized[:sidebar_sections].first.links.map { |link| link.id }).to eq( expect(serialized[:sidebar_sections].last.links.map { |link| link.id }).to eq(
[custom_sidebar_section_link_1.linkable.id, custom_sidebar_section_link_2.linkable.id], [custom_sidebar_section_link_1.linkable.id, custom_sidebar_section_link_2.linkable.id],
) )
end.count end.count

View File

@ -209,7 +209,9 @@ RSpec.describe SiteSerializer do
it "includes only public sidebar sections serialised object when user is anonymous" do it "includes only public sidebar sections serialised object when user is anonymous" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_sidebar_sections].map(&:title)).to eq(["Public section"]) expect(serialized[:anonymous_sidebar_sections].map(&:title)).to eq(
["Community", "Public section"],
)
end end
it "eager loads sidebar_urls" do it "eager loads sidebar_urls" do
@ -222,11 +224,9 @@ RSpec.describe SiteSerializer do
track_sql_queries do track_sql_queries do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect( expect(serialized[:anonymous_sidebar_sections].count).to eq(2)
serialized[:anonymous_sidebar_sections].map { |sidebar_section| sidebar_section.id },
).to eq([public_sidebar_section.id])
expect(serialized[:anonymous_sidebar_sections].first.links.map { |link| link.id }).to eq( expect(serialized[:anonymous_sidebar_sections].last.links.map { |link| link.id }).to eq(
[public_section_link.linkable.id], [public_section_link.linkable.id],
) )
end.count end.count
@ -240,11 +240,9 @@ RSpec.describe SiteSerializer do
track_sql_queries do track_sql_queries do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect( expect(serialized[:anonymous_sidebar_sections].count).to eq(2)
serialized[:anonymous_sidebar_sections].map { |sidebar_section| sidebar_section.id },
).to eq([public_sidebar_section.id])
expect(serialized[:anonymous_sidebar_sections].first.links.map { |link| link.id }).to eq( expect(serialized[:anonymous_sidebar_sections].last.links.map { |link| link.id }).to eq(
[ [
public_section_link.linkable.id, public_section_link.linkable.id,
public_section_link_2.linkable.id, public_section_link_2.linkable.id,

View File

@ -6,14 +6,6 @@ describe "Custom sidebar sections", type: :system, js: true do
let(:section_modal) { PageObjects::Modals::SidebarSectionForm.new } let(:section_modal) { PageObjects::Modals::SidebarSectionForm.new }
let(:sidebar) { PageObjects::Components::Sidebar.new } let(:sidebar) { PageObjects::Components::Sidebar.new }
before do
### TODO remove when enable_custom_sidebar_sections SiteSetting is removed
group = Fabricate(:group)
Fabricate(:group_user, group: group, user: user)
Fabricate(:group_user, group: group, user: admin)
SiteSetting.enable_custom_sidebar_sections = group.id.to_s
end
it "allows the user to create custom section" do it "allows the user to create custom section" do
sign_in user sign_in user
visit("/latest") visit("/latest")
@ -112,11 +104,11 @@ describe "Custom sidebar sections", type: :system, js: true do
sign_in user sign_in user
visit("/latest") visit("/latest")
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(1)") do within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(1)") do
expect(sidebar).to have_section_link("Sidebar Tags") expect(sidebar).to have_section_link("Sidebar Tags")
end end
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(2)") do within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(2)") do
expect(sidebar).to have_section_link("Sidebar Categories") expect(sidebar).to have_section_link("Sidebar Categories")
end end
@ -124,11 +116,11 @@ describe "Custom sidebar sections", type: :system, js: true do
categories_link = find(".sidebar-section-link[data-link-name='Sidebar Categories']") categories_link = find(".sidebar-section-link[data-link-name='Sidebar Categories']")
tags_link.drag_to(categories_link, html5: true, delay: 0.4) tags_link.drag_to(categories_link, html5: true, delay: 0.4)
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(1)") do within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(1)") do
expect(sidebar).to have_section_link("Sidebar Categories") expect(sidebar).to have_section_link("Sidebar Categories")
end end
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(2)") do within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(2)") do
expect(sidebar).to have_section_link("Sidebar Tags") expect(sidebar).to have_section_link("Sidebar Tags")
end end
end end