diff --git a/app/assets/javascripts/admin/addon/components/admin-config-area-sidebar-experiment.hbs b/app/assets/javascripts/admin/addon/components/admin-config-area-sidebar-experiment.hbs new file mode 100644 index 00000000000..45964d85fd6 --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/admin-config-area-sidebar-experiment.hbs @@ -0,0 +1,35 @@ +
+

Sidebar Experiment

+

Changes you make here will be applied to the admin sidebar and persist + between reloads + on this device only. Note that in addition to the + text + and + route + options, you can also specify a + icon + or a + href, if you want to link to a specific page but don't know the + Ember route.

+ + + + +
+ +
+
\ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/components/admin-config-area-sidebar-experiment.js b/app/assets/javascripts/admin/addon/components/admin-config-area-sidebar-experiment.js new file mode 100644 index 00000000000..ff1571c5088 --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/admin-config-area-sidebar-experiment.js @@ -0,0 +1,66 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { + buildAdminSidebar, + useAdminNavConfig, +} from "discourse/instance-initializers/admin-sidebar"; +import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map"; +import { resetPanelSections } from "discourse/lib/sidebar/custom-sections"; +import { ADMIN_PANEL } from "discourse/services/sidebar-state"; + +export default class AdminConfigAreaSidebarExperiment extends Component { + @service adminSidebarExperimentStateManager; + @service toasts; + @tracked editedNavConfig; + + get defaultAdminNav() { + return JSON.stringify(ADMIN_NAV_MAP, null, 2); + } + + @action + loadDefaultNavConfig() { + const savedConfig = this.adminSidebarExperimentStateManager.navConfig; + this.editedNavConfig = savedConfig + ? JSON.stringify(savedConfig, null, 2) + : this.defaultAdminNav; + } + + @action + resetToDefault() { + this.editedNavConfig = this.defaultAdminNav; + this.#saveConfig(ADMIN_NAV_MAP); + } + + @action + applyConfig() { + let config = null; + try { + config = JSON.parse(this.editedNavConfig); + } catch { + this.toasts.error({ + duration: 3000, + data: { + message: "There was an error, make sure the structure is valid JSON.", + }, + }); + return; + } + + this.#saveConfig(config); + } + + #saveConfig(config) { + this.adminSidebarExperimentStateManager.navConfig = config; + resetPanelSections( + ADMIN_PANEL, + useAdminNavConfig(config), + buildAdminSidebar + ); + this.toasts.success({ + duration: 3000, + data: { message: "Sidebar navigation applied successfully!" }, + }); + } +} diff --git a/app/assets/javascripts/admin/addon/routes/admin-revamp-config-area.js b/app/assets/javascripts/admin/addon/routes/admin-revamp-config-area.js index 6751bb0aa61..438c67a950b 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-revamp-config-area.js +++ b/app/assets/javascripts/admin/addon/routes/admin-revamp-config-area.js @@ -1,10 +1,19 @@ import Route from "@ember/routing/route"; import { inject as service } from "@ember/service"; +import { dasherize } from "@ember/string"; +import AdminConfigAreaSidebarExperiment from "admin/components/admin-config-area-sidebar-experiment"; + +const CONFIG_AREA_COMPONENT_MAP = { + "sidebar-experiment": AdminConfigAreaSidebarExperiment, +}; export default class AdminRevampConfigAreaRoute extends Route { @service router; async model(params) { - return { area: params.area }; + return { + area: params.area, + configAreaComponent: CONFIG_AREA_COMPONENT_MAP[dasherize(params.area)], + }; } } diff --git a/app/assets/javascripts/admin/addon/routes/admin-revamp.js b/app/assets/javascripts/admin/addon/routes/admin-revamp.js index 9f6ec2ec6a0..880d47d40e9 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-revamp.js +++ b/app/assets/javascripts/admin/addon/routes/admin-revamp.js @@ -32,8 +32,10 @@ export default class AdminRoute extends DiscourseRoute { }); } - deactivate() { + deactivate(transition) { this.controllerFor("application").set("showTop", true); - this.sidebarState.setPanel(MAIN_PANEL); + if (!transition?.to.name.startsWith("admin")) { + this.sidebarState.setPanel(MAIN_PANEL); + } } } diff --git a/app/assets/javascripts/admin/addon/routes/admin.js b/app/assets/javascripts/admin/addon/routes/admin.js index 87ccb1a888c..ded5cf48e37 100644 --- a/app/assets/javascripts/admin/addon/routes/admin.js +++ b/app/assets/javascripts/admin/addon/routes/admin.js @@ -1,7 +1,11 @@ +import { inject as service } from "@ember/service"; import DiscourseRoute from "discourse/routes/discourse"; +import { MAIN_PANEL } from "discourse/services/sidebar-state"; import I18n from "discourse-i18n"; export default class AdminRoute extends DiscourseRoute { + @service sidebarState; + titleToken() { return I18n.t("admin_title"); } @@ -14,5 +18,6 @@ export default class AdminRoute extends DiscourseRoute { deactivate() { this.controllerFor("application").set("showTop", true); + this.sidebarState.setPanel(MAIN_PANEL); } } diff --git a/app/assets/javascripts/admin/addon/services/admin-sidebar-experiment-state-manager.js b/app/assets/javascripts/admin/addon/services/admin-sidebar-experiment-state-manager.js new file mode 100644 index 00000000000..e86677435fe --- /dev/null +++ b/app/assets/javascripts/admin/addon/services/admin-sidebar-experiment-state-manager.js @@ -0,0 +1,16 @@ +import Service from "@ember/service"; +import KeyValueStore from "discourse/lib/key-value-store"; + +export default class AdminSidebarExperimentStateManager extends Service { + STORE_NAMESPACE = "discourse_admin_sidebar_experiment_"; + + store = new KeyValueStore(this.STORE_NAMESPACE); + + get navConfig() { + return this.store.getObject("navConfig"); + } + + set navConfig(value) { + this.store.setObject({ key: "navConfig", value }); + } +} diff --git a/app/assets/javascripts/admin/addon/templates/admin-revamp-config-area.hbs b/app/assets/javascripts/admin/addon/templates/admin-revamp-config-area.hbs index 13526e8a57e..71b3e7a52ca 100644 --- a/app/assets/javascripts/admin/addon/templates/admin-revamp-config-area.hbs +++ b/app/assets/javascripts/admin/addon/templates/admin-revamp-config-area.hbs @@ -1,3 +1,5 @@
- Config Area ({{@model.area}}) + {{#if @model.configAreaComponent}} + <@model.configAreaComponent /> + {{/if}}
\ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/templates/admin-revamp-config.hbs b/app/assets/javascripts/admin/addon/templates/admin-revamp-config.hbs index a70240c2105..8f90c9441ca 100644 --- a/app/assets/javascripts/admin/addon/templates/admin-revamp-config.hbs +++ b/app/assets/javascripts/admin/addon/templates/admin-revamp-config.hbs @@ -1,5 +1,3 @@
- Config - {{outlet}}
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js b/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js index d3b448050a1..97a0b99a849 100644 --- a/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js +++ b/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js @@ -1,3 +1,4 @@ +import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map"; import { addSidebarPanel, addSidebarSection, @@ -23,6 +24,10 @@ function defineAdminSectionLink(BaseCustomSidebarSectionLink) { return this.adminSidebarNavLink.route; } + get href() { + return this.adminSidebarNavLink.href; + } + get models() { return this.adminSidebarNavLink.routeModels; } @@ -90,14 +95,81 @@ function defineAdminSection( return AdminNavSection; } +export function useAdminNavConfig(navMap) { + const adminNavSections = [ + { + text: "", + name: "root", + hideSectionHeader: true, + links: [ + { + name: "Back to Forum", + route: "discovery.latest", + text: "Back to Forum", + icon: "arrow-left", + }, + { + name: "Lobby", + route: "admin-revamp.lobby", + text: "Lobby", + icon: "home", + }, + { + name: "legacy", + route: "admin", + text: "Legacy Admin", + icon: "wrench", + }, + ], + }, + ]; + + return adminNavSections.concat(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 + ); + }); +} + export default { initialize(owner) { this.currentUser = owner.lookup("service:currentUser"); + this.siteSettings = owner.lookup("service:site-settings"); if (!this.currentUser?.staff) { return; } + if ( + !this.siteSettings.userInAnyGroups( + "enable_experimental_admin_ui_groups", + this.currentUser + ) + ) { + return; + } + + this.adminSidebarExperimentStateManager = owner.lookup( + "service:admin-sidebar-experiment-state-manager" + ); + addSidebarPanel( (BaseCustomSidebarPanel) => class AdminSidebarPanel extends BaseCustomSidebarPanel { @@ -106,71 +178,8 @@ export default { } ); - let adminSectionLinkClass = null; - - // HACK: This is just an example, we need a better way of defining this data. - const adminNavSections = [ - { - text: "", - name: "root", - hideSectionHeader: true, - links: [ - { - name: "Back to Forum", - route: "discovery.latest", - text: "Back to Forum", - icon: "arrow-left", - }, - { - name: "Lobby", - route: "admin-revamp.lobby", - text: "Lobby", - icon: "home", - }, - { - name: "legacy", - route: "admin", - text: "Legacy Admin", - icon: "wrench", - }, - ], - }, - { - text: "Community", - name: "community", - links: [ - { - name: "Item 1", - route: "admin-revamp.config.area", - routeModels: [{ area: "item-1" }], - text: "Item 1", - }, - { - name: "Item 2", - route: "admin-revamp.config.area", - routeModels: [{ area: "item-2" }], - text: "Item 2", - }, - ], - }, - ]; - - adminNavSections.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 - ); - }); + const savedConfig = this.adminSidebarExperimentStateManager.navConfig; + const navConfig = useAdminNavConfig(savedConfig || ADMIN_NAV_MAP); + buildAdminSidebar(navConfig, adminSectionLinkClass); }, }; diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/admin-nav-map.js b/app/assets/javascripts/discourse/app/lib/sidebar/admin-nav-map.js new file mode 100644 index 00000000000..ffb3c696ac2 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/sidebar/admin-nav-map.js @@ -0,0 +1,245 @@ +// DO NOT EDIT THIS FILE!!! +// Update it by running `rake javascript:update_constants` + +export const ADMIN_NAV_MAP = [ + { + name: "root", + text: "Root", + links: [ + { name: "admin-revamp", route: "admin-revamp", text: "Revamp" }, + { name: "admin", route: "admin", text: "Admin" }, + ], + }, + { + name: "plugins", + text: "Plugins", + links: [ + { name: "admin_plugins", route: "adminPlugins", text: "Plugins" }, + { name: "admin_plugins_chat", route: "adminPlugins.chat", text: "Chat" }, + { + name: "admin_plugins_discourse-automation", + route: "adminPlugins.discourse-automation", + text: "Discourse Automation", + }, + { + name: "admin_plugins_discourse-automation_new", + route: "adminPlugins.discourse-automation.new", + text: "Discourse Automation New", + }, + ], + }, + { + name: "site_settings", + text: "Site Settings", + links: [ + { + name: "admin_site_settings", + route: "adminSiteSettings", + text: "Site Settings", + }, + ], + }, + { + name: "reports", + text: "Reports", + links: [{ name: "admin_reports", route: "adminReports", text: "Reports" }], + }, + { + name: "users", + text: "Users", + links: [ + { name: "admin_users_list", route: "adminUsersList", text: "List" }, + { name: "admin_users", route: "adminUsers", text: "Users" }, + ], + }, + { + name: "email", + text: "Email", + links: [ + { name: "admin_email_sent", route: "adminEmail.sent", text: "Sent" }, + { + name: "admin_email_skipped", + route: "adminEmail.skipped", + text: "Skipped", + }, + { + name: "admin_email_bounced", + route: "adminEmail.bounced", + text: "Bounced", + }, + { + name: "admin_email_received", + route: "adminEmail.received", + text: "Received", + }, + { + name: "admin_email_rejected", + route: "adminEmail.rejected", + text: "Rejected", + }, + { + name: "admin_email_preview-digest", + route: "adminEmail.previewDigest", + text: "Preview Digest", + }, + { + name: "admin_email_advanced-test", + route: "adminEmail.advancedTest", + text: "Advanced Test", + }, + { name: "admin_email", route: "adminEmail", text: "Email" }, + ], + }, + { + name: "logs", + text: "Logs", + links: [ + { + name: "admin_logs_staff_action_logs", + route: "adminLogs.staffActionLogs", + text: "Staff Action Logs", + }, + { + name: "admin_logs_screened_emails", + route: "adminLogs.screenedEmails", + text: "Screened Emails", + }, + { + name: "admin_logs_screened_ip_addresses", + route: "adminLogs.screenedIpAddresses", + text: "Screened Ip Addresses", + }, + { + name: "admin_logs_screened_urls", + route: "adminLogs.screenedUrls", + text: "Screened Urls", + }, + { + name: "admin_logs_search_logs", + route: "adminSearchLogs", + text: "Search Logs", + }, + { + name: "admin_logs_search_logs_term", + route: "adminSearchLogs.term", + text: "Search Term", + }, + { name: "admin_logs", route: "adminLogs", text: "Logs" }, + ], + }, + { + name: "customize", + text: "Customize", + links: [ + { name: "admin_customize", route: "adminCustomize", text: "Customize" }, + { + name: "admin_customize_themes", + route: "adminCustomizeThemes", + text: "Themes", + }, + { + name: "admin_customize_colors", + route: "adminCustomize.colors", + text: "Colors", + }, + { + name: "admin_customize_permalinks", + route: "adminPermalinks", + text: "Permalinks", + }, + { + name: "admin_customize_embedding", + route: "adminEmbedding", + text: "Embedding", + }, + { + name: "admin_customize_user_fields", + route: "adminUserFields", + text: "User Fields", + }, + { name: "admin_customize_emojis", route: "adminEmojis", text: "Emojis" }, + { + name: "admin_customize_form-templates", + route: "adminCustomizeFormTemplates", + text: "Form Templates", + }, + { + name: "admin_customize_form-templates_new", + route: "adminCustomizeFormTemplates.new", + text: "Form Templates New", + }, + { + name: "admin_customize_site_texts", + route: "adminSiteText", + text: "Site Texts", + }, + { + name: "admin_customize_email_templates", + route: "adminCustomizeEmailTemplates", + text: "Email Templates", + }, + { + name: "admin_customize_robots", + route: "adminCustomizeRobotsTxt", + text: "Robots", + }, + { + name: "admin_customize_email_style", + route: "adminCustomizeEmailStyle", + text: "Email Style", + }, + { + name: "admin_customize_watched_words", + route: "adminWatchedWords", + text: "Watched Words", + }, + ], + }, + { + name: "dashboard", + text: "Dashboard", + links: [ + { + name: "admin_dashboard_moderation", + route: "admin.dashboardModeration", + text: "Moderation", + }, + { + name: "admin_dashboard_security", + route: "admin.dashboardSecurity", + text: "Security", + }, + { + name: "admin_dashboard_reports", + route: "admin.dashboardReports", + text: "Reports", + }, + ], + }, + { + name: "api", + text: "Api", + links: [ + { name: "admin_api_keys", route: "adminApiKeys", text: "Keys" }, + { + name: "admin_api_web_hooks", + route: "adminWebHooks", + text: "Web Hooks", + }, + { name: "admin_api", route: "adminApi", text: "Api" }, + ], + }, + { + name: "backups", + text: "Backups", + links: [ + { name: "admin_backups_logs", route: "admin.backups.logs", text: "Logs" }, + { name: "admin_backups", route: "admin.backups", text: "Backups" }, + ], + }, + { + name: "badges", + text: "Badges", + links: [{ name: "admin_badges", route: "adminBadges", text: "Badges" }], + }, +]; diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/base-custom-sidebar-panel.js b/app/assets/javascripts/discourse/app/lib/sidebar/base-custom-sidebar-panel.js index c941a792292..b98850b5141 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/base-custom-sidebar-panel.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/base-custom-sidebar-panel.js @@ -1,8 +1,10 @@ +import { tracked } from "@glimmer/tracking"; + /** * Base class representing a sidebar section header interface. */ export default class BaseCustomSidebarPanel { - sections = []; + @tracked sections = []; /** * @returns {boolean} Controls whether the panel is hidden, which means that diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/custom-sections.js b/app/assets/javascripts/discourse/app/lib/sidebar/custom-sections.js index 2fa1025b9fc..0a3d98fe30d 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/custom-sections.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/custom-sections.js @@ -33,7 +33,7 @@ export function addSidebarPanel(func) { } export function addSidebarSection(func, panelKey) { - const panel = customPanels.find((p) => p.key === panelKey); + const panel = customPanels.findBy("key", panelKey); if (!panel) { // eslint-disable-next-line no-console return console.warn( @@ -45,6 +45,20 @@ export function addSidebarSection(func, panelKey) { ); } +export function resetPanelSections( + panelKey, + newSections = null, + sectionBuilder = null +) { + const panel = customPanels.findBy("key", panelKey); + if (newSections) { + panel.sections = []; + sectionBuilder(newSections); + } else { + panel.sections = []; + } +} + export function resetSidebarPanels() { customPanels = [new MainSidebarPanel()]; currentPanelKey = "main"; diff --git a/app/assets/stylesheets/common/admin/admin_revamp.scss b/app/assets/stylesheets/common/admin/admin_revamp.scss index f23e723854c..160a3e512da 100644 --- a/app/assets/stylesheets/common/admin/admin_revamp.scss +++ b/app/assets/stylesheets/common/admin/admin_revamp.scss @@ -1,12 +1,27 @@ .admin-revamp { - &__config { - padding: 1em; - background-color: var(--primary-low); - } - &__config-area { padding: 1em; margin: 1em 0; background-color: var(--primary-very-low); } } + +.admin-config-area-sidebar-experiment { + &__editor { + margin-top: 1em; + + .ace-wrapper { + position: relative; + width: 100%; + height: calc(100vh); + min-height: 500px; + .ace_editor { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + } + } + } +} diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index 915411d3a90..4a3559b5b3f 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -130,6 +130,135 @@ def absolute_sourcemap(dest) end end +def generate_admin_sidebar_nav_map + vague_categories = { "root" => [] } + + admin_routes = + Rails + .application + .routes + .routes + .map do |route| + next if route.verb != "GET" + path = route.path.spec.to_s.gsub("(.:format)", "") + next if !path.include?("admin") + next if path.include?("/:") || path.include?("admin-login") + path + end + .compact + + engine_routes = + Rails::Engine + .subclasses + .map do |engine| + engine + .routes + .routes + .map do |route| + next if route.verb != "GET" + path = route.path.spec.to_s.gsub("(.:format)", "") + next if !path.include?("admin") + next if path.include?("/:") || path.include?("admin-login") + path + end + .compact + end + .flatten + + admin_routes = admin_routes.concat(engine_routes) + + admin_routes.each do |path| + split_path = path.split("/") + if split_path.length >= 3 + vague_categories[split_path[2]] ||= [] + vague_categories[split_path[2]] << { path: path } + else + vague_categories["root"] << { path: path } + end + end + + # Copy this JS to your browser to get the Ember routes. + # + <<~JS + let routeMap = {} + for (const [key, value] of Object.entries( + Object.fromEntries( + Object.entries( + Discourse.__container__.lookup("service:router")._router._routerMicrolib + .recognizer.names + ).filter(([key]) => key.includes("admin")) + ) + )) { + let route = value.segments + .map((s) => s.value) + .join("/") + .replace("//", "/"); + if ( + route.includes("dummy") || + route.includes("loading") || + route.includes("_id") || + route.includes("admin-invite") + ) { + continue; + } + routeMap[key] = route; + } + console.log(JSON.stringify(routeMap)); +JS + + # Paste the output below between ROUTE_MAP. + # + ember_route_map = <<~ROUTE_MAP + {"admin.dashboard.general":"/admin/","admin.dashboard":"/admin/","admin":"/admin/","admin.dashboardModeration":"/admin/dashboard/moderation","admin.dashboardSecurity":"/admin/dashboard/security","admin.dashboardReports":"/admin/dashboard/reports","adminSiteSettings.index":"/admin/site_settings/","adminSiteSettings":"/admin/site_settings/","adminEmail.sent":"/admin/email/sent","adminEmail.skipped":"/admin/email/skipped","adminEmail.bounced":"/admin/email/bounced","adminEmail.received":"/admin/email/received","adminEmail.rejected":"/admin/email/rejected","adminEmail.previewDigest":"/admin/email/preview-digest","adminEmail.advancedTest":"/admin/email/advanced-test","adminEmail.index":"/admin/email/","adminEmail":"/admin/email/","adminCustomize.colors.index":"/admin/customize/colors/","adminCustomize.colors":"/admin/customize/colors/","adminCustomizeThemes.index":"/admin/customize/themes/","adminCustomizeThemes":"/admin/customize/themes/","adminSiteText.edit":"/admin/customize/site_texts/id","adminSiteText.index":"/admin/customize/site_texts/","adminSiteText":"/admin/customize/site_texts/","adminUserFields":"/admin/customize/user_fields","adminEmojis":"/admin/customize/emojis","adminPermalinks":"/admin/customize/permalinks","adminEmbedding":"/admin/customize/embedding","adminCustomizeEmailTemplates.edit":"/admin/customize/email_templates/id","adminCustomizeEmailTemplates.index":"/admin/customize/email_templates/","adminCustomizeEmailTemplates":"/admin/customize/email_templates/","adminCustomizeRobotsTxt":"/admin/customize/robots","adminCustomizeEmailStyle.edit":"/admin/customize/email_style/field_name","adminCustomizeEmailStyle.index":"/admin/customize/email_style/","adminCustomizeEmailStyle":"/admin/customize/email_style/","adminCustomizeFormTemplates.new":"/admin/customize/form-templates/new","adminCustomizeFormTemplates.edit":"/admin/customize/form-templates/id","adminCustomizeFormTemplates.index":"/admin/customize/form-templates/","adminCustomizeFormTemplates":"/admin/customize/form-templates/","adminWatchedWords.index":"/admin/customize/watched_words/","adminWatchedWords":"/admin/customize/watched_words/","adminCustomize.index":"/admin/customize/","adminCustomize":"/admin/customize/","adminApiKeys.new":"/admin/api/keys/new","adminApiKeys.index":"/admin/api/keys/","adminApiKeys":"/admin/api/keys/","adminWebHooks.index":"/admin/api/web_hooks/","adminWebHooks":"/admin/api/web_hooks/","adminApi.index":"/admin/api/","adminApi":"/admin/api/","admin.backups.logs":"/admin/backups/logs","admin.backups.index":"/admin/backups/","admin.backups":"/admin/backups/","adminReports.show":"/admin/reports/type","adminReports.index":"/admin/reports/","adminReports":"/admin/reports/","adminLogs.staffActionLogs":"/admin/logs/staff_action_logs","adminLogs.screenedEmails":"/admin/logs/screened_emails","adminLogs.screenedIpAddresses":"/admin/logs/screened_ip_addresses","adminLogs.screenedUrls":"/admin/logs/screened_urls","adminSearchLogs.index":"/admin/logs/search_logs/","adminSearchLogs":"/admin/logs/search_logs/","adminSearchLogs.term":"/admin/logs/search_logs/term","adminLogs.index":"/admin/logs/","adminLogs":"/admin/logs/","adminUsersList.show":"/admin/users/list/filter","adminUsersList.index":"/admin/users/list/","adminUsersList":"/admin/users/list/","adminUsers.index":"/admin/users/","adminUsers":"/admin/users/","adminBadges.index":"/admin/badges/","adminBadges":"/admin/badges/","adminPlugins.index":"/admin/plugins/","adminPlugins":"/admin/plugins/","adminPlugins.chat":"/admin/plugins/chat","adminPlugins.discourse-automation.new":"/admin/plugins/discourse-automation/new","adminPlugins.discourse-automation.edit":"/admin/plugins/discourse-automation/id","adminPlugins.discourse-automation.index":"/admin/plugins/discourse-automation/","adminPlugins.discourse-automation":"/admin/plugins/discourse-automation/","admin-revamp.lobby":"/admin-revamp/","admin-revamp":"/admin-revamp/","admin-revamp.config.area":"/admin-revamp/config/area","admin-revamp.config.index":"/admin-revamp/config/","admin-revamp.config":"/admin-revamp/config/"} + ROUTE_MAP + ember_route_map = JSON.parse(ember_route_map) + + # Match the Ember routes to the rails routes. + vague_categories.each do |category, route_data| + route_data.each do |rails_route| + ember_route_map.each do |ember_route_name, ember_path| + rails_route[:ember_route] = ember_route_name if ember_path == rails_route[:path] || + ember_path == rails_route[:path] + "/" + end + end + end + + # Remove all rails routes that don't have an Ember equivalent. + vague_categories.each do |category, route_data| + vague_categories[category] = route_data.reject { |rails_route| !rails_route.key?(:ember_route) } + end + + # Remove all categories that don't have any routes (meaning they are all rails-only). + vague_categories.each do |category, route_data| + vague_categories.delete(category) if route_data.length == 0 + end + + # Output in the format needed for sidebar sections and links. + vague_categories.map do |category, route_data| + category_text = category.titleize.gsub("Admin ", "") + { + name: category, + text: category_text, + links: + route_data.map do |rails_route| + { + name: rails_route[:path].split("/").compact_blank.join("_").chomp, + route: rails_route[:ember_route], + text: + rails_route[:path] + .split("/") + .compact_blank + .join(" ") + .chomp + .titleize + .gsub("Admin ", "") + .gsub("#{category_text} ", ""), + } + end, + } + end +end + task "javascript:update_constants" => :environment do task_name = "update_constants" @@ -163,6 +292,10 @@ task "javascript:update_constants" => :environment do export const AUTO_GROUPS = #{auto_groups.to_json}; JS + write_template("discourse/app/lib/sidebar/admin-nav-map.js", task_name, <<~JS) + export const ADMIN_NAV_MAP = #{generate_admin_sidebar_nav_map.to_json} + JS + pretty_notifications = Notification.types.map { |n| " #{n[0]}: #{n[1]}," }.join("\n") write_template("discourse/tests/fixtures/concerns/notification-types.js", task_name, <<~JS) diff --git a/spec/system/admin_revamp_sidebar_navigation_spec.rb b/spec/system/admin_revamp_sidebar_navigation_spec.rb index 08bfe633a10..3f24c8c6ab0 100644 --- a/spec/system/admin_revamp_sidebar_navigation_spec.rb +++ b/spec/system/admin_revamp_sidebar_navigation_spec.rb @@ -13,6 +13,7 @@ describe "Admin Revamp | Sidebar Naviagion", type: :system do it "navigates to the admin revamp from the sidebar" do visit("/latest") sidebar_page.click_section_link("Admin Revamp") - expect(page).to have_content("Admin Revamp Lobby") + expect(page).to have_content("Lobby") + expect(page).to have_content("Legacy Admin") end end