mw.editcheck.BaseEditCheck = function MWBaseEditCheck( config ) { this.config = ve.extendObject( {}, this.constructor.static.defaultConfig, config ); }; OO.initClass( mw.editcheck.BaseEditCheck ); mw.editcheck.BaseEditCheck.static.onlyCoveredNodes = false; mw.editcheck.BaseEditCheck.static.choices = [ { action: 'accept', label: ve.msg( 'editcheck-dialog-action-yes' ), icon: 'check' }, { action: 'reject', label: ve.msg( 'editcheck-dialog-action-no' ), icon: 'close' } ]; mw.editcheck.BaseEditCheck.static.defaultConfig = { account: false, // 'loggedin', 'loggedout', anything non-truthy means allow either maximumEditcount: 100, ignoreSections: [], ignoreLeadSection: false }; mw.editcheck.BaseEditCheck.static.description = ve.msg( 'editcheck-dialog-addref-description' ); /** * @param {ve.dm.Surface} surfaceModel * @return {mw.editcheck.EditCheckAction[]} */ mw.editcheck.BaseEditCheck.prototype.onBeforeSave = null; /** * @param {ve.dm.Surface} surfaceModel * @return {mw.editcheck.EditCheckAction[]} */ mw.editcheck.BaseEditCheck.prototype.onDocumentChange = null; /** * @param {string} choice `action` key from static.choices * @param {mw.editcheck.EditCheckAction} action * @param {ve.ui.EditCheckContextItem} contextItem */ mw.editcheck.BaseEditCheck.prototype.act = null; /** * @param {mw.editcheck.EditCheckAction} action * @return {Object[]} */ mw.editcheck.BaseEditCheck.prototype.getChoices = function () { return this.constructor.static.choices; }; /** * @param {mw.editcheck.EditCheckAction} action * @return {string} */ mw.editcheck.BaseEditCheck.prototype.getDescription = function () { return this.constructor.static.description; }; /** * Find out whether the check should be applied * * This is a general check for its applicability to the viewer / page, rather * than a specific check based on the current edit. It's used to filter out * checks before any maybe-expensive content analysis happens. * * @return {boolean} Whether the check should be shown */ mw.editcheck.BaseEditCheck.prototype.canBeShown = function () { // all checks are only in the main namespace for now if ( mw.config.get( 'wgNamespaceNumber' ) !== mw.config.get( 'wgNamespaceIds' )[ '' ] ) { return false; } // some checks are configured to only be for logged in / out users if ( mw.editcheck.ecenable ) { return true; } // account status: // loggedin, loggedout, or any-other-value meaning 'both' // we'll count temporary users as "logged out" by using isNamed here if ( this.config.account === 'loggedout' && mw.user.isNamed() ) { return false; } if ( this.config.account === 'loggedin' && !mw.user.isNamed() ) { return false; } // some checks are only shown for newer users if ( this.config.maximumEditcount && mw.config.get( 'wgUserEditCount', 0 ) > this.config.maximumEditcount ) { return false; } return true; }; /** * Get content ranges where at least the minimum about of text has been changed * * @param {ve.dm.Document} documentModel * @return {ve.Range[]} */ mw.editcheck.BaseEditCheck.prototype.getModifiedContentRanges = function ( documentModel ) { return mw.editcheck.getModifiedRanges( documentModel, this.constructor.static.onlyCoveredNodes ) .filter( ( range ) => range.getLength() >= this.config.minimumCharacters && this.isRangeInValidSection( range, documentModel ) ); }; /** * Check if a modified range is a section we don't ignore (config.ignoreSections) * * @param {ve.Range} range * @param {ve.dm.Document} documentModel * @return {boolean} */ mw.editcheck.BaseEditCheck.prototype.isRangeInValidSection = function ( range, documentModel ) { const ignoreSections = this.config.ignoreSections || []; if ( ignoreSections.length === 0 && !this.config.ignoreLeadSection ) { // Nothing is forbidden, so everything is permitted return true; } const isHeading = ( nodeType ) => nodeType === 'mwHeading'; // Note: we set a limit of 1 here because otherwise this will turn around // to keep looking when it hits the document boundary: const heading = documentModel.getNearestNodeMatching( isHeading, range.start, -1, 1 ); if ( !heading ) { // There's no preceding heading, so work out if we count as being in a // lead section. It's only a lead section if there's more headings // later in the document, otherwise it's just a stub article. return !( this.config.ignoreLeadSection && !!documentModel.getNearestNodeMatching( isHeading, range.start, 1 ) ); } if ( ignoreSections.length === 0 ) { // There's nothing left to deny return true; } const compare = new Intl.Collator( documentModel.getLang(), { sensitivity: 'accent' } ).compare; const headingText = documentModel.data.getText( false, heading.getRange() ); // If the heading text matches any of ignoreSections, return false. return !ignoreSections.some( ( section ) => compare( headingText, section ) === 0 ); };