2024-03-14 17:17:36 +00:00
|
|
|
mw.editcheck.BaseEditCheck = function MWBaseEditCheck( config ) {
|
2024-11-22 16:34:06 +00:00
|
|
|
this.config = ve.extendObject( {}, this.constructor.static.defaultConfig, config );
|
2024-03-14 17:17:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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'
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2024-11-22 16:34:06 +00:00
|
|
|
mw.editcheck.BaseEditCheck.static.defaultConfig = {
|
|
|
|
account: false, // 'loggedin', 'loggedout', anything non-truthy means allow either
|
|
|
|
maximumEditcount: 100,
|
|
|
|
ignoreSections: [],
|
|
|
|
ignoreLeadSection: false
|
|
|
|
};
|
|
|
|
|
2024-03-14 17:17:36 +00:00
|
|
|
mw.editcheck.BaseEditCheck.static.description = ve.msg( 'editcheck-dialog-addref-description' );
|
|
|
|
|
|
|
|
/**
|
2024-10-01 11:31:17 +00:00
|
|
|
* @param {ve.dm.Surface} surfaceModel
|
2024-03-14 17:17:36 +00:00
|
|
|
* @return {mw.editcheck.EditCheckAction[]}
|
|
|
|
*/
|
|
|
|
mw.editcheck.BaseEditCheck.prototype.onBeforeSave = null;
|
|
|
|
|
|
|
|
/**
|
2024-10-01 11:31:17 +00:00
|
|
|
* @param {ve.dm.Surface} surfaceModel
|
2024-03-14 17:17:36 +00:00
|
|
|
* @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;
|
|
|
|
|
2024-09-29 10:52:58 +00:00
|
|
|
/**
|
|
|
|
* @param {mw.editcheck.EditCheckAction} action
|
|
|
|
* @return {Object[]}
|
|
|
|
*/
|
|
|
|
mw.editcheck.BaseEditCheck.prototype.getChoices = function () {
|
2024-03-14 17:17:36 +00:00
|
|
|
return this.constructor.static.choices;
|
|
|
|
};
|
2024-09-29 10:52:58 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {mw.editcheck.EditCheckAction} action
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
mw.editcheck.BaseEditCheck.prototype.getDescription = function () {
|
2024-03-14 17:17:36 +00:00
|
|
|
return this.constructor.static.description;
|
|
|
|
};
|
|
|
|
|
2024-09-04 15:23:43 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2024-09-09 15:50:11 +00:00
|
|
|
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() ) {
|
2024-09-04 15:23:43 +00:00
|
|
|
return false;
|
2024-03-14 17:17:36 +00:00
|
|
|
}
|
2024-09-04 15:23:43 +00:00
|
|
|
// some checks are only shown for newer users
|
|
|
|
if ( this.config.maximumEditcount && mw.config.get( 'wgUserEditCount', 0 ) > this.config.maximumEditcount ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2024-09-29 10:52:58 +00:00
|
|
|
/**
|
|
|
|
* Get content ranges where at least the minimum about of text has been changed
|
|
|
|
*
|
2024-10-01 11:31:17 +00:00
|
|
|
* @param {ve.dm.Document} documentModel
|
2024-09-29 10:52:58 +00:00
|
|
|
* @return {ve.Range[]}
|
|
|
|
*/
|
2024-10-01 11:31:17 +00:00
|
|
|
mw.editcheck.BaseEditCheck.prototype.getModifiedContentRanges = function ( documentModel ) {
|
|
|
|
return mw.editcheck.getModifiedRanges( documentModel, this.constructor.static.onlyCoveredNodes )
|
2024-09-29 10:52:58 +00:00
|
|
|
.filter(
|
|
|
|
( range ) => range.getLength() >= this.config.minimumCharacters &&
|
2024-10-01 11:31:17 +00:00
|
|
|
this.isRangeInValidSection( range, documentModel )
|
2024-09-29 10:52:58 +00:00
|
|
|
);
|
2024-03-14 17:17:36 +00:00
|
|
|
};
|
|
|
|
|
2024-09-29 10:52:58 +00:00
|
|
|
/**
|
|
|
|
* Check if a modified range is a section we don't ignore (config.ignoreSections)
|
|
|
|
*
|
|
|
|
* @param {ve.Range} range
|
2024-09-29 11:06:26 +00:00
|
|
|
* @param {ve.dm.Document} documentModel
|
2024-09-29 10:52:58 +00:00
|
|
|
* @return {boolean}
|
|
|
|
*/
|
2024-09-29 11:06:26 +00:00
|
|
|
mw.editcheck.BaseEditCheck.prototype.isRangeInValidSection = function ( range, documentModel ) {
|
2024-03-14 17:17:36 +00:00
|
|
|
const ignoreSections = this.config.ignoreSections || [];
|
|
|
|
if ( ignoreSections.length === 0 && !this.config.ignoreLeadSection ) {
|
|
|
|
// Nothing is forbidden, so everything is permitted
|
|
|
|
return true;
|
|
|
|
}
|
2024-09-29 11:06:26 +00:00
|
|
|
const isHeading = ( nodeType ) => nodeType === 'mwHeading';
|
2024-03-14 17:17:36 +00:00
|
|
|
// 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() );
|
2024-09-29 11:06:26 +00:00
|
|
|
// If the heading text matches any of ignoreSections, return false.
|
|
|
|
return !ignoreSections.some( ( section ) => compare( headingText, section ) === 0 );
|
2024-03-14 17:17:36 +00:00
|
|
|
};
|