DEV: Automatically generate all admin links for app for new sidebar (#24175)
NOTE: Most of this is experimental and will be removed at a later time, which is why things like translations have not been added. The new /admin-revamp UI uses a sidebar for admin nav. This initial step adds a script to generate a map of all the current admin nav into a format the sidebar to read. Then, people can experiment with different changes to this structure. The structure can then be edited from `/admin-revamp/config/sidebar-experiment`, and it is saved to local storage so people can visually experiment with different ways of showing the admin sidebar links.
This commit is contained in:
parent
1c395e1a01
commit
b53449eac9
|
@ -0,0 +1,35 @@
|
|||
<div
|
||||
class="admin-config-area-sidebar-experiment"
|
||||
{{did-insert this.loadDefaultNavConfig}}
|
||||
>
|
||||
<h4>Sidebar Experiment</h4>
|
||||
<p>Changes you make here will be applied to the admin sidebar and persist
|
||||
between reloads
|
||||
<em>on this device only</em>. Note that in addition to the
|
||||
<code>text</code>
|
||||
and
|
||||
<code>route</code>
|
||||
options, you can also specify a
|
||||
<code>icon</code>
|
||||
or a
|
||||
<code>href</code>, if you want to link to a specific page but don't know the
|
||||
Ember route.</p>
|
||||
|
||||
<DButton
|
||||
@action={{this.resetToDefault}}
|
||||
@translatedLabel="Reset to Default"
|
||||
/>
|
||||
<DButton
|
||||
class="btn-primary"
|
||||
@action={{this.applyConfig}}
|
||||
@translatedLabel="Apply Config"
|
||||
/>
|
||||
|
||||
<div class="admin-config-area-sidebar-experiment__editor">
|
||||
<AceEditor
|
||||
@content={{this.editedNavConfig}}
|
||||
@editorId="admin-config-area-sidebar-experiment"
|
||||
@save={{this.applyNav}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -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!" },
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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)],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
<div class="admin-revamp__config-area">
|
||||
Config Area ({{@model.area}})
|
||||
{{#if @model.configAreaComponent}}
|
||||
<@model.configAreaComponent />
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,5 +1,3 @@
|
|||
<div class="admin-revamp__config">
|
||||
Config
|
||||
|
||||
{{outlet}}
|
||||
</div>
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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" }],
|
||||
},
|
||||
];
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue