DEV: Add new experimental admin UI route and sidebar (#23952)
This commit adds a new admin UI under the route `/admin-revamp`, which is only accessible if the user is in a group defined by the new `enable_experimental_admin_ui_groups` site setting. It also adds a special `admin` sidebar panel that is shown instead of the `main` forum one when the admin is in this area. ![image](https://github.com/discourse/discourse/assets/920448/fa0f25e1-e178-4d94-aa5f-472fd3efd787) We also add an "Admin Revamp" sidebar link to the community section, which will only appear if the user is in the setting group: ![image](https://github.com/discourse/discourse/assets/920448/ec05ca8b-5a54-442b-ba89-6af35695c104) Within this there are subroutes defined like `/admin-revamp/config/:area`, these areas could contain any UI imaginable, this is just laying down an initial idea of the structure and how the sidebar will work. Sidebar links are currently hardcoded. Some other changes: * Changed the `main` and `chat` panels sidebar panel keys to use exported const values for reuse * Allowed custom sidebar sections to hide their headers with the `hideSectionHeader` option * Add a `groupSettingArray` setting on `this.siteSettings` in JS, which accepts a group site setting name and splits it by `|` then converts the items in the array to integers, similar to the `_map` magic for ruby group site settings * Adds a `hidden` option for sidebar panels which prevents them from showing in separated mode and prevents the switch button from being shown --------- Co-authored-by: Krzysztof Kotlarek <kotlarek.krzysztof@gmail.com>
This commit is contained in:
parent
47b2667099
commit
9ef3a18ce4
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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" });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="admin-revamp__config-area">
|
||||
Config Area ({{@model.area}})
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
<div class="admin-revamp__config">
|
||||
Config
|
||||
|
||||
{{outlet}}
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
Admin Revamp Lobby
|
|
@ -0,0 +1,12 @@
|
|||
{{hide-application-footer}}
|
||||
<AdminWrapper @class="container">
|
||||
<div class="row">
|
||||
<div class="full-width">
|
||||
<div class="boxed white admin-content">
|
||||
<div class="admin-contents {{this.adminContentsClassName}}">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminWrapper>
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
@willDestroy={{this.section.willDestroy}}
|
||||
@collapsable={{@collapsable}}
|
||||
@displaySection={{this.section.displaySection}}
|
||||
@hideSectionHeader={{this.section.hideSectionHeader}}
|
||||
>
|
||||
|
||||
{{#each this.section.links as |link|}}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -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.
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue