( function () { /** * @typedef PageIssue * @property {string} severity A SEVERITY_LEVEL key. * @property {boolean} grouped True if part of a group of multiple issues, false if singular. * @property {Icon} icon */ /** * @typedef {Object} IssueSummary * @property {PageIssue} issue * @property {jQuery} $el where the issue was extracted from * @property {string} iconString a string representation of icon. * This is kept for template compatibility (our views do not yet support composition). * @property {string} text HTML string. */ // 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. const ICON_NAME = { // Generic severity icons. SEVERITY: { DEFAULT: 'issue-generic', LOW: 'issue-severity-low', MEDIUM: 'issue-severity-medium', HIGH: 'issue-generic' }, // Icons customized by type. TYPE: { MOVE: 'issue-type-move', POINT_OF_VIEW: 'issue-type-point-of-view' } }; const ICON_COLOR = { DEFAULT: 'defaultColor', LOW: 'lowColor', MEDIUM: 'mediumColor', HIGH: 'highColor' }; // How severities order and compare from least to greatest. For the multiple issues // template, severity should be considered the maximum of all its contained issues. const SEVERITY_LEVEL = { DEFAULT: 0, LOW: 1, MEDIUM: 2, HIGH: 3 }; // Match the template's color CSS selector to a severity level concept. Derived via the // Ambox templates and sub-templates for the top five wikis and tested on page issues // inventory: // - https://people.wikimedia.org/~jdrewniak/page_issues_inventory // - https://en.wikipedia.org/wiki/Template:Ambox // - https://es.wikipedia.org/wiki/Plantilla:Metaplantilla_de_avisos // - https://ja.wikipedia.org/wiki/Template:Ambox // - https://ru.wikipedia.org/wiki/Шаблон:Ambox // - https://it.wikipedia.org/wiki/Template:Avviso // Severity is the class associated with the color. The ResourceLoader config mimics the // idea by using severity for color variants. Severity is determined independently of icons. // These selectors should be migrated to their templates. const SEVERITY_REGEX = { // recommended (T206177), en, it LOW: /mobile-issue-severity-low|ambox-style|avviso-stile/, // recommended, en, it MEDIUM: /mobile-issue-severity-medium|ambox-content|avviso-contenuto/, // recommended, en, en, es / ru, it HIGH: /mobile-issue-severity-high|ambox-speedy|ambox-delete|ambox-serious|avviso-importante/ // ..And everything else that doesn't match should be considered DEFAULT. }; // As above but used to identify specific templates requiring icon customization. const TYPE_REGEX = { // recommended (opt-in) / en, es / ru, it (long term only recommended should be used) MOVE: /mobile-issue-move|ambox-converted|ambox-move|ambox-merge|avviso-struttura/, // eslint-disable-next-line security/detect-non-literal-regexp POINT_OF_VIEW: new RegExp( [ // recommended (opt-in) 'mobile-issue-pov', // FIXME: en classes: plan to remove these provided can get adoption of recommended 'ambox-Advert', 'ambox-autobiography', 'ambox-believerpov', 'ambox-COI', 'ambox-coverage', 'ambox-criticism', 'ambox-fanpov', 'ambox-fringe-theories', 'ambox-geographical-imbalance', 'ambox-globalize', 'ambox-npov-language', 'ambox-POV', 'ambox-pseudo', 'ambox-systemic-bias', 'ambox-unbalanced', 'ambox-usgovtpov' ].join( '|' ) ) // ..And everything else that doesn't match is mapped to a "SEVERITY" type. }; const GROUPED_PARENT_REGEX = /mw-collapsible-content/; // Variants supported by specific types. The "severity icon" supports all severities but the // type icons only support one each by ResourceLoader. const TYPE_SEVERITY = { MOVE: 'DEFAULT', POINT_OF_VIEW: 'MEDIUM' }; /** * @param {Element} box * @return {string} An SEVERITY_SELECTOR key. */ function parseSeverity( box ) { let severity; const identified = Object.keys( SEVERITY_REGEX ).some( function ( key ) { const regex = SEVERITY_REGEX[ key ]; severity = key; return regex.test( box.className ); } ); return identified ? severity : 'DEFAULT'; } /** * @param {Element} box * @param {string} severity An SEVERITY_LEVEL key. * @return {{name: string, severity: string}} An ICON_NAME. */ function parseType( box, severity ) { let identifiedType; const identified = Object.keys( TYPE_REGEX ).some( function ( type ) { const regex = TYPE_REGEX[ type ]; identifiedType = type; return regex.test( box.className ); } ); return { name: identified ? ICON_NAME.TYPE[ identifiedType ] : ICON_NAME.SEVERITY[ severity ], severity: identified ? TYPE_SEVERITY[ identifiedType ] : severity }; } /** * @param {Element} box * @return {boolean} True if part of a group of multiple issues, false if singular. */ function parseGroup( box ) { return !!box.parentNode && GROUPED_PARENT_REGEX.test( box.parentNode.className ); } /** * @param {Element} box * @param {string} severity An SEVERITY_LEVEL key. * @return {string} A severity or type ISSUE_ICON. */ function iconName( box, severity ) { const nameSeverity = parseType( box, severity ); // The icon with color variant as expected by ResourceLoader, // {iconName}-{severityColorVariant}. return nameSeverity.name + '-' + ICON_COLOR[ nameSeverity.severity ]; } /** * @param {string[]} severityLevels an array of SEVERITY_KEY values. * @return {string} The greatest SEVERITY_LEVEL key. */ function maxSeverity( severityLevels ) { return severityLevels.reduce( function ( max, severity ) { return SEVERITY_LEVEL[ max ] > SEVERITY_LEVEL[ severity ] ? max : severity; }, 'DEFAULT' ); } /** * @param {Element} box * @return {PageIssue} */ function parse( box ) { const severity = parseSeverity( box ); const iconElement = document.createElement( 'div' ); iconElement.classList.add( `minerva-icon--${ iconName( box, severity ) }`, 'minerva-ambox-icon' ); return { severity, grouped: parseGroup( box ), iconElement }; } /** * 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 extract( $box ) { const SELECTOR = '.mbox-text, .ambox-text'; const $container = $( '
' ); $box.find( SELECTOR ).each( function () { const $this = $( this ); // Clean up talk page boxes $this.find( 'table, .noprint' ).remove(); const contents = $this.html(); if ( contents ) { $( '

' ).html( contents ).appendTo( $container ); } } ); const pageIssue = parse( $box.get( 0 ) ); return { issue: pageIssue, $el: $box, text: $container.html() }; } module.exports = { extract, parse, maxSeverity, iconName, test: { parseSeverity, parseType, parseGroup } }; }() );