mediawiki-extensions-Discus.../modules/dt-ve/dt.ui.UsernameCompletionAction.js
Ed Sanders c15399568f Wait for API response before updating suggestion list
We can update the local filtered list before then, but then we would
have to do two updates for each keystroke, one to do an instant local
filter and another to back-fill the list when the remote results
become available. Currently CompletionAction is not set up for this,
and this might be confusing behaviour.

Bug: T256974
Change-Id: I6194cdcd6459be17fb142e644d73c9ec4036ba08
2020-07-02 15:12:30 +01:00

159 lines
5.3 KiB
JavaScript

/*!
* VisualEditor UserInterface MWUsernameCompletionAction class.
*
* @copyright 2011-2019 VisualEditor Team and others; see http://ve.mit-license.org
*/
/**
* MWUsernameCompletionAction action.
*
* Controls autocompletion of usernames
*
* @class
* @extends ve.ui.CompletionAction
* @constructor
* @param {ve.ui.Surface} surface Surface to act on
*/
function MWUsernameCompletionAction( surface ) {
var action = this;
// Parent constructor
MWUsernameCompletionAction.super.call( this, surface );
this.api = new mw.Api( { parameters: { formatversion: 2 } } );
this.searchedPrefixes = {};
this.localUsers = [];
this.ipUsers = [];
this.surface.authors.forEach( function ( user ) {
if ( mw.util.isIPAddress( user ) ) {
action.ipUsers.push( user );
} else {
action.localUsers.push( user );
}
} );
this.remoteUsers = [];
}
/* Inheritance */
OO.inheritClass( MWUsernameCompletionAction, ve.ui.CompletionAction );
/* Static Properties */
MWUsernameCompletionAction.static.name = 'mwUsernameCompletion';
MWUsernameCompletionAction.static.methods = OO.copy( MWUsernameCompletionAction.static.methods );
MWUsernameCompletionAction.static.methods.push( 'insertAndOpen' );
/* Methods */
MWUsernameCompletionAction.prototype.insertAndOpen = function () {
// This is opening a window in a slightly weird way, so the normal logging
// doesn't catch it. This assumes that the only way to get here is from
// the tool. If we add other paths, we'd need to change the logging.
ve.track(
'activity.' + this.constructor.static.name,
{ action: 'window-open-from-tool' }
);
this.surface.getModel().getFragment().insertContent( '@' ).collapseToEnd().select();
return this.open();
};
MWUsernameCompletionAction.prototype.getSuggestions = function ( input ) {
var apiPromise,
capitalizedInput = input.length > 0 && input[ 0 ].toUpperCase() + input.slice( 1 ),
action = this;
this.api.abort(); // Abort all unfinished API requests
if ( capitalizedInput && !this.searchedPrefixes[ capitalizedInput ] ) {
apiPromise = this.api.get( {
action: 'query',
list: 'allusers',
// Prefix of list=allusers is case sensitive, and users are stored in the DB capitalized, so:
auprefix: capitalizedInput,
aulimit: this.limit
} ).then( function ( response ) {
var suggestions = response.query.allusers.map( function ( user ) {
return user.name;
} ).filter( function ( username ) {
// API doesn't return IPs
return action.localUsers.indexOf( username ) === -1 &&
action.remoteUsers.indexOf( username ) === -1;
} );
action.remoteUsers.push.apply( action.remoteUsers, suggestions );
action.remoteUsers.sort();
action.searchedPrefixes[ capitalizedInput ] = true;
} );
} else {
apiPromise = ve.createDeferred().resolve().promise();
}
return apiPromise.then( function () {
// By concatenating on-thread authors and remote-fetched authors, both
// sorted alphabetically, we'll get our suggestion popup sorted so all
// on-thread matches come first.
return action.filterSuggestionsForInput(
action.localUsers
// Show no remote users if no input provided
.concat( capitalizedInput ? action.remoteUsers : [] ),
// TODO: Consider showing IP users
// * Change link to Special:Contributions/<ip> (localised)
// * Let users know that mentioning an IP will not create a notification?
// .concat( this.ipUsers )
input
);
} );
};
MWUsernameCompletionAction.prototype.insertCompletion = function ( word, range ) {
var fragment,
// TODO: Allow output customisation (T250332)
prefix = '@',
title = mw.Title.newFromText( word, mw.config.get( 'wgNamespaceIds' ).user ),
annotation = ve.dm.MWInternalLinkAnnotation.static.newFromTitle( title );
if ( this.surface.getMode() === 'source' ) {
// TODO: this should be configurable per-wiki so that e.g. custom templates can be used
word = prefix + '[[' + title.getPrefixedText() + '|' + word + ']]';
return MWUsernameCompletionAction.super.prototype.insertCompletion.call( this, word, range );
}
fragment = MWUsernameCompletionAction.super.prototype.insertCompletion.apply( this, arguments )
.annotateContent( 'clear', 'link' ).annotateContent( 'clear', 'link/mwInternal' )
.annotateContent( 'set', annotation );
fragment.collapseToStart().insertContent( '@' );
return fragment;
};
MWUsernameCompletionAction.prototype.shouldAbandon = function ( input ) {
// TODO: need to consider whether pending loads from server are happening here
return MWUsernameCompletionAction.super.prototype.shouldAbandon.apply( this, arguments ) && input.split( /\s+/ ).length > 2;
};
/* Registration */
ve.ui.actionFactory.register( MWUsernameCompletionAction );
ve.ui.commandRegistry.register(
new ve.ui.Command(
'openMWUsernameCompletions', MWUsernameCompletionAction.static.name, 'open',
{ supportedSelections: [ 'linear' ] }
)
);
ve.ui.commandRegistry.register(
new ve.ui.Command(
'insertAndOpenMWUsernameCompletions', MWUsernameCompletionAction.static.name, 'insertAndOpen',
{ supportedSelections: [ 'linear' ] }
)
);
ve.ui.sequenceRegistry.register(
new ve.ui.Sequence( 'autocompleteMWUsernames', 'openMWUsernameCompletions', '@', 0, false, false, true, true )
);
ve.ui.wikitextSequenceRegistry.register(
new ve.ui.Sequence( 'autocompleteMWUsernamesWikitext', 'openMWUsernameCompletions', '@', 0, false, false, true, true )
);
module.exports = MWUsernameCompletionAction;