diff --git a/app/assets/javascripts/admin/addon/controllers/admin-revamp.js b/app/assets/javascripts/admin/addon/controllers/admin-revamp.js new file mode 100644 index 00000000000..d7ba6a28f06 --- /dev/null +++ b/app/assets/javascripts/admin/addon/controllers/admin-revamp.js @@ -0,0 +1,31 @@ +import Controller from "@ember/controller"; +import { inject as service } from "@ember/service"; +import { dasherize } from "@ember/string"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default class AdminRevampController extends Controller { + @service router; + + @discourseComputed("router._router.currentPath") + adminContentsClassName(currentPath) { + let cssClasses = currentPath + .split(".") + .filter((segment) => { + return ( + segment !== "index" && + segment !== "loading" && + segment !== "show" && + segment !== "admin" + ); + }) + .map(dasherize) + .join(" "); + + // this is done to avoid breaking css customizations + if (cssClasses.includes("dashboard")) { + cssClasses = `${cssClasses} dashboard-next`; + } + + return cssClasses; + } +} 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 new file mode 100644 index 00000000000..6751bb0aa61 --- /dev/null +++ b/app/assets/javascripts/admin/addon/routes/admin-revamp-config-area.js @@ -0,0 +1,10 @@ +import Route from "@ember/routing/route"; +import { inject as service } from "@ember/service"; + +export default class AdminRevampConfigAreaRoute extends Route { + @service router; + + async model(params) { + return { area: params.area }; + } +} diff --git a/app/assets/javascripts/admin/addon/routes/admin-revamp-config.js b/app/assets/javascripts/admin/addon/routes/admin-revamp-config.js new file mode 100644 index 00000000000..ee0e05f7b0c --- /dev/null +++ b/app/assets/javascripts/admin/addon/routes/admin-revamp-config.js @@ -0,0 +1,6 @@ +import Route from "@ember/routing/route"; +import { inject as service } from "@ember/service"; + +export default class AdminRevampConfigRoute extends Route { + @service router; +} diff --git a/app/assets/javascripts/admin/addon/routes/admin-revamp-lobby.js b/app/assets/javascripts/admin/addon/routes/admin-revamp-lobby.js new file mode 100644 index 00000000000..b4fbfadb9ac --- /dev/null +++ b/app/assets/javascripts/admin/addon/routes/admin-revamp-lobby.js @@ -0,0 +1,6 @@ +import Route from "@ember/routing/route"; +import { inject as service } from "@ember/service"; + +export default class AdminRevampLobbyRoute extends Route { + @service router; +} diff --git a/app/assets/javascripts/admin/addon/routes/admin-revamp.js b/app/assets/javascripts/admin/addon/routes/admin-revamp.js new file mode 100644 index 00000000000..a21b1676155 --- /dev/null +++ b/app/assets/javascripts/admin/addon/routes/admin-revamp.js @@ -0,0 +1,40 @@ +import { inject as service } from "@ember/service"; +import DiscourseURL from "discourse/lib/url"; +import DiscourseRoute from "discourse/routes/discourse"; +import { ADMIN_PANEL, MAIN_PANEL } from "discourse/services/sidebar-state"; +import I18n from "discourse-i18n"; + +export default class AdminRoute extends DiscourseRoute { + @service siteSettings; + @service currentUser; + @service sidebarState; + + titleToken() { + return I18n.t("admin_title"); + } + + activate() { + if ( + !this.currentUser.isInAnyGroups( + this.siteSettings.groupSettingArray( + "enable_experimental_admin_ui_groups" + ) + ) + ) { + return DiscourseURL.redirectTo("/admin"); + } + + this.sidebarState.setPanel(ADMIN_PANEL); + this.sidebarState.setSeparatedMode(); + this.sidebarState.hideSwitchPanelButtons(); + + this.controllerFor("application").setProperties({ + showTop: false, + }); + } + + deactivate() { + this.controllerFor("application").set("showTop", true); + this.sidebarState.setPanel(MAIN_PANEL); + } +} diff --git a/app/assets/javascripts/admin/addon/routes/admin-route-map.js b/app/assets/javascripts/admin/addon/routes/admin-route-map.js index ef5d507922d..c180f64dab8 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-route-map.js +++ b/app/assets/javascripts/admin/addon/routes/admin-route-map.js @@ -211,4 +211,14 @@ export default function () { } ); }); + + // EXPERIMENTAL: These admin routes are hidden behind an `enable_experimental_admin_ui_groups` + // site setting and are subject to constant change. + this.route("admin-revamp", { resetNamespace: true }, function () { + this.route("lobby", { path: "/" }, function () {}); + + this.route("config", { path: "config" }, function () { + this.route("area", { path: "/:area" }); + }); + }); } 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 new file mode 100644 index 00000000000..13526e8a57e --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/admin-revamp-config-area.hbs @@ -0,0 +1,3 @@ +
+ Config Area ({{@model.area}}) +
\ 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 new file mode 100644 index 00000000000..a70240c2105 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/admin-revamp-config.hbs @@ -0,0 +1,5 @@ +
+ Config + + {{outlet}} +
\ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/templates/admin-revamp-lobby.hbs b/app/assets/javascripts/admin/addon/templates/admin-revamp-lobby.hbs new file mode 100644 index 00000000000..57d2c28438f --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/admin-revamp-lobby.hbs @@ -0,0 +1 @@ +Admin Revamp Lobby \ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/templates/admin-revamp.hbs b/app/assets/javascripts/admin/addon/templates/admin-revamp.hbs new file mode 100644 index 00000000000..9e53b938259 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/admin-revamp.hbs @@ -0,0 +1,12 @@ +{{hide-application-footer}} + +
+
+
+
+ {{outlet}} +
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/sidebar.js b/app/assets/javascripts/discourse/app/components/sidebar.js index 94884b7c6ad..7043231b821 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar.js +++ b/app/assets/javascripts/discourse/app/components/sidebar.js @@ -31,7 +31,7 @@ export default class Sidebar extends Component { } return this.sidebarState.panels.filter( - (panel) => panel !== this.sidebarState.currentPanel + (panel) => panel !== this.sidebarState.currentPanel && !panel.hidden ); } diff --git a/app/assets/javascripts/discourse/app/components/sidebar/api-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/api-section.hbs index acb73faa18a..c48f0c4093a 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/api-section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/api-section.hbs @@ -7,6 +7,7 @@ @willDestroy={{this.section.willDestroy}} @collapsable={{@collapsable}} @displaySection={{this.section.displaySection}} + @hideSectionHeader={{this.section.hideSectionHeader}} > {{#each this.section.links as |link|}} diff --git a/app/assets/javascripts/discourse/app/components/sidebar/api-sections.js b/app/assets/javascripts/discourse/app/components/sidebar/api-sections.js index e9ef60b5023..28e5c7b50eb 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/api-sections.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/api-sections.js @@ -6,7 +6,10 @@ export default class SidebarApiSections extends Component { get sections() { if (this.sidebarState.combinedMode) { - return this.sidebarState.panels.map((panel) => panel.sections).flat(); + return this.sidebarState.panels + .filter((panel) => !panel.hidden) + .map((panel) => panel.sections) + .flat(); } else { return this.sidebarState.currentPanel.sections; } diff --git a/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js b/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js new file mode 100644 index 00000000000..d3b448050a1 --- /dev/null +++ b/app/assets/javascripts/discourse/app/instance-initializers/admin-sidebar.js @@ -0,0 +1,176 @@ +import { + addSidebarPanel, + addSidebarSection, +} from "discourse/lib/sidebar/custom-sections"; +import { ADMIN_PANEL } from "discourse/services/sidebar-state"; + +function defineAdminSectionLink(BaseCustomSidebarSectionLink) { + const SidebarAdminSectionLink = class extends BaseCustomSidebarSectionLink { + constructor({ adminSidebarNavLink }) { + super(...arguments); + this.adminSidebarNavLink = adminSidebarNavLink; + } + + get name() { + return this.adminSidebarNavLink.name; + } + + get classNames() { + return "admin-sidebar-nav-link"; + } + + get route() { + return this.adminSidebarNavLink.route; + } + + get models() { + return this.adminSidebarNavLink.routeModels; + } + + get text() { + return this.adminSidebarNavLink.text; + } + + get prefixType() { + return "icon"; + } + + get prefixValue() { + return this.adminSidebarNavLink.icon; + } + + get title() { + return this.adminSidebarNavLink.text; + } + }; + + return SidebarAdminSectionLink; +} + +function defineAdminSection( + adminNavSectionData, + BaseCustomSidebarSection, + adminSectionLinkClass +) { + const AdminNavSection = class extends BaseCustomSidebarSection { + constructor() { + super(...arguments); + this.adminNavSectionData = adminNavSectionData; + this.hideSectionHeader = adminNavSectionData.hideSectionHeader; + } + + get sectionLinks() { + return this.adminNavSectionData.links; + } + + get name() { + return `admin-nav-section-${this.adminNavSectionData.name}`; + } + + get title() { + return this.adminNavSectionData.text; + } + + get text() { + return this.adminNavSectionData.text; + } + + get links() { + return this.sectionLinks.map( + (sectionLinkData) => + new adminSectionLinkClass({ adminSidebarNavLink: sectionLinkData }) + ); + } + + get displaySection() { + return true; + } + }; + + return AdminNavSection; +} + +export default { + initialize(owner) { + this.currentUser = owner.lookup("service:currentUser"); + + if (!this.currentUser?.staff) { + return; + } + + addSidebarPanel( + (BaseCustomSidebarPanel) => + class AdminSidebarPanel extends BaseCustomSidebarPanel { + key = ADMIN_PANEL; + hidden = true; + } + ); + + 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 + ); + }); + }, +}; diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 27fb8847508..9a348b2ffa7 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -136,7 +136,7 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api"; // docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version // using the format described at https://keepachangelog.com/en/1.0.0/. -export const PLUGIN_API_VERSION = "1.14.0"; +export const PLUGIN_API_VERSION = "1.15.0"; // This helper prevents us from applying the same `modifyClass` over and over in test mode. function canModify(klass, type, resolverName, changes) { @@ -2207,6 +2207,14 @@ class PluginApi { this._lookupContainer("service:sidebar-state")?.setPanel(name); } + /** + * EXPERIMENTAL. Do not use. + * Support for getting the current Sidebar panel. + */ + getSidebarPanel() { + return this._lookupContainer("service:sidebar-state")?.currentPanel; + } + /** * EXPERIMENTAL. Do not use. * Set combined sidebar section mode. In this mode, sections from all panels are displayed together. 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 f9a8ceecbbe..c941a792292 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 @@ -4,6 +4,15 @@ export default class BaseCustomSidebarPanel { sections = []; + /** + * @returns {boolean} Controls whether the panel is hidden, which means that + * it will not show up in combined sidebar mode, and its switch button will + * never show either. + */ + get hidden() { + return false; + } + /** * @returns {string} Identifier for sidebar panel */ @@ -12,24 +21,24 @@ export default class BaseCustomSidebarPanel { } /** - * @returns {string} Text for the switch button + * @returns {string} Text for the switch button. Obsolete when panel is hidden. */ get switchButtonLabel() { - this.#notImplemented(); + this.hidden || this.#notImplemented(); } /** - * @returns {string} Icon for the switch button + * @returns {string} Icon for the switch button. Obsolete when panel is hidden. */ get switchButtonIcon() { - this.#notImplemented(); + this.hidden || this.#notImplemented(); } /** - * @returns {string} Default path to panel + * @returns {string} Default path to panel. Obsolete when panel is hidden. */ get switchButtonDefaultUrl() { - this.#notImplemented(); + this.hidden || this.#notImplemented(); } #notImplemented() { diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/section.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/section.js index ad81a2bbeef..4b1e5067afd 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/section.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/section.js @@ -12,6 +12,7 @@ import { secondaryCustomSectionLinks, } from "discourse/lib/sidebar/custom-community-section-links"; import SectionLink from "discourse/lib/sidebar/section-link"; +import AdminRevampSectionLink from "discourse/lib/sidebar/user/community-section/admin-revamp-section-link"; import AdminSectionLink from "discourse/lib/sidebar/user/community-section/admin-section-link"; import MyPostsSectionLink from "discourse/lib/sidebar/user/community-section/my-posts-section-link"; import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link"; @@ -25,6 +26,7 @@ const SPECIAL_LINKS_MAP = { "/review": ReviewSectionLink, "/badges": BadgesSectionLink, "/admin": AdminSectionLink, + "/admin-revamp": AdminRevampSectionLink, "/g": GroupsSectionLink, }; diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-revamp-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-revamp-section-link.js new file mode 100644 index 00000000000..c38cc9b4f16 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/community-section/admin-revamp-section-link.js @@ -0,0 +1,45 @@ +import { inject as service } from "@ember/service"; +import BaseSectionLink from "discourse/lib/sidebar/base-community-section-link"; +import I18n from "discourse-i18n"; + +export default class AdminRevampSectionLink extends BaseSectionLink { + @service siteSettings; + + get name() { + return "admin-revamp"; + } + + get route() { + return "admin-revamp"; + } + + get title() { + return I18n.t("sidebar.sections.community.links.admin.content"); + } + + get text() { + return I18n.t( + `sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`, + { defaultValue: this.overridenName } + ); + } + + get shouldDisplay() { + if (!this.currentUser) { + return false; + } + + return ( + this.currentUser.staff && + this.currentUser.isInAnyGroups( + this.siteSettings.groupSettingArray( + "enable_experimental_admin_ui_groups" + ) + ) + ); + } + + get defaultPrefixValue() { + return "star"; + } +} diff --git a/app/assets/javascripts/discourse/app/services/sidebar-state.js b/app/assets/javascripts/discourse/app/services/sidebar-state.js index 4ca6f11c074..6e12bad088e 100644 --- a/app/assets/javascripts/discourse/app/services/sidebar-state.js +++ b/app/assets/javascripts/discourse/app/services/sidebar-state.js @@ -8,7 +8,8 @@ import { const COMBINED_MODE = "combined"; const SEPARATED_MODE = "separated"; -const MAIN_PANEL = "main"; +export const MAIN_PANEL = "main"; +export const ADMIN_PANEL = "admin"; @disableImplicitInjections export default class SidebarState extends Service { diff --git a/app/assets/javascripts/discourse/app/services/site-settings.js b/app/assets/javascripts/discourse/app/services/site-settings.js index 733e530a592..85778e3661a 100644 --- a/app/assets/javascripts/discourse/app/services/site-settings.js +++ b/app/assets/javascripts/discourse/app/services/site-settings.js @@ -7,6 +7,21 @@ export default class SiteSettingsService { static isServiceFactory = true; static create() { - return new TrackedObject(PreloadStore.get("siteSettings")); + const settings = new TrackedObject(PreloadStore.get("siteSettings")); + + settings.groupSettingArray = (groupSetting) => { + const setting = settings[groupSetting]; + if (!setting) { + return []; + } + + return setting + .toString() + .split("|") + .filter(Boolean) + .map((groupId) => parseInt(groupId, 10)); + }; + + return settings; } } diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js index 2384ea75122..4a34b24f3c3 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js @@ -1084,4 +1084,118 @@ acceptance("Sidebar - Plugin API", function (needs) { await visit("/"); assert.dom(".sidebar__panel-switch-button").exists(); }); + + test("New hidden custom sidebar panel", async function (assert) { + withPluginApi(PLUGIN_API_VERSION, (api) => { + api.addSidebarPanel((BaseCustomSidebarPanel) => { + const AdminSidebarPanel = class extends BaseCustomSidebarPanel { + get key() { + return "admin-panel"; + } + + get hidden() { + return true; + } + }; + return AdminSidebarPanel; + }); + api.addSidebarSection( + (BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => { + return class extends BaseCustomSidebarSection { + get name() { + return "test-admin-section"; + } + + get text() { + return "test admin section"; + } + + get actionsIcon() { + return "cog"; + } + + get links() { + return [ + new (class extends BaseCustomSidebarSectionLink { + get name() { + return "admin-link"; + } + + get classNames() { + return "my-class-name"; + } + + get route() { + return "topic"; + } + + get models() { + return ["some-slug", 1]; + } + + get title() { + return "admin link"; + } + + get text() { + return "admin link"; + } + + get prefixType() { + return "icon"; + } + + get prefixValue() { + return "cog"; + } + + get prefixColor() { + return "FF0000"; + } + + get prefixBadge() { + return "lock"; + } + + get suffixType() { + return "icon"; + } + + get suffixValue() { + return "circle"; + } + + get suffixCSSClass() { + return "unread"; + } + })(), + ]; + } + }; + }, + "admin-panel" + ); + api.setSidebarPanel("admin-panel"); + api.setSeparatedSidebarMode(); + }); + + await visit("/"); + + assert.strictEqual( + query( + ".sidebar-section[data-section-name='test-admin-section'] .sidebar-section-header-text" + ).textContent.trim(), + "test admin section", + "displays header with correct text" + ); + withPluginApi(PLUGIN_API_VERSION, (api) => { + api.setSidebarPanel("main-panel"); + api.setCombinedSidebarMode(); + }); + await visit("/"); + assert.dom(".sidebar__panel-switch-button").doesNotExist(); + assert + .dom(".sidebar-section[data-section-name='test-admin-section']") + .doesNotExist(); + }); }); diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 5d566df986c..2cabf8157ed 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -1052,3 +1052,6 @@ a.inline-editable-field { @import "common/admin/admin_intro"; @import "common/admin/admin_emojis"; @import "common/admin/mini_profiler"; + +// EXPERIMENTAL: Revamped admin styles, probably can be split up later down the line. +@import "common/admin/admin_revamp"; diff --git a/app/assets/stylesheets/common/admin/admin_revamp.scss b/app/assets/stylesheets/common/admin/admin_revamp.scss new file mode 100644 index 00000000000..f23e723854c --- /dev/null +++ b/app/assets/stylesheets/common/admin/admin_revamp.scss @@ -0,0 +1,12 @@ +.admin-revamp { + &__config { + padding: 1em; + background-color: var(--primary-low); + } + + &__config-area { + padding: 1em; + margin: 1em 0; + background-color: var(--primary-very-low); + } +} diff --git a/app/models/sidebar_url.rb b/app/models/sidebar_url.rb index 6e21a3e76c5..76a498f6c50 100644 --- a/app/models/sidebar_url.rb +++ b/app/models/sidebar_url.rb @@ -22,6 +22,12 @@ class SidebarUrl < ActiveRecord::Base }, { name: "Review", path: "/review", icon: "flag", segment: SidebarUrl.segments["primary"] }, { name: "Admin", path: "/admin", icon: "wrench", segment: SidebarUrl.segments["primary"] }, + { + name: "Admin Revamp", + path: "/admin-revamp", + icon: "star", + segment: SidebarUrl.segments["primary"], + }, { name: "Users", path: "/u", icon: "users", segment: SidebarUrl.segments["secondary"] }, { name: "About", diff --git a/config/routes.rb b/config/routes.rb index c5055da4e67..1d4ffc9f891 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -100,6 +100,14 @@ Discourse::Application.routes.draw do get "wizard/steps/:id" => "wizard#index" put "wizard/steps/:id" => "steps#update" + namespace :admin_revamp, + path: "admin-revamp", + module: "admin", + constraints: StaffConstraint.new do + get "" => "admin#index" + get "config/:area" => "admin#index" + end + namespace :admin, constraints: StaffConstraint.new do get "" => "admin#index" diff --git a/config/site_settings.yml b/config/site_settings.yml index 66036e5f9f3..c0e91b0c4d4 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -2178,6 +2178,14 @@ developer: instrument_gc_stat_per_request: default: false hidden: true + enable_experimental_admin_ui_groups: + type: group_list + list_type: compact + default: "" + allow_any: false + refresh: true + hidden: true + client: true lazy_load_categories: default: false client: true diff --git a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md index 43e7c475d86..a6aab86a274 100644 --- a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md +++ b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md @@ -7,6 +7,13 @@ in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.15.0] - 2023-10-18 + +### Added + +- Added `hidden` option to `addSidebarPanel`, this can be used to remove the panel from combined sidebar mode as well as hiding its switch button. Useful for cases where only one sidebar should be shown at a time regardless of other panels. +- Added `getSidebarPanel` function, which returns the current sidebar panel object for comparison. + ## [1.14.0] - 2023-10-06 ### Added diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js index f51308bcdf1..abb06ed438e 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js @@ -11,7 +11,10 @@ import getURL from "discourse-common/lib/get-url"; import { bind } from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message"; -import { initSidebarState } from "discourse/plugins/chat/discourse/lib/init-sidebar-state"; +import { + CHAT_PANEL, + initSidebarState, +} from "discourse/plugins/chat/discourse/lib/init-sidebar-state"; export default { name: "chat-sidebar", @@ -28,7 +31,7 @@ export default { api.addSidebarPanel( (BaseCustomSidebarPanel) => class ChatSidebarPanel extends BaseCustomSidebarPanel { - key = "chat"; + key = CHAT_PANEL; switchButtonLabel = I18n.t("sidebar.panels.chat.label"); switchButtonIcon = "d-chat"; switchButtonDefaultUrl = getURL("/chat"); @@ -196,7 +199,7 @@ export default { return SidebarChatChannelsSection; }, - "chat" + CHAT_PANEL ); } diff --git a/plugins/chat/assets/javascripts/discourse/lib/init-sidebar-state.js b/plugins/chat/assets/javascripts/discourse/lib/init-sidebar-state.js index 4706366fc84..68ff26cf142 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/init-sidebar-state.js +++ b/plugins/chat/assets/javascripts/discourse/lib/init-sidebar-state.js @@ -1,7 +1,14 @@ +import { ADMIN_PANEL, MAIN_PANEL } from "discourse/services/sidebar-state"; import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; +export const CHAT_PANEL = "chat"; + export function initSidebarState(api, user) { - api.setSidebarPanel("main"); + if (api.getSidebarPanel()?.key === ADMIN_PANEL) { + return; + } + + api.setSidebarPanel(MAIN_PANEL); const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(user); if (chatSeparateSidebarMode.fullscreen) { diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat.js b/plugins/chat/assets/javascripts/discourse/routes/chat.js index 731dc7ef3e8..e4ffe65c6c8 100644 --- a/plugins/chat/assets/javascripts/discourse/routes/chat.js +++ b/plugins/chat/assets/javascripts/discourse/routes/chat.js @@ -6,7 +6,10 @@ import { scrollTop } from "discourse/mixins/scroll-top"; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "discourse-i18n"; import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; -import { initSidebarState } from "discourse/plugins/chat/discourse/lib/init-sidebar-state"; +import { + CHAT_PANEL, + initSidebarState, +} from "discourse/plugins/chat/discourse/lib/init-sidebar-state"; export default class ChatRoute extends DiscourseRoute { @service chat; @@ -62,7 +65,7 @@ export default class ChatRoute extends DiscourseRoute { activate() { withPluginApi("1.8.0", (api) => { - api.setSidebarPanel("chat"); + api.setSidebarPanel(CHAT_PANEL); const chatSeparateSidebarMode = getUserChatSeparateSidebarMode( this.currentUser diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js index 86a7c6c60b2..54ba27f5686 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-state-manager.js @@ -4,6 +4,7 @@ import KeyValueStore from "discourse/lib/key-value-store"; import { withPluginApi } from "discourse/lib/plugin-api"; import { defaultHomepage } from "discourse/lib/utilities"; import Site from "discourse/models/site"; +import { MAIN_PANEL } from "discourse/services/sidebar-state"; import getURL from "discourse-common/lib/get-url"; import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode"; @@ -60,7 +61,7 @@ export default class ChatStateManager extends Service { didOpenDrawer(url = null) { withPluginApi("1.8.0", (api) => { if (getUserChatSeparateSidebarMode(this.currentUser).always) { - api.setSidebarPanel("main"); + api.setSidebarPanel(MAIN_PANEL); api.setSeparatedSidebarMode(); api.hideSidebarSwitchPanelButtons(); } else { @@ -81,7 +82,7 @@ export default class ChatStateManager extends Service { didCloseDrawer() { withPluginApi("1.8.0", (api) => { - api.setSidebarPanel("main"); + api.setSidebarPanel(MAIN_PANEL); const chatSeparateSidebarMode = getUserChatSeparateSidebarMode( this.currentUser diff --git a/plugins/chat/assets/javascripts/discourse/services/chat.js b/plugins/chat/assets/javascripts/discourse/services/chat.js index 6bbd3e7593d..4e523e10be0 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat.js @@ -68,9 +68,7 @@ export default class Chat extends Service { return ( this.currentUser.staff || this.currentUser.isInAnyGroups( - (this.siteSettings.direct_message_enabled_groups || "11") // trust level 1 auto group - .split("|") - .map((groupId) => parseInt(groupId, 10)) + this.siteSettings.groupSettingArray("direct_message_enabled_groups") ) ); } diff --git a/spec/models/sidebar_section_spec.rb b/spec/models/sidebar_section_spec.rb index 557e2a3d262..d9960da4336 100644 --- a/spec/models/sidebar_section_spec.rb +++ b/spec/models/sidebar_section_spec.rb @@ -22,7 +22,18 @@ RSpec.describe SidebarSection do expect(community_section.reload.title).to eq("Community") expect(community_section.sidebar_section_links.all.map { |link| link.linkable.name }).to eq( - ["Topics", "My Posts", "Review", "Admin", "Users", "About", "FAQ", "Groups", "Badges"], + [ + "Topics", + "My Posts", + "Review", + "Admin", + "Admin Revamp", + "Users", + "About", + "FAQ", + "Groups", + "Badges", + ], ) end end diff --git a/spec/system/admin_revamp_sidebar_navigation_spec.rb b/spec/system/admin_revamp_sidebar_navigation_spec.rb new file mode 100644 index 00000000000..08bfe633a10 --- /dev/null +++ b/spec/system/admin_revamp_sidebar_navigation_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +describe "Admin Revamp | Sidebar Naviagion", type: :system do + fab!(:admin) { Fabricate(:admin) } + let(:sidebar_page) { PageObjects::Components::NavigationMenu::Sidebar.new } + + before do + SiteSetting.enable_experimental_admin_ui_groups = Group::AUTO_GROUPS[:staff] + SidebarSection.find_by(section_type: "community").reset_community! + sign_in(admin) + end + + 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") + end +end