mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 02:11:08 -06:00
DEV: Untangle the admin sidebar from the sidebar code (#27640)
This commit is contained in:
parent
640dccd224
commit
b36cbc7d21
@ -1,99 +1,55 @@
|
|||||||
import Component from "@glimmer/component";
|
|
||||||
import { getOwner, setOwner } from "@ember/owner";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import Section from "./section";
|
import Section from "./section";
|
||||||
import SectionLink from "./section-link";
|
import SectionLink from "./section-link";
|
||||||
|
|
||||||
export default class SidebarApiSection extends Component {
|
const SidebarApiSection = <template>
|
||||||
@service sidebarState;
|
{{#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}}
|
||||||
|
>
|
||||||
|
{{#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}}
|
||||||
|
@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}}
|
||||||
|
@currentWhen={{link.currentWhen}}
|
||||||
|
@didInsert={{link.didInsert}}
|
||||||
|
@willDestroy={{link.willDestroy}}
|
||||||
|
@content={{link.text}}
|
||||||
|
@contentComponent={{component
|
||||||
|
link.contentComponent
|
||||||
|
status=link.contentComponentArgs
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</Section>
|
||||||
|
{{/if}}
|
||||||
|
</template>;
|
||||||
|
|
||||||
constructor() {
|
export default SidebarApiSection;
|
||||||
super(...arguments);
|
|
||||||
|
|
||||||
this.section = new this.args.sectionConfig();
|
|
||||||
setOwner(this.section, getOwner(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
get shouldDisplay() {
|
|
||||||
return (
|
|
||||||
!this.sidebarState.currentPanel.filterable ||
|
|
||||||
this.sidebarState.filter.length === 0 ||
|
|
||||||
this.filteredLinks.length > 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get filteredLinks() {
|
|
||||||
if (!this.sidebarState.filter) {
|
|
||||||
return this.section.links;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.section.text.toLowerCase().match(this.sidebarState.sanitizedFilter)
|
|
||||||
) {
|
|
||||||
return this.section.links;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.section.links.filter((link) => {
|
|
||||||
return (
|
|
||||||
link.text
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.match(this.sidebarState.sanitizedFilter) ||
|
|
||||||
link.keywords.navigation.some((keyword) =>
|
|
||||||
keyword.match(this.sidebarState.filter)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
|
||||||
{{#if this.shouldDisplay}}
|
|
||||||
<Section
|
|
||||||
@sectionName={{this.section.name}}
|
|
||||||
@headerLinkText={{this.section.text}}
|
|
||||||
@headerLinkTitle={{this.section.title}}
|
|
||||||
@headerActionsIcon={{this.section.actionsIcon}}
|
|
||||||
@headerActions={{this.section.actions}}
|
|
||||||
@willDestroy={{this.section.willDestroy}}
|
|
||||||
@collapsable={{@collapsable}}
|
|
||||||
@displaySection={{this.section.displaySection}}
|
|
||||||
@hideSectionHeader={{this.section.hideSectionHeader}}
|
|
||||||
@collapsedByDefault={{this.section.collapsedByDefault}}
|
|
||||||
>
|
|
||||||
{{#each this.filteredLinks key="name" as |link|}}
|
|
||||||
<SectionLink
|
|
||||||
@linkName={{link.name}}
|
|
||||||
@linkClass={{link.classNames}}
|
|
||||||
@route={{link.route}}
|
|
||||||
@model={{link.model}}
|
|
||||||
@query={{link.query}}
|
|
||||||
@models={{link.models}}
|
|
||||||
@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}}
|
|
||||||
@currentWhen={{link.currentWhen}}
|
|
||||||
@didInsert={{link.didInsert}}
|
|
||||||
@willDestroy={{link.willDestroy}}
|
|
||||||
@content={{link.text}}
|
|
||||||
@contentComponent={{component
|
|
||||||
link.contentComponent
|
|
||||||
status=link.contentComponentArgs
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
</Section>
|
|
||||||
{{/if}}
|
|
||||||
</template>
|
|
||||||
}
|
|
||||||
|
@ -1,32 +1,89 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { cached } from "@glimmer/tracking";
|
||||||
|
import { getOwner, setOwner } from "@ember/owner";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import AdminHeader from "./admin-header";
|
|
||||||
import ApiSection from "./api-section";
|
import ApiSection from "./api-section";
|
||||||
import FilterNoResults from "./filter-no-results";
|
import PanelHeader from "./panel-header";
|
||||||
|
|
||||||
export default class SidebarApiSections extends Component {
|
export default class SidebarApiSections extends Component {
|
||||||
@service sidebarState;
|
@service sidebarState;
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
|
let sectionConfigs;
|
||||||
|
|
||||||
if (this.sidebarState.combinedMode) {
|
if (this.sidebarState.combinedMode) {
|
||||||
return this.sidebarState.panels
|
sectionConfigs = this.sidebarState.panels
|
||||||
.filter((panel) => !panel.hidden)
|
.filter((panel) => !panel.hidden)
|
||||||
.flatMap((panel) => panel.sections);
|
.flatMap((panel) => panel.sections);
|
||||||
} else {
|
} else {
|
||||||
return this.sidebarState.currentPanel.sections;
|
sectionConfigs = this.sidebarState.currentPanel.sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sectionConfigs.map((Section) => {
|
||||||
|
const SidebarSection = prepareSidebarSectionClass(Section);
|
||||||
|
|
||||||
|
const sectionInstance = new SidebarSection({
|
||||||
|
filterable:
|
||||||
|
!this.sidebarState.combinedMode &&
|
||||||
|
this.sidebarState.currentPanel.filterable,
|
||||||
|
sidebarState: this.sidebarState,
|
||||||
|
});
|
||||||
|
|
||||||
|
setOwner(sectionInstance, getOwner(this));
|
||||||
|
|
||||||
|
return sectionInstance;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get filteredSections() {
|
||||||
|
return this.sections.filter((section) => section.filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AdminHeader />
|
<PanelHeader @sections={{this.filteredSections}} />
|
||||||
|
|
||||||
{{#each this.sections as |sectionConfig|}}
|
{{#each this.filteredSections as |section|}}
|
||||||
<ApiSection
|
<ApiSection @section={{section}} @collapsable={{@collapsable}} />
|
||||||
@sectionConfig={{sectionConfig}}
|
|
||||||
@collapsable={{@collapsable}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<FilterNoResults />
|
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extends the class provided for the section to add functionality we don't want to be overridable when defining custom
|
||||||
|
// sections using the plugin API, like for example the filtering capabilities
|
||||||
|
function prepareSidebarSectionClass(Section) {
|
||||||
|
return class extends Section {
|
||||||
|
constructor({ filterable, sidebarState }) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.filterable = filterable;
|
||||||
|
this.sidebarState = sidebarState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@cached
|
||||||
|
get filteredLinks() {
|
||||||
|
if (!this.filterable || !this.sidebarState.filter) {
|
||||||
|
return this.links;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.text.toLowerCase().match(this.sidebarState.sanitizedFilter)) {
|
||||||
|
return this.links;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.links.filter((link) => {
|
||||||
|
return (
|
||||||
|
link.text
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.match(this.sidebarState.sanitizedFilter) ||
|
||||||
|
link.keywords.navigation.some((keyword) =>
|
||||||
|
keyword.match(this.sidebarState.filter)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get filtered() {
|
||||||
|
return !this.filterable || this.filteredLinks?.length > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,28 +1,17 @@
|
|||||||
import Component from "@glimmer/component";
|
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
import { service } from "@ember/service";
|
|
||||||
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
|
|
||||||
import { defaultHomepage } from "discourse/lib/utilities";
|
import { defaultHomepage } from "discourse/lib/utilities";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
|
||||||
export default class BackToForum extends Component {
|
const BackToForum = <template>
|
||||||
@service sidebarState;
|
<LinkTo
|
||||||
|
@route="discovery.{{(defaultHomepage)}}"
|
||||||
|
class="sidebar-sections__back-to-forum"
|
||||||
|
>
|
||||||
|
{{icon "arrow-left"}}
|
||||||
|
|
||||||
get shouldDisplay() {
|
<span>{{i18n "sidebar.back_to_forum"}}</span>
|
||||||
return this.sidebarState.isCurrentPanel(ADMIN_PANEL);
|
</LinkTo>
|
||||||
}
|
</template>;
|
||||||
|
|
||||||
<template>
|
export default BackToForum;
|
||||||
{{#if this.shouldDisplay}}
|
|
||||||
<LinkTo
|
|
||||||
@route="discovery.{{(defaultHomepage)}}"
|
|
||||||
class="sidebar-sections__back-to-forum"
|
|
||||||
>
|
|
||||||
{{icon "arrow-left"}}
|
|
||||||
|
|
||||||
<span>{{i18n "admin.back_to_forum"}}</span>
|
|
||||||
</LinkTo>
|
|
||||||
{{/if}}
|
|
||||||
</template>
|
|
||||||
}
|
|
||||||
|
@ -1,32 +1,21 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
|
||||||
import I18n from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class FilterNoResults extends Component {
|
export default class FilterNoResults extends Component {
|
||||||
@service sidebarState;
|
@service sidebarState;
|
||||||
|
|
||||||
/**
|
|
||||||
* Component is rendered when panel is filtreable
|
|
||||||
* Visibility is additionally controlled by CSS rule `.sidebar-section-wrapper + .sidebar-no-results`
|
|
||||||
*/
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
return this.sidebarState.currentPanel.filterable;
|
return (
|
||||||
|
this.sidebarState.currentPanel.filterable &&
|
||||||
|
!!(this.args.sections?.length === 0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get noResultsDescription() {
|
get noResultsDescription() {
|
||||||
const params = {
|
return this.sidebarState.currentPanel.filterNoResultsDescription(
|
||||||
filter: this.sidebarState.filter,
|
this.sidebarState.filter
|
||||||
settings_filter_url: getURL(
|
);
|
||||||
`/admin/site_settings/category/all_results?filter=${this.sidebarState.filter}`
|
|
||||||
),
|
|
||||||
user_list_filter_url: getURL(
|
|
||||||
`/admin/users/list/active?username=${this.sidebarState.filter}`
|
|
||||||
),
|
|
||||||
};
|
|
||||||
return htmlSafe(I18n.t("sidebar.no_results.description", params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -35,9 +24,11 @@ export default class FilterNoResults extends Component {
|
|||||||
<h4 class="sidebar-no-results__title">{{i18n
|
<h4 class="sidebar-no-results__title">{{i18n
|
||||||
"sidebar.no_results.title"
|
"sidebar.no_results.title"
|
||||||
}}</h4>
|
}}</h4>
|
||||||
<p
|
{{#if this.noResultsDescription}}
|
||||||
class="sidebar-no-results__description"
|
<p class="sidebar-no-results__description">
|
||||||
>{{this.noResultsDescription}}</p>
|
{{this.noResultsDescription}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
|
@ -5,7 +5,7 @@ import DButton from "discourse/components/d-button";
|
|||||||
import SidebarSectionForm from "discourse/components/modal/sidebar-section-form";
|
import SidebarSectionForm from "discourse/components/modal/sidebar-section-form";
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import routeAction from "discourse/helpers/route-action";
|
import routeAction from "discourse/helpers/route-action";
|
||||||
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
|
import { MAIN_PANEL } from "discourse/lib/sidebar/panels";
|
||||||
|
|
||||||
export default class SidebarFooter extends Component {
|
export default class SidebarFooter extends Component {
|
||||||
@service capabilities;
|
@service capabilities;
|
||||||
@ -16,7 +16,7 @@ export default class SidebarFooter extends Component {
|
|||||||
@service sidebarState;
|
@service sidebarState;
|
||||||
|
|
||||||
get showManageSectionsButton() {
|
get showManageSectionsButton() {
|
||||||
return this.currentUser && !this.sidebarState.isCurrentPanel(ADMIN_PANEL);
|
return this.currentUser && this.sidebarState.isCurrentPanel(MAIN_PANEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
get showToggleMobileButton() {
|
get showToggleMobileButton() {
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
|
|
||||||
import BackToForum from "./back-to-forum";
|
import BackToForum from "./back-to-forum";
|
||||||
import Filter from "./filter";
|
import Filter from "./filter";
|
||||||
|
import FilterNoResults from "./filter-no-results";
|
||||||
import ToggleAllSections from "./toggle-all-sections";
|
import ToggleAllSections from "./toggle-all-sections";
|
||||||
|
|
||||||
export default class AdminHeader extends Component {
|
export default class PanelHeader extends Component {
|
||||||
@service sidebarState;
|
@service sidebarState;
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
return this.sidebarState.isCurrentPanel(ADMIN_PANEL);
|
return this.sidebarState.currentPanel.displayHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{#if this.shouldDisplay}}
|
{{#if this.shouldDisplay}}
|
||||||
<div class="sidebar-admin-header">
|
<div class="sidebar-panel-header">
|
||||||
<div class="sidebar-admin-header__row">
|
<div class="sidebar-panel-header__row">
|
||||||
<BackToForum />
|
<BackToForum />
|
||||||
<ToggleAllSections @sections={{@sections}} />
|
<ToggleAllSections @sections={{@sections}} />
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-admin-header__row">
|
<div class="sidebar-panel-header__row">
|
||||||
<Filter />
|
<Filter />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<FilterNoResults @sections={{@sections}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ const SidebarSectionHeader = <template>
|
|||||||
<DButton
|
<DButton
|
||||||
@title="sidebar.toggle_section"
|
@title="sidebar.toggle_section"
|
||||||
@action={{@toggleSectionDisplay}}
|
@action={{@toggleSectionDisplay}}
|
||||||
aria-controls={{@sidebarSectionContentID}}
|
aria-controls={{@sidebarSectionContentId}}
|
||||||
aria-expanded={{if @isExpanded "true" "false"}}
|
aria-expanded={{if @isExpanded "true" "false"}}
|
||||||
class="sidebar-section-header sidebar-section-header-collapsable btn-transparent"
|
class="sidebar-section-header sidebar-section-header-collapsable btn-transparent"
|
||||||
>
|
>
|
||||||
|
@ -5,6 +5,10 @@ import { action } from "@ember/object";
|
|||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
|
import {
|
||||||
|
getCollapsedSidebarSectionKey,
|
||||||
|
getSidebarSectionContentId,
|
||||||
|
} from "discourse/lib/sidebar/helpers";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
@ -16,8 +20,10 @@ export default class SidebarSection extends Component {
|
|||||||
@service keyValueStore;
|
@service keyValueStore;
|
||||||
@service sidebarState;
|
@service sidebarState;
|
||||||
|
|
||||||
sidebarSectionContentID = `sidebar-section-content-${this.args.sectionName}`;
|
sidebarSectionContentId = getSidebarSectionContentId(this.args.sectionName);
|
||||||
collapsedSidebarSectionKey = `sidebar-section-${this.args.sectionName}-collapsed`;
|
collapsedSidebarSectionKey = getCollapsedSidebarSectionKey(
|
||||||
|
this.args.sectionName
|
||||||
|
);
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
@ -116,7 +122,7 @@ export default class SidebarSection extends Component {
|
|||||||
<div class="sidebar-section-header-wrapper sidebar-row">
|
<div class="sidebar-section-header-wrapper sidebar-row">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
@collapsable={{@collapsable}}
|
@collapsable={{@collapsable}}
|
||||||
@sidebarSectionContentID={{this.sidebarSectionContentID}}
|
@sidebarSectionContentId={{this.sidebarSectionContentId}}
|
||||||
@toggleSectionDisplay={{this.toggleSectionDisplay}}
|
@toggleSectionDisplay={{this.toggleSectionDisplay}}
|
||||||
@isExpanded={{this.displaySectionContent}}
|
@isExpanded={{this.displaySectionContent}}
|
||||||
>
|
>
|
||||||
@ -174,7 +180,7 @@ export default class SidebarSection extends Component {
|
|||||||
|
|
||||||
{{#if this.displaySectionContent}}
|
{{#if this.displaySectionContent}}
|
||||||
<ul
|
<ul
|
||||||
id={{this.sidebarSectionContentID}}
|
id={{this.sidebarSectionContentId}}
|
||||||
class="sidebar-section-content"
|
class="sidebar-section-content"
|
||||||
>
|
>
|
||||||
{{yield}}
|
{{yield}}
|
||||||
|
@ -2,24 +2,30 @@ import Component from "@glimmer/component";
|
|||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map";
|
import { getCollapsedSidebarSectionKey } from "discourse/lib/sidebar/helpers";
|
||||||
|
|
||||||
export default class ToggleAllSections extends Component {
|
export default class ToggleAllSections extends Component {
|
||||||
@service sidebarState;
|
@service sidebarState;
|
||||||
@service keyValueStore;
|
@service keyValueStore;
|
||||||
|
|
||||||
|
get collapsableSections() {
|
||||||
|
return this.args.sections.filter(
|
||||||
|
(section) => section.displaySection && !section.hideSectionHeader
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get allSectionsExpanded() {
|
get allSectionsExpanded() {
|
||||||
return ADMIN_NAV_MAP.every((adminNav) => {
|
return this.collapsableSections.every((section) => {
|
||||||
return !this.sidebarState.collapsedSections.has(
|
return !this.sidebarState.collapsedSections.has(
|
||||||
`sidebar-section-${this.sidebarState.currentPanel.key}-${adminNav.name}-collapsed`
|
getCollapsedSidebarSectionKey(section.name)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return this.allSectionsExpanded
|
return this.allSectionsExpanded
|
||||||
? "admin.collapse_all_sections"
|
? "sidebar.collapse_all_sections"
|
||||||
: "admin.expand_all_sections";
|
: "sidebar.expand_all_sections";
|
||||||
}
|
}
|
||||||
|
|
||||||
get icon() {
|
get icon() {
|
||||||
@ -30,12 +36,11 @@ export default class ToggleAllSections extends Component {
|
|||||||
toggleAllSections() {
|
toggleAllSections() {
|
||||||
const collapse = this.allSectionsExpanded;
|
const collapse = this.allSectionsExpanded;
|
||||||
|
|
||||||
ADMIN_NAV_MAP.forEach((adminNav) => {
|
this.collapsableSections.forEach((section) => {
|
||||||
const key = `${this.sidebarState.currentPanel.key}-${adminNav.name}`;
|
|
||||||
if (collapse) {
|
if (collapse) {
|
||||||
this.sidebarState.collapseSection(key);
|
this.sidebarState.collapseSection(section.name);
|
||||||
} else {
|
} else {
|
||||||
this.sidebarState.expandSection(key);
|
this.sidebarState.expandSection(section.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { cached } from "@glimmer/tracking";
|
import { cached } from "@glimmer/tracking";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map";
|
import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map";
|
||||||
import BaseCustomSidebarPanel from "discourse/lib/sidebar/base-custom-sidebar-panel";
|
import BaseCustomSidebarPanel from "discourse/lib/sidebar/base-custom-sidebar-panel";
|
||||||
@ -6,6 +7,7 @@ import BaseCustomSidebarSection from "discourse/lib/sidebar/base-custom-sidebar-
|
|||||||
import BaseCustomSidebarSectionLink from "discourse/lib/sidebar/base-custom-sidebar-section-link";
|
import BaseCustomSidebarSectionLink from "discourse/lib/sidebar/base-custom-sidebar-section-link";
|
||||||
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
|
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
|
||||||
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
||||||
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
let additionalAdminSidebarSectionLinks = {};
|
let additionalAdminSidebarSectionLinks = {};
|
||||||
@ -81,6 +83,7 @@ class SidebarAdminSectionLink extends BaseCustomSidebarSectionLink {
|
|||||||
|
|
||||||
return this.adminSidebarNavLink.route;
|
return this.adminSidebarNavLink.route;
|
||||||
}
|
}
|
||||||
|
|
||||||
get keywords() {
|
get keywords() {
|
||||||
return (
|
return (
|
||||||
this.adminSidebarStateManager.keywords[this.adminSidebarNavLink.name] || {
|
this.adminSidebarStateManager.keywords[this.adminSidebarNavLink.name] || {
|
||||||
@ -257,6 +260,7 @@ function installedPluginsLinkKeywords() {
|
|||||||
export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
|
export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
|
||||||
key = ADMIN_PANEL;
|
key = ADMIN_PANEL;
|
||||||
hidden = true;
|
hidden = true;
|
||||||
|
displayHeader = true;
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get sections() {
|
get sections() {
|
||||||
@ -346,4 +350,18 @@ export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
|
|||||||
get filterable() {
|
get filterable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterNoResultsDescription(filter) {
|
||||||
|
const params = {
|
||||||
|
filter,
|
||||||
|
settings_filter_url: getURL(
|
||||||
|
`/admin/site_settings/category/all_results?filter=${filter}`
|
||||||
|
),
|
||||||
|
user_list_filter_url: getURL(
|
||||||
|
`/admin/users/list/active?username=${filter}`
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return htmlSafe(I18n.t("sidebar.no_results.description", params));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,13 @@ export default class BaseCustomSidebarPanel {
|
|||||||
this.hidden || this.#notImplemented();
|
this.hidden || this.#notImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} Controls whether the panel will display a header
|
||||||
|
*/
|
||||||
|
get displayHeader() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Controls whether the filter is shown
|
* @returns {boolean} Controls whether the filter is shown
|
||||||
*/
|
*/
|
||||||
@ -50,6 +57,17 @@ export default class BaseCustomSidebarPanel {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filter filter applied
|
||||||
|
*
|
||||||
|
* @returns {string | SafeString} Description displayed when the applied filter has no results.
|
||||||
|
* Use `htmlSafe` from `from "@ember/template` to use HTML strings.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
filterNoResultsDescription(filter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
#notImplemented() {
|
#notImplemented() {
|
||||||
throw "not implemented";
|
throw "not implemented";
|
||||||
}
|
}
|
||||||
|
@ -15,3 +15,11 @@ export function hasDefaultSidebarCategories(siteSettings) {
|
|||||||
export function hasDefaultSidebarTags(siteSettings) {
|
export function hasDefaultSidebarTags(siteSettings) {
|
||||||
return siteSettings.default_navigation_menu_tags.length > 0;
|
return siteSettings.default_navigation_menu_tags.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSidebarSectionContentId(name) {
|
||||||
|
return `sidebar-section-content-${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCollapsedSidebarSectionKey(name) {
|
||||||
|
return `sidebar-section-${name}-collapsed`;
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
currentPanelKey,
|
currentPanelKey,
|
||||||
customPanels as panels,
|
customPanels as panels,
|
||||||
} from "discourse/lib/sidebar/custom-sections";
|
} from "discourse/lib/sidebar/custom-sections";
|
||||||
|
import { getCollapsedSidebarSectionKey } from "discourse/lib/sidebar/helpers";
|
||||||
import {
|
import {
|
||||||
COMBINED_MODE,
|
COMBINED_MODE,
|
||||||
MAIN_PANEL,
|
MAIN_PANEL,
|
||||||
@ -81,13 +82,15 @@ export default class SidebarState extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
collapseSection(sectionKey) {
|
collapseSection(sectionKey) {
|
||||||
const collapsedSidebarSectionKey = `sidebar-section-${sectionKey}-collapsed`;
|
const collapsedSidebarSectionKey =
|
||||||
|
getCollapsedSidebarSectionKey(sectionKey);
|
||||||
this.keyValueStore.setItem(collapsedSidebarSectionKey, true);
|
this.keyValueStore.setItem(collapsedSidebarSectionKey, true);
|
||||||
this.collapsedSections.add(collapsedSidebarSectionKey);
|
this.collapsedSections.add(collapsedSidebarSectionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSection(sectionKey) {
|
expandSection(sectionKey) {
|
||||||
const collapsedSidebarSectionKey = `sidebar-section-${sectionKey}-collapsed`;
|
const collapsedSidebarSectionKey =
|
||||||
|
getCollapsedSidebarSectionKey(sectionKey);
|
||||||
this.keyValueStore.setItem(collapsedSidebarSectionKey, false);
|
this.keyValueStore.setItem(collapsedSidebarSectionKey, false);
|
||||||
this.collapsedSections.delete(collapsedSidebarSectionKey);
|
this.collapsedSections.delete(collapsedSidebarSectionKey);
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,7 @@
|
|||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-admin-header__row {
|
.sidebar-panel-header__row {
|
||||||
width: calc(320px - 2 * var(--d-sidebar-row-horizontal-padding));
|
width: calc(320px - 2 * var(--d-sidebar-row-horizontal-padding));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,11 +373,8 @@
|
|||||||
.sidebar-no-results {
|
.sidebar-no-results {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.sidebar-section-wrapper + .sidebar-no-results {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-admin-header__row {
|
.sidebar-panel-header__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
@ -4806,6 +4806,9 @@ en:
|
|||||||
panels:
|
panels:
|
||||||
forum:
|
forum:
|
||||||
label: Forum
|
label: Forum
|
||||||
|
back_to_forum: "Back to Forum"
|
||||||
|
collapse_all_sections: "Collapse all sections"
|
||||||
|
expand_all_sections: "Expand all sections"
|
||||||
filter: "Filter..."
|
filter: "Filter..."
|
||||||
clear_filter: "Clear filter"
|
clear_filter: "Clear filter"
|
||||||
no_results:
|
no_results:
|
||||||
@ -4926,10 +4929,7 @@ en:
|
|||||||
admin:
|
admin:
|
||||||
title: "Discourse Admin"
|
title: "Discourse Admin"
|
||||||
moderator: "Moderator"
|
moderator: "Moderator"
|
||||||
back_to_forum: "Back to Forum"
|
|
||||||
filter_reports: Filter reports
|
filter_reports: Filter reports
|
||||||
expand_all_sections: "Expand all sections"
|
|
||||||
collapse_all_sections: "Collapse all sections"
|
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
remove_muted_tags_from_latest:
|
remove_muted_tags_from_latest:
|
||||||
|
@ -31,6 +31,11 @@ describe "Admin Revamp | Sidebar Navigation", type: :system do
|
|||||||
expect(sidebar).to have_no_section("admin-root")
|
expect(sidebar).to have_no_section("admin-root")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "displays the panel header" do
|
||||||
|
visit("/admin")
|
||||||
|
expect(sidebar).to have_panel_header
|
||||||
|
end
|
||||||
|
|
||||||
it "collapses sections by default" do
|
it "collapses sections by default" do
|
||||||
visit("/admin")
|
visit("/admin")
|
||||||
links = page.all(".sidebar-section-link-content-text")
|
links = page.all(".sidebar-section-link-content-text")
|
||||||
@ -113,6 +118,27 @@ describe "Admin Revamp | Sidebar Navigation", type: :system do
|
|||||||
expect(page).to have_no_css(".sidebar-no-results")
|
expect(page).to have_no_css(".sidebar-no-results")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "displays the no results description message correctly when the filter has no results" do
|
||||||
|
visit("/admin")
|
||||||
|
|
||||||
|
filter.filter("ieeee")
|
||||||
|
expect(page).to have_no_css(".sidebar-section-link-content-text")
|
||||||
|
expect(page).to have_css(".sidebar-no-results")
|
||||||
|
|
||||||
|
no_results_description = page.find(".sidebar-no-results__description")
|
||||||
|
expect(no_results_description.text).to eq(
|
||||||
|
"We couldn’t find anything matching ‘ieeee’.\n\nDid you want to search site settings or the admin user list?",
|
||||||
|
)
|
||||||
|
expect(no_results_description).to have_link(
|
||||||
|
"search site settings",
|
||||||
|
href: "/admin/site_settings/category/all_results?filter=ieeee",
|
||||||
|
)
|
||||||
|
expect(no_results_description).to have_link(
|
||||||
|
"admin user list?",
|
||||||
|
href: "/admin/users/list/active?username=ieeee",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it "temporarily expands section when filter" do
|
it "temporarily expands section when filter" do
|
||||||
visit("/admin")
|
visit("/admin")
|
||||||
links = page.all(".sidebar-section-link-content-text")
|
links = page.all(".sidebar-section-link-content-text")
|
||||||
|
@ -49,6 +49,14 @@ module PageObjects
|
|||||||
find("#discourse-modal-title")
|
find("#discourse-modal-title")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_panel_header?
|
||||||
|
page.has_css?(".sidebar-panel-header")
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_no_panel_header?
|
||||||
|
page.has_no_css?(".sidebar-panel-header")
|
||||||
|
end
|
||||||
|
|
||||||
def toggle_all_sections
|
def toggle_all_sections
|
||||||
find(".sidebar-toggle-all-sections").click
|
find(".sidebar-toggle-all-sections").click
|
||||||
end
|
end
|
||||||
|
@ -136,4 +136,10 @@ describe "Viewing sidebar as logged in user", type: :system do
|
|||||||
expect(sidebar).to have_all_tags_section_link
|
expect(sidebar).to have_all_tags_section_link
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "shouldn't display the panel header for the main sidebar" do
|
||||||
|
visit("/latest")
|
||||||
|
expect(sidebar).to be_visible
|
||||||
|
expect(sidebar).to have_no_panel_header
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user