Merge "Drop mw.mobileFrontend references"

This commit is contained in:
jenkins-bot 2023-11-01 15:45:31 +00:00 committed by Gerrit Code Review
commit d73e1f5cf6
28 changed files with 387 additions and 457 deletions

View file

@ -176,7 +176,7 @@ Group assignment is universal no matter how many tests are running since both
Group membership can be debugged from the console via:
```js
const AB = mw.mobileFrontend.require('skins.minerva.scripts/AB')
const AB = require('skins.minerva.scripts/AB')
new AB({
testName: 'WME.PageIssuesAB',
samplingRate: mw.config.get( 'wgMinervaABSamplingRate', 0 ),

View file

@ -25,6 +25,6 @@
},
{
"resourceModule": "skins.minerva.scripts",
"maxSize": "11.6KB"
"maxSize": "12.6KB"
}
]

View file

@ -1,7 +1,7 @@
{
"root": true,
"extends": [
"wikimedia/client",
"wikimedia/client-es6",
"wikimedia/jquery",
"wikimedia/mediawiki"
],

View file

@ -103,7 +103,7 @@ table.ambox {
display: none;
}
.mf-icon {
.minerva-ambox-icon {
position: absolute;
left: @amboxPadding;
top: 11px;

View file

@ -1,7 +0,0 @@
<?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>
alert
</title>
<path d="M19.64 16.36 11.53 2.3A1.85 1.85 0 0 0 10 1.21 1.85 1.85 0 0 0 8.48 2.3L.36 16.36C-.48 17.81.21 19 1.88 19h16.24c1.67 0 2.36-1.19 1.52-2.64zM11 16H9v-2h2zm0-4H9V6h2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 336 B

View file

@ -1,7 +0,0 @@
<?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>
notice
</title>
<path d="M10 0a10 10 0 1 0 10 10A10 10 0 0 0 10 0zm1 16H9v-2h2zm0-4H9V4h2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 237 B

View file

@ -1,7 +0,0 @@
<?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>
point of view
</title>
<path d="m6 8 2-3 2-3H2l2 3zm8 4-2 3-2 3h8l-2-3zm-12.236.329L17.47 5.823l.766 1.848L2.53 14.177z"/>
</svg>

Before

Width:  |  Height:  |  Size: 266 B

View file

@ -1,8 +0,0 @@
<?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>
low severity
</title>
<path fill="#f5a623" d="M17.9 11.9c-1.4-.3-3.4-2.8-4.8-4.6l-8.8 6.2 2.3 3.3c.8 1.2 2.3 1.8 4 1.7l-1.6-2c-.1-.2 0-.4.1-.5.2-.1.4-.1.5.1l1.8 2.3c.8-.2 1.7-.6 2.6-1.1-1.3-.8-2.2-2.4-2.2-2.4-.1-.2 0-.4.1-.5.2-.1.4 0 .5.1 0 0 1 1.7 2.3 2.3.9-.7 1.6-1.3 2.1-1.9-2.1-.4-3.1-1.9-3.2-1.9-.1-.2-.1-.4.1-.5.2-.1.4-.1.5.1a5 5 0 0 0 3.1 1.7 4 4 0 0 0 .8-2c.1-.2 0-.3-.2-.4z"/>
<path fill="#774b20" d="M11.6 5.2a1 1 0 0 0-1.3-.2L7.9 6.6 4.5 1.8a.7.7 0 0 0-.9-.2L2.1 2.7c-.3.2-.3.6-.2.8l3.4 4.8L3 10a1 1 0 0 0-.2 1.3L4 13l8.8-6.1-1.2-1.7z"/>
</svg>

Before

Width:  |  Height:  |  Size: 693 B

View file

@ -1,8 +0,0 @@
<?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>
move
</title>
<path fill="#36c" d="M6 8.7V12H0v4h6v3l3-2.6 3-2.6-3-2.5z"/>
<path fill="#d33" d="M14 4V.7l-3 2.6-3 2.5 3 2.6 3 2.6V8h6V4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View file

@ -1,9 +1,7 @@
// eslint-disable-next-line no-restricted-properties
var mobile = mw.mobileFrontend.require( 'mobile.startup' ),
var mobile = require( 'mobile.startup' ),
drawers = require( './drawers.js' ),
CtaDrawer = mobile.CtaDrawer,
Button = mobile.Button,
Anchor = mobile.Anchor;
CtaDrawer = mobile.CtaDrawer;
/**
* Initialize red links call-to-action
@ -27,16 +25,16 @@ var mobile = mw.mobileFrontend.require( 'mobile.startup' ),
function initRedlinksCta( $redLinks ) {
$redLinks.on( 'click', function ( ev ) {
var drawerOptions = {
progressiveButton: new Button( {
progressiveButton: {
progressive: true,
label: mw.msg( 'mobile-frontend-editor-redlink-create' ),
href: $( this ).attr( 'href' )
} ).options,
actionAnchor: new Anchor( {
},
actionAnchor: {
progressive: true,
label: mw.msg( 'mobile-frontend-editor-redlink-leave' ),
additionalClassNames: 'cancel'
} ).options,
},
onBeforeHide: drawers.discardDrawer,
content: mw.msg( 'mobile-frontend-editor-redlink-explain' )
},

View file

@ -1,10 +1,18 @@
( function ( M, track ) {
( function ( track ) {
var MAX_PRINT_TIMEOUT = 3000,
printSetTimeoutReference = 0,
mobile = M.require( 'mobile.startup' ),
icons = mobile.icons,
lazyImageLoader = mobile.lazyImages.lazyImageLoader,
browser = mobile.Browser.getSingleton();
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
@ -59,7 +67,7 @@
return false;
}
if ( browser.isIos() || chromeVersion === false ||
if ( isIos( userAgent ) || chromeVersion === false ||
windowObj.chrome === undefined
) {
// we support only chrome/chromium on desktop/android
@ -76,7 +84,7 @@
* @param {HTMLElement} portletItem
* @param {Icon} spinner
*/
function onClick( portletItem, spinner ) {
function onClick( portletItem, spinner, loadAllImagesInPage ) {
var icon = portletItem.querySelector( '.minerva-icon--download' );
function doPrint() {
printSetTimeoutReference = clearTimeout( printSetTimeoutReference );
@ -104,8 +112,7 @@
// 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 );
lazyImageLoader.loadImages( lazyImageLoader.queryPlaceholders( document.getElementById( 'content' ) ) )
.then( doPrintBeforeTimeout, doPrintBeforeTimeout );
( loadAllImagesInPage || mobile.loadAllImagesInPage )().then( doPrintBeforeTimeout, doPrintBeforeTimeout );
}
}
@ -124,10 +131,10 @@
function downloadPageAction( page, supportedNamespaces, windowObj, overflowList ) {
var
portletLink, iconElement,
spinner = ( overflowList ) ? icons.spinner( {
spinner = ( overflowList ) ? mobile.spinner( {
label: '',
isIconOnly: false
} ) : icons.spinner();
} ) : mobile.spinner();
if (
isAvailable(
@ -151,7 +158,7 @@
);
if ( portletLink ) {
portletLink.addEventListener( 'click', function () {
onClick( portletLink, spinner );
onClick( portletLink, spinner, mobile.loadAllImagesInPage );
} );
iconElement = portletLink.querySelector( '.minerva-icon' );
if ( iconElement ) {
@ -168,12 +175,12 @@
}
module.exports = {
downloadPageAction: downloadPageAction,
downloadPageAction,
test: {
isAvailable: isAvailable,
onClick: onClick
isAvailable,
onClick
}
};
// eslint-disable-next-line no-restricted-properties
}( mw.mobileFrontend, mw.track ) );
}( mw.track ) );

View file

@ -1,21 +1,13 @@
/**
* Initialise code that requires MobileFrontend.
*
* @todo anything that doesn't require MobileFrontend should be moved into ./setup.js
* @todo anything that can be rewritten without MobileFrontend (possibly using new frontend
* framework or upstreamed from MobileFrotend to core) should be and moved into ./setup.js
* @todo anything left should be moved to MobileFrontend extension and removed from here.
*/
module.exports = function () {
const
ms = require( 'mobile.startup' ),
// eslint-disable-next-line no-restricted-properties
mobile = mw.mobileFrontend.require( 'mobile.startup' ),
PageHTMLParser = mobile.PageHTMLParser,
LanguageInfo = mobile.LanguageInfo,
PageHTMLParser = ms.PageHTMLParser,
permissions = mw.config.get( 'wgMinervaPermissions' ) || {},
toast = mobile.toast,
notifyOnPageReload = ms.notifyOnPageReload,
time = ms.time,
preInit = require( './preInit.js' ),
mobileRedirect = require( './mobileRedirect.js' ),
@ -30,11 +22,10 @@ module.exports = function () {
ctaDrawers = require( './ctaDrawers.js' ),
drawers = require( './drawers.js' ),
desktopMMV = mw.loader.getState( 'mmv.bootstrap' ),
overlayManager = mobile.OverlayManager.getSingleton(),
currentPage = mobile.currentPage(),
currentPageHTMLParser = mobile.currentPageHTMLParser(),
overlayManager = ms.getOverlayManager(),
currentPage = ms.currentPage(),
currentPageHTMLParser = ms.currentPageHTMLParser(),
api = new mw.Api(),
eventBus = mobile.eventBusSingleton,
namespaceIDs = mw.config.get( 'wgNamespaceIds' );
/**
@ -130,28 +121,27 @@ module.exports = function () {
return;
}
return mobile.mediaViewer.overlay( {
api: api,
return ms.mediaViewer.overlay( {
api,
thumbnails: currentPageHTMLParser.getThumbnails(),
title: title,
eventBus: eventBus
title
} );
}
// Routes
overlayManager.add( /^\/media\/(.+)$/, makeMediaViewerOverlayIfNeeded );
overlayManager.add( /^\/languages$/, function () {
return mobile.languageOverlay();
return ms.languages.languageOverlay();
} );
// Register a LanguageInfo overlay which has no built-in functionality;
// a hook is fired when a language is selected, and extensions can respond
// to that hook. See GrowthExperiments WelcomeSurvey feature (in gerrit
// Ib558dc7c46cc56ff667957f9126bbe0471d25b8e for example usage).
overlayManager.add( /^\/languages\/all$/, function () {
return mobile.languageInfoOverlay( new LanguageInfo( api ), true );
return ms.languages.languageInfoOverlay( api, true );
} );
overlayManager.add( /^\/languages\/all\/no-suggestions$/, function () {
return mobile.languageInfoOverlay( new LanguageInfo( api ), false );
return ms.languages.languageInfoOverlay( api, false );
} );
// Setup
@ -204,10 +194,10 @@ module.exports = function () {
function amcHistoryClickHandler( ev ) {
var
self = this,
amcOutreach = mobile.amcOutreach,
amcOutreach = ms.amcOutreach,
amcCampaign = amcOutreach.loadCampaign(),
onDismiss = function () {
toast.showOnPageReload( mw.msg( 'mobile-frontend-amc-outreach-dismissed-message' ) );
notifyOnPageReload( mw.msg( 'mobile-frontend-amc-outreach-dismissed-message' ) );
window.location = self.href;
},
drawer = amcCampaign.showIfEligible( amcOutreach.ACTIONS.onHistoryLink, onDismiss, currentPage.title, 'action=history' );
@ -411,7 +401,7 @@ module.exports = function () {
// - search
search();
// - mobile redirect
mobileRedirect( mobile.amcOutreach, currentPage );
mobileRedirect( ms.amcOutreach, currentPage );
// Enhance timestamps on last-modified bar and watchlist
// to show relative time.

View file

@ -1,206 +1,204 @@
( function ( M ) {
/** @typedef {Object.<string, IssueSummary[]>} IssueSummaryMap */
/** @typedef {Object.<string, IssueSummary[]>} IssueSummaryMap */
var PageHTMLParser = M.require( 'mobile.startup' ).PageHTMLParser,
KEYWORD_ALL_SECTIONS = 'all',
config = mw.config,
NS_MAIN = 0,
NS_CATEGORY = 14,
CURRENT_NS = config.get( 'wgNamespaceNumber' ),
features = mw.config.get( 'wgMinervaFeatures', {} ),
pageIssuesParser = require( './parser.js' ),
pageIssuesOverlay = require( './overlay/pageIssuesOverlay.js' ),
pageIssueFormatter = require( './page/pageIssueFormatter.js' ),
// When the query string flag is set force on new treatment.
// When wgMinervaPageIssuesNewTreatment is the default this line can be removed.
QUERY_STRING_FLAG = mw.util.getParamValue( 'minerva-issues' ),
newTreatmentEnabled = features.pageIssues || QUERY_STRING_FLAG;
var PageHTMLParser = require( 'mobile.startup' ).PageHTMLParser,
KEYWORD_ALL_SECTIONS = 'all',
config = mw.config,
NS_MAIN = 0,
NS_CATEGORY = 14,
CURRENT_NS = config.get( 'wgNamespaceNumber' ),
features = mw.config.get( 'wgMinervaFeatures', {} ),
pageIssuesParser = require( './parser.js' ),
pageIssuesOverlay = require( './overlay/pageIssuesOverlay.js' ),
pageIssueFormatter = require( './page/pageIssueFormatter.js' ),
// When the query string flag is set force on new treatment.
// When wgMinervaPageIssuesNewTreatment is the default this line can be removed.
QUERY_STRING_FLAG = mw.util.getParamValue( 'minerva-issues' ),
newTreatmentEnabled = features.pageIssues || QUERY_STRING_FLAG;
/**
* Render a banner in a containing element.
* if in group B, a learn more link will be append to any amboxes inside $container
* if in group A or control, any amboxes in container will be removed and a link "page issues"
* will be rendered above the heading.
* This function comes with side effects. It will populate a global "allIssues" object which
* will link section numbers to issues.
*
* @param {PageHTMLParser} pageHTMLParser parser to search for page issues
* @param {string} labelText what the label of the page issues banner should say
* @param {string} section that the banner and its issues belong to.
* If string KEYWORD_ALL_SECTIONS banner should apply to entire page.
* @param {boolean} inline - if true the first ambox in the section will become the entry point
* for the issues overlay
* and if false, a link will be rendered under the heading.
* @param {OverlayManager} overlayManager
* @ignore
*
* @return {{ambox: jQuery, issueSummaries: IssueSummary[]}}
*/
function insertBannersOrNotice( pageHTMLParser, labelText, section, inline, overlayManager ) {
var
$metadata,
issueUrl = section === KEYWORD_ALL_SECTIONS ? '#/issues/' + KEYWORD_ALL_SECTIONS : '#/issues/' + section,
selector = [ '.ambox', '.tmbox', '.cmbox', '.fmbox' ].join( ',' ),
issueSummaries = [];
/**
* Render a banner in a containing element.
* if in group B, a learn more link will be append to any amboxes inside $container
* if in group A or control, any amboxes in container will be removed and a link "page issues"
* will be rendered above the heading.
* This function comes with side effects. It will populate a global "allIssues" object which
* will link section numbers to issues.
*
* @param {PageHTMLParser} pageHTMLParser parser to search for page issues
* @param {string} labelText what the label of the page issues banner should say
* @param {string} section that the banner and its issues belong to.
* If string KEYWORD_ALL_SECTIONS banner should apply to entire page.
* @param {boolean} inline - if true the first ambox in the section will become the entry point
* for the issues overlay
* and if false, a link will be rendered under the heading.
* @param {OverlayManager} overlayManager
* @ignore
*
* @return {{ambox: jQuery, issueSummaries: IssueSummary[]}}
*/
function insertBannersOrNotice( pageHTMLParser, labelText, section, inline, overlayManager ) {
var
$metadata,
issueUrl = section === KEYWORD_ALL_SECTIONS ? '#/issues/' + KEYWORD_ALL_SECTIONS : '#/issues/' + section,
selector = [ '.ambox', '.tmbox', '.cmbox', '.fmbox' ].join( ',' ),
issueSummaries = [];
if ( section === KEYWORD_ALL_SECTIONS ) {
$metadata = pageHTMLParser.$el.find( selector );
} else {
// find heading associated with the section
$metadata = pageHTMLParser.findChildInSectionLead( parseInt( section, 10 ), selector );
}
// clean it up a little
$metadata.find( '.NavFrame' ).remove();
$metadata.each( function () {
var issueSummary,
$this = $( this );
if ( $this.find( selector ).length === 0 ) {
issueSummary = pageIssuesParser.extract( $this );
// Some issues after "extract" has been run will have no text.
// For example in Template:Talk header the table will be removed and no issue found.
// These should not be rendered.
if ( issueSummary.text ) {
issueSummaries.push( issueSummary );
}
}
} );
if ( inline ) {
issueSummaries.forEach( function ( issueSummary, i ) {
var isGrouped = issueSummary.issue.grouped,
lastIssueIsGrouped = issueSummaries[ i - 1 ] &&
issueSummaries[ i - 1 ].issue.grouped,
multiple = isGrouped && !lastIssueIsGrouped;
// only render the first grouped issue of each group
pageIssueFormatter.insertPageIssueBanner(
issueSummary,
mw.msg( 'skin-minerva-issue-learn-more' ),
issueUrl,
overlayManager,
multiple
);
} );
} else if ( issueSummaries.length ) {
pageIssueFormatter.insertPageIssueNotice( labelText, section );
}
return {
ambox: $metadata,
issueSummaries: issueSummaries
};
if ( section === KEYWORD_ALL_SECTIONS ) {
$metadata = pageHTMLParser.$el.find( selector );
} else {
// find heading associated with the section
$metadata = pageHTMLParser.findChildInSectionLead( parseInt( section, 10 ), selector );
}
// clean it up a little
$metadata.find( '.NavFrame' ).remove();
$metadata.each( function () {
var issueSummary,
$this = $( this );
/**
* Obtains the list of issues for the current page and provided section
*
* @param {IssueSummaryMap} allIssues mapping section {number} to {IssueSummary}
* @param {number|string} section either KEYWORD_ALL_SECTIONS or a number relating to the
* section the issues belong to
* @return {jQuery[]} array of all issues.
*/
function getIssues( allIssues, section ) {
if ( section !== KEYWORD_ALL_SECTIONS ) {
return allIssues[ section ] || [];
}
// Note section.all may not exist, depending on the structure of the HTML page.
// It will only exist when Minerva has been run in desktop mode.
// If it's absent, we'll reduce all the other lists into one.
return allIssues[ KEYWORD_ALL_SECTIONS ] || Object.keys( allIssues ).reduce(
function ( all, key ) {
return all.concat( allIssues[ key ] );
},
[]
);
}
/**
* Scan an element for any known cleanup templates and replace them with a button
* that opens them in a mobile friendly overlay.
*
* @ignore
* @param {OverlayManager} overlayManager
* @param {PageHTMLParser} pageHTMLParser
*/
function initPageIssues( overlayManager, pageHTMLParser ) {
var
section,
issueSummaries = [],
allIssues = {},
label,
$lead = pageHTMLParser.getLeadSectionElement(),
issueOverlayShowAll = CURRENT_NS === NS_CATEGORY || !$lead,
inline = newTreatmentEnabled && CURRENT_NS === 0;
// set A-B test class.
// When wgMinervaPageIssuesNewTreatment is the default this can be removed.
if ( newTreatmentEnabled ) {
$( document.documentElement ).addClass( 'issues-group-B' );
}
if ( CURRENT_NS === NS_CATEGORY ) {
section = KEYWORD_ALL_SECTIONS;
// e.g. Template:English variant category; Template:WikiProject
issueSummaries = insertBannersOrNotice( pageHTMLParser, mw.msg( 'mobile-frontend-meta-data-issues-header' ),
section, inline, overlayManager ).issueSummaries;
allIssues[ section ] = issueSummaries;
} else if ( CURRENT_NS === NS_MAIN ) {
label = mw.msg( 'mobile-frontend-meta-data-issues-header' );
if ( issueOverlayShowAll ) {
section = KEYWORD_ALL_SECTIONS;
issueSummaries = insertBannersOrNotice(
pageHTMLParser, label, section, inline, overlayManager
).issueSummaries;
allIssues[ section ] = issueSummaries;
} else {
// parse lead
section = '0';
issueSummaries = insertBannersOrNotice(
pageHTMLParser, label, section, inline, overlayManager
).issueSummaries;
allIssues[ section ] = issueSummaries;
if ( newTreatmentEnabled ) {
// parse other sections but only in group B. In treatment A no issues are shown
// for sections.
pageHTMLParser.$el.find( PageHTMLParser.HEADING_SELECTOR ).each(
function ( i, headingEl ) {
var $headingEl = $( headingEl ),
// section number is absent on protected pages, when this is the case use i,
// otherwise icon will not show (T340910)
sectionNum = $headingEl.find( '.edit-page' ).data( 'section' ) || i;
// Note certain headings matched using
// PageHTMLParser.HEADING_SELECTOR may not be headings and will
// not have a edit link. E.g. table of contents.
if ( sectionNum ) {
// Render banner for sectionNum associated with headingEl inside
// Page
section = sectionNum.toString();
issueSummaries = insertBannersOrNotice(
pageHTMLParser, label, section, inline, overlayManager
).issueSummaries;
allIssues[ section ] = issueSummaries;
}
}
);
}
if ( $this.find( selector ).length === 0 ) {
issueSummary = pageIssuesParser.extract( $this );
// Some issues after "extract" has been run will have no text.
// For example in Template:Talk header the table will be removed and no issue found.
// These should not be rendered.
if ( issueSummary.text ) {
issueSummaries.push( issueSummary );
}
}
} );
// Setup the overlay route.
overlayManager.add( new RegExp( '^/issues/(\\d+|' + KEYWORD_ALL_SECTIONS + ')$' ), function ( s ) {
return pageIssuesOverlay(
getIssues( allIssues, s ), s, CURRENT_NS
if ( inline ) {
issueSummaries.forEach( function ( issueSummary, i ) {
var isGrouped = issueSummary.issue.grouped,
lastIssueIsGrouped = issueSummaries[ i - 1 ] &&
issueSummaries[ i - 1 ].issue.grouped,
multiple = isGrouped && !lastIssueIsGrouped;
// only render the first grouped issue of each group
pageIssueFormatter.insertPageIssueBanner(
issueSummary,
mw.msg( 'skin-minerva-issue-learn-more' ),
issueUrl,
overlayManager,
multiple
);
} );
} else if ( issueSummaries.length ) {
pageIssueFormatter.insertPageIssueNotice( labelText, section );
}
module.exports = {
init: initPageIssues,
test: {
insertBannersOrNotice: insertBannersOrNotice
}
return {
ambox: $metadata,
issueSummaries: issueSummaries
};
}
/**
* Obtains the list of issues for the current page and provided section
*
* @param {IssueSummaryMap} allIssues mapping section {number} to {IssueSummary}
* @param {number|string} section either KEYWORD_ALL_SECTIONS or a number relating to the
* section the issues belong to
* @return {jQuery[]} array of all issues.
*/
function getIssues( allIssues, section ) {
if ( section !== KEYWORD_ALL_SECTIONS ) {
return allIssues[ section ] || [];
}
// Note section.all may not exist, depending on the structure of the HTML page.
// It will only exist when Minerva has been run in desktop mode.
// If it's absent, we'll reduce all the other lists into one.
return allIssues[ KEYWORD_ALL_SECTIONS ] || Object.keys( allIssues ).reduce(
function ( all, key ) {
return all.concat( allIssues[ key ] );
},
[]
);
}
/**
* Scan an element for any known cleanup templates and replace them with a button
* that opens them in a mobile friendly overlay.
*
* @ignore
* @param {OverlayManager} overlayManager
* @param {PageHTMLParser} pageHTMLParser
*/
function initPageIssues( overlayManager, pageHTMLParser ) {
var
section,
issueSummaries = [],
allIssues = {},
label,
$lead = pageHTMLParser.getLeadSectionElement(),
issueOverlayShowAll = CURRENT_NS === NS_CATEGORY || !$lead,
inline = newTreatmentEnabled && CURRENT_NS === 0;
// set A-B test class.
// When wgMinervaPageIssuesNewTreatment is the default this can be removed.
if ( newTreatmentEnabled ) {
$( document.documentElement ).addClass( 'issues-group-B' );
}
if ( CURRENT_NS === NS_CATEGORY ) {
section = KEYWORD_ALL_SECTIONS;
// e.g. Template:English variant category; Template:WikiProject
issueSummaries = insertBannersOrNotice( pageHTMLParser, mw.msg( 'mobile-frontend-meta-data-issues-header' ),
section, inline, overlayManager ).issueSummaries;
allIssues[ section ] = issueSummaries;
} else if ( CURRENT_NS === NS_MAIN ) {
label = mw.msg( 'mobile-frontend-meta-data-issues-header' );
if ( issueOverlayShowAll ) {
section = KEYWORD_ALL_SECTIONS;
issueSummaries = insertBannersOrNotice(
pageHTMLParser, label, section, inline, overlayManager
).issueSummaries;
allIssues[ section ] = issueSummaries;
} else {
// parse lead
section = '0';
issueSummaries = insertBannersOrNotice(
pageHTMLParser, label, section, inline, overlayManager
).issueSummaries;
allIssues[ section ] = issueSummaries;
if ( newTreatmentEnabled ) {
// parse other sections but only in group B. In treatment A no issues are shown
// for sections.
pageHTMLParser.$el.find( PageHTMLParser.HEADING_SELECTOR ).each(
function ( i, headingEl ) {
var $headingEl = $( headingEl ),
// section number is absent on protected pages, when this is the case use i,
// otherwise icon will not show (T340910)
sectionNum = $headingEl.find( '.edit-page' ).data( 'section' ) || i;
// Note certain headings matched using
// PageHTMLParser.HEADING_SELECTOR may not be headings and will
// not have a edit link. E.g. table of contents.
if ( sectionNum ) {
// Render banner for sectionNum associated with headingEl inside
// Page
section = sectionNum.toString();
issueSummaries = insertBannersOrNotice(
pageHTMLParser, label, section, inline, overlayManager
).issueSummaries;
allIssues[ section ] = issueSummaries;
}
}
);
}
}
}
// Setup the overlay route.
overlayManager.add( new RegExp( '^/issues/(\\d+|' + KEYWORD_ALL_SECTIONS + ')$' ), function ( s ) {
return pageIssuesOverlay(
getIssues( allIssues, s ), s, CURRENT_NS
);
} );
}
module.exports = {
init: initPageIssues,
test: {
insertBannersOrNotice: insertBannersOrNotice
}
};
// eslint-disable-next-line no-restricted-properties
}( mw.mobileFrontend ) );

View file

@ -1,7 +1,6 @@
( function ( M ) {
var
mobile = M.require( 'mobile.startup' ),
mfExtend = mobile.mfExtend,
( function () {
const
mobile = require( 'mobile.startup' ),
View = mobile.View,
IssueNotice = require( './IssueNotice.js' );
@ -13,24 +12,21 @@
*
* @param {IssueSummary} issues
*/
function IssueList( issues ) {
this.issues = issues;
View.call( this, { className: 'cleanup' } );
}
mfExtend( IssueList, View, {
tagName: 'ul',
postRender: function () {
View.prototype.postRender.apply( this, arguments );
class IssueList extends View {
constructor( issues ) {
super( { className: 'cleanup' } );
this.issues = issues;
this.tagName = 'ul';
}
postRender() {
super.postRender();
this.append(
this.issues.map( function ( issue ) {
return new IssueNotice( issue ).$el;
} )
);
}
} );
}
module.exports = IssueList;
// eslint-disable-next-line no-restricted-properties
}( mw.mobileFrontend ) );
}() );

View file

@ -1,7 +1,6 @@
( function ( M ) {
var
mobile = M.require( 'mobile.startup' ),
mfExtend = mobile.mfExtend,
( function () {
const
mobile = require( 'mobile.startup' ),
View = mobile.View;
/**
@ -12,18 +11,17 @@
*
* @param {IssueSummary} props
*/
function IssueNotice( props ) {
View.call( this, props );
}
mfExtend( IssueNotice, View, {
tagName: 'li',
template: mw.template.get( 'skins.minerva.scripts', 'IssueNotice.mustache' ),
postRender: function () {
View.prototype.postRender.apply( this, arguments );
class IssueNotice extends View {
constructor() {
super( { className: 'cleanup' } );
this.tagName = 'li';
this.template = mw.template.get( 'skins.minerva.scripts', 'IssueNotice.mustache' );
}
postRender() {
super.postRender();
this.$el.find( '.issue-notice' ).prepend( this.options.issue.icon.$el );
}
} );
}
module.exports = IssueNotice;
// eslint-disable-next-line no-restricted-properties
}( mw.mobileFrontend ) );
}() );

View file

@ -1,6 +1,6 @@
( function ( M ) {
var
Overlay = M.require( 'mobile.startup' ).Overlay,
( function () {
const
Overlay = require( 'mobile.startup' ).Overlay,
IssueList = require( './IssueList.js' ),
KEYWORD_ALL_SECTIONS = 'all',
NS_MAIN = 0,
@ -53,5 +53,4 @@
module.exports = pageIssuesOverlay;
// eslint-disable-next-line no-restricted-properties
}( mw.mobileFrontend ) );
}() );

View file

@ -20,9 +20,7 @@
issue.$el.find( '.mbox-text' ),
$clickContainer = multiple ? issue.$el.parents( '.mbox-text' ) : issue.$el;
$issueContainer.prepend(
issue.issue.icon.$el.clone()
);
$issueContainer.prepend( issue.issue.iconElement );
$issueContainer.prepend( $learnMoreEl );
$clickContainer.on( 'click', function () {
@ -47,7 +45,7 @@
}
module.exports = {
insertPageIssueBanner: insertPageIssueBanner,
insertPageIssueNotice: insertPageIssueNotice
insertPageIssueBanner,
insertPageIssueNotice
};
}() );

View file

@ -1,4 +1,4 @@
( function ( M ) {
( function () {
/**
* @typedef PageIssue
* @property {string} severity A SEVERITY_LEVEL key.
@ -14,7 +14,7 @@
* @property {string} text HTML string.
*/
var Icon = M.require( 'mobile.startup' ).Icon,
var
// Icons are matching the type selector below use a TYPE_* icon. When unmatched, the icon is
// chosen by severity. Their color is always determined by severity, too.
ICON_NAME = {
@ -169,14 +169,13 @@
* @return {PageIssue}
*/
function parse( box ) {
var severity = parseSeverity( box );
const severity = parseSeverity( box );
const iconElement = document.createElement( 'div' );
iconElement.classList.add( `minerva-icon--${iconName( box, severity )}`, 'minerva-ambox-icon' );
return {
severity: severity,
severity,
grouped: parseGroup( box ),
icon: new Icon( {
glyphPrefix: 'minerva',
icon: iconName( box, severity )
} )
iconElement
};
}
@ -214,16 +213,16 @@
}
module.exports = {
extract: extract,
parse: parse,
maxSeverity: maxSeverity,
iconName: iconName,
extract,
parse,
maxSeverity,
iconName,
test: {
parseSeverity: parseSeverity,
parseType: parseType,
parseGroup: parseGroup
parseSeverity,
parseType,
parseGroup
}
};
// eslint-disable-next-line no-restricted-properties
}( mw.mobileFrontend ) );
}() );

View file

@ -0,0 +1,40 @@
@import 'mediawiki.skin.variables';
@import 'overlay/pageIssuesOverlay.less';
@minerva-icon-issue-generic: '<path d="M19.64 16.36 11.53 2.3A1.85 1.85 0 0 0 10 1.21 1.85 1.85 0 0 0 8.48 2.3L.36 16.36C-.48 17.81.21 19 1.88 19h16.24c1.67 0 2.36-1.19 1.52-2.64zM11 16H9v-2h2zm0-4H9V6h2z"/>';
@minerva-icon-issue-severity-medium: '<path d="M10 0a10 10 0 1 0 10 10A10 10 0 0 0 10 0zm1 16H9v-2h2zm0-4H9V4h2z"/>';
@minerva-icon-issue-type-point-of-view: '<path d="m6 8 2-3 2-3H2l2 3zm8 4-2 3-2 3h8l-2-3zm-12.236.329L17.47 5.823l.766 1.848L2.53 14.177z"/>';
@minerva-icon-issue-severity-low: '<path d="M17.9 11.9c-1.4-.3-3.4-2.8-4.8-4.6l-8.8 6.2 2.3 3.3c.8 1.2 2.3 1.8 4 1.7l-1.6-2c-.1-.2 0-.4.1-.5.2-.1.4-.1.5.1l1.8 2.3c.8-.2 1.7-.6 2.6-1.1-1.3-.8-2.2-2.4-2.2-2.4-.1-.2 0-.4.1-.5.2-.1.4 0 .5.1 0 0 1 1.7 2.3 2.3.9-.7 1.6-1.3 2.1-1.9-2.1-.4-3.1-1.9-3.2-1.9-.1-.2-.1-.4.1-.5.2-.1.4-.1.5.1a5 5 0 0 0 3.1 1.7 4 4 0 0 0 .8-2c.1-.2 0-.3-.2-.4z M11.6 5.2a1 1 0 0 0-1.3-.2L7.9 6.6 4.5 1.8a.7.7 0 0 0-.9-.2L2.1 2.7c-.3.2-.3.6-.2.8l3.4 4.8L3 10a1 1 0 0 0-.2 1.3L4 13l8.8-6.1-1.2-1.7z"/>';
@minerva-icon-issue-type-move: '<path d="M6 8.7V12H0v4h6v3l3-2.6 3-2.6-3-2.5z M14 4V.7l-3 2.6-3 2.5 3 2.6 3 2.6V8h6V4z"/>';
.minerva-icon--issue-severity-medium-mediumColor {
.cdx-mixin-css-icon( @minerva-icon-issue-severity-medium, #ff5d01 );
}
.minerva-icon--issue-type-point-of-view-mediumColor {
.cdx-mixin-css-icon( @minerva-icon-issue-type-point-of-view, #ff5d01 );
}
.minerva-icon--issue-severity-low-lowColor {
.cdx-mixin-css-icon( @minerva-icon-issue-severity-low, #f5a623 );
}
.minerva-icon--issue-type-move-defaultColor {
.cdx-mixin-css-icon( @minerva-icon-issue-type-move, #36c );
}
.minerva-icon--issue-generic-defaultColor {
.cdx-mixin-css-icon( @minerva-icon-issue-generic, #54595d );
}
.minerva-icon--issue-generic-lowColor {
.cdx-mixin-css-icon( @minerva-icon-issue-generic, #fc3 );
}
.minerva-icon--issue-generic-mediumColor {
.cdx-mixin-css-icon( @minerva-icon-issue-generic, #ff5d01 );
}
.minerva-icon--issue-generic-highColor {
.cdx-mixin-css-icon( @minerva-icon-issue-generic, #d33 );
}

View file

@ -1,11 +1,3 @@
// FIXME: make this an object with a constructor to facilitate testing
// (see https://bugzilla.wikimedia.org/show_bug.cgi?id=44264)
/**
* mobileFrontend namespace
*
* @class mw.mobileFrontend
* @singleton
*/
module.exports = function () {
var menus = require( './menu.js' );

View file

@ -2,12 +2,11 @@ var drawers = require( './drawers.js' );
module.exports = function () {
// eslint-disable-next-line no-restricted-properties
var M = mw.mobileFrontend,
mobile = M.require( 'mobile.startup' ),
var mobile = require( 'mobile.startup' ),
references = mobile.references,
currentPage = mobile.currentPage(),
currentPageHTMLParser = mobile.currentPageHTMLParser(),
ReferencesHtmlScraperGateway = mobile.ReferencesHtmlScraperGateway,
ReferencesHtmlScraperGateway = mobile.references.ReferencesHtmlScraperGateway,
gateway = new ReferencesHtmlScraperGateway( new mw.Api() );
/**
@ -60,7 +59,7 @@ module.exports = function () {
function init() {
// Make references clickable and show a drawer when clicked on.
mobile.util.getDocument().on( 'click', 'sup.reference a', onClickReference );
$( document ).on( 'click', 'sup.reference a', onClickReference );
}
init();

View file

@ -1,11 +1,9 @@
module.exports = function () {
var
// eslint-disable-next-line no-restricted-properties
M = mw.mobileFrontend,
mobile = M.require( 'mobile.startup' ),
mobile = require( 'mobile.startup' ),
SearchOverlay = mobile.search.SearchOverlay,
SearchGateway = mobile.search.SearchGateway,
overlayManager = mobile.OverlayManager.getSingleton(),
overlayManager = mobile.getOverlayManager(),
// eslint-disable-next-line no-jquery/no-global-selector
$searchInput = $( '#searchInput' ),
placeholder = $searchInput.attr( 'placeholder' ),

View file

@ -3,8 +3,17 @@
@import 'mediawiki.mixins.less';
@import 'toc.less';
@import 'watchStar.less';
@import 'page-issues/styles.less';
@import 'Toolbar.less';
.minerva-icon--download {
.cdx-mixin-css-icon( @cdx-icon-download, #54595d );
}
.minerva-icon--listBullet {
.cdx-mixin-css-icon( @cdx-icon-list-bullet, #54595d );
}
.mw-mf-page-center__mask {
position: absolute;
top: 0;

View file

@ -364,56 +364,6 @@
"unStar"
]
},
"skins.minerva.icons.images.scripts.misc": {
"class": "ResourceLoaderOOUIIconPackModule",
"selectorWithoutVariant": ".minerva-icon--{name}",
"defaultColor": "#54595d",
"icons": [
"download",
"listBullet"
],
"variants": []
},
"skins.minerva.icons.page.issues.uncolored": {
"class": "ResourceLoaderImageModule",
"selector": ".mw-ui-icon-minerva-{name}",
"images": {
"issue-severity-low-lowColor": "resources/skins.minerva.icons.page.issues.uncolored/issue-severity-low.svg",
"issue-type-move-defaultColor": "resources/skins.minerva.icons.page.issues.uncolored/issue-type-move.svg"
}
},
"skins.minerva.icons.page.issues.default.color": {
"class": "ResourceLoaderImageModule",
"selectorWithoutVariant": ".mw-ui-icon-minerva-{name}-defaultColor",
"selectorWithVariant": ".mw-ui-icon-minerva-{name}-{variant}",
"defaultColor": "#54595d",
"variants": {
"lowColor": {
"color": "#fc3",
"global": true
},
"mediumColor": {
"color": "#ff5d01",
"global": true
},
"highColor": {
"color": "#d33",
"global": true
}
},
"images": {
"issue-generic": "resources/skins.minerva.icons.page.issues.default.color/issue-generic.svg"
}
},
"skins.minerva.icons.page.issues.medium.color": {
"class": "ResourceLoaderImageModule",
"selector": ".mw-ui-icon-minerva-{name}-mediumColor",
"defaultColor": "#ff5d01",
"images": {
"issue-severity-medium": "resources/skins.minerva.icons.page.issues.medium.color/issue-severity-medium.svg",
"issue-type-point-of-view": "resources/skins.minerva.icons.page.issues.medium.color/issue-type-point-of-view.svg"
}
},
"skins.minerva.mainPage.styles": {
"styles": [
"resources/skins.minerva.mainPage.styles/common.less"
@ -478,10 +428,6 @@
"skins.minerva.mainMenu.icons",
"skins.minerva.mainMenu.styles",
"mediawiki.cookie",
"skins.minerva.icons.images.scripts.misc",
"skins.minerva.icons.page.issues.uncolored",
"skins.minerva.icons.page.issues.default.color",
"skins.minerva.icons.page.issues.medium.color",
"skins.minerva.messageBox.styles",
"mediawiki.util",
"mediawiki.router",
@ -517,8 +463,7 @@
"mobile-frontend-redirected-from"
],
"styles": [
"resources/skins.minerva.scripts/styles.less",
"resources/skins.minerva.scripts/page-issues/overlay/pageIssuesOverlay.less"
"resources/skins.minerva.scripts/styles.less"
],
"templates": {
"IssueNotice.mustache": "resources/skins.minerva.scripts/page-issues/overlay/IssueNotice.mustache"

View file

@ -1,7 +1,6 @@
( function ( M ) {
( function () {
var AB = require( '../../../resources/skins.minerva.scripts/AB.js' ),
util = M.require( 'mobile.startup' ).util,
defaultConfig = {
testName: 'WME.MinervaABTest',
samplingRate: 0.5,
@ -22,7 +21,7 @@
i;
for ( i = 0; i < maxUsers; i++ ) {
config = util.extend( {}, defaultConfig, {
config = Object.assign( {}, defaultConfig, {
sessionId: mw.user.generateRandomSessionId()
} );
bucketingTest = new AB( config );
@ -53,4 +52,4 @@
true, 'test new treatment group is about 25% (' + userBuckets.treatment / 10 + '%)' );
} );
}( mw.mobileFrontend ) );
}() );

View file

@ -1,39 +1,46 @@
( function ( M ) {
( function () {
var VALID_UA = 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36',
VALID_SUPPORTED_NAMESPACES = [ 0 ],
mobile = M.require( 'mobile.startup' ),
icons = mobile.icons,
spinner = () => ( {
$el: $( '<span>' )
} ),
Deferred = $.Deferred,
windowChrome = { chrome: true },
windowNotChrome = {},
downloadAction = require( '../../../resources/skins.minerva.scripts/downloadPageAction.js' ),
onClick = downloadAction.test.onClick,
isAvailable = downloadAction.test.isAvailable,
browser = mobile.Browser.getSingleton(),
lazyImageLoader = mobile.lazyImages.lazyImageLoader,
Page = mobile.Page;
isAvailable = downloadAction.test.isAvailable;
class Page {
constructor( options ) {
this.isMissing = options.isMissing;
this._isMainPage = !!options.isMainPage;
}
isMainPage() {
return this._isMainPage;
}
getNamespaceId() {
return 0;
}
}
QUnit.module( 'Minerva DownloadIcon', {
beforeEach: function () {
this.getOnClickHandler = function () {
var portletLink = document.createElement( 'li' ),
spinner = icons.spinner();
this.getOnClickHandler = function ( onLoadAllImages ) {
var portletLink = document.createElement( 'li' );
return function () {
onClick( portletLink, spinner );
onClick( portletLink, spinner(), onLoadAllImages );
};
};
}
} );
QUnit.test( '#getOnClickHandler (print after image download)', function ( assert ) {
var handler = this.getOnClickHandler(),
const
d = Deferred(),
handler = this.getOnClickHandler( () => d.resolve() ),
spy = this.sandbox.stub( window, 'print' );
this.sandbox.stub( lazyImageLoader, 'loadImages' ).returns( d.resolve() );
this.sandbox.stub( lazyImageLoader, 'queryPlaceholders' ).returns( [] );
handler();
d.then( function () {
assert.strictEqual( spy.callCount, 1, 'Print occurred.' );
@ -43,13 +50,11 @@
} );
QUnit.test( '#getOnClickHandler (print via timeout)', function ( assert ) {
var handler = this.getOnClickHandler(),
const
d = Deferred(),
handler = this.getOnClickHandler( () => d.resolve() ),
spy = this.sandbox.stub( window, 'print' );
this.sandbox.stub( lazyImageLoader, 'loadImages' ).returns( d );
this.sandbox.stub( lazyImageLoader, 'queryPlaceholders' ).returns( [] );
window.setTimeout( function () {
d.resolve();
}, 3400 );
@ -64,13 +69,10 @@
} );
QUnit.test( '#getOnClickHandler (multiple clicks)', function ( assert ) {
var handler = this.getOnClickHandler(),
d = Deferred(),
const d = Deferred(),
handler = this.getOnClickHandler( () => d.resolve() ),
spy = this.sandbox.stub( window, 'print' );
this.sandbox.stub( lazyImageLoader, 'loadImages' ).returns( d );
this.sandbox.stub( lazyImageLoader, 'queryPlaceholders' ).returns( [] );
window.setTimeout( function () {
d.resolve();
}, 3400 );
@ -133,8 +135,7 @@
} );
QUnit.test( 'isAvailable() returns false for iOS', function ( assert ) {
this.sandbox.stub( browser, 'isIos' ).returns( true );
assert.false( this.isAvailable( VALID_UA ) );
assert.false( this.isAvailable( 'ipad' ) );
} );
QUnit.test( 'isAvailable() uses window.chrome to filter certain chrome-like browsers', function ( assert ) {
@ -188,4 +189,4 @@
assert.true( this.isAvailable( 'Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G950U1 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/6.2 Chrome/56.0.2924.87 Mobile Safari/537.36' ) );
} );
}( mw.mobileFrontend ) );
}() );

View file

@ -1,11 +1,10 @@
( function ( M ) {
var
mobile = M.require( 'mobile.startup' ),
( function () {
const
mobile = require( 'mobile.startup' ),
pageIssues = require( '../../../../resources/skins.minerva.scripts/page-issues/index.js' ),
insertBannersOrNotice = pageIssues.test.insertBannersOrNotice,
OverlayManager = mobile.OverlayManager,
PageHTMLParser = mobile.PageHTMLParser,
overlayManager = OverlayManager.getSingleton(),
overlayManager = mobile.getOverlayManager(),
$mockContainer = $(
'<div id=\'bodyContent\'>' +
'<table class=\'ambox ambox-content\'>' +
@ -30,11 +29,10 @@
} );
QUnit.test( 'insertBannersOrNotice() should add an icon', function ( assert ) {
// FIXME: Remove mw-ui-icon when T346184 and/or T346162 has been resolved.
assert.true( /(mw-ui-icon|mf-icon)/.test( processedAmbox.html() ) );
assert.true( /(minerva-icon)/.test( processedAmbox.html() ) );
} );
QUnit.test( 'clicking on the product of insertBannersOrNotice() should trigger a URL change', function ( assert ) {
processedAmbox.click();
assert.strictEqual( window.location.hash, '#/issues/' + SECTION );
} );
}( mw.mobileFrontend ) );
}() );

View file

@ -1,8 +1,9 @@
( function () {
var icon = {},
const iconElement = document.createElement( 'div' ),
pageIssuesParser = require( '../../../../resources/skins.minerva.scripts/page-issues/parser.js' ),
extractMessage = pageIssuesParser.extract;
iconElement.classList.add( 'minerva-icon--issue-generic-defaultColor', 'minerva-ambox-icon' );
QUnit.module( 'Minerva pageIssuesParser' );
/**
@ -15,7 +16,7 @@
return box;
}
QUnit.test( 'extractMessage', function () {
QUnit.test( 'extractMessage', function ( assert ) {
[
[
$( '<div />' ).html(
@ -24,8 +25,8 @@
{
issue: {
severity: 'DEFAULT',
grouped: true,
icon: icon
iconElement,
grouped: true
},
text: '<p>Smelly</p>'
},
@ -38,16 +39,18 @@
{
issue: {
severity: 'DEFAULT',
grouped: false,
icon: icon
iconElement,
grouped: false
},
text: '<p>Dirty</p>'
},
'When the box is not child of mw-collapsible-content it !grouped'
]
].forEach( function ( test ) {
sinon.assert.match( // eslint-disable-line no-undef
extractMessage( test[ 0 ] ),
const msg = extractMessage( test[ 0 ] );
delete msg.$el;
assert.deepEqual(
msg,
test[ 1 ],
test[ 2 ]
);