const track = mw.track; const MAX_PRINT_TIMEOUT = 3000; let printSetTimeoutReference = 0; const mobile = require( 'mobile.startup' ); /** * Helper function to detect iOs * * @ignore * @param {string} userAgent User Agent * @return {boolean} */ function isIos( userAgent ) { return /ipad|iphone|ipod/i.test( userAgent ); } /** * Helper function to retrieve the Android version * * @ignore * @param {string} userAgent User Agent * @return {number|boolean} Integer version number, or false if not found */ function getAndroidVersion( userAgent ) { const match = userAgent.toLowerCase().match( /android\s(\d\.]*)/ ); return match ? parseInt( match[ 1 ] ) : false; } /** * Helper function to retrieve the Chrome/Chromium version * * @ignore * @param {string} userAgent User Agent * @return {number|boolean} Integer version number, or false if not found */ function getChromeVersion( userAgent ) { const match = userAgent.toLowerCase().match( /chrom(e|ium)\/(\d+)\./ ); return match ? parseInt( match[ 2 ] ) : false; } /** * Checks whether DownloadIcon is available for given user agent * * @memberof DownloadIcon * @instance * @param {Window} windowObj * @param {Page} page to download * @param {string} userAgent User agent * @param {number[]} supportedNamespaces where printing is possible * @return {boolean} */ function isAvailable( windowObj, page, userAgent, supportedNamespaces ) { const androidVersion = getAndroidVersion( userAgent ); const chromeVersion = getChromeVersion( userAgent ); if ( typeof window.print !== 'function' ) { // T309591: No window.print support return false; } // Download button is restricted to certain namespaces T181152. // Not shown on missing pages // Defaults to 0, in case cached JS has been served. if ( supportedNamespaces.indexOf( page.getNamespaceId() ) === -1 || page.isMainPage() || page.isMissing ) { // namespace is not supported or it's a main page return false; } if ( isIos( userAgent ) || chromeVersion === false || windowObj.chrome === undefined ) { // we support only chrome/chromium on desktop/android return false; } if ( ( androidVersion && androidVersion < 5 ) || chromeVersion < 41 ) { return false; } return true; } /** * onClick handler for button that invokes print function * * @private * @param {HTMLElement} portletItem * @param {Icon} spinner * @param {Function} [loadAllImagesInPage] */ function onClick( portletItem, spinner, loadAllImagesInPage ) { const icon = portletItem.querySelector( '.minerva-icon--download' ); function doPrint() { printSetTimeoutReference = clearTimeout( printSetTimeoutReference ); track( 'minerva.downloadAsPDF', { action: 'callPrint' } ); window.print(); $( icon ).show(); spinner.$el.hide(); } function doPrintBeforeTimeout() { if ( printSetTimeoutReference ) { doPrint(); } } // The click handler may be invoked multiple times so if a pending print is occurring // do nothing. if ( !printSetTimeoutReference ) { track( 'minerva.downloadAsPDF', { action: 'fetchImages' } ); $( icon ).hide(); spinner.$el.show(); // If all image downloads are taking longer to load then the MAX_PRINT_TIMEOUT // abort the spinner and print regardless. printSetTimeoutReference = setTimeout( doPrint, MAX_PRINT_TIMEOUT ); ( loadAllImagesInPage || mobile.loadAllImagesInPage )() .then( doPrintBeforeTimeout, doPrintBeforeTimeout ); } } /** * Generate a download icon for triggering print functionality if * printing is available. * Calling this method has side effects: * It calls mw.util.addPortletLink and may inject an element into the page. * * @ignore * @param {Page} page * @param {number[]} supportedNamespaces * @param {Window} [windowObj] window object * @param {boolean} [overflowList] Append to overflow list * @return {jQuery|null} */ function downloadPageAction( page, supportedNamespaces, windowObj, overflowList ) { const spinner = ( overflowList ) ? mobile.spinner( { label: '', isIconOnly: false } ) : mobile.spinner(); if ( isAvailable( windowObj, page, navigator.userAgent, supportedNamespaces ) ) { // FIXME: Use p-views when cache has cleared. const actionID = document.querySelector( '#p-views' ) ? 'p-views' : 'page-actions'; const portletLink = mw.util.addPortletLink( overflowList ? 'page-actions-overflow' : actionID, '#', mw.msg( 'minerva-download' ), // id 'minerva-download', // tooltip mw.msg( 'minerva-download' ), // access key 'p', overflowList ? null : document.getElementById( 'page-actions-watch' ) ); if ( portletLink ) { portletLink.addEventListener( 'click', () => { onClick( portletLink, spinner, mobile.loadAllImagesInPage ); } ); const iconElement = portletLink.querySelector( '.minerva-icon' ); if ( iconElement ) { iconElement.classList.add( 'minerva-icon--download' ); } spinner.$el.hide().insertBefore( $( portletLink ).find( '.minerva-icon' ) ); } return portletLink; } else { return null; } } module.exports = { downloadPageAction, test: { isAvailable, onClick } };