Refactor: Standardize menu data (DRY!)

VectorTemplate has various functions that repeat themselves, only
differing in their choice of names.

This refactor begins by focusing on the personal menu and introducing
a generic getMenuData function. Hardcoded `p-personal` is replaced with
an `id` template key and `msg-label` is renamed `label`.

Future patches will simplify VectorTemplate by using this new
function.

You'll note the resulting PersonalMenu.mustache file is identical
to VectorTabs. These will be merged in I098e6921e8f7ef65dacacf09b9c25f70c945e58e

Bug: T249372
Change-Id: I5ae44a1008b065381eeff93f9fa625be5c5a9de9
This commit is contained in:
jdlrobson 2020-04-03 13:05:22 -07:00
parent 16670834d7
commit 63ee9450b7
7 changed files with 120 additions and 53 deletions

View File

@ -29,6 +29,13 @@ use MediaWiki\MediaWikiServices;
* @ingroup Skins
*/
class VectorTemplate extends BaseTemplate {
/* @var int */
private const MENU_TYPE_DEFAULT = 0;
/* @var int */
private const MENU_TYPE_TABS = 1;
/* @var int */
private const MENU_TYPE_DROPDOWN = 2;
/**
* T243281: Code used to track clicks to opt-out link.
*
@ -179,7 +186,6 @@ class VectorTemplate extends BaseTemplate {
'array-footer-rows' => $this->getTemplateFooterRows(),
],
'html-navigation-heading' => $this->getMsg( 'navigation-heading' ),
'data-personal-menu' => $this->buildPersonalProps(),
'data-namespace-tabs' => $this->buildNamespacesProps(),
'data-variants' => $this->buildVariantsProps(),
'data-page-actions' => $this->buildViewsProps(),
@ -193,7 +199,7 @@ class VectorTemplate extends BaseTemplate {
]
)
] + $this->buildSidebarProps( $this->get( 'sidebar', [] ) ),
];
] + $this->getMenuProps();
// The following logic is unqiue to Vector (not used by legacy Vector) and
// is planned to be moved in a follow-up patch.
@ -491,38 +497,80 @@ class VectorTemplate extends BaseTemplate {
}
/**
* @param string $label to be used to derive the id and human readable label of the menu
* @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 array $options (optional) to be passed to makeListItem
* @return array
*/
private function buildPersonalProps() : array {
$personalTools = $this->getPersonalTools();
$props = [
'empty-portlet' => ( count( $this->get( 'personal_urls', [] ) ) == 0 ) ? 'emptyPortlet' : '',
'msg-label' => $this->getMsg( 'personaltools' )->text(),
'html-userlangattributes' => $this->get( 'userlangattributes', '' ),
'html-loggedin' => '',
'html-personal-tools' => '',
'html-lang-selector' => '',
private function getMenuData(
string $label, array $urls = [],
int $type = self::MENU_TYPE_DEFAULT, array $options = []
) : array {
$class = ( count( $urls ) == 0 ) ? 'emptyPortlet' : '';
$extraClasses = [
self::MENU_TYPE_DROPDOWN => 'vectorMenu',
self::MENU_TYPE_TABS => 'vectorTabs',
self::MENU_TYPE_DEFAULT => '',
];
$props = [
'id' => "p-$label",
'label-id' => "p-{$label}-label",
'label' => $this->getMsg( $label )->text(),
'html-userlangattributes' => $this->get( 'userlangattributes', '' ),
'html-items' => '',
'class' => trim( "$class $extraClasses[$type]" ),
];
foreach ( $urls as $key => $item ) {
$props['html-items'] .= $this->makeListItem( $key, $item, $options );
}
return $props;
}
/**
* @return array
*/
private function getMenuProps() : array {
$personalTools = $this->getPersonalTools();
// For logged out users Vector shows a "Not logged in message"
// This should be upstreamed to core, with instructions for how to hide it for skins
// that do not want it.
// For now we create a dedicated list item to avoid having to sync the API internals
// of makeListItem.
if ( !$this->getSkin()->getUser()->isLoggedIn() && User::groupHasPermission( '*', 'edit' ) ) {
$props['html-loggedin'] =
$loggedIn =
Html::element( 'li',
[ 'id' => 'pt-anonuserpage' ],
$this->getMsg( 'notloggedin' )->text()
);
} else {
$loggedIn = '';
}
// This code doesn't belong here, it belongs in the UniversalLanguageSelector
// It is here to workaround the fact that it wants to be the first item in the personal menus.
if ( array_key_exists( 'uls', $personalTools ) ) {
$props['html-lang-selector'] = $this->makeListItem( 'uls', $personalTools[ 'uls' ] );
$uls = $this->makeListItem( 'uls', $personalTools[ 'uls' ] );
unset( $personalTools[ 'uls' ] );
} else {
$uls = '';
}
foreach ( $personalTools as $key => $item ) {
$props['html-personal-tools'] .= $this->makeListItem( $key, $item );
}
$ptools = $this->getMenuData( 'personal', $personalTools );
// Append additional link items if present.
$ptools['html-items'] = $uls . $loggedIn . $ptools['html-items'];
// Unlike other menu items, there is no language key corresponding with its menu key.
// Inconsistently this language key lives inside `personaltools`
// This line can be removed once the core message `personal` has been added.
$ptools['label'] = $this->getMsg( 'personaltools' )->text();
return $props;
return [
'data-personal-menu' => $ptools,
];
}
/**

View File

@ -1,17 +1,10 @@
{{!
string|null empty-portlet
string msg-label
string|null html-userlangattributes
string|null html-lang-selector
string|null html-loggedin
string|null html-personal-tools
@see MenuDefinition
}}
<div id="p-personal" role="navigation" {{#empty-portlet}}class="{{empty-portlet}}"{{/empty-portlet}} aria-labelledby="p-personal-label">
<h3 id="p-personal-label">{{msg-label}}</h3>
<div id="{{id}}" role="navigation" class="{{class}}" aria-labelledby="{{label-id}}">
<h3 id="{{label-id}}">{{label}}</h3>
<ul {{{html-userlangattributes}}}>
{{{html-lang-selector}}}
{{{html-loggedin}}}
{{{html-personal-tools}}}
{{{html-items}}}
</ul>
</div>

View File

@ -22,7 +22,7 @@
string html-dataAfterContent
string html-navigation-heading heading for entire navigation that is usually hidden to screen
readers
object data-personal-menu See PersonalMenu.mustache for documentation.
MenuDefinition data-personal-menu See PersonalMenu.mustache for documentation.
object data-namespace-tabs. See VectorTabs.mustache for documentation.
object data-variants. See VectorMenu.mustache for documentation.
object data-page-actions. See VectorTabs.mustache for documentation.

View File

@ -22,7 +22,7 @@
string html-dataAfterContent
string html-navigation-heading heading for entire navigation that is
usually hidden to screen readers
object data-personal-menu See PersonalMenu.mustache for documentation.
MenuDefinition data-personal-menu See PersonalMenu.mustache for documentation.
object data-namespace-tabs. See VectorTabs.mustache for documentation.
object data-variants. See VectorMenu.mustache for documentation.
object data-page-actions. See VectorTabs.mustache for documentation.

View File

@ -1,28 +1,45 @@
import personalMenuTemplate from '!!raw-loader!../includes/templates/PersonalMenu.mustache';
import { htmluserlangattributes } from './utils';
/**
* @type {MenuDefinition}
*/
const loggedOut = {
'msg-label': 'Personal tools',
id: 'p-personal',
class: '',
'label-id': 'p-personal-label',
label: 'Personal tools',
'html-userlangattributes': htmluserlangattributes,
'html-loggedin': '<li id="pt-anonuserpage">Not logged in</li>',
'html-personal-tools': `<li id="pt-anontalk"><a href="/wiki/Special:MyTalk" title="Discussion about edits from this IP address [⌃⌥n]" accesskey="n">Talk</a></li><li id="pt-anoncontribs"><a href="/wiki/Special:MyContributions" title="A list of edits made from this IP address [⌃⌥y]" accesskey="y">Contributions</a></li><li id="pt-createaccount"><a href="/w/index.php?title=Special:CreateAccount&amp;returnto=Main+Page" title="You are encouraged to create an account and log in; however, it is not mandatory">Create account</a></li><li id="pt-login"><a href="/w/index.php?title=Special:UserLogin&amp;returnto=Main+Page" title="You're encouraged to log in; however, it's not mandatory. [⌃⌥o]" accesskey="o">Log in</a></li>`
'html-items': `'<li id="pt-anonuserpage">Not logged in</li><li id="pt-anontalk"><a href="/wiki/Special:MyTalk" title="Discussion about edits from this IP address [⌃⌥n]" accesskey="n">Talk</a></li><li id="pt-anoncontribs"><a href="/wiki/Special:MyContributions" title="A list of edits made from this IP address [⌃⌥y]" accesskey="y">Contributions</a></li><li id="pt-createaccount"><a href="/w/index.php?title=Special:CreateAccount&amp;returnto=Main+Page" title="You are encouraged to create an account and log in; however, it is not mandatory">Create account</a></li><li id="pt-login"><a href="/w/index.php?title=Special:UserLogin&amp;returnto=Main+Page" title="You're encouraged to log in; however, it's not mandatory. [⌃⌥o]" accesskey="o">Log in</a></li>`
};
/**
* @type {MenuDefinition}
*/
const loggedInWithEcho = {
'msg-label': 'Personal tools',
id: 'p-personal',
'label-id': 'p-personal-label',
label: 'Personal tools',
'html-userlangattributes': htmluserlangattributes,
'html-loggedin': '',
'html-personal-tools': `<li id="pt-userpage"><a href="/wiki/User:Jdlrobson" dir="auto" title="Your user page [⌃⌥.]" accesskey=".">Jdlrobson</a></li><li id="pt-notifications-alert"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-bell mw-echo-notifications-badge-all-read" data-counter-num="0" data-counter-text="0" title="Your alerts">Alerts (0)</a></li><li id="pt-notifications-notice"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-tray" data-counter-num="3" data-counter-text="3" title="Your notices">Notices (3)</a></li><li id="pt-mytalk"><a href="/wiki/User_talk:Jdlrobson" title="Your talk page [⌃⌥n]" accesskey="n">Talk</a></li><li id="pt-sandbox"><a href="/wiki/User:Jdlrobson/sandbox" title="Your sandbox">Sandbox</a></li><li id="pt-preferences"><a href="/wiki/Special:Preferences" title="Your preferences">Preferences</a></li><li id="pt-betafeatures"><a href="/wiki/Special:Preferences#mw-prefsection-betafeatures" title="Beta features">Beta</a></li><li id="pt-watchlist"><a href="/wiki/Special:Watchlist" title="A list of pages you are monitoring for changes [⌃⌥l]" accesskey="l">Watchlist</a></li><li id="pt-mycontris"><a href="/wiki/Special:Contributions/Jdlrobson" title="A list of your contributions [⌃⌥y]" accesskey="y">Contributions</a></li><li id="pt-logout"><a href="/w/index.php?title=Special:UserLogout&amp;returnto=Main+Page&amp;returntoquery=useskin%3Dvector" title="Log out">Log out</a></li>`
'html-items': `<li id="pt-userpage"><a href="/wiki/User:Jdlrobson" dir="auto" title="Your user page [⌃⌥.]" accesskey=".">Jdlrobson</a></li><li id="pt-notifications-alert"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-bell mw-echo-notifications-badge-all-read" data-counter-num="0" data-counter-text="0" title="Your alerts">Alerts (0)</a></li><li id="pt-notifications-notice"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-tray" data-counter-num="3" data-counter-text="3" title="Your notices">Notices (3)</a></li><li id="pt-mytalk"><a href="/wiki/User_talk:Jdlrobson" title="Your talk page [⌃⌥n]" accesskey="n">Talk</a></li><li id="pt-sandbox"><a href="/wiki/User:Jdlrobson/sandbox" title="Your sandbox">Sandbox</a></li><li id="pt-preferences"><a href="/wiki/Special:Preferences" title="Your preferences">Preferences</a></li><li id="pt-betafeatures"><a href="/wiki/Special:Preferences#mw-prefsection-betafeatures" title="Beta features">Beta</a></li><li id="pt-watchlist"><a href="/wiki/Special:Watchlist" title="A list of pages you are monitoring for changes [⌃⌥l]" accesskey="l">Watchlist</a></li><li id="pt-mycontris"><a href="/wiki/Special:Contributions/Jdlrobson" title="A list of your contributions [⌃⌥y]" accesskey="y">Contributions</a></li><li id="pt-logout"><a href="/w/index.php?title=Special:UserLogout&amp;returnto=Main+Page&amp;returntoquery=useskin%3Dvector" title="Log out">Log out</a></li>`
};
const ULS_LANGUAGE_SELECTOR = '<li class="uls-trigger active"><a href="#">English</a></li>';
/**
* @type {MenuDefinition}
*/
const loggedInWithULS = {
'msg-label': 'Personal tools',
id: 'p-personal',
'label-id': 'p-personal-label',
label: 'Personal tools',
'html-userlangattributes': htmluserlangattributes,
'html-lang-selector': '<li class="uls-trigger active"><a href="#">English</a></li>',
'html-loggedin': '',
'html-personal-tools': `<li id="pt-userpage"><a href="/wiki/User:Jdlrobson" dir="auto" title="Your user page [⌃⌥.]" accesskey=".">Jdlrobson</a></li><li id="pt-notifications-alert"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-bell mw-echo-notifications-badge-all-read" data-counter-num="0" data-counter-text="0" title="Your alerts">Alerts (0)</a></li><li id="pt-notifications-notice"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-tray" data-counter-num="3" data-counter-text="3" title="Your notices">Notices (3)</a></li><li id="pt-mytalk"><a href="/wiki/User_talk:Jdlrobson" title="Your talk page [⌃⌥n]" accesskey="n">Talk</a></li><li id="pt-sandbox"><a href="/wiki/User:Jdlrobson/sandbox" title="Your sandbox">Sandbox</a></li><li id="pt-preferences"><a href="/wiki/Special:Preferences" title="Your preferences">Preferences</a></li><li id="pt-betafeatures"><a href="/wiki/Special:Preferences#mw-prefsection-betafeatures" title="Beta features">Beta</a></li><li id="pt-watchlist"><a href="/wiki/Special:Watchlist" title="A list of pages you are monitoring for changes [⌃⌥l]" accesskey="l">Watchlist</a></li><li id="pt-mycontris"><a href="/wiki/Special:Contributions/Jdlrobson" title="A list of your contributions [⌃⌥y]" accesskey="y">Contributions</a></li><li id="pt-logout"><a href="/w/index.php?title=Special:UserLogout&amp;returnto=Main+Page&amp;returntoquery=useskin%3Dvector" title="Log out">Log out</a></li>`
'html-items': `${ULS_LANGUAGE_SELECTOR}<li id="pt-userpage"><a href="/wiki/User:Jdlrobson" dir="auto" title="Your user page [⌃⌥.]" accesskey=".">Jdlrobson</a></li><li id="pt-notifications-alert"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-bell mw-echo-notifications-badge-all-read" data-counter-num="0" data-counter-text="0" title="Your alerts">Alerts (0)</a></li><li id="pt-notifications-notice"><a href="/wiki/Special:Notifications" class="mw-echo-notifications-badge mw-echo-notification-badge-nojs oo-ui-icon-tray" data-counter-num="3" data-counter-text="3" title="Your notices">Notices (3)</a></li><li id="pt-mytalk"><a href="/wiki/User_talk:Jdlrobson" title="Your talk page [⌃⌥n]" accesskey="n">Talk</a></li><li id="pt-sandbox"><a href="/wiki/User:Jdlrobson/sandbox" title="Your sandbox">Sandbox</a></li><li id="pt-preferences"><a href="/wiki/Special:Preferences" title="Your preferences">Preferences</a></li><li id="pt-betafeatures"><a href="/wiki/Special:Preferences#mw-prefsection-betafeatures" title="Beta features">Beta</a></li><li id="pt-watchlist"><a href="/wiki/Special:Watchlist" title="A list of pages you are monitoring for changes [⌃⌥l]" accesskey="l">Watchlist</a></li><li id="pt-mycontris"><a href="/wiki/Special:Contributions/Jdlrobson" title="A list of your contributions [⌃⌥y]" accesskey="y">Contributions</a></li><li id="pt-logout"><a href="/w/index.php?title=Special:UserLogout&amp;returnto=Main+Page&amp;returntoquery=useskin%3Dvector" title="Log out">Log out</a></li>`
};
/**
* @type {Object.<string, MenuDefinition>}
*/
const PERSONAL_MENU_TEMPLATE_DATA = {
loggedOut,
loggedInWithEcho,

View File

@ -4,18 +4,6 @@ import '../resources/skins.vector.styles/Portal.less';
import '../.storybook/common.less';
import { placeholder, htmluserlangattributes } from './utils';
/**
* @typedef {Object} PortletContext
* @prop {string} portal-id
* @prop {string} html-tooltip
* @prop {string} msg-label-id
* @prop {string} [html-userlangattributes]
* @prop {string} msg-label
* @prop {string} html-portal-content
* @prop {string} [html-after-portal]
* @prop {string} [html-hook-vector-after-toolbox] Deprecated and used by the toolbox portal.
*/
/**
* @param {PortletContext} data
* @return {HTMLElement}

21
stories/types.js Normal file
View File

@ -0,0 +1,21 @@
/**
* @typedef {Object} MenuDefinition
* @prop {string} id
* @prop {string} label-id
* @prop {string} label
* @prop {string} html-items
* @prop {string} [class] of menu
* @prop {string} [html-userlangattributes]
*/
/**
* @typedef {Object} PortletContext
* @prop {string} portal-id
* @prop {string} html-tooltip
* @prop {string} msg-label-id
* @prop {string} [html-userlangattributes]
* @prop {string} msg-label
* @prop {string} html-portal-content
* @prop {string} [html-after-portal]
* @prop {string} [html-hook-vector-after-toolbox] Deprecated and used by the toolbox portal.
*/