FEATURE: Force admin sidebar for all admins in `admin_sidebar_enabled_groups` and handle legacy "hamburger dropdown" in this mode (#26899)

Some sites are still on the legacy "hamburger dropdown"
navigation_menu setting. In this case to avoid confusion,
we want to show both the sidebar icon and the header dropdown
hamburger when visiting the admin portal. Otherwise, the
hamburger switches sides from right to left for admins
and takes on different behaviour.

The hamburger in this case _only_ shows the main panel, not
other sidebar panels like the admin one.
This commit is contained in:
Martin Brennan 2024-05-13 14:40:23 +10:00 committed by GitHub
parent 10b2715cb3
commit 9bcbfbba43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 332 additions and 29 deletions

View File

@ -40,6 +40,7 @@ export default class GlimmerHeader extends Component {
@service router;
@service search;
@service currentUser;
@service siteSettings;
@service site;
@service appEvents;
@service header;
@ -71,7 +72,7 @@ export default class GlimmerHeader extends Component {
this.toggleUserMenu();
break;
case "hamburger":
this.toggleHamburger();
this.toggleNavigationMenu();
break;
case "page-search":
if (!this.togglePageSearch()) {
@ -148,15 +149,33 @@ export default class GlimmerHeader extends Component {
}
@action
toggleHamburger() {
if (this.args.sidebarEnabled && !this.site.narrowDesktopView) {
this.args.toggleSidebar();
this.args.animateMenu();
} else {
this.header.hamburgerVisible = !this.header.hamburgerVisible;
this.toggleBodyScrolling(this.header.hamburgerVisible);
this.args.animateMenu();
toggleNavigationMenu(override = null) {
if (override === "sidebar") {
return this.toggleSidebar();
}
if (override === "hamburger") {
return this.toggleHamburger();
}
if (this.args.sidebarEnabled && !this.site.narrowDesktopView) {
this.toggleSidebar();
} else {
this.toggleHamburger();
}
}
@action
toggleHamburger() {
this.header.hamburgerVisible = !this.header.hamburgerVisible;
this.toggleBodyScrolling(this.header.hamburgerVisible);
this.args.animateMenu();
}
@action
toggleSidebar() {
this.args.toggleSidebar();
this.args.animateMenu();
}
@action
@ -171,7 +190,7 @@ export default class GlimmerHeader extends Component {
<div class="wrap">
<Contents
@sidebarEnabled={{@sidebarEnabled}}
@toggleHamburger={{this.toggleHamburger}}
@toggleNavigationMenu={{this.toggleNavigationMenu}}
@showSidebar={{@showSidebar}}
>
<span class="header-buttons">
@ -194,7 +213,7 @@ export default class GlimmerHeader extends Component {
<Icons
@sidebarEnabled={{@sidebarEnabled}}
@toggleSearchMenu={{this.toggleSearchMenu}}
@toggleHamburger={{this.toggleHamburger}}
@toggleNavigationMenu={{this.toggleNavigationMenu}}
@toggleUserMenu={{this.toggleUserMenu}}
@searchButtonId={{SEARCH_BUTTON_ID}}
/>
@ -204,7 +223,8 @@ export default class GlimmerHeader extends Component {
<SearchMenuWrapper @closeSearchMenu={{this.toggleSearchMenu}} />
{{else if this.header.hamburgerVisible}}
<HamburgerDropdownWrapper
@toggleHamburger={{this.toggleHamburger}}
@toggleNavigationMenu={{this.toggleNavigationMenu}}
@sidebarEnabled={{@sidebarEnabled}}
/>
{{else if this.header.userVisible}}
<UserMenuWrapper @toggleUserMenu={{this.toggleUserMenu}} />

View File

@ -13,18 +13,28 @@ export default class Contents extends Component {
@service currentUser;
@service siteSettings;
@service header;
@service sidebarState;
get topicPresent() {
return !!this.header.topic;
}
get sidebarIcon() {
if (this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu) {
return "discourse-sidebar";
}
return "bars";
}
<template>
<div class="contents">
{{#if this.site.desktopView}}
{{#if @sidebarEnabled}}
<SidebarToggle
@toggleHamburger={{@toggleHamburger}}
@toggleNavigationMenu={{@toggleNavigationMenu}}
@showSidebar={{@showSidebar}}
@icon={{this.sidebarIcon}}
/>
{{/if}}
{{/if}}

View File

@ -2,22 +2,36 @@ import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { waitForPromise } from "@ember/test-waiters";
import { isDocumentRTL } from "discourse/lib/text-direction";
import { prefersReducedMotion } from "discourse/lib/utilities";
import { isTesting } from "discourse-common/config/environment";
import discourseLater from "discourse-common/lib/later";
import closeOnClickOutside from "../../modifiers/close-on-click-outside";
import HamburgerDropdown from "../sidebar/hamburger-dropdown";
import SidebarHamburgerDropdown from "../sidebar/hamburger-dropdown";
const CLOSE_ON_CLICK_SELECTORS =
"a[href], .sidebar-section-header-button, .sidebar-section-link-button, .sidebar-section-link";
export default class HamburgerDropdownWrapper extends Component {
@service currentUser;
@service siteSettings;
@service sidebarState;
@action
toggleNavigation() {
this.args.toggleNavigationMenu(
this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu
? "hamburger"
: null
);
}
@action
click(e) {
if (e.target.closest(CLOSE_ON_CLICK_SELECTORS)) {
this.args.toggleHamburger();
this.toggleNavigation();
}
}
@ -38,9 +52,9 @@ export default class HamburgerDropdownWrapper extends Component {
})
.finished.then(() => {
if (isTesting()) {
this.args.toggleHamburger();
this.toggleNavigation();
} else {
discourseLater(() => this.args.toggleHamburger());
discourseLater(() => this.toggleNavigation());
}
});
const cloakAnimatePromise = headerCloak.animate([{ opacity: 0 }], {
@ -51,9 +65,24 @@ export default class HamburgerDropdownWrapper extends Component {
waitForPromise(panelAnimatePromise);
waitForPromise(cloakAnimatePromise);
} else {
this.args.toggleHamburger();
this.toggleNavigation();
}
}
get forceMainSidebarPanel() {
// NOTE: In this scenario, we are forcing the sidebar on admin users,
// so we need to still show the hamburger menu and always show the main
// panel in that menu.
if (
this.args.sidebarEnabled &&
this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu
) {
return true;
}
return false;
}
<template>
<div
class="hamburger-dropdown-wrapper"
@ -69,7 +98,9 @@ export default class HamburgerDropdownWrapper extends Component {
)
}}
>
<HamburgerDropdown />
<SidebarHamburgerDropdown
@forceMainSidebarPanel={{this.forceMainSidebarPanel}}
/>
</div>
</template>
}

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { eq, not, or } from "truth-helpers";
import { eq } from "truth-helpers";
import DAG from "discourse/lib/dag";
import getURL from "discourse-common/lib/get-url";
import Dropdown from "./dropdown";
@ -27,9 +28,34 @@ export function clearExtraHeaderIcons() {
export default class Icons extends Component {
@service site;
@service currentUser;
@service siteSettings;
@service sidebarState;
@service header;
@service search;
get showHamburger() {
// NOTE: In this scenario, we are forcing the sidebar on admin users,
// so we need to still show the hamburger menu to be able to
// access the legacy hamburger forum menu.
if (
this.args.sidebarEnabled &&
this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu
) {
return true;
}
return !this.args.sidebarEnabled || this.site.mobileView;
}
@action
toggleHamburger() {
if (this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu) {
this.args.toggleNavigationMenu("hamburger");
} else {
this.args.toggleNavigationMenu();
}
}
<template>
<ul class="icons d-header-icons">
{{#each (headerIcons.resolve) as |entry|}}
@ -45,13 +71,13 @@ export default class Icons extends Component {
@targetSelector=".search-menu-panel"
/>
{{else if (eq entry.key "hamburger")}}
{{#if (or (not @sidebarEnabled) this.site.mobileView)}}
{{#if this.showHamburger}}
<Dropdown
@title="hamburger_menu"
@icon="bars"
@iconId="toggle-hamburger-menu"
@active={{this.header.hamburgerVisible}}
@onClick={{@toggleHamburger}}
@onClick={{this.toggleHamburger}}
@className="hamburger-dropdown"
/>
{{/if}}

View File

@ -8,10 +8,16 @@ import i18n from "discourse-common/helpers/i18n";
export default class SidebarToggle extends Component {
@service site;
@service sidebarState;
@action
toggleWithBlur(e) {
this.args.toggleHamburger();
if (this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu) {
this.args.toggleNavigationMenu("sidebar");
} else {
this.args.toggleNavigationMenu();
}
// remove the focus of the header dropdown button after clicking
e.target.tagName.toLowerCase() === "button"
? e.target.blur()
@ -30,7 +36,7 @@ export default class SidebarToggle extends Component {
aria-controls="d-sidebar"
{{on "click" this.toggleWithBlur}}
>
{{icon "bars"}}
{{icon @icon}}
</button>
</span>
</template>

View File

@ -2,6 +2,7 @@ import Component from "@glimmer/component";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import { or } from "truth-helpers";
import ApiPanels from "./api-panels";
import Footer from "./footer";
import Sections from "./sections";
@ -39,11 +40,14 @@ export default class SidebarHamburgerDropdown extends Component {
<div class="panel-body">
<div class="panel-body-contents">
<div class="sidebar-hamburger-dropdown">
{{#if this.sidebarState.showMainPanel}}
{{#if
(or this.sidebarState.showMainPanel @forceMainSidebarPanel)
}}
<Sections
@currentUser={{this.currentUser}}
@collapsableSections={{this.collapsableSections}}
@panel={{this.sidebarState.currentPanel}}
@hideApiSections={{@forceMainSidebarPanel}}
/>
{{else}}
<ApiPanels

View File

@ -6,6 +6,7 @@ const SidebarSections = <template>
<UserSections
@collapsableSections={{@collapsableSections}}
@panel={{@panel}}
@hideApiSections={{@hideApiSections}}
/>
{{else}}
<AnonymousSections @collapsableSections={{@collapsableSections}} />

View File

@ -22,7 +22,9 @@ export default class SidebarUserSections extends Component {
<MessagesSection @collapsable={{@collapsableSections}} />
{{/if}}
<ApiSections @collapsable={{@collapsableSections}} />
{{#unless @hideApiSections}}
<ApiSections @collapsable={{@collapsableSections}} />
{{/unless}}
</div>
</template>
}

View File

@ -97,6 +97,14 @@ export default Controller.extend({
return false;
}
// Always show sidebar for admin if user can see the admin sidbar
if (
this.router.currentRouteName.startsWith("admin.") &&
this.currentUser?.use_admin_sidebar
) {
return true;
}
return this.siteSettings.navigation_menu === "sidebar";
},

View File

@ -16,11 +16,14 @@ import {
@disableImplicitInjections
export default class SidebarState extends Service {
@service keyValueStore;
@service currentUser;
@service siteSettings;
@tracked currentPanelKey = currentPanelKey;
@tracked mode = COMBINED_MODE;
@tracked displaySwitchPanelButtons = false;
@tracked filter = "";
panels = panels;
collapsedSections = new TrackedSet();
previousState = {};
@ -119,6 +122,13 @@ export default class SidebarState extends Service {
return this.currentPanelKey === MAIN_PANEL;
}
get adminSidebarAllowedWithLegacyNavigationMenu() {
return (
this.currentUser?.use_admin_sidebar &&
this.siteSettings.navigation_menu === "header dropdown"
);
}
clearFilter() {
this.filter = "";
}

View File

@ -289,6 +289,16 @@ createWidget("header-icons", {
}
});
if (attrs.user) {
icons.push(
this.attach("user-dropdown", {
active: attrs.userVisible,
action: "toggleUserMenu",
user: attrs.user,
})
);
}
return icons;
},
});
@ -644,7 +654,12 @@ export default createWidget("header", {
toggleHamburger() {
if (this.attrs.sidebarEnabled && !this.site.narrowDesktopView) {
this.sendWidgetAction("toggleSidebar");
if (!this.attrs.showSidebar) {
this.sendWidgetAction("toggleSidebar");
this.closeAll();
} else {
this.state.hamburgerVisible = !this.state.hamburgerVisible;
}
} else {
this.state.hamburgerVisible = !this.state.hamburgerVisible;
this.toggleBodyScrolling(this.state.hamburgerVisible);

View File

@ -78,7 +78,9 @@ RSpec.describe "Editing Sidebar Community Section", type: :system do
visit("/latest")
modal = sidebar_header_dropdown.open.click_customize_community_section_button
sidebar_header_dropdown.open
expect(sidebar_header_dropdown).to have_dropdown_visible
modal = sidebar_header_dropdown.click_customize_community_section_button
expect(modal).to be_visible
end

View File

@ -0,0 +1,119 @@
# frozen_string_literal: true
describe "Navigation menu states", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
let!(:sidebar_navigation) { PageObjects::Components::NavigationMenu::Sidebar.new }
let!(:header_dropdown) { PageObjects::Components::NavigationMenu::HeaderDropdown.new }
before { sign_in(current_user) }
context "when navigation_menu is 'header dropdown'" do
before { SiteSetting.navigation_menu = "header dropdown" }
it "does not show the sidebar" do
visit "/"
expect(sidebar_navigation).to be_not_visible
end
it "opens and closes the hamburger menu from the toggle" do
visit "/"
expect(header_dropdown).to be_visible
header_dropdown.open
expect(header_dropdown).to have_dropdown_visible
expect(header_dropdown).to have_sidebar_panel("main")
expect(header_dropdown).to have_no_sidebar_panel("admin")
header_dropdown.close
expect(header_dropdown).to have_no_dropdown_visible
end
context "for admins" do
fab!(:current_user) { Fabricate(:admin, refresh_auto_groups: true) }
it "shows the sidebar and allows toggling it" do
visit "/admin"
expect(sidebar_navigation).to be_visible
sidebar_navigation.click_header_toggle
expect(sidebar_navigation).to be_not_visible
sidebar_navigation.click_header_toggle
expect(sidebar_navigation).to be_visible
expect(find(sidebar_navigation.header_toggle_css)).to have_css(".d-icon-discourse-sidebar")
end
it "shows the hamburger menu and allows toggling it, which shows the MAIN_PANEL onls" do
visit "/admin"
expect(header_dropdown).to be_visible
header_dropdown.open
expect(header_dropdown).to have_dropdown_visible
expect(header_dropdown).to have_sidebar_panel("main")
expect(header_dropdown).to have_no_sidebar_panel("admin")
header_dropdown.close
expect(header_dropdown).to have_no_dropdown_visible
end
context "when the user is not in admin_sidebar_enabled_groups" do
before { SiteSetting.admin_sidebar_enabled_groups = "" }
it "does not show the sidebar" do
visit "/admin"
expect(sidebar_navigation).to be_not_visible
end
end
end
end
context "when navigation_menu is 'sidebar'" do
before { SiteSetting.navigation_menu = "sidebar" }
it "shows the sidebar" do
visit "/"
expect(sidebar_navigation).to be_visible
end
it "does not show the hamburger menu" do
visit "/"
expect(header_dropdown).to be_not_visible
end
it "opens and closes the sidebar from the toggle" do
visit "/"
sidebar_navigation.click_header_toggle
expect(sidebar_navigation).to be_not_visible
sidebar_navigation.click_header_toggle
expect(sidebar_navigation).to be_visible
end
context "for admins" do
fab!(:current_user) { Fabricate(:admin, refresh_auto_groups: true) }
it "shows the sidebar and allows toggling it" do
visit "/admin"
expect(sidebar_navigation).to be_visible
sidebar_navigation.click_header_toggle
expect(sidebar_navigation).to be_not_visible
sidebar_navigation.click_header_toggle
expect(sidebar_navigation).to be_visible
expect(find(sidebar_navigation.header_toggle_css)).to have_css(".d-icon-bars")
end
it "does not show the hamburger menu" do
visit "/admin"
expect(header_dropdown).to be_not_visible
end
context "when the user is not in admin_sidebar_enabled_groups" do
before { SiteSetting.admin_sidebar_enabled_groups = "" }
it "shows the MAIN_PANEL of the sidebar" do
visit "/admin"
expect(sidebar_navigation).to have_no_section("admin-root")
expect(sidebar_navigation).to have_section("community")
end
it "does show the sidebar toggle" do
visit "/admin"
expect(page).to have_css(sidebar_navigation.header_toggle_css)
end
end
end
end
end

View File

@ -6,10 +6,41 @@ module PageObjects
class HeaderDropdown < Base
def open
find(".header-dropdown-toggle.hamburger-dropdown").click
expect(page).to have_css(".sidebar-hamburger-dropdown")
self
end
def close
open
end
def has_sidebar_panel?(panel)
has_css?(
".sidebar-hamburger-dropdown .sidebar-section-wrapper[data-section-name=\"#{panel_id(panel)}\"]",
)
end
def has_no_sidebar_panel?(panel)
has_no_css?(
".sidebar-hamburger-dropdown .sidebar-section-wrapper[data-section-name=\"#{panel_id(panel)}\"]",
)
end
def has_dropdown_visible?
page.has_css?(".sidebar-hamburger-dropdown")
end
def has_no_dropdown_visible?
page.has_no_css?(".sidebar-hamburger-dropdown")
end
def visible?
page.has_css?(".hamburger-dropdown.header-dropdown-toggle")
end
def not_visible?
page.has_no_css?(".hamburger-dropdown.header-dropdown-toggle")
end
def click_customize_community_section_button
community_section.click_button(
I18n.t("js.sidebar.sections.community.edit_section.header_dropdown"),
@ -19,6 +50,16 @@ module PageObjects
PageObjects::Modals::SidebarSectionForm.new
end
private
def panel_id(panel)
if panel == "admin"
"admin-root"
elsif panel == "main"
"community"
end
end
end
end
end

View File

@ -9,6 +9,14 @@ module PageObjects
wait_for_animation(find("div.menu-panel"))
end
def click_header_toggle
find(header_toggle_css).click
end
def header_toggle_css
".header-sidebar-toggle"
end
def visible?
page.has_css?("#d-sidebar")
end