Icons: Watchstar and wikilove are upgraded

Can be tested by appending ?vectorvisualenhancementnext=1
to URL

Bug: T310838
Bug: T234990
Bug: T234550
Depends-On: I76d0d94c9006cc5f5680849ecdd1c382c16e34ba
Depends-On: Ib7c3021db014827b4b88cac855afc0b54a360f8c
Change-Id: Ie2ffa5c3ecf270c1bb1f315937023ae7ace5ed30
This commit is contained in:
Jon Robson 2022-09-02 07:57:31 -07:00 committed by Jdlrobson
parent e629775aff
commit 97cf735de2
7 changed files with 173 additions and 30 deletions

View File

@ -153,7 +153,7 @@ class Hooks implements
} }
/** /**
* Transforms watch item inside the action navigation menu * Moves watch item from actions to views menu.
* *
* @param array &$content_navigation * @param array &$content_navigation
*/ */
@ -168,15 +168,60 @@ class Hooks implements
// Promote watch link from actions to views and add an icon // Promote watch link from actions to views and add an icon
if ( $key !== null ) { if ( $key !== null ) {
self::appendClassToItem(
$content_navigation['actions'][$key]['class'],
[ 'icon' ]
);
$content_navigation['views'][$key] = $content_navigation['actions'][$key]; $content_navigation['views'][$key] = $content_navigation['actions'][$key];
unset( $content_navigation['actions'][$key] ); unset( $content_navigation['actions'][$key] );
} }
} }
/**
* Adds icons to items in the "views" menu.
*
* @param array &$content_navigation
* @param bool $isLegacy is this the legacy Vector skin?
*/
private static function updateViewsMenuIcons( &$content_navigation, $isLegacy ) {
$featureManager = VectorServices::getFeatureManager();
$visualEnhancements = $featureManager->isFeatureEnabled( Constants::FEATURE_VISUAL_ENHANCEMENTS );
foreach ( $content_navigation['views'] as $key => $item ) {
$icon = $item['icon'] ?? null;
if ( $icon ) {
if ( $isLegacy || !$featureManager->isFeatureEnabled( Constants::FEATURE_VISUAL_ENHANCEMENTS ) ) {
self::appendClassToItem(
$item['class'],
[ 'icon' ]
);
} else {
// Force the item as a button with hidden text.
$item['button'] = true;
$item['text-hidden'] = true;
$item = self::updateMenuItemData( $item, true );
}
} else {
self::appendClassToItem(
$item['class'],
[ 'vector-tab-noicon' ]
);
}
$content_navigation['views'][$key] = $item;
}
}
/**
* All associated pages menu items do not have icons so are given the vector-tab-noicon class.
*
* @param array &$content_navigation
*/
private static function updateAssociatedPagesMenuIcons( &$content_navigation ) {
foreach ( $content_navigation['associated-pages'] as $key => $item ) {
self::appendClassToItem(
$item['class'],
[ 'vector-tab-noicon' ]
);
$content_navigation['associated-pages'][$key] = $item;
}
}
/** /**
* Adds class to a property * Adds class to a property
* *
@ -376,9 +421,10 @@ class Hooks implements
* @param array $item data to update * @param array $item data to update
* @param string $buttonClassProp property to append button classes * @param string $buttonClassProp property to append button classes
* @param string $iconHtmlProp property to set icon HTML * @param string $iconHtmlProp property to set icon HTML
* @param bool $isSmallIcon when set a small icon will be applied rather than the standard icon size
* @return array $item Updated data * @return array $item Updated data
*/ */
private static function updateItemData( $item, $buttonClassProp, $iconHtmlProp ) { private static function updateItemData( $item, $buttonClassProp, $iconHtmlProp, $isSmallIcon = false ) {
$hasButton = $item['button'] ?? false; $hasButton = $item['button'] ?? false;
$hideText = $item['text-hidden'] ?? false; $hideText = $item['text-hidden'] ?? false;
$isCollapsible = $item['collapsible'] ?? false; $isCollapsible = $item['collapsible'] ?? false;
@ -403,6 +449,9 @@ class Hooks implements
// We should seek to remove all these instances. // We should seek to remove all these instances.
'mw-ui-icon-wikimedia-' . $icon 'mw-ui-icon-wikimedia-' . $icon
]; ];
if ( $isSmallIcon ) {
$iconElementClasses[] = 'mw-ui-icon-small';
}
self::appendClassToItem( $item[ $buttonClassProp ], $iconElementClasses ); self::appendClassToItem( $item[ $buttonClassProp ], $iconElementClasses );
} else { } else {
$item[ $iconHtmlProp ] = self::makeIcon( $icon ); $item[ $iconHtmlProp ] = self::makeIcon( $icon );
@ -439,12 +488,13 @@ class Hooks implements
* Updates template data for Vector menu items. * Updates template data for Vector menu items.
* *
* @param array $item menu item data to update * @param array $item menu item data to update
* @param bool $isSmallIcon when set a small icon will be applied rather than the standard icon size
* @return array $item Updated menu item data * @return array $item Updated menu item data
*/ */
public static function updateMenuItemData( $item ) { public static function updateMenuItemData( $item, $isSmallIcon = false ) {
$buttonClassProp = 'link-class'; $buttonClassProp = 'link-class';
$iconHtmlProp = 'link-html'; $iconHtmlProp = 'link-html';
return self::updateItemData( $item, $buttonClassProp, $iconHtmlProp ); return self::updateItemData( $item, $buttonClassProp, $iconHtmlProp, $isSmallIcon );
} }
/** /**
@ -500,19 +550,27 @@ class Hooks implements
$title = $sk->getRelevantTitle(); $title = $sk->getRelevantTitle();
$skinName = $sk->getSkinName(); $skinName = $sk->getSkinName();
if ( self::isVectorSkin( $skinName ) ) { // These changes should only happen in Vector.
if ( if ( !$skinName || !self::isVectorSkin( $skinName ) ) {
$sk->getConfig()->get( 'VectorUseIconWatch' ) && return;
$title && $title->canExist()
) {
self::updateActionsMenu( $content_navigation );
}
self::updateUserLinksItems( $sk, $content_navigation );
} }
if (
$sk->getConfig()->get( 'VectorUseIconWatch' ) &&
$title && $title->canExist()
) {
self::updateActionsMenu( $content_navigation );
}
self::updateUserLinksItems( $sk, $content_navigation );
if ( $skinName === Constants::SKIN_NAME_MODERN ) { if ( $skinName === Constants::SKIN_NAME_MODERN ) {
self::createMoreOverflowMenu( $content_navigation ); self::createMoreOverflowMenu( $content_navigation );
} }
// The updating of the views menu happens /after/ the overflow menu has been created
// this avoids icons showing in the more overflow menu.
self::updateViewsMenuIcons( $content_navigation, self::isSkinVersionLegacy( $skinName ) );
self::updateAssociatedPagesMenuIcons( $content_navigation );
} }
/** /**

View File

@ -2,6 +2,7 @@ var languageButton = require( './languageButton.js' ),
initSearchLoader = require( './searchLoader.js' ).initSearchLoader, initSearchLoader = require( './searchLoader.js' ).initSearchLoader,
dropdownMenus = require( './dropdownMenus.js' ).dropdownMenus, dropdownMenus = require( './dropdownMenus.js' ).dropdownMenus,
sidebarPersistence = require( './sidebarPersistence.js' ), sidebarPersistence = require( './sidebarPersistence.js' ),
watchstar = require( './watchstar.js' ),
checkbox = require( './checkbox.js' ); checkbox = require( './checkbox.js' );
/** /**
@ -74,6 +75,9 @@ function main( window ) {
languageButton(); languageButton();
dropdownMenus(); dropdownMenus();
addNamespacesGadgetSupport(); addNamespacesGadgetSupport();
if ( document.body.classList.contains( 'vector-feature-visual-enhancement-next-enabled' ) ) {
watchstar();
}
} }
/** /**

View File

@ -0,0 +1,24 @@
module.exports = function () {
mw.hook( 'wikipage.watchlistChange' ).add(
function ( /** @type {boolean} */ isWatched, /** @type {string} */ expiry ) {
var watchElement = document.querySelectorAll( '#ca-watch a, #ca-unwatch a' )[ 0 ];
if ( !watchElement ) {
return;
}
watchElement.classList.remove(
'mw-ui-icon-wikimedia-unStar',
'mw-ui-icon-wikimedia-star',
'mw-ui-icon-wikimedia-halfStar'
);
if ( isWatched ) {
if ( expiry === 'infinity' ) {
watchElement.classList.add( 'mw-ui-icon-wikimedia-unStar' );
} else {
watchElement.classList.add( 'mw-ui-icon-wikimedia-halfStar' );
}
} else {
watchElement.classList.add( 'mw-ui-icon-wikimedia-star' );
}
}
);
};

View File

@ -15,8 +15,11 @@
} }
/* focus and hover have outlines. Text underline interferes with bottom border */ /* focus and hover have outlines. Text underline interferes with bottom border */
.mw-list-item a:focus, /* FIXME: Remove 2 not selectors when cache has cleared for Ie2ffa5c3ecf270c1bb1f315937023ae7ace5ed30 */
.mw-list-item a:hover { .mw-list-item a:not( .mw-ui-icon ):focus,
.mw-list-item a:not( .mw-ui-icon ):hover,
.mw-list-item.vector-tab-noicon a:focus,
.mw-list-item.vector-tab-noicon a:hover {
text-decoration: none; text-decoration: none;
border-bottom: @border-width-base @border-style-base; border-bottom: @border-width-base @border-style-base;
} }
@ -37,11 +40,18 @@
* Tab list item appearance. Applies to both <li>'s inside .vector-menu-tabs * Tab list item appearance. Applies to both <li>'s inside .vector-menu-tabs
* and dropdown menus inside the article toolbar * and dropdown menus inside the article toolbar
*/ */
// FIXME: Remove the body selector once Ie2ffa5c3ecf270c1bb1f315937023ae7ace5ed30 is in production
/* for cached HTML */ body:not( .vector-feature-visual-enhancement-next-enabled ) .vector-menu-tabs .mw-list-item,
.vector-menu-tabs .mw-list-item.vector-tab-noicon,
.mw-article-toolbar-container .vector-menu-dropdown {
margin: 0 @padding-horizontal-tabs;
}
.vector-menu-tabs .mw-list-item, .vector-menu-tabs .mw-list-item,
.mw-article-toolbar-container .vector-menu-dropdown { .mw-article-toolbar-container .vector-menu-dropdown {
float: left; float: left;
white-space: nowrap; white-space: nowrap;
margin: 0 @padding-horizontal-tabs; margin-bottom: 0; /* overrides default `li` styling */
// target links inside of .vector-tab-menu // target links inside of .vector-tab-menu
// and dropdown menu headings inside the article toolbar. // and dropdown menu headings inside the article toolbar.
@ -51,19 +61,42 @@
.vector-menu-heading { .vector-menu-heading {
display: inline-flex; display: inline-flex;
position: relative; position: relative;
// Top & bottom padding to increase clickable area.
padding: 18px 0 7px 0;
// bottom margin to overlap border with toolbar border.
margin-bottom: -1px;
cursor: pointer; cursor: pointer;
border-bottom: @border-width-base @border-style-base transparent;
// max-height & box-sizing to make link, watchstar & dropdown height consistent. // max-height & box-sizing to make link, watchstar & dropdown height consistent.
// NOTE: Was 40px instead of 41, but changed to avoid visual regressions. // NOTE: Was 40px instead of 41, but changed to avoid visual regressions.
max-height: unit( 41 / @font-size-tabs / @font-size-browser, em ); max-height: unit( 41 / @font-size-tabs / @font-size-browser, em );
box-sizing: border-box; box-sizing: border-box;
font-weight: normal;
}
.vector-menu-heading {
// For better compatibility with gadgets (like Twinkle) that append // For better compatibility with gadgets (like Twinkle) that append
// <H3> elements as dropdown headings (which was the convention in legacy Vector). // <H3> elements as dropdown headings (which was the convention in legacy Vector).
font-size: inherit; font-size: inherit;
font-weight: normal; }
/* FIXME: Remove cached HTML selector (> a:not( .mw-ui-icon ))
when Ie2ffa5c3ecf270c1bb1f315937023ae7ace5ed30 is in production */
&.vector-tab-noicon > a,
& > a:not( .mw-ui-icon ),
.vector-menu-heading {
// Top & bottom padding to increase clickable area.
padding: 18px 0 7px 0;
// bottom margin to overlap border with toolbar border.
margin-bottom: -1px;
}
}
// With mw-ui-icons, expand watchstar and wikilove links it to cover full touch area
.vector-feature-visual-enhancement-next-enabled {
.vector-menu-tabs {
.mw-list-item {
.mw-ui-icon {
// Align small icons with the bottom of the tabs.
// Height of tab is 41px, and small icon is 36px,
// With 1px border, 41 - 36 + 1;
margin: 4px 0 0 0;
}
}
} }
} }

View File

@ -5,7 +5,8 @@
/* Watch/Unwatch Icon Styling */ /* Watch/Unwatch Icon Styling */
/* Only use icon if the menu item is not collapsed into the "More" dropdown /* Only use icon if the menu item is not collapsed into the "More" dropdown
* (in which case it is inside `.vector-menu-dropdown` instead of `.vector-menu-tabs`). */ * (in which case it is inside `.vector-menu-dropdown` instead of `.vector-menu-tabs`). */
.vector-menu-tabs { // Note: there's no watchstar for anon users so no need to worry about cached HTML when changing this class
.vector-feature-visual-enhancement-next-disabled .vector-menu-tabs {
@size-watchlink-icon: unit( 16 / @font-size-tabs / @font-size-browser, em ); @size-watchlink-icon: unit( 16 / @font-size-tabs / @font-size-browser, em );
.mw-watchlink.icon a { .mw-watchlink.icon a {
@ -59,3 +60,15 @@
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }
} }
// Loading watchstar link class.
.vector-feature-visual-enhancement-next-enabled {
.mw-watchlink .loading:before {
.rotation( 500ms );
/* Suppress the hilarious rotating focus outline on Firefox */
outline: 0;
cursor: default;
pointer-events: none;
transform-origin: 50% 50%;
}
}

View File

@ -311,6 +311,7 @@
} }
}, },
"icons": [ "icons": [
"heart",
"language", "language",
"ellipsis", "ellipsis",
"userAvatar", "userAvatar",
@ -387,6 +388,7 @@
"name": "resources/skins.vector.js/config.json", "name": "resources/skins.vector.js/config.json",
"callback": "MediaWiki\\Skins\\Vector\\Hooks::getVectorResourceLoaderConfig" "callback": "MediaWiki\\Skins\\Vector\\Hooks::getVectorResourceLoaderConfig"
}, },
"resources/skins.vector.js/watchstar.js",
"resources/skins.vector.js/dropdownMenus.js", "resources/skins.vector.js/dropdownMenus.js",
"resources/skins.vector.js/checkbox.js", "resources/skins.vector.js/checkbox.js",
"resources/skins.vector.js/sidebarPersistence.js", "resources/skins.vector.js/sidebarPersistence.js",

View File

@ -394,19 +394,24 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
*/ */
public function testOnSkinTemplateNavigation() { public function testOnSkinTemplateNavigation() {
$this->setMwGlobals( [ $this->setMwGlobals( [
'wgVectorUseIconWatch' => true 'wgVectorUseIconWatch' => true,
'wgVectorVisualEnhancementNext' => false,
] ); ] );
$skin = new SkinVector22( [ 'name' => 'vector' ] ); $skin = new SkinVector22( [ 'name' => 'vector' ] );
$skin->getContext()->setTitle( Title::newFromText( 'Foo' ) ); $skin->getContext()->setTitle( Title::newFromText( 'Foo' ) );
$contentNavWatch = [ $contentNavWatch = [
'associated-pages' => [],
'views' => [],
'actions' => [ 'actions' => [
'watch' => [ 'class' => [ 'watch' ] ], 'watch' => [ 'class' => [ 'watch' ], 'icon' => 'star' ],
] ]
]; ];
$contentNavUnWatch = [ $contentNavUnWatch = [
'associated-pages' => [],
'views' => [],
'actions' => [ 'actions' => [
'move' => [ 'class' => [ 'move' ] ], 'move' => [ 'class' => [ 'move' ] ],
'unwatch' => [], 'unwatch' => [ 'icon' => 'unStar' ],
], ],
]; ];
@ -433,6 +438,8 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
public function testUpdateUserLinksItems() { public function testUpdateUserLinksItems() {
$vector2022Skin = new SkinVector22( [ 'name' => 'vector-2022' ] ); $vector2022Skin = new SkinVector22( [ 'name' => 'vector-2022' ] );
$contentNav = [ $contentNav = [
'associated-pages' => [],
'views' => [],
'user-page' => [ 'user-page' => [
'userpage' => [ 'class' => [], 'icon' => 'userpage' ], 'userpage' => [ 'class' => [], 'icon' => 'userpage' ],
], ],
@ -442,6 +449,8 @@ class VectorHooksTest extends MediaWikiIntegrationTestCase {
]; ];
$vectorLegacySkin = new SkinVectorLegacy( [ 'name' => 'vector' ] ); $vectorLegacySkin = new SkinVectorLegacy( [ 'name' => 'vector' ] );
$contentNavLegacy = [ $contentNavLegacy = [
'associated-pages' => [],
'views' => [],
'user-page' => [ 'user-page' => [
'userpage' => [ 'class' => [], 'icon' => 'userpage' ], 'userpage' => [ 'class' => [], 'icon' => 'userpage' ],
] ]