mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-23 19:43:19 +00:00
fccb247285
The pause is to show the user the result of their action (sepcifically it was designed for AddReferenceEditCheck to let them see the reference in the page). If they reject, there is no need to pause. This applies to mulit-check too where it would cause a pause before moving on to the next check. Change-Id: I57f881b37051cc2e5ea6bda23fea66a2de2b342d
416 lines
13 KiB
JavaScript
416 lines
13 KiB
JavaScript
/*!
|
|
* VisualEditor UserInterface EditCheckDialog class.
|
|
*
|
|
* @copyright See AUTHORS.txt
|
|
*/
|
|
|
|
/**
|
|
* Find and replace dialog.
|
|
*
|
|
* @class
|
|
* @extends ve.ui.ToolbarDialog
|
|
*
|
|
* @constructor
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.ui.EditCheckDialog = function VeUiEditCheckDialog( config ) {
|
|
// Parent constructor
|
|
ve.ui.EditCheckDialog.super.call( this, config );
|
|
|
|
// Don't run a scroll if the previous animation is still running (which is jQuery 'fast' === 200ms)
|
|
this.scrollCurrentCheckIntoViewDebounced = ve.debounce( this.scrollCurrentCheckIntoView.bind( this ), 200, true );
|
|
|
|
// Pre-initialization
|
|
this.$element.addClass( 've-ui-editCheckDialog' );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.ui.EditCheckDialog, ve.ui.ToolbarDialog );
|
|
|
|
ve.ui.EditCheckDialog.static.name = 'editCheckDialog';
|
|
|
|
ve.ui.EditCheckDialog.static.position = OO.ui.isMobile() ? 'below' : 'side';
|
|
|
|
ve.ui.EditCheckDialog.static.size = OO.ui.isMobile() ? 'full' : 'medium';
|
|
|
|
ve.ui.EditCheckDialog.static.framed = false;
|
|
|
|
// Invisible title for accessibility
|
|
ve.ui.EditCheckDialog.static.title = OO.ui.deferMsg( 'editcheck-review-title' );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.initialize = function () {
|
|
// Parent method
|
|
ve.ui.EditCheckDialog.super.prototype.initialize.call( this );
|
|
|
|
this.title = new OO.ui.LabelWidget( {
|
|
label: this.constructor.static.title,
|
|
classes: [ 've-ui-editCheckDialog-title' ]
|
|
} );
|
|
|
|
// FIXME: click handlers are getting unbound when the window is closed
|
|
|
|
this.closeButton = new OO.ui.ButtonWidget( {
|
|
classes: [ 've-ui-editCheckDialog-close' ],
|
|
framed: false,
|
|
label: ve.msg( 'visualeditor-contextitemwidget-label-close' ),
|
|
invisibleLabel: true,
|
|
icon: 'expand'
|
|
} ).connect( this, {
|
|
click: 'onCloseButtonClick'
|
|
} );
|
|
|
|
this.currentOffset = 0;
|
|
|
|
this.footerLabel = new OO.ui.LabelWidget();
|
|
this.previousButton = new OO.ui.ButtonWidget( {
|
|
icon: 'previous',
|
|
title: ve.msg( 'last' ),
|
|
invisibleLabel: true,
|
|
framed: false
|
|
} ).connect( this, {
|
|
click: 'onPreviousButtonClick'
|
|
} );
|
|
this.nextButton = new OO.ui.ButtonWidget( {
|
|
icon: 'next',
|
|
title: ve.msg( 'next' ),
|
|
invisibleLabel: true,
|
|
framed: false
|
|
} ).connect( this, {
|
|
click: 'onNextButtonClick'
|
|
} );
|
|
this.footer = new OO.ui.HorizontalLayout( {
|
|
classes: [ 've-ui-editCheckDialog-footer' ],
|
|
items: [
|
|
this.footerLabel,
|
|
this.previousButton,
|
|
this.nextButton
|
|
]
|
|
} );
|
|
|
|
this.$checks = $( '<div>' );
|
|
this.$body.append( this.title.$element, this.closeButton.$element, this.$checks, this.footer.$element );
|
|
|
|
this.$highlights = $( '<div>' );
|
|
|
|
this.updateDebounced = ve.debounce( this.update.bind( this ), 100 );
|
|
this.positionDebounced = ve.debounce( this.position.bind( this ), 100 );
|
|
};
|
|
|
|
ve.ui.EditCheckDialog.prototype.update = function () {
|
|
const surfaceView = this.surface.getView();
|
|
// We only regenerate the checks on-change during the edit. If we're in
|
|
// the proofreading step, no new checks should appear based on changes:
|
|
if ( this.listener === 'onDocumentChange' || !this.currentChecks ) {
|
|
this.currentChecks = mw.editcheck.editCheckFactory.createAllByListener( this.listener, this.surface.getModel() );
|
|
}
|
|
if ( this.listener === 'onBeforeSave' && this.currentChecks.length === 0 ) {
|
|
return this.close( 'complete' );
|
|
}
|
|
const checks = this.currentChecks;
|
|
const newOffset = Math.min( this.currentOffset, checks.length - 1 );
|
|
this.$checks.empty();
|
|
this.$highlights.empty();
|
|
|
|
checks.forEach( ( check, index ) => {
|
|
const widget = check.render( index !== newOffset, this.listener === 'onBeforeSave', this.surface );
|
|
widget.on( 'togglecollapse', this.onToggleCollapse, [ check, index ], this );
|
|
widget.on( 'act', this.onAct, [ widget ], this );
|
|
this.$checks.append( widget.$element );
|
|
check.widget = widget;
|
|
} );
|
|
|
|
if ( this.reviewMode ) {
|
|
// Review mode grays out everything that's not highlighted:
|
|
const highlightNodes = [];
|
|
checks.forEach( ( check ) => {
|
|
check.getHighlightSelections().forEach( ( selection ) => {
|
|
highlightNodes.push.apply( highlightNodes, surfaceView.getDocument().selectNodes( selection.getCoveringRange(), 'branches' ).map( ( spec ) => spec.node ) );
|
|
} );
|
|
} );
|
|
surfaceView.setReviewMode( true, highlightNodes );
|
|
}
|
|
|
|
this.setCurrentOffset( newOffset );
|
|
};
|
|
|
|
ve.ui.EditCheckDialog.prototype.position = function () {
|
|
this.drawHighlights();
|
|
this.scrollCurrentCheckIntoViewDebounced();
|
|
};
|
|
|
|
ve.ui.EditCheckDialog.prototype.drawHighlights = function () {
|
|
const surfaceView = this.surface.getView();
|
|
this.$highlights.empty();
|
|
|
|
this.currentChecks.forEach( ( check, index ) => {
|
|
check.getHighlightSelections().forEach( ( selection ) => {
|
|
const selectionView = ve.ce.Selection.static.newFromModel( selection, surfaceView );
|
|
const rect = selectionView.getSelectionBoundingRect();
|
|
// The following classes are used here:
|
|
// * ve-ui-editCheck-gutter-highlight-error
|
|
// * ve-ui-editCheck-gutter-highlight-warning
|
|
// * ve-ui-editCheck-gutter-highlight-notice
|
|
// * ve-ui-editCheck-gutter-highlight-success
|
|
// * ve-ui-editCheck-gutter-highlight-active
|
|
// * ve-ui-editCheck-gutter-highlight-inactive
|
|
this.$highlights.append( $( '<div>' )
|
|
.addClass( 've-ui-editCheck-gutter-highlight' )
|
|
.addClass( 've-ui-editCheck-gutter-highlight-' + check.getType() )
|
|
.addClass( 've-ui-editCheck-gutter-highlight-' + ( index === this.currentOffset ? 'active' : 'inactive' ) )
|
|
.css( {
|
|
top: rect.top - 2,
|
|
height: rect.height + 4
|
|
} )
|
|
);
|
|
} );
|
|
} );
|
|
|
|
surfaceView.appendHighlights( this.$highlights, false );
|
|
};
|
|
|
|
/**
|
|
* Set the offset of the current check, within the list of all checks
|
|
*
|
|
* @param {number} offset
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.setCurrentOffset = function ( offset ) {
|
|
// TODO: work out how to tell the window to recalculate height here
|
|
this.currentOffset = Math.max( 0, offset );
|
|
|
|
this.$body.find( '.ve-ui-editCheckActionWidget' ).each( ( i, el ) => {
|
|
$( el ).toggleClass( 've-ui-editCheckActionWidget-collapsed', i !== this.currentOffset );
|
|
} );
|
|
|
|
this.footerLabel.setLabel(
|
|
ve.msg( 'visualeditor-find-and-replace-results',
|
|
ve.init.platform.formatNumber( this.currentOffset + 1 ),
|
|
ve.init.platform.formatNumber( this.currentChecks.length )
|
|
)
|
|
);
|
|
this.nextButton.setDisabled( this.currentOffset >= this.currentChecks.length - 1 );
|
|
this.previousButton.setDisabled( this.currentOffset <= 0 );
|
|
|
|
this.updateSize();
|
|
|
|
if ( this.isOpening() ) {
|
|
return;
|
|
}
|
|
|
|
const surfaceView = this.surface.getView();
|
|
if ( this.currentChecks.length > 0 ) {
|
|
// The currently-focused check gets a selection:
|
|
// TODO: clicking the selection should activate the sidebar-action
|
|
surfaceView.getSelectionManager().drawSelections(
|
|
'editCheckWarning',
|
|
this.currentChecks[ this.currentOffset ].getHighlightSelections().map(
|
|
( selection ) => ve.ce.Selection.static.newFromModel( selection, surfaceView )
|
|
)
|
|
);
|
|
|
|
this.scrollCurrentCheckIntoViewDebounced();
|
|
} else {
|
|
surfaceView.getSelectionManager().drawSelections( 'editCheckWarning', [] );
|
|
}
|
|
|
|
this.drawHighlights();
|
|
};
|
|
|
|
ve.ui.EditCheckDialog.prototype.scrollCurrentCheckIntoView = function () {
|
|
const currentCheck = this.currentChecks[ this.currentOffset ];
|
|
if ( currentCheck ) {
|
|
// scrollSelectionIntoView scrolls to the focus of a selection, but we
|
|
// want the very beginning to be in view, so collapse it:
|
|
const selection = currentCheck.getHighlightSelections()[ 0 ].collapseToStart();
|
|
this.surface.scrollSelectionIntoView( selection, {
|
|
animate: true,
|
|
padding: {
|
|
top: ( OO.ui.isMobile() ? 80 : currentCheck.widget.$element[ 0 ].getBoundingClientRect().top ),
|
|
bottom: ( OO.ui.isMobile() ? this.getContentHeight() : 0 ) + 20
|
|
},
|
|
alignToTop: true
|
|
} );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.getSetupProcess = function ( data ) {
|
|
return ve.ui.EditCheckDialog.super.prototype.getSetupProcess.call( this, data )
|
|
.first( () => {
|
|
this.currentOffset = 0;
|
|
this.listener = data.listener || 'onDocumentChange';
|
|
this.reviewMode = data.reviewMode;
|
|
this.surface = data.surface;
|
|
|
|
this.surface.getModel().on( 'undoStackChange', this.updateDebounced );
|
|
this.surface.getView().on( 'position', this.positionDebounced );
|
|
|
|
this.closeButton.toggle( OO.ui.isMobile() );
|
|
this.footer.toggle(
|
|
this.listener === 'onBeforeSave' &&
|
|
!mw.config.get( 'wgVisualEditorConfig' ).editCheckSingle
|
|
);
|
|
|
|
this.$element.toggleClass( 've-ui-editCheckDialog-singleAction', this.listener === 'onBeforeSave' );
|
|
|
|
this.surface.context.hide();
|
|
|
|
this.update();
|
|
}, this );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.getReadyProcess = function ( data ) {
|
|
return ve.ui.EditCheckDialog.super.prototype.getReadyProcess.call( this, data )
|
|
.next( () => {
|
|
// Call update again after the dialog has transitioned open, as the first
|
|
// call of update will not have drawn any selections.
|
|
setTimeout( () => {
|
|
this.update();
|
|
}, OO.ui.theme.getDialogTransitionDuration() );
|
|
}, this );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.getTeardownProcess = function ( data ) {
|
|
return ve.ui.EditCheckDialog.super.prototype.getTeardownProcess.call( this, data )
|
|
.next( () => {
|
|
this.surface.getView().setReviewMode( false );
|
|
this.surface.getView().getSelectionManager().drawSelections( 'editCheckWarning', [] );
|
|
this.surface.getView().off( 'position', this.positionDebounced );
|
|
this.surface.getModel().off( 'undoStackChange', this.updateDebounced );
|
|
this.$highlights.remove().empty();
|
|
this.$checks.empty();
|
|
}, this );
|
|
};
|
|
|
|
/**
|
|
* Handle 'act' events from the mw.widget.EditCheckActionWidget
|
|
*
|
|
* @param {mw.editcheck.EditCheckActionWidget} widget
|
|
* @param {Object} choice Choice object (with 'reason', 'object', 'label')
|
|
* @param {string} actionChosen Choice action
|
|
* @param {jQuery.Promise} promise Promise which resolves when the action is complete
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.onAct = function ( widget, choice, actionChosen, promise ) {
|
|
widget.setDisabled( true );
|
|
this.nextButton.setDisabled( true );
|
|
this.previousButton.setDisabled( true );
|
|
promise.then( ( data ) => {
|
|
widget.setDisabled( false );
|
|
this.nextButton.setDisabled( false );
|
|
this.previousButton.setDisabled( false );
|
|
this.surface.getModel().setNullSelection();
|
|
if ( OO.ui.isMobile() ) {
|
|
// Delay on mobile means we need to rehide this
|
|
setTimeout( () => this.surface.getModel().setNullSelection(), 300 );
|
|
}
|
|
|
|
if ( !data ) {
|
|
// Nothing happened, just fall back and leave the check
|
|
return;
|
|
}
|
|
|
|
if ( this.listener === 'onBeforeSave' ) {
|
|
// If an action has been taken, we want to linger for a brief moment
|
|
// to show the result of the action before moving away
|
|
// TODO: This was written for AddReferenceEditCheck but should be
|
|
// more generic
|
|
const pause = data.action !== 'reject' ? 500 : 0;
|
|
setTimeout( () => {
|
|
// We must have been acting on the currentOffset
|
|
this.currentChecks.splice( this.currentOffset, 1 );
|
|
this.currentOffset = Math.max( 0, this.currentOffset - 1 );
|
|
this.update();
|
|
}, pause );
|
|
}
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Handle 'togglecollapse' events from the mw.widget.EditCheckActionWidget
|
|
*
|
|
* @param {mw.editcheck.EditCheckAction} check
|
|
* @param {number} index
|
|
* @param {boolean} collapsed
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.onToggleCollapse = function ( check, index, collapsed ) {
|
|
if ( !collapsed ) {
|
|
// expanded one
|
|
this.setCurrentOffset( this.currentChecks.indexOf( check ) );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle click events from the close button
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.onCloseButtonClick = function () {
|
|
// eslint-disable-next-line no-jquery/no-class-state
|
|
const collapse = !this.$element.hasClass( 've-ui-editCheckDialog-collapsed' );
|
|
this.$element.toggleClass( 've-ui-editCheckDialog-collapsed', collapse );
|
|
this.closeButton.setIcon( collapse ? 'collapse' : 'expand' );
|
|
};
|
|
|
|
/**
|
|
* Handle click events from the next button
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.onNextButtonClick = function () {
|
|
this.setCurrentOffset( this.currentOffset + 1 );
|
|
};
|
|
|
|
/**
|
|
* Handle click events from the previous button
|
|
*/
|
|
ve.ui.EditCheckDialog.prototype.onPreviousButtonClick = function () {
|
|
this.setCurrentOffset( this.currentOffset - 1 );
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ui.windowFactory.register( ve.ui.EditCheckDialog );
|
|
|
|
ve.ui.commandRegistry.register(
|
|
new ve.ui.Command(
|
|
'editCheckDialogInProcess', 'window', 'toggle', { args: [ 'editCheckDialog', { listener: 'onDocumentChange' } ] }
|
|
)
|
|
);
|
|
|
|
ve.ui.commandRegistry.register(
|
|
new ve.ui.Command(
|
|
'editCheckDialogBeforeSave', 'window', 'toggle', { args: [ 'editCheckDialog', { listener: 'onBeforeSave', reviewMode: true } ] }
|
|
)
|
|
);
|
|
|
|
/**
|
|
* @class
|
|
* @extends ve.ui.ToolbarDialogTool
|
|
* @constructor
|
|
* @param {OO.ui.ToolGroup} toolGroup
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.ui.EditCheckDialogTool = function VeUiEditCheckDialogTool() {
|
|
ve.ui.EditCheckDialogTool.super.apply( this, arguments );
|
|
};
|
|
OO.inheritClass( ve.ui.EditCheckDialogTool, ve.ui.ToolbarDialogTool );
|
|
ve.ui.EditCheckDialogTool.static.name = 'editCheckDialog';
|
|
ve.ui.EditCheckDialogTool.static.group = 'notices';
|
|
ve.ui.EditCheckDialogTool.static.icon = 'robot';
|
|
ve.ui.EditCheckDialogTool.static.title = 'Edit check'; // OO.ui.deferMsg( 'visualeditor-dialog-command-help-title' );
|
|
ve.ui.EditCheckDialogTool.static.autoAddToCatchall = false;
|
|
ve.ui.EditCheckDialogTool.static.commandName = 'editCheckDialogInProcess';
|
|
// ve.ui.EditCheckDialogTool.static.commandName = 'editCheckDialogBeforeSave';
|
|
|
|
// Demo button for opening edit check sidebar
|
|
// ve.ui.toolFactory.register( ve.ui.EditCheckDialogTool );
|