mediawiki-extensions-WikiEd.../modules/insertlink/TitleInputWidget.js
Sam Wilson 6384498d75 Switch to OOUI TitleInputWidget for the link-inserter
In order to get a better look-up experience (images, description,
redirects, and disambigutation pages listed last), swich from
a custom title-autocomplete input (that used jquery.suggestions)
to OOUI's standard one (which is used in a bunch of other places
in MediaWiki).

This means a fair bit of code can be deleted from
jquery.wikiEditor.dialogs.config.js, and some of it moved to
the new OOUI classes.

This patch aims to be the minimum required, and so leaves a few
things for subsequent patches (such as converting the other parts
of the insert-link dialog to OOUI).

Bug: T289214
Change-Id: I9fb7e66bf925eb9a8260d6245d2a7db54a7a2fec
2021-10-08 12:43:55 +00:00

147 lines
4.5 KiB
JavaScript

var InsertLinkTitleOptionWidget = require( './TitleOptionWidget.js' );
/**
* A custom TitleInputWidget that adds support for external links
* (any string that starts with a protocol or `www.` and doesn't exist as a page).
*
* @class
* @extends mw.widgets.TitleInputWidget
* @constructor
*/
function TitleInputWidget() {
TitleInputWidget.parent.call( this, {
showImages: true,
showDescriptions: true,
showDisambigsLast: true,
placeholder: mw.msg( 'wikieditor-toolbar-tool-link-int-target-tooltip' ),
$overlay: this.getOverlay(),
validateTitle: false,
showInterwikis: true,
required: true,
addQueryInput: true
} );
}
OO.inheritClass( TitleInputWidget, mw.widgets.TitleInputWidget );
/**
* Regular expression for determining what might be an external link.
*
* @static
* @property {RegExp}
*/
TitleInputWidget.static.urlRegex = new RegExp( '^(' + mw.config.get( 'wgUrlProtocols' ) + '|www\\.)', 'i' );
/**
* When leaving the input without selecting a menu item,
* automatically select a matching item if there is one.
* Even though we specify addQueryInput=true in the config, the entered string
* is not always available to be selected. See T291056.
*/
TitleInputWidget.prototype.onLookupInputBlur = function () {
TitleInputWidget.parent.prototype.onLookupInputBlur.apply( this );
var that = this;
this.getLookupMenuItems().done( function ( items ) {
// The matching item is not always the first,
// because disambiguation pages are moved to the end.
for ( var i = 0; i < items.length; i++ ) {
var item = items[ i ];
var queryVal = that.getQueryValue();
// Check for exact match, or a match with uppercase first character.
if ( item.getData() === queryVal ||
item.getData() === queryVal.charAt( 0 ).toUpperCase() + queryVal.slice( 1 )
) {
// If a matching title is is found, fire an event and stop looking.
that.emit( 'select', item );
break;
}
}
} );
};
/**
* Get menu option widget data from the title and page data,
* adding an `external` property.
*
* @param {string} title Page title
* @param {Object} data Page data
* @return {Object} Data for option widget
*/
TitleInputWidget.prototype.getOptionWidgetData = function ( title, data ) {
var widgetData = TitleInputWidget.parent.prototype.getOptionWidgetData.call( this, title, data );
widgetData.external = data.originalData.external;
return widgetData;
};
/**
* Create a InsertLinkTitleOptionWidget.
*
* @param {Object} data Data for option widget
* @return {OO.ui.MenuOptionWidget} The option widget
*/
TitleInputWidget.prototype.createOptionWidget = function ( data ) {
return new InsertLinkTitleOptionWidget( data );
};
/**
* Get pages' data from the API response, adding an `external` property for
* pages that do not exist and which look like external URLs.
*
* @param {Object} response
* @return {Object}
*/
TitleInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
var res = TitleInputWidget.parent.prototype.getLookupCacheDataFromResponse( response );
// Guard against zero responses.
if ( res.pages === undefined ) {
return res;
}
for ( var pageId in res.pages ) {
if ( Object.prototype.hasOwnProperty.call( res.pages, pageId ) ) {
var page = res.pages[ pageId ];
page.external = page.missing !== undefined && this.isExternalLink( page.title );
}
}
return res;
};
/**
* Handle menu item 'choose' event, updating the text input value to the value of the clicked item.
*
* @param {OO.ui.MenuOptionWidget} item Selected item
*/
TitleInputWidget.prototype.onLookupMenuChoose = function ( item ) {
TitleInputWidget.parent.prototype.onLookupMenuChoose.call( this, item );
this.emit( 'select', item );
};
/**
* Get a custom overlay for the dropdown menu, so it's not contained within the jQuery UI dialog.
*
* @private
* @return {jQuery}
*/
TitleInputWidget.prototype.getOverlay = function () {
// Overlay z-index must be greater than the jQuery UI dialog's of 1002.
var $overlay = OO.ui.getDefaultOverlay()
.clone()
.css( 'z-index', '1010' );
$( document.body ).append( $overlay );
return $overlay;
};
/**
* Determine if a given string is likely to be an external URL.
* External URLs start with either a valid protocol (from $wgUrlProtocols) or `www.`.
*
* @public
* @param {string} urlString The possible URL.
* @return {boolean}
*/
TitleInputWidget.prototype.isExternalLink = function ( urlString ) {
var matches = urlString.match( this.constructor.static.urlRegex );
return matches !== null && matches.length > 0;
};
module.exports = TitleInputWidget;