Merge "Simplify menu code"

This commit is contained in:
jenkins-bot 2021-01-06 16:28:25 +00:00 committed by Gerrit Code Review
commit f6e790b118
15 changed files with 131 additions and 199 deletions

View file

@ -130,9 +130,8 @@ class SkinVector extends SkinMustache {
'input-location' => $this->getSearchBoxInputLocation(),
'data-sidebar' => $this->getTemplateDataSidebar(),
'sidebar-visible' => $this->isSidebarVisible(),
], $this->getMenuProps() );
] );
if ( $skin->getUser()->isRegistered() ) {
// Note: This data is also passed to legacy template where it is unused.
@ -190,149 +189,70 @@ class SkinVector extends SkinMustache {
}
/**
* Render a series of portals
*
* @return array
* helper for applying Vector menu classes to portlets
* @param array $portletData returned by SkinMustache to decorate
* @param int $type representing one of the menu types (see MENU_TYPE_* constants)
* @return array modified version of portletData input
*/
private function getTemplateDataSidebar() {
$skin = $this;
$portals = $this->buildSidebar();
$props = [];
$languages = null;
// Render portals
foreach ( $portals as $name => $content ) {
if ( $content === false ) {
continue;
}
// Numeric strings gets an integer when set as key, cast back - T73639
$name = (string)$name;
switch ( $name ) {
case 'SEARCH':
break;
case 'TOOLBOX':
$props[] = $this->getMenuData(
'tb', $content, self::MENU_TYPE_PORTAL
);
break;
case 'LANGUAGES':
$portal = $this->getMenuData(
'lang', $content, self::MENU_TYPE_PORTAL
);
// The language portal will be added provided either
// languages exist or there is a value in html-after-portal
// for example to show the add language wikidata link (T252800)
if ( count( $content ) || $portal['html-after-portal'] ) {
$languages = $portal;
}
break;
default:
$props[] = $this->getMenuData(
$name, $content, self::MENU_TYPE_PORTAL
);
break;
}
}
$firstPortal = $props[0] ?? null;
if ( $firstPortal ) {
$firstPortal[ 'class' ] .= ' portal-first';
}
return [
'html-logo-attributes' => Xml::expandAttributes(
Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) + [
'class' => 'mw-wiki-logo',
'href' => Skin::makeMainPageUrl(),
]
),
'array-portals-rest' => array_slice( $props, 1 ),
'data-portals-first' => $firstPortal,
'data-portals-languages' => $languages,
];
}
/**
* @param string $label to be used to derive the id and human readable label of the menu
* Note certain keys are special cased for historic reasons in core.
* @param array $urls to convert to list items stored as string in html-items key
* @param int $type of menu (optional) - a plain list (MENU_TYPE_DEFAULT),
* a tab (MENU_TYPE_TABS) or a dropdown (MENU_TYPE_DROPDOWN)
* @param bool $setLabelToSelected (optional) the menu label will take the value of the
* selected item if found.
* @return array
*/
private function getMenuData(
string $label,
array $urls = [],
int $type = self::MENU_TYPE_DEFAULT,
bool $setLabelToSelected = false
) : array {
$portletData = $this->getPortletData( $label, $urls );
private function decoratePortletClass(
array $portletData,
int $type = self::MENU_TYPE_DEFAULT
) {
$extraClasses = [
self::MENU_TYPE_DROPDOWN => 'vector-menu vector-menu-dropdown',
self::MENU_TYPE_TABS => 'vector-menu vector-menu-tabs',
self::MENU_TYPE_PORTAL => 'vector-menu vector-menu-portal portal',
self::MENU_TYPE_DEFAULT => 'vector-menu',
];
$isPortal = $type === self::MENU_TYPE_PORTAL;
$class = $portletData['class'];
$portletData['class'] = trim( "$class $extraClasses[$type]" );
return $portletData;
}
$props = $portletData + [
'label-id' => "p-{$label}-label",
'is-dropdown' => $type === self::MENU_TYPE_DROPDOWN,
];
/**
* @inheritDoc
* @return array
*/
protected function getPortletData(
$label,
array $urls = []
) : array {
switch ( $label ) {
case 'actions':
case 'variants':
$type = self::MENU_TYPE_DROPDOWN;
break;
case 'views':
case 'namespaces':
$type = self::MENU_TYPE_TABS;
break;
case 'personal':
$type = self::MENU_TYPE_DEFAULT;
break;
default:
$type = self::MENU_TYPE_PORTAL;
break;
}
$portletData = $this->decoratePortletClass(
parent::getPortletData( $label, $urls ),
$type
);
// Special casing for Variant to change label to selected.
// Hopefully we can revisit and possibly remove this code when the language switcher is moved.
foreach ( $urls as $key => $item ) {
if ( $setLabelToSelected ) {
if ( $label === 'variants' ) {
foreach ( $urls as $key => $item ) {
// Check the class of the item for a `selected` class and if so, propagate the items
// label to the main label.
if ( isset( $item['class'] ) && stripos( $item['class'], 'selected' ) !== false ) {
$props['label'] = $item['text'];
$portletData['label'] = $item['text'];
}
}
}
// Mark the portal as empty if it has no content
$class = $props['class'];
$props['class'] = trim( "$class $extraClasses[$type]" );
return $props;
}
/**
* @return array
*/
private function getMenuProps() : array {
$contentNavigation = $this->buildContentNavigationUrls();
$personalTools = self::getPersonalToolsForMakeListItem(
$this->buildPersonalUrls()
);
$ptools = $this->getMenuData( 'personal', $personalTools );
return [
'data-personal-menu' => $ptools,
'data-namespace-tabs' => $this->getMenuData(
'namespaces',
$contentNavigation[ 'namespaces' ] ?? [],
self::MENU_TYPE_TABS
),
'data-variants' => $this->getMenuData(
'variants',
$contentNavigation[ 'variants' ] ?? [],
self::MENU_TYPE_DROPDOWN,
true
),
'data-page-actions' => $this->getMenuData(
'views',
$contentNavigation[ 'views' ] ?? [],
self::MENU_TYPE_TABS
),
'data-page-actions-more' => $this->getMenuData(
'cactions',
$contentNavigation[ 'actions' ] ?? [],
self::MENU_TYPE_DROPDOWN
),
return $portletData + [
'is-dropdown' => $type === self::MENU_TYPE_DROPDOWN,
];
}
}

View file

@ -11,5 +11,7 @@
</label>
{{>Logo}}
{{#data-search-box}}{{>SearchBox}}{{/data-search-box}}
{{#data-personal-menu}}{{>Menu}}{{/data-personal-menu}}
{{#data-portlets}}
{{#data-personal}}{{>Menu}}{{/data-personal}}
{{/data-portlets}}
</header>

View file

@ -2,14 +2,16 @@
<h2>{{msg-navigation-heading}}</h2>
<div id="mw-head">
<div class="mw-article-toolbar-container">
{{#data-portlets}}
<div id="left-navigation">
{{#data-namespace-tabs}}{{>Menu}}{{/data-namespace-tabs}}
{{#data-namespaces}}{{>Menu}}{{/data-namespaces}}
{{#data-variants}}{{>Menu}}{{/data-variants}}
</div>
<div id="right-navigation">
{{#data-page-actions}}{{>Menu}}{{/data-page-actions}}
{{#data-page-actions-more}}{{>Menu}}{{/data-page-actions-more}}
</div>
{{/data-portlets}}
</div>
</div>
</div>

View file

@ -4,19 +4,19 @@
@prop string text
string html-logo-attributes for site logo. Must be used inside tag e.g. `class="logo" lang="en-gb"`
MenuDefinition data-portals-first
MenuDefinition[] array-portals-rest
MenuDefinition data-portlets-first
MenuDefinition[] array-portlets-rest
emphasized-sidebar-action data-emphasized-sidebar-action For displaying an emphasized action in the sidebar.
}}
<div id="mw-panel" class="mw-sidebar">
{{#data-portals-first}}{{>Menu}}{{/data-portals-first}}
{{#data-portlets-first}}{{>Menu}}{{/data-portlets-first}}
{{#data-emphasized-sidebar-action}}
<div class="mw-sidebar-action">
<a class="mw-sidebar-action-link" title="{{msg-vector-opt-out-tooltip}}"
href="{{href}}">{{msg-vector-opt-out}}</a>
</div>
{{/data-emphasized-sidebar-action}}
{{#array-portals-rest}}{{>Menu}}{{/array-portals-rest}}
{{#data-portals-languages}}{{>Menu}}{{/data-portals-languages}}
{{#array-portlets-rest}}{{>Menu}}{{/array-portlets-rest}}
{{#data-portlets.data-languages}}{{>Menu}}{{/data-portlets.data-languages}}
</div>

View file

@ -1,13 +1,13 @@
{{!
See @typedef SidebarData
string html-logo-attributes for site logo. Must be used inside tag e.g. `class="logo" lang="en-gb"`
}}
<div id="mw-panel">
<div id="p-logo" role="banner">
<a {{{html-logo-attributes}}}></a>
<a class="mw-wiki-logo" href="{{main-page-href}}"
title="{{msg-tooltip-p-logo}}"></a>
</div>
{{#data-portals-first}}{{>Menu}}{{/data-portals-first}}
{{#array-portals-rest}}{{>Menu}}{{/array-portals-rest}}
{{#data-portals-languages}}{{>Menu}}{{/data-portals-languages}}
{{#data-portlets-first}}{{>Menu}}{{/data-portlets-first}}
{{#array-portlets-rest}}{{>Menu}}{{/array-portlets-rest}}
{{#data-portlets.data-languages}}{{>Menu}}{{/data-portlets.data-languages}}
</div>

View file

@ -17,13 +17,13 @@
string html-after-content
string msg-navigation-heading heading for entire navigation that is
usually hidden to screen readers
MenuDefinition data-personal-menu
MenuDefinition data-namespace-tabs
MenuDefinition data-variants
MenuDefinition data-page-actions
MenuDefinition data-page-actions-more
MenuDefinition data-portlets.data-personal
MenuDefinition data-portlets.data-namespaces
MenuDefinition data-portlets.data-variants
MenuDefinition data-portlets.data-views
MenuDefinition data-portlets.data-actions
object data-search-box. See SearchBox.mustache for documentation.
object data-sidebar. See Sidebar.mustache for documentation.
object data-portlets-sidebar. See Sidebar.mustache for documentation.
object data-footer for footer template partial. see Footer.mustache for documentation.
}}
<div id="mw-page-base" class="noprint"></div>
@ -53,17 +53,19 @@
<div id="mw-navigation">
<h2>{{msg-navigation-heading}}</h2>
<div id="mw-head">
{{#data-personal-menu}}{{>Menu}}{{/data-personal-menu}}
{{#data-portlets}}
{{#data-personal}}{{>Menu}}{{/data-personal}}
<div id="left-navigation">
{{#data-namespace-tabs}}{{>Menu}}{{/data-namespace-tabs}}
{{#data-namespaces}}{{>Menu}}{{/data-namespaces}}
{{#data-variants}}{{>Menu}}{{/data-variants}}
</div>
<div id="right-navigation">
{{#data-page-actions}}{{>Menu}}{{/data-page-actions}}
{{#data-page-actions-more}}{{>Menu}}{{/data-page-actions-more}}
{{#data-views}}{{>Menu}}{{/data-views}}
{{#data-actions}}{{>Menu}}{{/data-actions}}
{{#data-search-box}}{{>SearchBox}}{{/data-search-box}}
</div>
{{/data-portlets}}
</div>
{{#data-sidebar}}{{>legacy/Sidebar}}{{/data-sidebar}}
{{#data-portlets-sidebar}}{{>legacy/Sidebar}}{{/data-portlets-sidebar}}
</div>
{{#data-footer}}{{>Footer}}{{/data-footer}}

View file

@ -18,16 +18,17 @@
string html-after-content
string msg-navigation-heading
LogoOptions data-logos
MenuDefinition data-personal-menu
MenuDefinition data-namespace-tabs
MenuDefinition data-variants
MenuDefinition data-page-actions
MenuDefinition data-page-actions-more
object data-portlets
MenuDefinition data-portlets.data-personal
MenuDefinition data-portlets.data-namespaces
MenuDefinition data-portlets.data-variants
MenuDefinition data-portlets.data-views
MenuDefinition data-portlets.data-actions
object data-search-box. See SearchBox.mustache for documentation.
boolean sidebar-visible For users that want to see the sidebar on initial render, this should be
true.
string msg-vector-action-toggle-sidebar The label used by the sidebar button.
object data-sidebar. See Sidebar.mustache for documentation.
object data-portlets-sidebar. See Sidebar.mustache for documentation.
object data-footer for footer template partial. see Footer.mustache for documentation.
}}
<div class="mw-page-container">
@ -43,7 +44,7 @@
{{>Header}}
<div class="mw-workspace-container">
{{#data-sidebar}}{{>Sidebar}}{{/data-sidebar}}
{{#data-portlets-sidebar}}{{>Sidebar}}{{/data-portlets-sidebar}}
{{>Navigation}}
<div class="mw-content-container">
{{! `role` is unnecessary but kept to support selectors in any gadgets or user styles. }}

View file

@ -4,7 +4,7 @@
#mw-panel {
font-size: @font-size-nav-main;
.portal-first {
nav:first-child {
background-image: none;
h3 {

View file

@ -146,6 +146,11 @@ body {
left: 0;
}
// hide the heading of the first menu
#p-logo + .mw-portlet h3 {
display: none;
}
.mw-footer {
margin-left: 10em;
margin-top: 0;

View file

@ -29,6 +29,7 @@
"mediawiki.ui.icon"
],
"messages": [
"tooltip-p-logo",
"vector-opt-out-tooltip",
"vector-opt-out",
"navigation-heading",

View file

@ -50,7 +50,7 @@ export const PORTALS = {
},
navigation: {
id: 'p-navigation',
class: 'vector-menu-portal portal portal-first',
class: 'vector-menu-portal portal',
'html-tooltip': 'A message tooltip-p-navigation must exist for this to appear',
label: 'Navigation',
'html-user-language-attributes': htmlUserLanguageAttributes,

View file

@ -1,11 +1,8 @@
/* eslint-disable quotes */
import sidebarTemplate from '!!raw-loader!../includes/templates/Sidebar.mustache';
import sidebarLegacyTemplate from '!!raw-loader!../includes/templates/legacy/Sidebar.mustache';
import { vectorMenuTemplate } from './MenuDropdown.stories.data';
import { PORTALS } from './MenuPortal.stories.data';
const HTML_LOGO_ATTRIBUTES = `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`;
const SIDEBAR_BEFORE_OUTPUT_HOOKINFO = `Beware: Portals can be added, removed or reordered using
SidebarBeforeOutput hook as in this example.`;
@ -25,34 +22,31 @@ export const OPT_OUT_DATA = {
export const SIDEBAR_DATA = {
withNoPortals: {
'array-portals-rest': [],
'html-logo-attributes': HTML_LOGO_ATTRIBUTES
'array-portlets-rest': []
},
withPortals: {
'data-portals-first': PORTALS.navigation,
'array-portals-rest': [
'data-portlets-first': PORTALS.navigation,
'array-portlets-rest': [
PORTALS.toolbox,
PORTALS.otherProjects
],
'data-portals-languages': PORTALS.langlinks,
'html-logo-attributes': HTML_LOGO_ATTRIBUTES
'data-portals-languages': PORTALS.langlinks
},
withoutLogo: {
'data-portals-languages': PORTALS.langlinks,
'array-portals-first': PORTALS.navigation,
'array-portals-rest': [
'array-portlets-rest': [
PORTALS.toolbox,
PORTALS.otherProjects
]
},
thirdParty: {
'array-portals-rest': [
'array-portlets-rest': [
PORTALS.toolbox,
PORTALS.navigation,
{
'html-portal-content': SIDEBAR_BEFORE_OUTPUT_HOOKINFO
}
],
'html-logo-attributes': HTML_LOGO_ATTRIBUTES
]
}
};

View file

@ -19,32 +19,38 @@ import { logoTemplate } from './Logo.stories.data';
export const NAVIGATION_TEMPLATE_DATA = {
loggedInWithVariantsAndOptOut: Object.assign( {}, {
'data-personal-menu': PERSONAL_MENU_TEMPLATE_DATA.loggedInWithEcho,
'data-namespace-tabs': namespaceTabsData,
'data-page-actions': pageActionsData,
'data-variants': variantsData,
'data-portlets': {
'data-personal': PERSONAL_MENU_TEMPLATE_DATA.loggedInWithEcho,
'data-namespaces': namespaceTabsData,
'data-views': pageActionsData,
'data-variants': variantsData
},
'data-search-box': searchBoxData,
'data-sidebar': SIDEBAR_DATA.withPortals,
'data-portlets-sidebar': SIDEBAR_DATA.withPortals,
'msg-navigation-heading': 'Navigation menu',
'html-logo-attributes': `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`
}, OPT_OUT_DATA ),
loggedOutWithVariants: {
'data-personal-menu': PERSONAL_MENU_TEMPLATE_DATA.loggedOut,
'data-namespace-tabs': namespaceTabsData,
'data-page-actions': pageActionsData,
'data-variants': variantsData,
'data-portlets': {
'data-personal': PERSONAL_MENU_TEMPLATE_DATA.loggedOut,
'data-namespaces': namespaceTabsData,
'data-views': pageActionsData,
'data-variants': variantsData
},
'data-search-box': searchBoxData,
'data-sidebar': SIDEBAR_DATA.withPortals,
'data-portlets-sidebar': SIDEBAR_DATA.withPortals,
'msg-navigation-heading': 'Navigation menu',
'html-logo-attributes': `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`
},
loggedInWithMoreActions: {
'data-personal-menu': PERSONAL_MENU_TEMPLATE_DATA.loggedInWithEcho,
'data-namespace-tabs': namespaceTabsData,
'data-page-actions': pageActionsData,
'data-page-actions-more': moreData,
'data-portlets': {
'data-personal': PERSONAL_MENU_TEMPLATE_DATA.loggedInWithEcho,
'data-namespaces': namespaceTabsData,
'data-views': pageActionsData,
'data-actions': moreData
},
'data-search-box': searchBoxData,
'data-sidebar': SIDEBAR_DATA.withPortals,
'data-portlets-sidebar': SIDEBAR_DATA.withPortals,
'msg-navigation-heading': 'Navigation menu',
'html-logo-attributes': `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`
}

View file

@ -32,8 +32,8 @@
/**
* @typedef {Object} SidebarData
* @property {MenuDefinition} data-portals-languages
* @property {MenuDefinition} data-portals-first
* @property {MenuDefinition[]} array-portals-rest
* @property {MenuDefinition} data-portlets-first
* @property {MenuDefinition[]} array-portlets-rest
*/
/**

View file

@ -45,9 +45,9 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
}
/**
* @covers ::getMenuProps
* @covers ::getTemplateData
*/
public function testGetMenuProps() {
public function testGetTemplateData() {
$title = Title::newFromText( 'SkinVector' );
$context = RequestContext::getMain();
$context->setTitle( $title );
@ -78,9 +78,9 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
] );
$openVectorTemplate = TestingAccessWrapper::newFromObject( $vectorTemplate );
$props = $openVectorTemplate->getMenuProps();
$views = $props['data-page-actions'];
$namespaces = $props['data-namespace-tabs'];
$props = $openVectorTemplate->getTemplateData()['data-portlets'];
$views = $props['data-views'];
$namespaces = $props['data-namespaces'];
$this->assertSame(
[
@ -91,14 +91,13 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
'html-items' => '',
'html-after-portal' => '',
'label' => $context->msg( 'views' )->text(),
'label-id' => 'p-views-label',
'is-dropdown' => false,
],
$views
);
$variants = $props['data-variants'];
$actions = $props['data-page-actions-more'];
$actions = $props['data-actions'];
$this->assertSame(
'mw-portlet mw-portlet-namespaces vector-menu vector-menu-tabs',
$namespaces['class']
@ -113,7 +112,7 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
);
$this->assertSame(
'mw-portlet mw-portlet-personal vector-menu',
$props['data-personal-menu']['class']
$props['data-personal']['class']
);
}