dc6f707ea1
createBanner was incorrectly always assigning the 'all' keyword to the old page issues banner. Instead it should use the section number in the function signature - as this decision of which issues to show is made inside initPageIssues For the old treatment, in the main namespace, we only show issues in the lead section. When we use the 'all' keyword the visual is the same, but it breaks the instrumentation requirement that sectionNumbers and issuesSeverity should be the correct length Note, for the talk and category pages treatment, we do not log any events so the instrumentation doesn't matter here and the 'all' keyword correctly targets all issues in the page as before. Bug: T203050 Change-Id: I63e45da05ca033fe282633f7fd59038a8e5d8c8d
343 lines
12 KiB
JavaScript
343 lines
12 KiB
JavaScript
( function ( M, $ ) {
|
|
var AB = M.require( 'skins.minerva.scripts/AB' ),
|
|
Page = M.require( 'mobile.startup/Page' ),
|
|
allIssues = {},
|
|
KEYWORD_ALL_SECTIONS = 'all',
|
|
config = mw.config,
|
|
user = mw.user,
|
|
NS_MAIN = 0,
|
|
NS_TALK = 1,
|
|
NS_CATEGORY = 14,
|
|
CURRENT_NS = config.get( 'wgNamespaceNumber' ),
|
|
Icon = M.require( 'mobile.startup/Icon' ),
|
|
pageIssuesLogger = M.require( 'skins.minerva.scripts/pageIssuesLogger' ),
|
|
pageIssuesParser = M.require( 'skins.minerva.scripts/pageIssuesParser' ),
|
|
PageIssuesOverlay = M.require( 'skins.minerva.scripts/PageIssuesOverlay' ),
|
|
// setup ab test
|
|
abTest = new AB( {
|
|
testName: 'WME.PageIssuesAB',
|
|
// Run AB only on article namespace, otherwise set samplingRate to 0,
|
|
// forcing user into control (i.e. ignored/not logged) group.
|
|
samplingRate: ( CURRENT_NS === NS_MAIN ) ? config.get( 'wgMinervaABSamplingRate', 0 ) : 0,
|
|
sessionId: user.sessionId()
|
|
} ),
|
|
newTreatmentEnabled = abTest.isB();
|
|
|
|
/**
|
|
* @typedef {Object} IssueSummary
|
|
* @prop {string} severity A PageIssue.severity.
|
|
* @prop {Boolean} isMultiple Whether or not the issue is part of a "multiple issues" template.
|
|
* @prop {string} icon HTML string.
|
|
* @prop {string} text HTML string.
|
|
*/
|
|
|
|
function isLoggingRequired( pageIssues ) {
|
|
// No logging necessary when the A/B test is disabled (control group).
|
|
return abTest.isEnabled() && pageIssues.length;
|
|
}
|
|
|
|
/**
|
|
* Array.reduce callback that returns the severity of page issues.
|
|
* In the case that a page-issue is part of a "multiple issues" template,
|
|
* returns the maximum severity for that group of issues.
|
|
*
|
|
* @param {array} acc - the return array containing severities
|
|
* @param {IssueSummary} summary current IssueSummary object
|
|
* @param {number} currentIndex current index of pageIssues
|
|
* @param {array} pageIssues array of pageIssues
|
|
*
|
|
* @return {array} acc
|
|
*/
|
|
function formatPageIssuesSeverity( acc, summary, currentIndex, pageIssues ) {
|
|
var lastItem = pageIssues[ currentIndex - 1 ];
|
|
if ( lastItem && lastItem.isMultiple && summary.isMultiple ) {
|
|
acc[ acc.length - 1 ] = pageIssuesParser.maxSeverity(
|
|
[ lastItem.severity, summary.severity ]
|
|
);
|
|
} else {
|
|
acc.push( summary.severity );
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
/**
|
|
* Extract a summary message from a cleanup template generated element that is
|
|
* friendly for mobile display.
|
|
* @param {Object} $box element to extract the message from
|
|
* @return {IssueSummary}
|
|
*/
|
|
function extractMessage( $box ) {
|
|
var SELECTOR = '.mbox-text, .ambox-text',
|
|
MULTIPLE_SELECTOR = '.mw-collapsible-content',
|
|
$container = $( '<div>' ),
|
|
pageIssue;
|
|
|
|
$box.find( SELECTOR ).each( function () {
|
|
var contents,
|
|
$this = $( this );
|
|
// Clean up talk page boxes
|
|
$this.find( 'table, .noprint' ).remove();
|
|
contents = $this.html();
|
|
|
|
if ( contents ) {
|
|
$( '<p>' ).html( contents ).appendTo( $container );
|
|
}
|
|
} );
|
|
|
|
pageIssue = pageIssuesParser.parse( $box.get( 0 ) );
|
|
|
|
return {
|
|
severity: pageIssue.severity,
|
|
isMultiple: $box.parent().is( MULTIPLE_SELECTOR ),
|
|
icon: pageIssue.icon.toHtmlString(),
|
|
text: $container.html()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a link element that opens the issues overlay.
|
|
*
|
|
* @ignore
|
|
*
|
|
* @param {string} labelText The text value of the element
|
|
* @return {JQuery}
|
|
*/
|
|
function createLinkElement( labelText ) {
|
|
return $( '<a class="cleanup mw-mf-cleanup"></a>' )
|
|
.text( labelText );
|
|
}
|
|
|
|
/**
|
|
* 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 {Page} page to search for page issues inside
|
|
* @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 {JQuery.Object}
|
|
*/
|
|
function createBanner( page, labelText, section, inline, overlayManager ) {
|
|
var $learnMore, $metadata,
|
|
issueUrl = section === KEYWORD_ALL_SECTIONS ? '#/issues/' + KEYWORD_ALL_SECTIONS : '#/issues/' + section,
|
|
selector = 'table.ambox, table.tmbox, table.cmbox, table.fmbox',
|
|
issues = [],
|
|
$link,
|
|
severity;
|
|
|
|
if ( section === KEYWORD_ALL_SECTIONS ) {
|
|
$metadata = page.$( selector );
|
|
} else {
|
|
// find heading associated with the section
|
|
$metadata = page.findChildInSectionLead( parseInt( section, 10 ), selector );
|
|
}
|
|
// clean it up a little
|
|
$metadata.find( '.NavFrame' ).remove();
|
|
$metadata.each( function () {
|
|
var issue,
|
|
$this = $( this );
|
|
|
|
if ( $this.find( selector ).length === 0 ) {
|
|
issue = extractMessage( $this );
|
|
// Some issues after "extractMessage" 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 ( issue.text ) {
|
|
issues.push( issue );
|
|
}
|
|
}
|
|
} );
|
|
// store it for later
|
|
allIssues[section] = issues;
|
|
|
|
if ( $metadata.length && inline ) {
|
|
severity = pageIssuesParser.maxSeverity(
|
|
issues.map( function ( issue ) { return issue.severity; } )
|
|
);
|
|
new Icon( {
|
|
glyphPrefix: 'minerva',
|
|
name: pageIssuesParser.iconName( $metadata.get( 0 ), severity )
|
|
} ).prependTo( $metadata.find( '.mbox-text' ) );
|
|
$learnMore = $( '<span>' )
|
|
.addClass( 'ambox-learn-more' )
|
|
.text( mw.msg( 'skin-minerva-issue-learn-more' ) );
|
|
if ( $( '.mw-collapsible-content' ).length ) {
|
|
// e.g. Template:Multiple issues
|
|
$learnMore.insertAfter( $metadata.find( '.mbox-text-span' ) );
|
|
} else {
|
|
// e.g. Template:merge from
|
|
$learnMore.appendTo( $metadata.find( '.mbox-text' ) );
|
|
}
|
|
$metadata.click( function () {
|
|
var pageIssue = pageIssuesParser.parse( this );
|
|
pageIssuesLogger.log( {
|
|
action: 'issueClicked',
|
|
issuesSeverity: [ pageIssue.severity ],
|
|
sectionNumbers: [ section ]
|
|
} );
|
|
overlayManager.router.navigate( issueUrl );
|
|
return false;
|
|
} );
|
|
} else {
|
|
$link = createLinkElement( labelText );
|
|
$link.attr( 'href', '#/issues/' + section );
|
|
$link.click( function () {
|
|
pageIssuesLogger.log( {
|
|
action: 'issueClicked',
|
|
issuesSeverity: [
|
|
pageIssuesParser.maxSeverity(
|
|
getIssues( '0' )
|
|
.map( function ( issue ) { return issue.severity; } )
|
|
)
|
|
],
|
|
sectionNumbers: getAllIssuesSections( allIssues )
|
|
} );
|
|
} );
|
|
if ( $metadata.length ) {
|
|
$link.insertAfter( $( 'h1#section_0' ) );
|
|
$metadata.remove();
|
|
}
|
|
}
|
|
|
|
return $metadata;
|
|
}
|
|
|
|
/**
|
|
* Obtains the list of issues for the current page and provided section
|
|
* @param {number|string} section either KEYWORD_ALL_SECTIONS or a number relating to the section
|
|
* the issues belong to
|
|
* @return {jQuery.Object[]} array of all issues.
|
|
*/
|
|
function getIssues( section ) {
|
|
if ( section === KEYWORD_ALL_SECTIONS ) {
|
|
// 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.all || Object.keys( allIssues ).reduce(
|
|
function ( all, key ) {
|
|
return all.concat( allIssues[key] );
|
|
},
|
|
[]
|
|
);
|
|
} else {
|
|
return allIssues[section] || [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an array containing the section of each page issue.
|
|
* In the case that several page issues are grouped in a 'multiple issues' template,
|
|
* returns the section of those issues as one item.
|
|
* @param {Object} allIssues mapping section {Number} to {IssueSummary}
|
|
* @return {array}
|
|
*/
|
|
function getAllIssuesSections( allIssues ) {
|
|
return Object.keys( allIssues ).reduce( function ( acc, section ) {
|
|
if ( allIssues[ section ].length ) {
|
|
allIssues[ section ].forEach( function ( issue, i ) {
|
|
var lastIssue = allIssues[ section ][i - 1];
|
|
// If the last issue belongs to a "Multiple issues" template,
|
|
// and so does the current one, don't add the current one.
|
|
if ( lastIssue && lastIssue.isMultiple && issue.isMultiple ) {
|
|
acc[ acc.length - 1 ] = section;
|
|
} else {
|
|
acc.push( section );
|
|
}
|
|
} );
|
|
}
|
|
return acc;
|
|
}, [] );
|
|
}
|
|
|
|
/**
|
|
* 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 {Page} page
|
|
*/
|
|
function initPageIssues( overlayManager, page ) {
|
|
var label,
|
|
$lead = page.getLeadSectionElement(),
|
|
issueOverlayShowAll = CURRENT_NS === NS_CATEGORY || CURRENT_NS === NS_TALK || !$lead,
|
|
inline = newTreatmentEnabled && CURRENT_NS === 0;
|
|
|
|
// set A-B test class.
|
|
$( 'html' ).addClass( newTreatmentEnabled ? 'issues-group-B' : 'issues-group-A' );
|
|
|
|
if ( CURRENT_NS === NS_TALK || CURRENT_NS === NS_CATEGORY ) {
|
|
// e.g. Template:English variant category; Template:WikiProject
|
|
createBanner( page, mw.msg( 'mobile-frontend-meta-data-issues-header-talk' ),
|
|
KEYWORD_ALL_SECTIONS, inline, overlayManager );
|
|
} else if ( CURRENT_NS === NS_MAIN ) {
|
|
label = mw.msg( 'mobile-frontend-meta-data-issues-header' );
|
|
if ( issueOverlayShowAll ) {
|
|
createBanner( page, label, KEYWORD_ALL_SECTIONS, inline, overlayManager );
|
|
} else {
|
|
// parse lead
|
|
createBanner( page, label, '0', inline, overlayManager );
|
|
if ( newTreatmentEnabled ) {
|
|
// parse other sections but only in group B. In treatment A no issues are shown for sections.
|
|
page.$( Page.HEADING_SELECTOR ).each( function ( i, headingEl ) {
|
|
var $headingEl = $( headingEl ),
|
|
sectionNum = $headingEl.find( '.edit-page' ).data( 'section' );
|
|
|
|
// Note certain headings matched using Page.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
|
|
createBanner( page, label, sectionNum.toString(), inline, overlayManager );
|
|
}
|
|
} );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( isLoggingRequired( getIssues( KEYWORD_ALL_SECTIONS ) ) ) {
|
|
// Enable logging of the PageIssues schema, setting up defaults.
|
|
pageIssuesLogger.subscribe(
|
|
newTreatmentEnabled,
|
|
pageIssuesLogger.newPageIssueSchemaData(
|
|
newTreatmentEnabled,
|
|
CURRENT_NS,
|
|
getIssues( KEYWORD_ALL_SECTIONS ).reduce( formatPageIssuesSeverity, [] ),
|
|
getAllIssuesSections( allIssues )
|
|
)
|
|
);
|
|
|
|
// Report that the page has been loaded.
|
|
pageIssuesLogger.log( {
|
|
action: 'pageLoaded'
|
|
} );
|
|
}
|
|
|
|
// Setup the overlay route.
|
|
overlayManager.add( new RegExp( '^/issues/(\\d+|' + KEYWORD_ALL_SECTIONS + ')$' ), function ( section ) {
|
|
return new PageIssuesOverlay( getIssues( section ), pageIssuesLogger, section, CURRENT_NS );
|
|
} );
|
|
}
|
|
|
|
M.define( 'skins.minerva.scripts/pageIssues', {
|
|
init: initPageIssues,
|
|
// The logger requires initialization (subscription). Ideally, the logger would be initialized
|
|
// and passed to initPageIssues() by the client. Since it's not, expose a log method and hide
|
|
// the subscription call in cleanuptemplates.
|
|
log: pageIssuesLogger.log,
|
|
test: {
|
|
extractMessage: extractMessage,
|
|
getAllIssuesSections: getAllIssuesSections,
|
|
createBanner: createBanner
|
|
}
|
|
} );
|
|
|
|
}( mw.mobileFrontend, jQuery ) );
|