mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 19:03:45 +08:00
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;
|
||||||
|
}
|
40
app/assets/javascripts/admin/addon/routes/admin-revamp.js
Normal file
40
app/assets/javascripts/admin/addon/routes/admin-revamp.js
Normal file
|
@ -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(
|
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}}
|
@willDestroy={{this.section.willDestroy}}
|
||||||
@collapsable={{@collapsable}}
|
@collapsable={{@collapsable}}
|
||||||
@displaySection={{this.section.displaySection}}
|
@displaySection={{this.section.displaySection}}
|
||||||
|
@hideSectionHeader={{this.section.hideSectionHeader}}
|
||||||
>
|
>
|
||||||
|
|
||||||
{{#each this.section.links as |link|}}
|
{{#each this.section.links as |link|}}
|
||||||
|
|
|
@ -6,7 +6,10 @@ export default class SidebarApiSections extends Component {
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
if (this.sidebarState.combinedMode) {
|
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 {
|
} else {
|
||||||
return this.sidebarState.currentPanel.sections;
|
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
|
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||||
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
// 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.
|
// This helper prevents us from applying the same `modifyClass` over and over in test mode.
|
||||||
function canModify(klass, type, resolverName, changes) {
|
function canModify(klass, type, resolverName, changes) {
|
||||||
|
@ -2207,6 +2207,14 @@ class PluginApi {
|
||||||
this._lookupContainer("service:sidebar-state")?.setPanel(name);
|
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.
|
* EXPERIMENTAL. Do not use.
|
||||||
* Set combined sidebar section mode. In this mode, sections from all panels are displayed together.
|
* Set combined sidebar section mode. In this mode, sections from all panels are displayed together.
|
||||||
|
|
|
@ -4,6 +4,15 @@
|
||||||
export default class BaseCustomSidebarPanel {
|
export default class BaseCustomSidebarPanel {
|
||||||
sections = [];
|
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
|
* @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() {
|
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() {
|
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() {
|
get switchButtonDefaultUrl() {
|
||||||
this.#notImplemented();
|
this.hidden || this.#notImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
#notImplemented() {
|
#notImplemented() {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
secondaryCustomSectionLinks,
|
secondaryCustomSectionLinks,
|
||||||
} from "discourse/lib/sidebar/custom-community-section-links";
|
} from "discourse/lib/sidebar/custom-community-section-links";
|
||||||
import SectionLink from "discourse/lib/sidebar/section-link";
|
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 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 MyPostsSectionLink from "discourse/lib/sidebar/user/community-section/my-posts-section-link";
|
||||||
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
|
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
|
||||||
|
@ -25,6 +26,7 @@ const SPECIAL_LINKS_MAP = {
|
||||||
"/review": ReviewSectionLink,
|
"/review": ReviewSectionLink,
|
||||||
"/badges": BadgesSectionLink,
|
"/badges": BadgesSectionLink,
|
||||||
"/admin": AdminSectionLink,
|
"/admin": AdminSectionLink,
|
||||||
|
"/admin-revamp": AdminRevampSectionLink,
|
||||||
"/g": GroupsSectionLink,
|
"/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 COMBINED_MODE = "combined";
|
||||||
const SEPARATED_MODE = "separated";
|
const SEPARATED_MODE = "separated";
|
||||||
const MAIN_PANEL = "main";
|
export const MAIN_PANEL = "main";
|
||||||
|
export const ADMIN_PANEL = "admin";
|
||||||
|
|
||||||
@disableImplicitInjections
|
@disableImplicitInjections
|
||||||
export default class SidebarState extends Service {
|
export default class SidebarState extends Service {
|
||||||
|
|
|
@ -7,6 +7,21 @@ export default class SiteSettingsService {
|
||||||
static isServiceFactory = true;
|
static isServiceFactory = true;
|
||||||
|
|
||||||
static create() {
|
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("/");
|
await visit("/");
|
||||||
assert.dom(".sidebar__panel-switch-button").exists();
|
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_intro";
|
||||||
@import "common/admin/admin_emojis";
|
@import "common/admin/admin_emojis";
|
||||||
@import "common/admin/mini_profiler";
|
@import "common/admin/mini_profiler";
|
||||||
|
|
||||||
|
// EXPERIMENTAL: Revamped admin styles, probably can be split up later down the line.
|
||||||
|
@import "common/admin/admin_revamp";
|
||||||
|
|
12
app/assets/stylesheets/common/admin/admin_revamp.scss
Normal file
12
app/assets/stylesheets/common/admin/admin_revamp.scss
Normal file
|
@ -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: "Review", path: "/review", icon: "flag", segment: SidebarUrl.segments["primary"] },
|
||||||
{ name: "Admin", path: "/admin", icon: "wrench", 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: "Users", path: "/u", icon: "users", segment: SidebarUrl.segments["secondary"] },
|
||||||
{
|
{
|
||||||
name: "About",
|
name: "About",
|
||||||
|
|
|
@ -100,6 +100,14 @@ Discourse::Application.routes.draw do
|
||||||
get "wizard/steps/:id" => "wizard#index"
|
get "wizard/steps/:id" => "wizard#index"
|
||||||
put "wizard/steps/:id" => "steps#update"
|
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
|
namespace :admin, constraints: StaffConstraint.new do
|
||||||
get "" => "admin#index"
|
get "" => "admin#index"
|
||||||
|
|
||||||
|
|
|
@ -2178,6 +2178,14 @@ developer:
|
||||||
instrument_gc_stat_per_request:
|
instrument_gc_stat_per_request:
|
||||||
default: false
|
default: false
|
||||||
hidden: true
|
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:
|
lazy_load_categories:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
|
|
|
@ -7,6 +7,13 @@ in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
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).
|
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
|
## [1.14.0] - 2023-10-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -11,7 +11,10 @@ import getURL from "discourse-common/lib/get-url";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import ChatModalNewMessage from "discourse/plugins/chat/discourse/components/chat/modal/new-message";
|
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 {
|
export default {
|
||||||
name: "chat-sidebar",
|
name: "chat-sidebar",
|
||||||
|
@ -28,7 +31,7 @@ export default {
|
||||||
api.addSidebarPanel(
|
api.addSidebarPanel(
|
||||||
(BaseCustomSidebarPanel) =>
|
(BaseCustomSidebarPanel) =>
|
||||||
class ChatSidebarPanel extends BaseCustomSidebarPanel {
|
class ChatSidebarPanel extends BaseCustomSidebarPanel {
|
||||||
key = "chat";
|
key = CHAT_PANEL;
|
||||||
switchButtonLabel = I18n.t("sidebar.panels.chat.label");
|
switchButtonLabel = I18n.t("sidebar.panels.chat.label");
|
||||||
switchButtonIcon = "d-chat";
|
switchButtonIcon = "d-chat";
|
||||||
switchButtonDefaultUrl = getURL("/chat");
|
switchButtonDefaultUrl = getURL("/chat");
|
||||||
|
@ -196,7 +199,7 @@ export default {
|
||||||
|
|
||||||
return SidebarChatChannelsSection;
|
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";
|
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";
|
||||||
|
|
||||||
|
export const CHAT_PANEL = "chat";
|
||||||
|
|
||||||
export function initSidebarState(api, user) {
|
export function initSidebarState(api, user) {
|
||||||
api.setSidebarPanel("main");
|
if (api.getSidebarPanel()?.key === ADMIN_PANEL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.setSidebarPanel(MAIN_PANEL);
|
||||||
|
|
||||||
const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(user);
|
const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(user);
|
||||||
if (chatSeparateSidebarMode.fullscreen) {
|
if (chatSeparateSidebarMode.fullscreen) {
|
||||||
|
|
|
@ -6,7 +6,10 @@ import { scrollTop } from "discourse/mixins/scroll-top";
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";
|
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";
|
||||||
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 {
|
export default class ChatRoute extends DiscourseRoute {
|
||||||
@service chat;
|
@service chat;
|
||||||
|
@ -62,7 +65,7 @@ export default class ChatRoute extends DiscourseRoute {
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
withPluginApi("1.8.0", (api) => {
|
withPluginApi("1.8.0", (api) => {
|
||||||
api.setSidebarPanel("chat");
|
api.setSidebarPanel(CHAT_PANEL);
|
||||||
|
|
||||||
const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(
|
const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(
|
||||||
this.currentUser
|
this.currentUser
|
||||||
|
|
|
@ -4,6 +4,7 @@ import KeyValueStore from "discourse/lib/key-value-store";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { defaultHomepage } from "discourse/lib/utilities";
|
import { defaultHomepage } from "discourse/lib/utilities";
|
||||||
import Site from "discourse/models/site";
|
import Site from "discourse/models/site";
|
||||||
|
import { MAIN_PANEL } from "discourse/services/sidebar-state";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";
|
import { getUserChatSeparateSidebarMode } from "discourse/plugins/chat/discourse/lib/get-user-chat-separate-sidebar-mode";
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ export default class ChatStateManager extends Service {
|
||||||
didOpenDrawer(url = null) {
|
didOpenDrawer(url = null) {
|
||||||
withPluginApi("1.8.0", (api) => {
|
withPluginApi("1.8.0", (api) => {
|
||||||
if (getUserChatSeparateSidebarMode(this.currentUser).always) {
|
if (getUserChatSeparateSidebarMode(this.currentUser).always) {
|
||||||
api.setSidebarPanel("main");
|
api.setSidebarPanel(MAIN_PANEL);
|
||||||
api.setSeparatedSidebarMode();
|
api.setSeparatedSidebarMode();
|
||||||
api.hideSidebarSwitchPanelButtons();
|
api.hideSidebarSwitchPanelButtons();
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,7 +82,7 @@ export default class ChatStateManager extends Service {
|
||||||
|
|
||||||
didCloseDrawer() {
|
didCloseDrawer() {
|
||||||
withPluginApi("1.8.0", (api) => {
|
withPluginApi("1.8.0", (api) => {
|
||||||
api.setSidebarPanel("main");
|
api.setSidebarPanel(MAIN_PANEL);
|
||||||
|
|
||||||
const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(
|
const chatSeparateSidebarMode = getUserChatSeparateSidebarMode(
|
||||||
this.currentUser
|
this.currentUser
|
||||||
|
|
|
@ -68,9 +68,7 @@ export default class Chat extends Service {
|
||||||
return (
|
return (
|
||||||
this.currentUser.staff ||
|
this.currentUser.staff ||
|
||||||
this.currentUser.isInAnyGroups(
|
this.currentUser.isInAnyGroups(
|
||||||
(this.siteSettings.direct_message_enabled_groups || "11") // trust level 1 auto group
|
this.siteSettings.groupSettingArray("direct_message_enabled_groups")
|
||||||
.split("|")
|
|
||||||
.map((groupId) => parseInt(groupId, 10))
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,18 @@ RSpec.describe SidebarSection do
|
||||||
expect(community_section.reload.title).to eq("Community")
|
expect(community_section.reload.title).to eq("Community")
|
||||||
|
|
||||||
expect(community_section.sidebar_section_links.all.map { |link| link.linkable.name }).to eq(
|
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
|
||||||
end
|
end
|
||||||
|
|
18
spec/system/admin_revamp_sidebar_navigation_spec.rb
Normal file
18
spec/system/admin_revamp_sidebar_navigation_spec.rb
Normal file
|
@ -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
Block a user