Skip to content

FEATURE: add API for custom sidebar sections to have expandable "more…" sections #33110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import ApiSections from "../api-sections";
import CategoriesSection from "./categories-section";
import CustomSections from "./custom-sections";
import TagsSection from "./tags-section";
Expand All @@ -18,6 +19,10 @@ export default class SidebarAnonymousSections extends Component {
{{#if this.siteSettings.tagging_enabled}}
<TagsSection @collapsable={{@collapsableSections}} />
{{/if}}

{{#unless @hideApiSections}}
<ApiSections @collapsable={{@collapsableSections}} />
{{/unless}}
</div>
</template>
}
165 changes: 102 additions & 63 deletions app/assets/javascripts/discourse/app/components/sidebar/api-section.gjs
Original file line number Diff line number Diff line change
@@ -1,69 +1,108 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { and, eq, not } from "truth-helpers";
import MoreSectionLink from "./more-section-link";
import MoreSectionLinks from "./more-section-links";
import Section from "./section";
import SectionLink from "./section-link";
import SectionLinkButton from "./section-link-button";

const SidebarApiSection = <template>
{{#if @section.filtered}}
<Section
@sectionName={{@section.name}}
@headerLinkText={{@section.text}}
@headerLinkTitle={{@section.title}}
@headerActionsIcon={{@section.actionsIcon}}
@headerActions={{@section.actions}}
@willDestroy={{@section.willDestroy}}
@collapsable={{@collapsable}}
@displaySection={{@section.displaySection}}
@hideSectionHeader={{@section.hideSectionHeader}}
@collapsedByDefault={{@section.collapsedByDefault}}
@activeLink={{@section.activeLink}}
@expandWhenActive={{@expandWhenActive}}
@scrollActiveLinkIntoView={{@scrollActiveLinkIntoView}}
>
{{#if
(and @section.emptyStateComponent (not @section.filteredLinks.length))
}}
<@section.emptyStateComponent />
{{/if}}
export default class SidebarApiSection extends Component {
@service navigationMenu;

{{#each @section.filteredLinks key="name" as |link|}}
<SectionLink
@linkName={{link.name}}
@linkClass={{link.classNames}}
@route={{link.route}}
@model={{link.model}}
@query={{link.query}}
@models={{link.models}}
@currentWhen={{link.currentWhen}}
@href={{link.href}}
@title={{link.title}}
@contentCSSClass={{link.contentCSSClass}}
@prefixColor={{link.prefixColor}}
@prefixBadge={{link.prefixBadge}}
@prefixType={{link.prefixType}}
@prefixValue={{link.prefixValue}}
@prefixCSSClass={{link.prefixCSSClass}}
@suffixType={{link.suffixType}}
@suffixValue={{link.suffixValue}}
@suffixCSSClass={{link.suffixCSSClass}}
@hoverType={{link.hoverType}}
@hoverValue={{link.hoverValue}}
@hoverAction={{link.hoverAction}}
@hoverTitle={{link.hoverTitle}}
@didInsert={{link.didInsert}}
@willDestroy={{link.willDestroy}}
@content={{link.text}}
@contentComponent={{component
link.contentComponent
status=link.contentComponentArgs
}}
@scrollIntoView={{and
@scrollActiveLinkIntoView
(eq link.name @section.activeLink.name)
}}
/>
{{/each}}
</Section>
{{/if}}
</template>;
<template>
{{#if @section.filtered}}
<Section
@sectionName={{@section.name}}
@headerLinkText={{@section.text}}
@headerLinkTitle={{@section.title}}
@headerActionsIcon={{@section.actionsIcon}}
@headerActions={{@section.actions}}
@willDestroy={{@section.willDestroy}}
@collapsable={{@collapsable}}
@displaySection={{@section.displaySection}}
@hideSectionHeader={{@section.hideSectionHeader}}
@collapsedByDefault={{@section.collapsedByDefault}}
@activeLink={{@section.activeLink}}
@expandWhenActive={{@expandWhenActive}}
@scrollActiveLinkIntoView={{@scrollActiveLinkIntoView}}
>
{{#if
(and @section.emptyStateComponent (not @section.filteredLinks.length))
}}
<@section.emptyStateComponent />
{{/if}}

export default SidebarApiSection;
{{#each @section.filteredLinks key="name" as |link|}}
<SectionLink
@linkName={{link.name}}
@linkClass={{link.classNames}}
@route={{link.route}}
@model={{link.model}}
@query={{link.query}}
@models={{link.models}}
@currentWhen={{link.currentWhen}}
@href={{link.href}}
@title={{link.title}}
@contentCSSClass={{link.contentCSSClass}}
@prefixColor={{link.prefixColor}}
@prefixBadge={{link.prefixBadge}}
@prefixType={{link.prefixType}}
@prefixValue={{link.prefixValue}}
@prefixCSSClass={{link.prefixCSSClass}}
@suffixType={{link.suffixType}}
@suffixValue={{link.suffixValue}}
@suffixCSSClass={{link.suffixCSSClass}}
@hoverType={{link.hoverType}}
@hoverValue={{link.hoverValue}}
@hoverAction={{link.hoverAction}}
@hoverTitle={{link.hoverTitle}}
@didInsert={{link.didInsert}}
@willDestroy={{link.willDestroy}}
@content={{link.text}}
@contentComponent={{component
link.contentComponent
status=link.contentComponentArgs
}}
@scrollIntoView={{and
@scrollActiveLinkIntoView
(eq link.name @section.activeLink.name)
}}
/>
{{/each}}

{{#if @section.moreLinks}}
{{#if this.navigationMenu.isDesktopDropdownMode}}
{{#each @section.moreLinks as |sectionLink|}}
<MoreSectionLink @sectionLink={{sectionLink}} />
{{/each}}

{{#if @section.moreSectionButtonAction}}
<SectionLinkButton
@action={{@section.moreSectionButtonAction}}
@icon={{@section.moreSectionButtonIcon}}
@text={{@section.moreSectionButtonText}}
/>
{{/if}}
{{else}}
<MoreSectionLinks
@sectionLinks={{@section.moreLinks}}
@moreText={{@section.moreSectionText}}
@moreIcon={{@section.moreSectionIcon}}
@moreButtonAction={{@section.moreSectionButtonAction}}
@moreButtonText={{@section.moreSectionButtonText}}
@moreButtonIcon={{@section.moreSectionButtonIcon}}
@toggleNavigationMenu={{@toggleNavigationMenu}}
/>
{{/if}}
{{else if @section.moreSectionButtonAction}}
<SectionLinkButton
@action={{@section.moreSectionButtonAction}}
@icon={{@section.moreSectionButtonIcon}}
@text={{@section.moreSectionButtonText}}
/>
{{/if}}
</Section>
{{/if}}
</template>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Component from "@glimmer/component";
import { cached } from "@glimmer/tracking";
import { getOwner, setOwner } from "@ember/owner";
import { service } from "@ember/service";
import { getCustomSectionMoreLinks } from "discourse/lib/sidebar/custom-section-more-links";
import ApiSection from "./api-section";
import PanelHeader from "./panel-header";

Expand Down Expand Up @@ -63,6 +64,31 @@ function prepareSidebarSectionClass(Section, routerService) {

this.filterable = filterable;
this.sidebarState = sidebarState;

// Add more links from plugin API registrations
this._setupMoreLinks();
}

_setupMoreLinks() {
const moreLinksClasses = getCustomSectionMoreLinks(this.name);
if (moreLinksClasses.length > 0) {
this._apiMoreLinks = moreLinksClasses.map((LinkClass) => {
const linkInstance = new LinkClass();
// Set owner so the link has access to services
const owner = getOwner(this);
if (owner) {
setOwner(linkInstance, owner);
}
return linkInstance;
});
}
}

get moreLinks() {
// Combine section-defined more links with API-registered more links
const sectionMoreLinks = super.moreLinks || [];
const apiMoreLinks = this._apiMoreLinks || [];
return [...sectionMoreLinks, ...apiMoreLinks];
}

@cached
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { fn, hash } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
Expand Down Expand Up @@ -90,6 +90,7 @@ export default class SidebarMoreSectionLinks extends Component {
@inline={{true}}
@identifier="sidebar-more-section"
@triggerComponent={{MoreSectionTrigger}}
@data={{hash moreText=@moreText moreIcon=@moreIcon}}
>

<:content as |menu|>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { or } from "truth-helpers";
import icon from "discourse/helpers/d-icon";
import { i18n } from "discourse-i18n";

const MoreSectionTrigger = <template>
<button ...attributes type="button" class="sidebar-section-link sidebar-row">
<span class="sidebar-section-link-prefix icon">
{{icon "ellipsis-vertical"}}
{{icon (or @componentArgs.data.moreIcon "ellipsis-vertical")}}
</span>
<span class="sidebar-section-link-content-text">
{{i18n "sidebar.more"}}
{{or @componentArgs.data.moreText (i18n "sidebar.more")}}
</span>
</button>
</template>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const SidebarSections = <template>
<AnonymousSections
@collapsableSections={{@collapsableSections}}
@toggleNavigationMenu={{@toggleNavigationMenu}}
@hideApiSections={{@hideApiSections}}
/>
{{/if}}
</template>;
Expand Down
76 changes: 76 additions & 0 deletions app/assets/javascripts/discourse/app/lib/plugin-api.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import {
import Sharing from "discourse/lib/sharing";
import { addAdminSidebarSectionLink } from "discourse/lib/sidebar/admin-sidebar";
import { addSectionLink as addCustomCommunitySectionLink } from "discourse/lib/sidebar/custom-community-section-links";
import { addCustomSectionMoreLink } from "discourse/lib/sidebar/custom-section-more-links";
import {
addSidebarPanel,
addSidebarSection,
Expand Down Expand Up @@ -3040,6 +3041,54 @@ class PluginApi {
* })()
* ];
* }
*
* get moreLinks() {
* return [
* new (class extends BaseCustomSidebarSectionLink {
* get name() {
* return "browse-all";
* }
* get route() {
* return "chat.browse";
* }
* get title() {
* return I18n.t("chat.browse.title");
* }
* get text() {
* return I18n.t("chat.browse.title");
* }
* get prefixType() {
* return "icon";
* }
* get prefixValue() {
* return "list";
* }
* })()
* ];
* }
*
* get moreSectionButtonAction() {
* return () => {
* // Action for the custom button in more section
* this.router.transitionTo('chat.settings');
* };
* }
*
* get moreSectionButtonText() {
* return I18n.t("chat.settings.title");
* }
*
* get moreSectionButtonIcon() {
* return "cog";
* }
*
* get moreSectionText() {
* return "Show All"; // Custom text for "More..." dropdown (defaults to "More...")
* }
*
* get moreSectionIcon() {
* return "plus"; // Custom icon for "More..." dropdown (defaults to "ellipsis-vertical")
* }
* }
* })
* ```
Expand All @@ -3048,6 +3097,33 @@ class PluginApi {
addSidebarSection(func, panelKey);
}

/**
* Add a link to the "More..." dropdown section of a custom sidebar section.
* This works similarly to `addCommunitySectionLink` but for custom sections.
*
* ```
* api.addCustomSectionMoreLink("my-section", {
* name: "my-custom-link",
* route: "my.route",
* title: I18n.t("my.title"),
* text: I18n.t("my.text"),
* icon: "star"
* });
* ```
*
* @param {string} sectionName - The name of the custom section to add the link to
* @param {Object|Function} linkArg - Link configuration object or callback function
* @param {string} linkArg.name - The name of the link
* @param {string} linkArg.route - The Ember route name
* @param {string} linkArg.title - The title attribute for the link
* @param {string} linkArg.text - The text to display for the link
* @param {string} [linkArg.icon] - The FontAwesome icon to display
* @param {string} [linkArg.href] - The href attribute for the link (alternative to route)
*/
addCustomSectionMoreLink(sectionName, linkArg) {
addCustomSectionMoreLink(sectionName, linkArg);
}

/**
* Register a custom renderer for a notification type or override the
* renderer of an existing type. See lib/notification-types/base.js for
Expand Down
Loading
Loading