diff --git a/includes/Constants.php b/includes/Constants.php index dbe1ebf..904b4e7 100644 --- a/includes/Constants.php +++ b/includes/Constants.php @@ -166,6 +166,21 @@ final class Constants { */ public const REQUIREMENT_USE_WVUI_SEARCH = 'VectorUseWvuiSearch'; + /** + * @var string + */ + public const CONFIG_CONSOLIDATE_USER_LINKS = 'VectorConsolidateUserLinks'; + + /** + * @var string + */ + public const REQUIREMENT_CONSOLIDATE_USER_LINKS = 'ConsolidateUserLinks'; + + /** + * @var string + */ + public const FEATURE_CONSOLIDATE_USER_LINKS = 'ConsolidateUserLinks'; + /** * The `mediawiki.searchSuggest` protocol piece of the SearchSatisfaction instrumention reads * the value of an element with the "data-search-loc" attribute and set the event's diff --git a/includes/Hooks.php b/includes/Hooks.php index 6588634..b2f5818 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -110,6 +110,24 @@ class Hooks { $sk->getSkinName() === 'vector' && $title && $title->canExist() ) { + if ( !self::isSkinVersionLegacy() + && isset( $content_navigation['user-menu'] ) + ) { + // If the consolidate user links feature is enabled, rearrange some links in the personal toolbar. + if ( VectorServices::getFeatureManager()->isFeatureEnabled( + Constants::FEATURE_CONSOLIDATE_USER_LINKS ) + ) { + if ( $sk->loggedin ) { + // Remove user page from personal menu dropdown for logged in users. + unset( $content_navigation['user-menu']['userpage'] ); + } + } else { + // Remove user page from personal toolbar since it will be inside the personal menu for logged in + // users when the feature flag is disabled. + unset( $content_navigation['user-page'] ); + } + } + $key = null; if ( isset( $content_navigation['actions']['watch'] ) ) { $key = 'watch'; diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index fdcc577..96433d2 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -106,6 +106,21 @@ return [ ] ); + // Feature: Consolidate personal menu links + // ================================ + $consolidateUserLinksConfig = $services->getMainConfig()->get( Constants::CONFIG_CONSOLIDATE_USER_LINKS ); + $featureManager->registerSimpleRequirement( + Constants::REQUIREMENT_CONSOLIDATE_USER_LINKS, + $consolidateUserLinksConfig[ $context->getUser()->isRegistered() ? 'logged_in' : 'logged_out' ] + ); + + $featureManager->registerFeature( + Constants::FEATURE_CONSOLIDATE_USER_LINKS, + [ + Constants::REQUIREMENT_CONSOLIDATE_USER_LINKS + ] + ); + return $featureManager; } ]; diff --git a/includes/SkinVector.php b/includes/SkinVector.php index 62c68d0..5fe05a2 100644 --- a/includes/SkinVector.php +++ b/includes/SkinVector.php @@ -158,6 +158,18 @@ class SkinVector extends SkinMustache { ( $this->isLanguagesInHeader() && empty( $this->getLanguagesCached() ) ); } + /** + * If in modern Vector and the config is set to consolidate user links, enable user link consolidation. + * @return bool + */ + private function consolidateUserLinks() { + $featureManager = VectorServices::getFeatureManager(); + return !$this->isLegacy() && + $featureManager->isFeatureEnabled( + Constants::FEATURE_CONSOLIDATE_USER_LINKS + ); + } + /** * @inheritDoc */ @@ -190,6 +202,8 @@ class SkinVector extends SkinMustache { // Conditionally used values must use null to indicate absence (not false or ''). $commonSkinData = array_merge( $parentData, [ + 'consolidate-user-links' => $this->consolidateUserLinks(), + 'page-isarticle' => (bool)$out->isArticle(), 'is-mainpage' => $title->isMainPage(), @@ -322,7 +336,12 @@ class SkinVector extends SkinMustache { self::MENU_TYPE_DEFAULT => 'vector-menu', ]; $portletData['heading-class'] = 'vector-menu-heading'; - + // Add target class to apply different icon to personal menu dropdown for logged in users. + if ( $portletData['id'] === 'p-personal' && $this->consolidateUserLinks() && !$this->getUser()->isAnon() ) { + $portletData['heading-class'] .= ' mw-portlet-personal-page__heading--auth'; + // Replace dropdown arrow with ellipsis icon if feature flag is enabled and user is logged in. + $portletData['heading-class'] .= ' mw-ui-icon mw-ui-icon-element mw-ui-icon-wikimedia-ellipsis'; + } if ( $portletData['id'] === 'p-lang' && $this->isLanguagesInHeader() ) { $portletData = $this->createULSLanguageButton( $portletData ); } @@ -340,6 +359,10 @@ class SkinVector extends SkinMustache { array $urls = [] ) : array { switch ( $label ) { + case 'user-menu': + $type = $this->consolidateUserLinks() && $this->getUser()->isRegistered() ? + self::MENU_TYPE_DROPDOWN : self::MENU_TYPE_DEFAULT; + break; case 'actions': case 'variants': $type = self::MENU_TYPE_DROPDOWN; @@ -348,7 +371,9 @@ class SkinVector extends SkinMustache { case 'namespaces': $type = self::MENU_TYPE_TABS; break; + case 'notifications': case 'personal': + case 'user-page': $type = self::MENU_TYPE_DEFAULT; break; case 'lang': diff --git a/includes/templates/Header.mustache b/includes/templates/Header.mustache index f1c2f42..28fcbee 100644 --- a/includes/templates/Header.mustache +++ b/includes/templates/Header.mustache @@ -11,7 +11,20 @@ {{>Logo}} {{#data-search-box}}{{>SearchBox}}{{/data-search-box}} - {{#data-portlets}} - {{#data-personal}}{{>Menu}}{{/data-personal}} - {{/data-portlets}} + {{^consolidate-user-links}} + {{#data-portlets}} + {{#data-personal}}{{>Menu}}{{/data-personal}} + {{/data-portlets}} + {{/consolidate-user-links}} + {{#consolidate-user-links}} + {{#data-portlets}} + {{#data-notifications}}{{>Menu}}{{/data-notifications}} + {{/data-portlets}} + {{#data-portlets}} + {{#data-user-page}}{{>Menu}}{{/data-user-page}} + {{/data-portlets}} + {{#data-portlets}} + {{#data-user-menu}}{{>Menu}}{{/data-user-menu}} + {{/data-portlets}} + {{/consolidate-user-links}} diff --git a/resources/common/components/Menu.less b/resources/common/components/Menu.less index 891c960..92ecf4b 100644 --- a/resources/common/components/Menu.less +++ b/resources/common/components/Menu.less @@ -34,7 +34,8 @@ /* Icon for registered user names & anonymous message */ #pt-anonuserpage, -#pt-userpage a { +#pt-userpage a, +#p-user-page a { background-image: url( images/user-avatar.svg ); background-position: @background-position-nav-personal-icon; background-repeat: no-repeat; @@ -44,6 +45,12 @@ padding-left: 16px !important; // stylelint-disable-line declaration-no-important } +#p-user-page a { + // Overrides directly above: padding-left: 16px !important; which does not allow for enough space between the + // personal icon asset and the user page link in the personal toolbar. + padding-left: 25px !important; // stylelint-disable-line declaration-no-important +} + #pt-userpage { padding-top: 0 !important; // stylelint-disable-line declaration-no-important @@ -56,3 +63,9 @@ #pt-anonuserpage { color: #54595d; } + +#p-user-page .vector-menu-content-list { + #ca-userpage { + padding-top: 0; + } +} diff --git a/resources/skins.vector.styles/components/UserMenu.less b/resources/skins.vector.styles/components/UserMenu.less new file mode 100644 index 0000000..02da702 --- /dev/null +++ b/resources/skins.vector.styles/components/UserMenu.less @@ -0,0 +1,17 @@ +// Overrides personal menu styles for consolidated user links. +.mw-portlet-personal.vector-menu-dropdown h3:after { + content: none; +} + +#p-personal.vector-menu-dropdown .vector-menu-content-list { + justify-content: flex-start; + + li { + font-size: 100%; + } +} + +#p-personal .vector-menu-content { + left: unset; + right: 0; +} diff --git a/resources/skins.vector.styles/layouts/screen.less b/resources/skins.vector.styles/layouts/screen.less index 36d4661..8f37193 100644 --- a/resources/skins.vector.styles/layouts/screen.less +++ b/resources/skins.vector.styles/layouts/screen.less @@ -250,6 +250,25 @@ body { } } +#p-personal, +.mw-portlet-notifications { + li { + float: left; + margin-left: 0.75em; + } +} + +.mw-portlet-personal.vector-menu-dropdown { + h3 { + margin: 0 0 0 12px; + padding: 0 12px 0 0; + } + + .vector-menu-content { + border-top-width: 1px; + } +} + #p-personal { flex-grow: 1; flex-basis: @min-width-personal-tools; @@ -258,6 +277,21 @@ body { float: right; } +#p-personal.vector-menu-dropdown { + flex-grow: inherit; + flex-basis: inherit; + margin-left: inherit; + float: inherit; + + li { + margin-left: 0; + } +} + +#p-user-page li { + margin-left: 1em; +} + #mw-sidebar-button { float: left; // Browser: IE9 support - button as flex-child fallback. margin-left: -@margin-horizontal-sidebar-button-icon; diff --git a/resources/skins.vector.styles/skin.less b/resources/skins.vector.styles/skin.less index 0b6c773..18f4030 100644 --- a/resources/skins.vector.styles/skin.less +++ b/resources/skins.vector.styles/skin.less @@ -15,6 +15,7 @@ @import './components/VueEnhancedSearchBox.less'; @import './components/Sidebar.less'; @import './components/LanguageButton.less'; + @import './components/UserMenu.less'; } @media all { diff --git a/skin.json b/skin.json index ad2fa3b..0c2451e 100644 --- a/skin.json +++ b/skin.json @@ -31,6 +31,7 @@ "mediawiki.ui.icon" ], "messages": [ + "createaccount", "otherlanguages", "tooltip-p-logo", "vector-opt-out-tooltip", @@ -143,7 +144,8 @@ "class": "ResourceLoaderOOUIIconPackModule", "variants": [], "icons": [ - "language" + "language", + "ellipsis" ] }, "skins.vector.js": { @@ -287,6 +289,13 @@ "value": false, "description": "@var boolean Enables or disables the language in header treatment A/B test. See https://phabricator.wikimedia.org/T280825 and associated tasks for additional detail." }, + "VectorConsolidateUserLinks": { + "value": { + "logged_in": false, + "logged_out": false + }, + "description": "@var array Moves the personal user links into a consolidated dropdown menu with the exception of the user page link. User page link moves out of personal menu into top level item next to notifications." + }, "VectorDisableSidebarPersistence": { "value": false, "description": "@var boolean Temporary feature flag that disables saving the sidebar expanded/collapsed state as a user-preference (triggered via clicking the main menu icon). This is intended as a temporary kill-switch in the event that the DB is overloaded with writes to the user_options table."