DEV: Move admin sidebar out of initializer (#25396)

Having the admin sidebar code in an instance initializer is not
ideal because:

* It runs during app boot which may not even be necessary based on site settings
* It makes it hard for plugins to register additional links in time without resorting
to before/after initializer gymnastics

This PR moves the admin sidebar into a lib and creates the panel
in custom-sections.js, then the sections and links are loaded when
the main sidebar component is rendered, which leaves plugins enough
time to add additional links in an initializer.

---------

Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
Martin Brennan
2024-01-25 10:45:14 +10:00
committed by GitHub
parent 6ad34a0152
commit 57ededb770
11 changed files with 83 additions and 116 deletions

View File

@ -2,13 +2,13 @@ import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map";
import { import {
buildAdminSidebar, buildAdminSidebar,
useAdminNavConfig, useAdminNavConfig,
} from "discourse/instance-initializers/admin-sidebar"; } from "discourse/lib/sidebar/admin-sidebar";
import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map";
import { resetPanelSections } from "discourse/lib/sidebar/custom-sections"; import { resetPanelSections } from "discourse/lib/sidebar/custom-sections";
import { ADMIN_PANEL } from "discourse/services/sidebar-state"; import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
// TODO (martin) (2024-02-01) Remove this experimental UI. // TODO (martin) (2024-02-01) Remove this experimental UI.
export default class AdminConfigAreaSidebarExperiment extends Component { export default class AdminConfigAreaSidebarExperiment extends Component {

View File

@ -1,7 +1,7 @@
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { ADMIN_PANEL, MAIN_PANEL } from "discourse/lib/sidebar/panels";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import { ADMIN_PANEL, MAIN_PANEL } from "discourse/services/sidebar-state";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default class AdminRoute extends DiscourseRoute { export default class AdminRoute extends DiscourseRoute {

View File

@ -1,6 +1,6 @@
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { ADMIN_PANEL, MAIN_PANEL } from "discourse/lib/sidebar/panels";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import { ADMIN_PANEL, MAIN_PANEL } from "discourse/services/sidebar-state";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default class AdminRoute extends DiscourseRoute { export default class AdminRoute extends DiscourseRoute {

View File

@ -42,7 +42,6 @@ import {
} from "discourse/helpers/category-link"; } from "discourse/helpers/category-link";
import { addUsernameSelectorDecorator } from "discourse/helpers/decorate-username-selector"; import { addUsernameSelectorDecorator } from "discourse/helpers/decorate-username-selector";
import { registerCustomAvatarHelper } from "discourse/helpers/user-avatar"; import { registerCustomAvatarHelper } from "discourse/helpers/user-avatar";
import { addAdminSidebarSectionLink } from "discourse/instance-initializers/admin-sidebar";
import { addBeforeAuthCompleteCallback } from "discourse/instance-initializers/auth-complete"; import { addBeforeAuthCompleteCallback } from "discourse/instance-initializers/auth-complete";
import { addPopupMenuOption } from "discourse/lib/composer/custom-popup-menu-options"; import { addPopupMenuOption } from "discourse/lib/composer/custom-popup-menu-options";
import { registerDesktopNotificationHandler } from "discourse/lib/desktop-notifications"; import { registerDesktopNotificationHandler } from "discourse/lib/desktop-notifications";
@ -67,6 +66,7 @@ import { addTagsHtmlCallback } from "discourse/lib/render-tags";
import { addFeaturedLinkMetaDecorator } from "discourse/lib/render-topic-featured-link"; import { addFeaturedLinkMetaDecorator } from "discourse/lib/render-topic-featured-link";
import { addSearchResultsCallback } from "discourse/lib/search"; import { addSearchResultsCallback } from "discourse/lib/search";
import Sharing from "discourse/lib/sharing"; 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 { addSectionLink as addCustomCommunitySectionLink } from "discourse/lib/sidebar/custom-community-section-links";
import { import {
addSidebarPanel, addSidebarPanel,

View File

@ -1,10 +1,11 @@
import { isEmpty } from "@ember/utils";
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 { import BaseCustomSidebarPanel from "discourse/lib/sidebar/base-custom-sidebar-panel";
addSidebarPanel, import BaseCustomSidebarSection from "discourse/lib/sidebar/base-custom-sidebar-section";
addSidebarSection, import BaseCustomSidebarSectionLink from "discourse/lib/sidebar/base-custom-sidebar-section-link";
} from "discourse/lib/sidebar/custom-sections"; import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
import { ADMIN_PANEL } from "discourse/services/sidebar-state"; import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
let additionalAdminSidebarSectionLinks = {}; let additionalAdminSidebarSectionLinks = {};
@ -14,60 +15,52 @@ export function clearAdditionalAdminSidebarSectionLinks() {
additionalAdminSidebarSectionLinks = {}; additionalAdminSidebarSectionLinks = {};
} }
function defineAdminSectionLink(BaseCustomSidebarSectionLink) { class SidebarAdminSectionLink extends BaseCustomSidebarSectionLink {
const SidebarAdminSectionLink = class extends BaseCustomSidebarSectionLink { constructor({ adminSidebarNavLink }) {
constructor({ adminSidebarNavLink }) { super(...arguments);
super(...arguments); this.adminSidebarNavLink = adminSidebarNavLink;
this.adminSidebarNavLink = adminSidebarNavLink; }
}
get name() { get name() {
return this.adminSidebarNavLink.name; return this.adminSidebarNavLink.name;
} }
get classNames() { get classNames() {
return "admin-sidebar-nav-link"; return "admin-sidebar-nav-link";
} }
get route() { get route() {
return this.adminSidebarNavLink.route; return this.adminSidebarNavLink.route;
} }
get href() { get href() {
return this.adminSidebarNavLink.href; return this.adminSidebarNavLink.href;
} }
get models() { get models() {
return this.adminSidebarNavLink.routeModels; return this.adminSidebarNavLink.routeModels;
} }
get text() { get text() {
return this.adminSidebarNavLink.label return this.adminSidebarNavLink.label
? I18n.t(this.adminSidebarNavLink.label) ? I18n.t(this.adminSidebarNavLink.label)
: this.adminSidebarNavLink.text; : this.adminSidebarNavLink.text;
} }
get prefixType() { get prefixType() {
return "icon"; return "icon";
} }
get prefixValue() { get prefixValue() {
return this.adminSidebarNavLink.icon; return this.adminSidebarNavLink.icon;
} }
get title() { get title() {
return this.adminSidebarNavLink.text; return this.adminSidebarNavLink.text;
} }
};
return SidebarAdminSectionLink;
} }
function defineAdminSection( function defineAdminSection(adminNavSectionData) {
adminNavSectionData,
BaseCustomSidebarSection,
adminSectionLinkClass
) {
const AdminNavSection = class extends BaseCustomSidebarSection { const AdminNavSection = class extends BaseCustomSidebarSection {
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -96,7 +89,7 @@ function defineAdminSection(
get links() { get links() {
return this.sectionLinks.map( return this.sectionLinks.map(
(sectionLinkData) => (sectionLinkData) =>
new adminSectionLinkClass({ adminSidebarNavLink: sectionLinkData }) new SidebarAdminSectionLink({ adminSidebarNavLink: sectionLinkData })
); );
} }
@ -163,27 +156,6 @@ export function useAdminNavConfig(navMap) {
return navMap; return navMap;
} }
let adminSectionLinkClass = null;
export function buildAdminSidebar(navConfig) {
navConfig.forEach((adminNavSectionData) => {
addSidebarSection(
(BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => {
// We only want to define the link class once even though we have many different sections.
adminSectionLinkClass =
adminSectionLinkClass ||
defineAdminSectionLink(BaseCustomSidebarSectionLink);
return defineAdminSection(
adminNavSectionData,
BaseCustomSidebarSection,
adminSectionLinkClass
);
},
ADMIN_PANEL
);
});
}
// This is used for a plugin API. // This is used for a plugin API.
export function addAdminSidebarSectionLink(sectionName, link) { export function addAdminSidebarSectionLink(sectionName, link) {
if (!additionalAdminSidebarSectionLinks.hasOwnProperty(sectionName)) { if (!additionalAdminSidebarSectionLinks.hasOwnProperty(sectionName)) {
@ -237,41 +209,26 @@ function pluginAdminRouteLinks() {
); );
} }
export default { export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
name: "admin-sidebar-initializer", key = ADMIN_PANEL;
hidden = true;
initialize(owner) { get sections() {
this.currentUser = owner.lookup("service:current-user"); const siteSettings = getOwnerWithFallback().lookup("service:site-settings");
this.siteSettings = owner.lookup("service:site-settings"); if (isEmpty(siteSettings.admin_sidebar_enabled_groups)) {
return [];
if (
!this.currentUser?.staff ||
!this.siteSettings.userInAnyGroups(
"admin_sidebar_enabled_groups",
this.currentUser
)
) {
return;
} }
this.adminSidebarExperimentStateManager = owner.lookup( this.adminSidebarExperimentStateManager = getOwnerWithFallback(this).lookup(
"service:admin-sidebar-experiment-state-manager" "service:admin-sidebar-experiment-state-manager"
); );
addSidebarPanel(
(BaseCustomSidebarPanel) =>
class AdminSidebarPanel extends BaseCustomSidebarPanel {
key = ADMIN_PANEL;
hidden = true;
}
);
const savedConfig = this.adminSidebarExperimentStateManager.navConfig; const savedConfig = this.adminSidebarExperimentStateManager.navConfig;
const navMap = savedConfig || ADMIN_NAV_MAP; const navMap = savedConfig || ADMIN_NAV_MAP;
navMap.findBy("name", "plugins").links.push(...pluginAdminRouteLinks()); navMap.findBy("name", "plugins").links.push(...pluginAdminRouteLinks());
if (this.siteSettings.experimental_form_templates) { if (siteSettings.experimental_form_templates) {
navMap.findBy("name", "customize").links.push({ navMap.findBy("name", "customize").links.push({
name: "admin_customize_form_templates", name: "admin_customize_form_templates",
route: "adminCustomizeFormTemplates", route: "adminCustomizeFormTemplates",
@ -280,6 +237,10 @@ export default {
}); });
} }
buildAdminSidebar(useAdminNavConfig(navMap), adminSectionLinkClass); const navConfig = useAdminNavConfig(navMap);
},
}; return navConfig.map((adminNavSectionData) => {
return defineAdminSection(adminNavSectionData);
});
}
}

View File

@ -1,7 +1,9 @@
import BaseCustomSidebarPanel from "discourse/lib/sidebar/base-custom-sidebar-panel"; import BaseCustomSidebarPanel from "discourse/lib/sidebar/base-custom-sidebar-panel";
import BaseCustomSidebarSection from "discourse/lib/sidebar/base-custom-sidebar-section"; import BaseCustomSidebarSection from "discourse/lib/sidebar/base-custom-sidebar-section";
import BaseCustomSidebarSectionLink from "discourse/lib/sidebar/base-custom-sidebar-section-link"; import BaseCustomSidebarSectionLink from "discourse/lib/sidebar/base-custom-sidebar-section-link";
import { MAIN_PANEL } from "discourse/lib/sidebar/panels";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import AdminSidebarPanel from "./admin-sidebar";
class MainSidebarPanel { class MainSidebarPanel {
sections = []; sections = [];
@ -23,9 +25,9 @@ class MainSidebarPanel {
} }
} }
export let customPanels = [new MainSidebarPanel()]; export let customPanels;
export let currentPanelKey;
export let currentPanelKey = "main"; resetSidebarPanels();
export function addSidebarPanel(func) { export function addSidebarPanel(func) {
const panelClass = func.call(this, BaseCustomSidebarPanel); const panelClass = func.call(this, BaseCustomSidebarPanel);
@ -60,6 +62,6 @@ export function resetPanelSections(
} }
export function resetSidebarPanels() { export function resetSidebarPanels() {
customPanels = [new MainSidebarPanel()]; customPanels = [new MainSidebarPanel(), new AdminSidebarPanel()];
currentPanelKey = "main"; currentPanelKey = MAIN_PANEL;
} }

View File

@ -0,0 +1,4 @@
export const COMBINED_MODE = "combined";
export const SEPARATED_MODE = "separated";
export const MAIN_PANEL = "main";
export const ADMIN_PANEL = "admin";

View File

@ -5,11 +5,11 @@ import {
currentPanelKey, currentPanelKey,
customPanels as panels, customPanels as panels,
} from "discourse/lib/sidebar/custom-sections"; } from "discourse/lib/sidebar/custom-sections";
import {
const COMBINED_MODE = "combined"; COMBINED_MODE,
const SEPARATED_MODE = "separated"; MAIN_PANEL,
export const MAIN_PANEL = "main"; SEPARATED_MODE,
export const ADMIN_PANEL = "admin"; } from "discourse/lib/sidebar/panels";
@disableImplicitInjections @disableImplicitInjections
export default class SidebarState extends Service { export default class SidebarState extends Service {

View File

@ -28,7 +28,6 @@ import { resetUserMenuProfileTabItems } from "discourse/components/user-menu/pro
import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic"; import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic";
import { clearHTMLCache } from "discourse/helpers/custom-html"; import { clearHTMLCache } from "discourse/helpers/custom-html";
import { resetUsernameDecorators } from "discourse/helpers/decorate-username-selector"; import { resetUsernameDecorators } from "discourse/helpers/decorate-username-selector";
import { clearAdditionalAdminSidebarSectionLinks } from "discourse/instance-initializers/admin-sidebar";
import { resetBeforeAuthCompleteCallbacks } from "discourse/instance-initializers/auth-complete"; import { resetBeforeAuthCompleteCallbacks } from "discourse/instance-initializers/auth-complete";
import { clearPopupMenuOptions } from "discourse/lib/composer/custom-popup-menu-options"; import { clearPopupMenuOptions } from "discourse/lib/composer/custom-popup-menu-options";
import { clearDesktopNotificationHandlers } from "discourse/lib/desktop-notifications"; import { clearDesktopNotificationHandlers } from "discourse/lib/desktop-notifications";
@ -50,6 +49,7 @@ import PreloadStore from "discourse/lib/preload-store";
import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
import { clearTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown"; import { clearTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown";
import { clearTagsHtmlCallbacks } from "discourse/lib/render-tags"; import { clearTagsHtmlCallbacks } from "discourse/lib/render-tags";
import { clearAdditionalAdminSidebarSectionLinks } from "discourse/lib/sidebar/admin-sidebar";
import { resetDefaultSectionLinks as resetTopicsSectionLinks } from "discourse/lib/sidebar/custom-community-section-links"; import { resetDefaultSectionLinks as resetTopicsSectionLinks } from "discourse/lib/sidebar/custom-community-section-links";
import { resetSidebarPanels } from "discourse/lib/sidebar/custom-sections"; import { resetSidebarPanels } from "discourse/lib/sidebar/custom-sections";
import { import {

View File

@ -1,4 +1,4 @@
import { ADMIN_PANEL, MAIN_PANEL } from "discourse/services/sidebar-state"; import { ADMIN_PANEL, MAIN_PANEL } from "discourse/lib/sidebar/panels";
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";
export const CHAT_PANEL = "chat"; export const CHAT_PANEL = "chat";

View File

@ -2,9 +2,9 @@ import { tracked } from "@glimmer/tracking";
import Service, { inject as service } from "@ember/service"; import Service, { inject as service } from "@ember/service";
import KeyValueStore from "discourse/lib/key-value-store"; import KeyValueStore from "discourse/lib/key-value-store";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
import { MAIN_PANEL } from "discourse/lib/sidebar/panels";
import { defaultHomepage } from "discourse/lib/utilities"; import { defaultHomepage } from "discourse/lib/utilities";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
import { MAIN_PANEL } from "discourse/services/sidebar-state";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";