Update: add secondary page actions submenu in AMC mode

When advanced mobile contributions mode is enabled and
`$wgMinervaOverflowInPageActions` is set, show a secondary overflow menu
on main namespace articles and user namespace pages. The menu content
varies for each namespace. The new submenu is *disabled* by default,
even when AMC is active. This feature should not be deployed until
instrumentation is available.

The secondary menu is rendered in PHP using the existing menu system
with some changes to the template. The checkbox hack is needed for no-
JavaScript keyboard navigation until :focus-within is supported. CSS
additions are documented in the source.

All client side toolbar execution occurs in Toolbar.js. Enhancements are
documented in the source.

Minor changes to the existing download button:
- Move download and edit button initialization to Toolbar.
- Update copy (not visible) from "Download" to "Download PDF".
- When the overflow menu is present, use the "hasText" / label style.

Wikimedia UI icons are copied from OOUI at d00a0ac and seem useful to
expose as HTTP URIs (not data URIs).

The overflow menu does not show for pages provided by the content proxy
since its entries depend on $tpl->data['nav_urls'] being populated.

Bug: T216418
Depends-On: I0781151a8232b6a7b52f79a34298afcecb8e4271
Change-Id: I4b50a0e519024a7ab91dae6ab40b23cf14a03861
This commit is contained in:
Stephen Niedzielski 2019-04-04 15:20:39 -06:00 committed by Niedzielski
parent ad7e941bde
commit 815f3386e3
27 changed files with 529 additions and 92 deletions

View File

@ -119,6 +119,19 @@ This will work for all pages except the main page.
```
Controls whether the history link appears in the page actions menu.
#### $wgMinervaOverflowInPageActions
* Type: `Array`
* Default:
```php
[
'beta' => false,
'base' => false,
'amc' => false,
]
```
Controls whether the overflow link appears in the page actions menu.
#### $wgMinervaShowShareButton
* Type: `Array`

View File

@ -6,7 +6,7 @@
"minerva-talk-add-topic": "Add discussion",
"mobile-frontend-cookies-required": "Cookies are required to switch view modes. Please enable them and try again.",
"mobile-frontend-editor-edit": "Edit",
"minerva-download": "Download",
"minerva-download": "Download PDF",
"skin-minerva-share": "Share",
"skin-minerva-mobile-option-MinervaShareButton": "Share",
"skin-minerva-mobile-option-MinervaShareButton-description": "Share current article via browser share feature (supported browsers only)",
@ -47,6 +47,14 @@
"mobile-frontend-user-page-member-since": "{{GENDER:$2|Joined}} $1",
"mobile-frontend-user-page-talk": "Talk",
"mobile-frontend-user-page-uploads": "Uploads",
"minerva-page-actions-overflow": "Secondary page actions submenu",
"minerva-page-actions-info": "Page information",
"minerva-page-actions-permalink": "Permanent link",
"minerva-page-actions-backlinks": "What links here",
"minerva-page-actions-cite": "Cite page",
"minerva-page-actions-uploads": "Uploads",
"minerva-page-actions-user-rights": "User rights",
"minerva-page-actions-logs": "Logs",
"skinname-minerva": "MinervaNeue",
"minerva-skin-desc": "A responsive mobile first skin",
"skin-minerva-issue-learn-more": "Learn more",

View File

@ -56,6 +56,14 @@
"mobile-frontend-user-page-member-since": "Message below the heading. $1 is the user registration date. $2 is the gender associated with the user account.",
"mobile-frontend-user-page-talk": "Text of the link to the user's talk page\n{{Identical|Talk}}",
"mobile-frontend-user-page-uploads": "Text of the link to the user's uploads page\n{{Identical|Upload}}",
"minerva-page-actions-overflow": "Text describing the secondary page menu button's action",
"minerva-page-actions-info": "In the secondary page menu, the page information button label",
"minerva-page-actions-permalink": "In the secondary page menu, the permanent link button label",
"minerva-page-actions-backlinks": "In the secondary page menu, the 'what links here' incoming links button label",
"minerva-page-actions-cite": "In the secondary page menu, the cite this page button label",
"minerva-page-actions-uploads": "In the secondary page menu for user pages, the user uploads button label",
"minerva-page-actions-user-rights": "In the secondary page menu for user pages, the user rights button label",
"minerva-page-actions-logs": "In the secondary page menu for user pages, the user logs button label",
"skinname-minerva": "{{name}}",
"minerva-skin-desc": "{{desc|name=Minerva Neue|url=https://www.mediawiki.org/wiki/Skin:Minerva_Neue|what=skin}}",
"skin-minerva-issue-learn-more": "Label for link that allows expanding of ambox issue templates.",

View File

@ -28,6 +28,8 @@ use MediaWiki\Minerva\SkinOptions;
* on<HookName>()
*/
class MinervaHooks {
const FEATURE_OVERFLOW_PAGE_ACTIONS = 'MinervaOverflowInPageActions';
/**
* Register mobile web beta features
* @see https://www.mediawiki.org/wiki/
@ -82,6 +84,13 @@ class MinervaHooks {
$config->get( 'MinervaHistoryInPageActions' )
)
);
$featureManager->registerFeature(
new MobileFrontend\Features\Feature(
self::FEATURE_OVERFLOW_PAGE_ACTIONS,
'skin-minerva',
$config->get( self::FEATURE_OVERFLOW_PAGE_ACTIONS )
)
);
} catch ( RuntimeException $e ) {
// features already registered...
// due to a bug it's possible for this to run twice
@ -222,6 +231,9 @@ class MinervaHooks {
SkinOptions::OPTIONS_HISTORY_PAGE_ACTIONS => $featureManager->isFeatureAvailableForCurrentUser(
'MinervaHistoryInPageActions'
),
SkinOptions::OPTION_OVERFLOW_SUBMENU => $featureManager->isFeatureAvailableForCurrentUser(
self::FEATURE_OVERFLOW_PAGE_ACTIONS
),
] );
}
}

View File

@ -34,6 +34,7 @@ final class SkinOptions {
const OPTIONS_MOBILE_BETA = 'beta';
const OPTIONS_TALK_AT_TOP = 'talkAtTop';
const OPTIONS_HISTORY_PAGE_ACTIONS = 'historyInPageActions';
const OPTION_OVERFLOW_SUBMENU = 'overflowSubmenu';
/** @var array skin specific options */
private $skinOptions = [
@ -56,6 +57,7 @@ final class SkinOptions {
self::OPTION_PAGE_ISSUES => false,
self::OPTIONS_TALK_AT_TOP => false,
self::OPTIONS_HISTORY_PAGE_ACTIONS => false,
self::OPTION_OVERFLOW_SUBMENU => false,
];
/**

View File

@ -98,11 +98,11 @@ class MinervaTemplate extends BaseTemplate {
*/
protected function getPageActionsHtml() {
$templateParser = new TemplateParser( __DIR__ );
$actions = $this->getPageActions();
$pageActions = $this->getPageActions();
$html = '';
if ( $actions ) {
$html = $templateParser->processTemplate( 'pageActionMenu', [ 'pageActions' => $actions ] );
if ( $pageActions && $pageActions['toolbar'] ) {
$html = $templateParser->processTemplate( 'pageActionMenu', $pageActions );
}
return $html;
}

View File

@ -29,7 +29,6 @@ use MediaWiki\Minerva\SkinUserPageHelper;
* @ingroup Skins
*/
class SkinMinerva extends SkinTemplate {
/** @const LEAD_SECTION_NUMBER integer which corresponds to the lead section
in editing mode */
const LEAD_SECTION_NUMBER = 0;
@ -231,6 +230,10 @@ class SkinMinerva extends SkinTemplate {
return $this->skinOptions->get( SkinOptions::OPTIONS_HISTORY_PAGE_ACTIONS );
}
if ( $action === SkinOptions::OPTION_OVERFLOW_SUBMENU ) {
return $this->skinOptions->get( SkinOptions::OPTION_OVERFLOW_SUBMENU );
}
if (
!in_array( $action, $config->get( 'MinervaPageActions' ) )
|| ( $this->getUserPageHelper()->isUserPage() && !$title->exists() )
@ -834,10 +837,11 @@ class SkinMinerva extends SkinTemplate {
* @param BaseTemplate $tpl
*/
protected function preparePageActions( BaseTemplate $tpl ) {
$menu = [];
$toolbar = [];
$overflowMenu = null;
if ( $this->isAllowedPageAction( 'switch-language' ) ) {
$menu[] = $this->createSwitchLanguageAction();
$toolbar[] = $this->createSwitchLanguageAction();
}
if ( $this->isAllowedPageAction( 'watch' ) ) {
@ -845,18 +849,25 @@ class SkinMinerva extends SkinTemplate {
// Pass these actions in as context for #createWatchPageAction.
$actions = $tpl->data['content_navigation']['actions'];
$menu[] = $this->createWatchPageAction( $actions );
$toolbar[] = $this->createWatchPageAction( $actions );
}
if ( $this->isAllowedPageAction( 'history' ) ) {
$menu[] = $this->getHistoryPageAction();
$toolbar[] = $this->getHistoryPageAction();
}
if ( $this->isAllowedPageAction( 'edit' ) ) {
$menu[] = $this->createEditPageAction();
$toolbar[] = $this->createEditPageAction();
}
$tpl->set( 'page_actions', $menu );
if ( $this->isAllowedPageAction( SkinOptions::OPTION_OVERFLOW_SUBMENU ) ) {
$overflowMenu = $this->newToolbarOverflowMenu( $tpl );
}
$tpl->set( 'page_actions', [
'toolbar' => $toolbar,
'overflowMenu' => $overflowMenu
] );
}
/**
@ -977,6 +988,85 @@ class SkinMinerva extends SkinTemplate {
];
}
/**
* Creates an overflow action: An icon that links to the overflow menu.
*
* @param BaseTemplate $tpl
* @return array|null A map of HTML attributes and a 'text' property to be used with the
* pageActionMenu.mustache template.
*/
private function newToolbarOverflowMenu( BaseTemplate $tpl ) {
$pageActions = $this->getUserPageHelper()->isUserPage()
? $this->getUserNamespaceOverflowPageActions( $tpl )
: $this->getDefaultOverflowPageActions( $tpl );
return empty( $pageActions ) ? null : [
'item-id' => 'page-actions-overflow',
'class' => MinervaUI::iconClass( 'page-actions-overflow' ),
'text' => $this->msg( 'minerva-page-actions-overflow' ),
'pageActions' => $pageActions
];
}
/**
* @param BaseTemplate $tpl
* @return array
*/
private function getDefaultOverflowPageActions( BaseTemplate $tpl ) {
return array_values( array_filter( [
$this->newOverflowPageAction( 'info', 'info', $tpl->data['nav_urls']['info']['href'] ?? null ),
$this->newOverflowPageAction(
'permalink', 'link', $tpl->data['nav_urls']['permalink']['href'] ?? null
),
$this->newOverflowPageAction(
'backlinks', 'articleRedirect', $tpl->data['nav_urls']['whatlinkshere']['href'] ?? null
),
$this->newOverflowPageAction(
'cite', 'quotes', $tpl->data['nav_urls']['citethispage']['href'] ?? null
)
] ) );
}
/**
* @param BaseTemplate $tpl
* @return array
*/
private function getUserNamespaceOverflowPageActions( BaseTemplate $tpl ) {
$pageUser = $this->getUserPageHelper()->getPageUser();
return [
$this->newOverflowPageAction(
'uploads', 'upload', SpecialPage::getTitleFor( 'Uploads', $pageUser )->getLocalURL()
),
$this->newOverflowPageAction(
'user-rights', 'userAvatar', $tpl->data['nav_urls']['userrights']['href'] ?? null
),
$this->newOverflowPageAction(
'logs', 'listBullet', $tpl->data['nav_urls']['log']['href'] ?? null
),
$this->newOverflowPageAction( 'info', 'info', $tpl->data['nav_urls']['info']['href'] ?? null ),
$this->newOverflowPageAction(
'permalink', 'link', $tpl->data['nav_urls']['permalink']['href'] ?? null
),
$this->newOverflowPageAction(
'backlinks', 'articleRedirect', $tpl->data['nav_urls']['whatlinkshere']['href'] ?? null
)
];
}
/**
* @param string $name
* @param string $icon Wikimedia UI icon name.
* @param string|null $href
* @return array
*/
private function newOverflowPageAction( $name, $icon, $href ) {
return $href ? [
'item-id' => 'page-actions-overflow-' . $name,
'class' => MinervaUI::iconClass( '', 'before', 'wikimedia-ui-' . $icon . '-base20' ),
'text' => $this->msg( 'minerva-page-actions-' . $name ),
'href' => $href
] : null;
}
/**
* Checks whether the editor can handle the existing content handler type.
*

View File

@ -1,11 +1,28 @@
<nav class="page-actions-menu">
<ul id="page-actions" class="page-actions-menu__list">
{{#pageActions}}
{{#toolbar}}
<li id="{{item-id}}" class="page-actions-menu__list-item">
<a id="{{id}}" href="{{href}}" class="{{class}}" role="button" title="{{title}}">
{{text}}
</a>
</li>
{{/pageActions}}
{{/toolbar}}
{{#overflowMenu}}
<li id="{{item-id}}" class="page-actions-menu__list-item">
<input type="checkbox" id="toolbar-overflow-menu__checkbox" role="button" aria-label="{{text}}" aria-expanded="false" >
<label class="toolbar-overflow-menu__button {{class}}" title="{{title}}" for="toolbar-overflow-menu__checkbox">
{{text}}
</label>
<ul class="toolbar-overflow-menu__list">
{{#pageActions}}
<li>
<a id="{{id}}" href="{{href}}" class="toolbar-overflow-menu__list-item {{class}}" title="{{title}}">
{{text}}
</a>
</li>
{{/pageActions}}
</ul>
</li>
{{/overflowMenu}}
</ul>
</nav>

View File

@ -1,7 +1,7 @@
@import 'mediawiki.ui/variables';
@z-indexBase: 0;
@z-indexOverOverlay: 2;
@z-indexOverOverlay: 3;
/**
* System font stack for sans-serif fonts
@ -38,7 +38,6 @@
// Headings
@firstHeadingFontSize: 2.6525em; // 42px
@pageActionFontSize: 1.1em; // Icons are 24px square.
@fontSizeH1: 1.7em;
@fontSizeH2: 1.5em;
@fontSizeH3: 1.2em;
@ -49,7 +48,7 @@
// Header
@headerHeight: 3.35em;
@headerMarginTop: -1px; // used to hide the header border top when a banner is not present
@headerMarginTop: -1px; // used to hide the header border top when a banner is not present
@searchBoxWidth: 375/16em;
@iconSizeTotal: @iconSize + @iconGutterWidth + @iconGutterWidth;
@deviceWidthTabletEms: unit( @width-breakpoint-tablet/16, em );
@ -61,6 +60,8 @@
// Page actions
@taglineFontSize: 0.85em;
@pageActionBorder: 1px;
@pageActionFontSize: 0.9em;
@pageActionToolbarHeight: 44px; // total height is 46px. 2px added by border on .page-actions-menu
// colors
@chromeColor: @grayLightest;
@ -125,7 +126,9 @@
@notificationColorUnread: #fff;
// z-index:
@z-indexOverlay: 1;
@z-indexOccluded: -1;
@z-indexDrawer: 1;
@z-indexOverlay: 2;
// Print specific
@colorPrintSubtle: #999;

View File

@ -21,8 +21,12 @@
opacity: 0.25;
}
#page-actions {
position: relative;
}
.page-actions-menu {
box-sizing: border-box;
.box-sizing( border-box );
border-top: 1px solid @colorGray14;
border-bottom: 1px solid @colorGray12;
}
@ -30,11 +34,10 @@
.page-actions-menu__list {
display: flex;
justify-content: space-between;
height: 44px; // total height is 46px. 2px added by border on .page-actions-menu
height: @pageActionToolbarHeight;
}
.page-actions-menu__list-item {
position: relative;
display: flex;
flex-basis: 4em;
justify-content: flex-end;
@ -68,6 +71,10 @@
}
}
.minerva--amc-enabled .page-actions-menu__list-item {
flex-basis: auto;
}
// Layout for less than 5 items - one item at the beginning, rest at the end.
// |1-----2--3--4|
.page-actions-menu__list-item:first-child {
@ -75,6 +82,99 @@
justify-content: flex-start;
}
// When AMC is enabled, space all items equally.
.minerva--amc-enabled .page-actions-menu__list-item:first-child {
flex-grow: 0;
}
#toolbar-overflow-menu__checkbox {
// Always occlude the checkbox. The checkbox display cannot be none since its focus state is used
// for other selectors.
position: absolute;
z-index: @z-indexOccluded;
opacity: 0;
}
.toolbar-overflow-menu__button {
// Use the hand icon for the overflow button which is actually a checkbox label.
cursor: pointer;
}
#toolbar-overflow-menu__checkbox:focus + .toolbar-overflow-menu__button {
// The overflow button / label itself cannot receive focus but the underlying checkbox can. Keep
// the button and checkbox focus presentation in sync. From
// resources/src/mediawiki.toc.styles/screen.less.
outline: dotted 1px; /* Firefox style for focus */
outline: auto @colorProgressiveHighlight; /* Webkit style for focus */
}
.touch-events #toolbar-overflow-menu__checkbox:focus + .toolbar-overflow-menu__button {
// Buttons have no focus outline on mobile.
outline: 0;
}
.toolbar-overflow-menu__list {
// The top of the menu is flush with the bottom of the page actions toolbar.
position: absolute;
top: 100%;
right: 0;
//
// A variable max-height is set in JavaScript so a minimum height is needed.
min-height: 200px;
//
// If the height exceeds the maximum allowed, add a vertical scrollbar.
overflow-y: auto;
//
// The menu floats over content but below overlays.
z-index: @z-indexDrawer;
//
font-size: @pageActionFontSize;
font-weight: bold;
background: @skinContentBgColor;
box-shadow: 0 5px 17px 0 rgba( 0, 0, 0, 0.24 ), 0 0 1px @colorGray10;
border-radius: @borderRadius;
//
// Animate menu visibility, opacity, and translation changes in and out. Visibility must be
// animated since it's a boolean and nothing can be seen in the hidden state.
visibility: hidden;
opacity: 0;
transform: translateY( -8px );
.transition( visibility .1s ease-in-out, opacity .1s ease-in-out, transform .1s ease-in-out; );
}
.toolbar-overflow-menu__list-item {
// Fill the list item cell.
.box-sizing( border-box );
display: inline-block;
width: 100%;
//
padding: 1em;
white-space: nowrap;
// Left-align text. Button elements are centered.
text-align: left;
//
color: @grayMediumDark;
&:visited, &:active {
// Visited and active links need extra specificity.
color: @grayMediumDark;
}
//
// Make the app feel like an app, not a JPEG. When hovering over a menu item, add a little
// interactivity.
&:hover {
text-decoration: none;
background: @grayLightest;
}
}
#toolbar-overflow-menu__checkbox:checked ~ .toolbar-overflow-menu__list {
// Reveal the overflow menu when checked.
visibility: visible;
opacity: 1;
transform: translateY( 0 );
}
// overriding common.less `display:inherit` (which causes `display: flex;` in this instance).
.client-js .jsonly#ca-watch {
display: list-item;

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" id="svg10"><title id="title2">ellipsis</title><circle cx="-10" cy="10" r="2" id="circle4" transform="rotate(-90)"/><circle cx="-17" cy="10" r="2" id="circle6" transform="rotate(-90)"/><circle cx="-3" cy="10" r="2" id="circle8" transform="rotate(-90)"/></svg>

After

Width:  |  Height:  |  Size: 395 B

View File

@ -0,0 +1,175 @@
( function ( M ) {
var
mobile = M.require( 'mobile.startup' ),
downloadPageAction = M.require( 'skins.minerva.scripts/downloadPageAction' ),
Icon = mobile.Icon,
skin = M.require( 'mobile.init/skin' ),
/** The top level menu. */
toolbarSelector = '.page-actions-menu',
/** The secondary overflow submenu component container. */
overflowSubmenuSelector = '#page-actions-overflow',
/** The visible label icon associated with the checkbox. */
overflowButtonSelector = '.toolbar-overflow-menu__button',
/** The underlying hidden checkbox that controls secondary overflow submenu visibility. */
overflowCheckboxSelector = '#toolbar-overflow-menu__checkbox',
overflowListSelector = '.toolbar-overflow-menu__list';
/**
* @param {Window} window
* @param {Element} toolbar
* @param {OO.EventEmitter} eventBus
* @return {void}
*/
function bind( window, toolbar, eventBus ) {
var
overflowSubmenu = toolbar.querySelector( overflowSubmenuSelector ),
overflowButton = toolbar.querySelector( overflowButtonSelector ),
overflowCheckbox = toolbar.querySelector( overflowCheckboxSelector ),
overflowList = toolbar.querySelector( overflowListSelector );
if ( overflowSubmenu ) {
bindOverflowSubmenu(
window, overflowSubmenu, overflowButton, overflowCheckbox, overflowList, eventBus
);
}
}
/**
* @param {Window} window
* @param {Element} toolbar
* @return {void}
*/
function render( window, toolbar ) {
var overflowList = toolbar.querySelector( overflowListSelector );
renderEditButton();
renderDownloadButton( window, overflowList );
if ( overflowList ) {
resizeOverflowList( overflowList );
}
}
/**
* Automatically dismiss the submenu when clicking or focusing elsewhere, resize the menu on
* scroll and window resize, and update the aria-expanded attribute based on submenu visibility.
* @param {Window} window
* @param {Element} submenu
* @param {Element} button
* @param {HTMLInputElement} checkbox
* @param {Element} list
* @param {OO.EventEmitter} eventBus
* @return {void}
*/
function bindOverflowSubmenu( window, submenu, button, checkbox, list, eventBus ) {
var
resize = resizeOverflowList.bind( undefined, list ),
updateAriaExpanded = function () {
checkbox.setAttribute( 'aria-expanded', ( !!checkbox.checked ).toString() );
};
window.addEventListener( 'click', function ( event ) {
if ( event.target !== button && event.target !== checkbox ) {
// Something besides the button or checkbox was tapped. Dismiss the submenu.
checkbox.checked = false;
updateAriaExpanded();
}
} );
// If focus is given to any element outside the menu, dismiss the submenu. Setting a
// focusout listener on submenu would be preferable, but this interferes with the click
// listener.
window.addEventListener( 'focusin', function ( event ) {
if ( event.target instanceof Node && !submenu.contains( event.target ) ) {
// Something besides the button or checkbox was focused. Dismiss the menu.
checkbox.checked = false;
updateAriaExpanded();
}
} );
eventBus.on( 'scroll:throttled', resize );
eventBus.on( 'resize:throttled', resize );
checkbox.addEventListener( 'change', updateAriaExpanded );
}
/**
* @param {HTMLElement} list
* @return {void}
*/
function resizeOverflowList( list ) {
var rect = list.getClientRects()[ 0 ];
if ( rect ) {
list.style.maxHeight = window.document.documentElement.clientHeight - rect.top + 'px';
}
}
/**
* Initialize page edit action link (#ca-edit)
*
* Mark the edit link as disabled if the user is not actually able to edit the page for some
* reason (e.g. page is protected or user is blocked).
*
* Note that the link is still clickable, but clicking it will probably open a view-source
* form or display an error message, rather than open an edit form.
*
* FIXME: Review this code as part of T206262
*
* @ignore
*/
function renderEditButton() {
var
// FIXME: create a utility method to generate class names instead of
// constructing temporary objects. This affects disabledEditIcon,
// enabledEditIcon, enabledEditIcon, and disabledClass and
// a number of other places in the code base.
disabledEditIcon = new Icon( {
name: 'edit',
glyphPrefix: 'minerva'
} ),
enabledEditIcon = new Icon( {
name: 'edit-enabled',
glyphPrefix: 'minerva'
} ),
enabledClass = enabledEditIcon.getGlyphClassName(),
disabledClass = disabledEditIcon.getGlyphClassName();
if ( mw.config.get( 'wgMinervaReadOnly' ) ) {
// eslint-disable-next-line no-jquery/no-global-selector
$( '#ca-edit' )
.removeClass( enabledClass )
.addClass( disabledClass );
}
}
/**
* Initialize and inject the download button
*
* There are many restrictions when we can show the download button, this function should handle
* all device/os/operating system related checks and if device supports printing it will inject
* the Download icon
* @param {Window} window
* @param {Element|null} overflowList
* @return {void}
*/
function renderDownloadButton( window, overflowList ) {
var $downloadAction = downloadPageAction( skin,
mw.config.get( 'wgMinervaDownloadNamespaces', [] ), window, !!overflowList );
if ( $downloadAction ) {
if ( overflowList ) {
$downloadAction.appendTo( overflowList );
} else {
$downloadAction.insertAfter( '.page-actions-menu__list-item:first-child' );
}
mw.track( 'minerva.downloadAsPDF', {
action: 'buttonVisible'
} );
}
}
M.define( 'skins.minerva.scripts/Toolbar', {
selector: toolbarSelector,
bind: bind,
render: render
} );
}( mw.mobileFrontend ) );

View File

@ -120,10 +120,17 @@
* @param {Skin} skin
* @param {number[]} supportedNamespaces
* @param {Window} [windowObj] window object
* @param {boolean} [hasText] Use icon + button style.
* @returns {jQuery.Object|null}
*/
function downloadPageAction( skin, supportedNamespaces, windowObj ) {
var icon, spinner = icons.spinner();
function downloadPageAction( skin, supportedNamespaces, windowObj, hasText ) {
var
modifier = hasText ? 'toolbar-overflow-menu__list-item' : 'mw-ui-icon-element',
icon,
spinner = icons.spinner( {
hasText: hasText,
modifier: modifier
} );
if (
isAvailable(
@ -139,11 +146,13 @@
events: {
// will be bound to `this`
click: getOnClickHandler( spinner )
}
},
hasText: hasText,
label: hasText ? mw.msg( 'minerva-download' ) : '',
modifier: modifier
} );
return $( '<li>' ).addClass( 'page-actions-menu__list-item' ).append( icon.$el ).append( spinner.$el.hide() );
} else {
return null;
}

View File

@ -1,17 +1,15 @@
( function ( M, track, config ) {
( function ( M ) {
var
mobile = M.require( 'mobile.startup' ),
PageGateway = mobile.PageGateway,
toast = mobile.toast,
time = mobile.time,
skin = M.require( 'mobile.init/skin' ),
TitleUtil = M.require( 'skins.minerva.scripts/TitleUtil' ),
issues = M.require( 'skins.minerva.scripts/pageIssues' ),
downloadPageAction = M.require( 'skins.minerva.scripts/downloadPageAction' ),
Toolbar = M.require( 'skins.minerva.scripts/Toolbar' ),
router = require( 'mediawiki.router' ),
OverlayManager = mobile.OverlayManager,
CtaDrawer = mobile.CtaDrawer,
Icon = mobile.Icon,
Button = mobile.Button,
Anchor = mobile.Anchor,
overlayManager = OverlayManager.getSingleton(),
@ -197,28 +195,6 @@
} );
}
/**
* Initialize and inject the download button
*
* There are many restrictions when we can show the download button, this function should handle
* all device/os/operating system related checks and if device supports printing it will inject
* the Download icon
* @ignore
*/
function appendDownloadButton() {
var $downloadAction = downloadPageAction( skin,
config.get( 'wgMinervaDownloadNamespaces', [] ), window );
if ( $downloadAction ) {
$downloadAction.insertAfter( '.page-actions-menu__list-item:first-child' );
track( 'minerva.downloadAsPDF', {
action: 'buttonVisible'
} );
}
}
/**
* Tests a URL to determine if it links to a local User namespace page or not.
*
@ -317,54 +293,19 @@
} );
}
/**
* Initialize page edit action link (#ca-edit)
*
* Mark the edit link as disabled if the user is not actually able to edit the page for some
* reason (e.g. page is protected or user is blocked).
*
* Note that the link is still clickable, but clicking it will probably open a view-source
* form or display an error message, rather than open an edit form.
*
* FIXME: Review this code as part of T206262
*
* @ignore
*/
function initEditLink() {
var
// FIXME: create a utility method to generate class names instead of
// constructing temporary objects. This affects disabledEditIcon,
// enabledEditIcon, enabledEditIcon, and disabledClass and
// a number of other places in the code base.
disabledEditIcon = new Icon( {
name: 'edit',
glyphPrefix: 'minerva'
} ),
enabledEditIcon = new Icon( {
name: 'edit-enabled',
glyphPrefix: 'minerva'
} ),
enabledClass = enabledEditIcon.getGlyphClassName(),
disabledClass = disabledEditIcon.getGlyphClassName();
if ( mw.config.get( 'wgMinervaReadOnly' ) ) {
// eslint-disable-next-line no-jquery/no-global-selector
$( '#ca-edit' )
.removeClass( enabledClass )
.addClass( disabledClass );
}
}
$( function () {
var toolbarElement = document.querySelector( Toolbar.selector );
// Update anything else that needs enhancing (e.g. watchlist)
initModifiedInfo();
initRegistrationInfo();
// eslint-disable-next-line no-jquery/no-global-selector
initHistoryLink( $( '.last-modifier-tagline a' ) );
appendDownloadButton();
if ( toolbarElement ) {
Toolbar.bind( window, toolbarElement, eventBus );
Toolbar.render( window, toolbarElement );
}
initRedlinksCta();
initUserRedLinks();
initEditLink();
// Setup the issues banner on the page
// Pages which dont exist (id 0) cannot have issues
if ( !page.isMissing ) {
@ -373,4 +314,4 @@
} );
M.define( 'skins.minerva.scripts/overlayManager', overlayManager );
}( mw.mobileFrontend, mw.track, mw.config ) );
}( mw.mobileFrontend ) );

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>article redirect</title><path d="M5 1a2 2 0 0 0-2 2v1c0 5 2 8 7 8V9l5 4-5 4v-3c-3.18 0-5.51-.85-7-2.68V17a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2z"/></svg>

After

Width:  |  Height:  |  Size: 290 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>article redirect</title><path d="M3 17c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-5.7c-1.5 1.8-3.8 2.7-7 2.7v3l-5-4 5-4v3c5 0 7-3 7-8V3c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2z"/></svg>

After

Width:  |  Height:  |  Size: 296 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>info</title><path d="M9.5 16A6.61 6.61 0 0 1 3 9.5 6.61 6.61 0 0 1 9.5 3 6.61 6.61 0 0 1 16 9.5 6.63 6.63 0 0 1 9.5 16zm0-14A7.5 7.5 0 1 0 17 9.5 7.5 7.5 0 0 0 9.5 2zm.5 6v4.08h1V13H8.07v-.92H9V9H8V8zM9 6h1v1H9z"/></svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>link</title><path d="M4.83 15h2.91a4.88 4.88 0 0 1-1.55-2H5a3 3 0 1 1 0-6h3a3 3 0 0 1 2.82 4h2.1a4.82 4.82 0 0 0 .08-.83v-.34A4.83 4.83 0 0 0 8.17 5H4.83A4.83 4.83 0 0 0 0 9.83v.34A4.83 4.83 0 0 0 4.83 15z"/><path d="M15.17 5h-2.91a4.88 4.88 0 0 1 1.55 2H15a3 3 0 1 1 0 6h-3a3 3 0 0 1-2.82-4h-2.1a4.82 4.82 0 0 0-.08.83v.34A4.83 4.83 0 0 0 11.83 15h3.34A4.83 4.83 0 0 0 20 10.17v-.34A4.83 4.83 0 0 0 15.17 5z"/></svg>

After

Width:  |  Height:  |  Size: 545 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>bullet list</title><path d="M7 15h12v2H7zm0-6h12v2H7zm0-6h12v2H7z"/><circle cx="3" cy="4" r="2"/><circle cx="3" cy="10" r="2"/><circle cx="3" cy="16" r="2"/></svg>

After

Width:  |  Height:  |  Size: 291 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>bullet list</title><path d="M1 15h12v2H1zm0-6h12v2H1zm0-6h12v2H1z"/><circle cx="17" cy="4" r="2"/><circle cx="17" cy="10" r="2"/><circle cx="17" cy="16" r="2"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" stroke-width="30.2" viewBox="0 0 20 20"><title>Wikidata logo</title><path stroke="#000" stroke-width=".743" d="M.371 4v12.257m1.482 0V4m.74 0v12.257m.741 0V4m1.481 0v12.257m.741 0V4m.74 0v12.257M7.778 4v12.257m1.481 0V4m8.888 0v12.257m1.482 0V4M10.74 4v12.257m.741 0V4m.741 0v12.257m1.481 0V4m1.482 0v12.257m.74 0V4m.74 0v12.257"/></svg>

After

Width:  |  Height:  |  Size: 438 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>Wikimedia logo</title><clipPath id="a"><path d="M0-33l471 471v363h82V438l471-471v1057H0"/></clipPath><g clip-path="url(#a)" transform="matrix(.021 0 0 .021 -.756 -.588)"><path d="M511 224a288 288 0 1 0 2 0"/><path fill="none" stroke="#000" stroke-width="116" d="M511 102a410 410 0 1 0 2 0"/></g><circle cx="10" cy="3.32" r="3.32"/></svg>

After

Width:  |  Height:  |  Size: 465 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>quotes</title><path d="M7 6l1-2H6C3.79 4 2 6.79 2 9v7h7V9H5c0-3 2-3 2-3zm7 3c0-3 2-3 2-3l1-2h-2c-2.21 0-4 2.79-4 5v7h7V9z"/></svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>quotes</title><path d="M11 9v7h7V9c0-2.2-1.8-5-4-5h-2l1 2s2 0 2 3zM2 9v7h7V9c0-2.2-1.8-5-4-5H3l1 2s2 0 2 3z"/></svg>

After

Width:  |  Height:  |  Size: 244 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>upload</title><path d="M17 12v5H3v-5H1v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5z"/><path d="M10 1L5 7h4v8h2V7h4z"/></svg>

After

Width:  |  Height:  |  Size: 244 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>user avatar</title><path d="M10 11c-5.92 0-8 3-8 5v3h16v-3c0-2-2.08-5-8-5z"/><circle cx="10" cy="5.5" r="4.5"/></svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -45,6 +45,11 @@
"beta": false,
"amc": true
},
"MinervaOverflowInPageActions": {
"base": false,
"beta": false,
"amc": false
},
"MinervaShowCategoriesButton": {
"base": false,
"beta": true
@ -231,6 +236,44 @@
"resources/skins.minerva.amc.styles/index.less"
]
},
"wikimedia.ui": {
"class": "ResourceLoaderImageModule",
"selectorWithoutVariant": ".wikimedia-ui-{name}:before",
"selectorWithVariant": ".wikimedia-ui-{name}-{variant}:before",
"useDataURI": false,
"variants": {
"base20": {
"color": "#54595d",
"global": true
}
},
"images": {
"articleRedirect": {
"file": {
"ltr": "resources/wikimedia.ui/articleRedirect-ltr.svg",
"rtl": "resources/wikimedia.ui/articleRedirect-rtl.svg"
}
},
"info": "resources/wikimedia.ui/info.svg",
"link": "resources/wikimedia.ui/link.svg",
"listBullet": {
"file": {
"ltr": "resources/wikimedia.ui/listBullet-ltr.svg",
"rtl": "resources/wikimedia.ui/listBullet-rtl.svg"
}
},
"logo-Wikidata": "resources/wikimedia.ui/logo-Wikidata.svg",
"logo-Wikimedia": "resources/wikimedia.ui/logo-Wikimedia.svg",
"quotes": {
"file": {
"ltr": "resources/wikimedia.ui/quotes-ltr.svg",
"rtl": "resources/wikimedia.ui/quotes-rtl.svg"
}
},
"upload": "resources/wikimedia.ui/upload.svg",
"userAvatar": "resources/wikimedia.ui/userAvatar.svg"
}
},
"skins.minerva.icons.images": {
"class": "ResourceLoaderImageModule",
"selectorWithoutVariant": ".mw-ui-icon-minerva-{name}:before",
@ -255,7 +298,8 @@
"clock": {
"file": "resources/skins.minerva.icons.images/clock.svg",
"variants": [ "invert" ]
}
},
"page-actions-overflow": "resources/skins.minerva.icons.images/page-actions-overflow.svg"
}
},
"skins.minerva.icons.images.scripts": {
@ -374,6 +418,7 @@
"desktop"
],
"dependencies": [
"wikimedia.ui",
"skins.minerva.mainMenu.icons",
"skins.minerva.mainMenu.styles",
"jquery.cookie",
@ -432,6 +477,7 @@
"resources/skins.minerva.scripts/pageIssues.js",
"resources/skins.minerva.scripts/UriUtil.js",
"resources/skins.minerva.scripts/TitleUtil.js",
"resources/skins.minerva.scripts/Toolbar.js",
"resources/skins.minerva.scripts/init.js",
"resources/skins.minerva.scripts/initLogging.js",
"resources/skins.minerva.scripts/mobileRedirect.js",