mediawiki-skins-MinervaNeue/resources/skins.minerva.scripts/Toolbar.js

176 lines
5.6 KiB
JavaScript
Raw Normal View History

( 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 ) );