Ver Fonte

Header: main menu button joins the member toolbar on the right (#5906)

The hamburger that opens the site sidebar was the only visible item of the
site toolbar, left of the logo. It is now the last item of the member
toolbar (order 9999), rendered like its neighbours and editable in Studio.
The header no longer renders the site toolbar, and the site sidebar slides
in from the trailing edge, where its button is.

Toolbar items in API mode have no class_add; guard the two places that
append to it so api.php output stays valid JSON.

Migration for existing sites:
UPDATE `sys_menu_items` SET `set_name`='sys_toolbar_member', `order`=9999 WHERE `set_name`='sys_toolbar_site' AND `module`='system' AND `name`='main-menu';

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Andrey Yasko há 1 dia atrás
pai
commit
ce85125e89

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
install/sql/system.sql


+ 3 - 14
modules/boonex/artificer/data/template/system/css/menu-sidebar.css

@@ -26,20 +26,8 @@
     -moz-transition: -moz-transform 0.5s 0s;
     transition: transform 0.5s 0s;
 }
-.bx-dir-ltr .bx-sidebar.bx-sidebar-site .bx-sidebar-cnt {
-    -webkit-transform: translateX(-100%);
-    -moz-transform: translateX(-100%);
-    -ms-transform: translateX(-100%);
-    -o-transform: translateX(-100%);
-    transform: translateX(-100%);
-}
-.bx-dir-rtl .bx-sidebar.bx-sidebar-site .bx-sidebar-cnt {
-    -webkit-transform: translateX(100%);
-    -moz-transform: translateX(100%);
-    -ms-transform: translateX(100%);
-    -o-transform: translateX(100%);
-    transform: translateX(100%);
-}
+/*--- Both sidebars slide in from the trailing edge, where their triggers are. ---*/
+.bx-dir-ltr .bx-sidebar.bx-sidebar-site .bx-sidebar-cnt,
 .bx-dir-ltr .bx-sidebar.bx-sidebar-account .bx-sidebar-cnt {
     -webkit-transform: translateX(100%);
     -moz-transform: translateX(100%);
@@ -47,6 +35,7 @@
     -o-transform: translateX(100%);
     transform: translateX(100%);
 }
+.bx-dir-rtl .bx-sidebar.bx-sidebar-site .bx-sidebar-cnt,
 .bx-dir-rtl .bx-sidebar.bx-sidebar-account .bx-sidebar-cnt {
     -webkit-transform: translateX(-100%);
     -moz-transform: translateX(-100%);

+ 2 - 2
modules/boonex/artificer/data/template/system/menu_toolbar.html

@@ -1,6 +1,6 @@
-<ul class="bx-menu-toolbar bx-menu-object-__object__ flex items-center pr-0 md:pr-2">
+<ul class="bx-menu-toolbar bx-menu-object-__object__ flex items-center gap-2 pr-0 md:pr-2">
     <bx_repeat:menu_items>
-        <li id="bx-menu-toolbar-item-__name__" class="bx-menu-toolbar-item bx-menu-item-__name__ group relative inline-flex items-center ltr:mr-2 ltr:last:mr-0 rtl:ml-2 rtl:last:ml-0 bx-mdr-hidden:mr-0 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 text-base font-medium leading-6 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 dark:bg-gray-700/50 dark:hover:bg-gray-700 dark:active:bg-gray-600 rounded-full __class_add__">
+        <li id="bx-menu-toolbar-item-__name__" class="bx-menu-toolbar-item bx-menu-item-__name__ group relative inline-flex items-center text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 text-base font-medium leading-6 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 dark:bg-gray-700/50 dark:hover:bg-gray-700 dark:active:bg-gray-600 rounded-full __class_add__">
             <a href="__link__" <bx_if:onclick>onclick="__onclick__"</bx_if:onclick> class="inline-block rounded-full __class_add_a__"__attrs__>
                 <bx_if:icon><span class="relative flex items-center justify-center h-11 w-11"><i class="sys-icon __icon__"></i></span></bx_if:icon>
                 <bx_if:icon-a><i class="sys-icon-a" data-rotate="down" data-icon="__icon-a__" data-color="#fff"></i></bx_if:icon-a>

+ 9 - 4
modules/boonex/artificer/data/template/system/scripts/BxTemplMenuToolbar.php

@@ -32,8 +32,8 @@ class BxTemplMenuToolbar extends BxBaseMenuToolbar
         if(in_array($a['hidden_on'], [7, 15])) 
             return false;
 
-        //--- Update '[Main Menu]' from Site Toolbar
-        if($this->_bSiteToolbar && $a['name'] == 'main-menu')
+        //--- '[Main Menu]' opens the site sidebar (see the switch below) instead of the sliding menu
+        if($a['name'] == 'main-menu')
             $a['onclick'] = '';
 
     	//--- Hide '[Search]' from Site Toolbar
@@ -54,6 +54,9 @@ class BxTemplMenuToolbar extends BxBaseMenuToolbar
         $sAttrsAdd = '';
         switch ($a['name']) {
             case 'main-menu':
+                //--- The sidebar it opens is for small screens only, wider ones show the main menu inline.
+                if(isset($a['class_add']))
+                    $a['class_add'] .= ' lg:hidden';
                 $a['class_add_a'] = ' bx-sidebar-site-trigger';
                 $a['link'] = 'javascript:void(0)';
                 $a['onclick'] = '';
@@ -68,7 +71,8 @@ class BxTemplMenuToolbar extends BxBaseMenuToolbar
                 break;
 
             case 'add-content':
-                $a['class_add'] = str_replace('bx-def-media-phone-hide', '',  $a['class_add']);
+                if(isset($a['class_add']))
+                    $a['class_add'] = str_replace('bx-def-media-phone-hide', '',  $a['class_add']);
 
                 if(isset($a['bx_if:onclick']['content']['onclick']))
                     $a['bx_if:onclick']['content']['onclick'] = str_replace('bx_menu_slide_inline', 'bx_menu_popup_inline', $a['bx_if:onclick']['content']['onclick']);
@@ -81,7 +85,8 @@ class BxTemplMenuToolbar extends BxBaseMenuToolbar
                 if($this->_bIsApi)
                     break;
 
-                $a['class_add'] = str_replace('bx-def-media-phone-hide', '',  $a['class_add']);
+                if(isset($a['class_add']))
+                    $a['class_add'] = str_replace('bx-def-media-phone-hide', '',  $a['class_add']);
 
                 //--- The floating search bar (see _page_toolbar_search.html) takes the button's place. Without it the button opens the search page.
                 if(getParam('bx_artificer_header_search') == 'on') {

+ 4 - 4
modules/boonex/artificer/data/template/system/sidebar_site.html

@@ -1,8 +1,9 @@
-<div id="bx-sidebar-site" class="bx-sidebar bx-sidebar-site bx-def-z-index-modal fixed flex lg:hidden inset-0" role="dialog" aria-modal="true" aria-label="<bx_text:_sys_menu_item_title_system_main_menu />">
+<div id="bx-sidebar-site" class="bx-sidebar bx-sidebar-site bx-def-z-index-modal fixed flex flex-row justify-end lg:hidden inset-0" role="dialog" aria-modal="true" aria-label="<bx_text:_sys_menu_item_title_system_main_menu />">
     <div class="bx-sidebar-site-bg fixed inset-0 bg-gray-600 opacity-75" aria-hidden="true"></div>
+    <div class="flex-shrink-0 w-14" aria-hidden="true"></div>
     <div class="bx-sidebar-cnt relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white dark:bg-gray-900">
-        <div class="absolute top-0 ltr:right-0 rtl:left-0 ltr:-mr-12 rtl:-ml-12 pt-2">
-            <button type="button" class="bx-sidebar-site-close ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
+        <div class="absolute top-0 ltr:left-0 rtl:right-0 ltr:-ml-12 rtl:-mr-12 pt-2">
+            <button type="button" class="bx-sidebar-site-close ltr:ml-1 rtl:mr-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
                 <span class="sr-only"><bx_text:_bx_artificer_txt_close_sidebar /></span>
                 <div class="bx-icon bx-icon-close text-white">
                     <bx_image_auto:times />
@@ -17,5 +18,4 @@
         </div>
         <div class="bx-focus-trap w-0 h-0 outline-none" tabindex="0"></div>
     </div>
-    <div class="flex-shrink-0 w-14" aria-hidden="true"></div>
 </div>

+ 0 - 1
modules/boonex/artificer/template/header.html

@@ -2,7 +2,6 @@
     <div class="bx-header-cnt bx-def-page-width mx-auto px-3 md:px-4 lg:px-6 box-border">
         <div class="bx-toolbar-content-wrapper">
             <div id="bx-toolbar-content" class="bx-toolbar-content relative flex items-center justify-start h-16">
-                <bx_menu:sys_toolbar_site />
                 <!-- Logo -->
                 <div id="bx-logo-container" class="bx-toolbar-item bx-ti-left bx-ti-logo flex-none items-center">
                     <bx_injection:injection_logo_before />

+ 1 - 1
modules/boonex/lucid/data/template/system/css/media-desktop.css

@@ -1,6 +1,6 @@
 @import url(../../../../../../../template/css/media-desktop.css);
 
 /*--- Header Toolbar ---*/ 
-.bx-media-desktop #bx-menu-toolbar-1-container .bx-menu-toolbar li.bx-menu-item-main-menu {
+.bx-media-desktop .bx-menu-toolbar li.bx-menu-item-main-menu {
     display: none;
 }

+ 1 - 1
modules/boonex/lucid/data/template/system/css/media-tablet.css

@@ -7,7 +7,7 @@
 }
 
 /*--- Header Toolbar ---*/ 
-.bx-media-tablet #bx-menu-toolbar-1-container .bx-menu-toolbar li.bx-menu-item-main-menu {
+.bx-media-tablet .bx-menu-toolbar li.bx-menu-item-main-menu {
     display: none;
 }
 

+ 1 - 1
modules/system/install/sql/enable-app-menus.sql

@@ -22,12 +22,12 @@ UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage_submen
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_footer' AND `module`='system' AND `name`='about';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_footer' AND `module`='system' AND `name`='terms';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_footer' AND `module`='system' AND `name`='privacy';
-UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_site' AND `module`='system' AND `name`='main-menu';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='search';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='add-content';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='apps';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='account';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='login';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='main-menu';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_popup' AND `module`='system' AND `name`='profile-active';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_popup' AND `module`='system' AND `name`='profile-notifications';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_popup' AND `module`='system' AND `name`='profile-switcher';

+ 0 - 1
template/_page_toolbar.html

@@ -3,7 +3,6 @@
     <div class="bx-header-cnt __sys_header_width__ mx-auto px-3 md:px-4 lg:px-6 box-border">
         <div class="bx-toolbar-content-wrapper">
             <div id="bx-toolbar-content" class="bx-toolbar-content relative flex items-center justify-start h-16">
-                <bx_menu:sys_toolbar_site />
                 <div class="bx-toolbar-items-group relative flex flex-1 items-center justify-start h-16">
                     <!-- Logo -->
                     <div id="bx-logo-container" class="bx-toolbar-item bx-ti-left bx-ti-logo flex-none items-center">

+ 138 - 0
tests/units/template/scripts/BxBaseMenuToolbarTest.php

@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * The '[Main Menu]' button (name 'main-menu') is an item of the member toolbar, on the right side of the header
+ * next to the account, notifications and add content buttons. It used to be the only item of the site toolbar
+ * on the left of the logo.
+ */
+class BxBaseMenuToolbarTest extends BxDolTestCase
+{
+    private static function readFile(string $sPath): string
+    {
+        $s = file_get_contents(__DIR__ . '/../../../../' . $sPath);
+        if ($s === false)
+            self::fail($sPath . ' is not readable.');
+
+        return $s;
+    }
+
+    private function callProtected($oObject, string $sMethod, array $aArgs = [])
+    {
+        $oMethod = new ReflectionMethod($oObject, $sMethod);
+        return $oMethod->invokeArgs($oObject, $aArgs);
+    }
+
+    private function getMemberToolbar(): BxTemplMenuToolbar
+    {
+        $aObject = BxDolMenuQuery::getMenuObject('sys_toolbar_member');
+        if (empty($aObject))
+            $this->markTestSkipped('The member toolbar menu is not installed.');
+
+        return new BxTemplMenuToolbar($aObject, BxDolTemplate::getInstance());
+    }
+
+    private function getMemberToolbarRows(BxTemplMenuToolbar $oMenu): array
+    {
+        $oQuery = new ReflectionProperty($oMenu, '_oQuery');
+        $aRows = [];
+        foreach ($oQuery->getValue($oMenu)->getMenuItems() as $aRow)
+            $aRows[$aRow['name']] = $aRow;
+
+        if (!isset($aRows['main-menu']))
+            $this->markTestSkipped("This site has no '[Main Menu]' item in the member toolbar (the migration is not applied).");
+
+        return $aRows;
+    }
+
+    private static function isArtificer(BxTemplMenuToolbar $oMenu): bool
+    {
+        return strpos((new ReflectionClass($oMenu))->getFileName(), '/artificer/') !== false;
+    }
+
+    public function testFreshInstallPutsTheMainMenuInTheMemberToolbar(): void
+    {
+        $sSql = self::readFile('install/sql/system.sql');
+
+        $this->assertDoesNotMatchRegularExpression("/\\('sys_toolbar_site', 'system', 'main-menu',/", $sSql, 'The site toolbar no longer has it.');
+
+        $bFound = preg_match("/\\('sys_toolbar_member', 'system', 'main-menu', '_sys_menu_item_title_system_main_menu', [^\\n]*?, (\\d+), (\\d+), (\\d+), (\\d+), (\\d+)\\)[,;]\\n/", $sSql, $aMatch);
+        $this->assertSame(1, $bFound, 'system.sql adds it to the member toolbar.');
+        [, $iVisibleFor, , $iActive, , $iOrder] = array_map('intval', $aMatch);
+        $this->assertSame(2147483647, $iVisibleFor, 'Guests see it too.');
+        $this->assertSame(1, $iActive);
+        $this->assertSame(9999, $iOrder, 'It stays the last button, also after modules add their own.');
+
+        $sAppSql = self::readFile('modules/system/install/sql/enable-app-menus.sql');
+        $this->assertStringContainsString("SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='system' AND `name`='main-menu';", $sAppSql);
+        $this->assertStringNotContainsString("`set_name`='sys_toolbar_site' AND `module`='system' AND `name`='main-menu'", $sAppSql);
+    }
+
+    public function testHeaderHasNoLeftToolbar(): void
+    {
+        foreach (['template/_page_toolbar.html', 'modules/boonex/artificer/template/header.html'] as $sPath) {
+            $sHtml = self::readFile($sPath);
+            $this->assertStringNotContainsString('<bx_menu:sys_toolbar_site />', $sHtml, $sPath . ' renders nothing left of the logo.');
+            $this->assertStringContainsString('<bx_menu:sys_toolbar_member />', $sHtml, $sPath);
+        }
+    }
+
+    public function testMainMenuOpensTheSiteSidebar(): void
+    {
+        $oMenu = $this->getMemberToolbar();
+        if (!self::isArtificer($oMenu))
+            $this->markTestSkipped('The site sidebar belongs to the Artificer template.');
+
+        $aRows = $this->getMemberToolbarRows($oMenu);
+        $a = $this->callProtected($oMenu, '_getMenuItem', [$aRows['main-menu']]);
+
+        $this->assertIsArray($a);
+        $this->assertStringContainsString('bx-sidebar-site-trigger', $a['class_add_a'], 'The sidebar script binds to this class.');
+        $this->assertFalse($a['bx_if:onclick']['condition'], 'No sliding menu opens on top of the sidebar.');
+        $this->assertContains('lg:hidden', explode(' ', $a['class_add']), 'Wide screens show the main menu inline and have no sidebar.');
+        $this->assertStringContainsString('aria-controls="bx-sidebar-site"', $a['attrs']);
+        $this->assertStringContainsString('aria-expanded="false"', $a['attrs']);
+    }
+
+    public function testApiReadsTheToolbarWithoutWarnings(): void
+    {
+        $mixedApiBackup = $_GET['api'] ?? null;
+        $_GET['api'] = 1;
+
+        $aWarnings = [];
+        set_error_handler(function ($iErrno, $sError, $sFile, $iLine) use (&$aWarnings) {
+            $aWarnings[] = $sError . ' in ' . $sFile . ':' . $iLine;
+            return true;
+        }, E_WARNING | E_NOTICE);
+
+        try {
+            //--- Show every item, as a signed-in member sees 'add-content', 'account' and the rest.
+            $aObject = BxDolMenuQuery::getMenuObject('sys_toolbar_member');
+            if (empty($aObject))
+                $this->markTestSkipped('The member toolbar menu is not installed.');
+
+            $oMenu = new class($aObject, BxDolTemplate::getInstance()) extends BxTemplMenuToolbar {
+                protected function _isVisible($a)
+                {
+                    return true;
+                }
+            };
+
+            $aItems = [];
+            foreach ($this->getMemberToolbarRows($oMenu) as $sName => $aRow)
+                if (($a = $this->callProtected($oMenu, '_getMenuItem', [$aRow])) !== false)
+                    $aItems[$sName] = $a;
+        }
+        finally {
+            restore_error_handler();
+            if ($mixedApiBackup === null)
+                unset($_GET['api']);
+            else
+                $_GET['api'] = $mixedApiBackup;
+        }
+
+        $this->assertSame([], $aWarnings, 'A PHP warning printed into api.php output breaks the JSON NEO reads.');
+        $this->assertArrayHasKey('main-menu', $aItems);
+        $this->assertArrayHasKey('add-content', $aItems, 'Members-only items are rendered too.');
+        $this->assertArrayNotHasKey('class_add', $aItems['main-menu'], 'HTML-only classes stay out of the API item.');
+    }
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff