X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki.widgets%2Fmw.widgets.TitleInputWidget.js;h=221de0f0a24a19a3938918a34ec18aeff2b6c6b6;hb=26edd30cc6108a9a3b875d94bb72be30bd2bac03;hp=bfedb6b38c6350331ee0e5985f639932cf667e42;hpb=6063ffb150b8bd64692db42a696a6aafcf6a438c;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js index bfedb6b38c..221de0f0a2 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js @@ -1,5 +1,5 @@ /*! - * MediaWiki Widgets – TitleInputWidget class. + * MediaWiki Widgets - TitleInputWidget class. * * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt @@ -10,13 +10,21 @@ * * @class * @extends OO.ui.TextInputWidget - * @mixins OO.ui.LookupElement + * @mixins OO.ui.mixin.LookupElement * * @constructor * @param {Object} [config] Configuration options + * @cfg {number} [limit=10] Number of results to show * @cfg {number} [namespace] Namespace to prepend to queries + * @cfg {boolean} [showRedirectTargets=true] Show the targets of redirects + * @cfg {boolean} [showRedlink] Show red link to exact match if it doesn't exist + * @cfg {boolean} [showImages] Show page images + * @cfg {boolean} [showDescriptions] Show page descriptions + * @cfg {Object} [cache] Result cache which implements a 'set' method, taking keyed values as an argument */ - mw.widgets.TitleInputWidget = function MWWTitleInputWidget( config ) { + mw.widgets.TitleInputWidget = function MwWidgetsTitleInputWidget( config ) { + var widget = this; + // Config initialization config = config || {}; @@ -24,21 +32,44 @@ OO.ui.TextInputWidget.call( this, config ); // Mixin constructors - OO.ui.LookupElement.call( this, config ); + OO.ui.mixin.LookupElement.call( this, config ); // Properties + this.limit = config.limit || 10; this.namespace = config.namespace || null; + this.showRedirectTargets = config.showRedirectTargets !== false; + this.showRedlink = !!config.showRedlink; + this.showImages = !!config.showImages; + this.showDescriptions = !!config.showDescriptions; + this.cache = config.cache; // Initialization - this.$element.addClass( 'mw-widget-TitleInputWidget' ); - this.lookupMenu.$element.addClass( 'mw-widget-TitleInputWidget-menu' ); + this.$element.addClass( 'mw-widget-titleInputWidget' ); + this.lookupMenu.$element.addClass( 'mw-widget-titleInputWidget-menu' ); + if ( this.showImages ) { + this.lookupMenu.$element.addClass( 'mw-widget-titleInputWidget-menu-withImages' ); + } + if ( this.showDescriptions ) { + this.lookupMenu.$element.addClass( 'mw-widget-titleInputWidget-menu-withDescriptions' ); + } + + this.interwikiPrefixes = []; + this.interwikiPrefixesPromise = new mw.Api().get( { + action: 'query', + meta: 'siteinfo', + siprop: 'interwikimap' + } ).done( function ( data ) { + $.each( data.query.interwikimap, function ( index, interwiki ) { + widget.interwikiPrefixes.push( interwiki.prefix ); + } ); + } ); }; /* Inheritance */ OO.inheritClass( mw.widgets.TitleInputWidget, OO.ui.TextInputWidget ); - OO.mixinClass( mw.widgets.TitleInputWidget, OO.ui.LookupElement ); + OO.mixinClass( mw.widgets.TitleInputWidget, OO.ui.mixin.LookupElement ); /* Methods */ @@ -55,60 +86,188 @@ /** * @inheritdoc */ - mw.widgets.TitleInputWidget.prototype.getLookupRequest = function () { - var value = this.value; + mw.widgets.TitleInputWidget.prototype.focus = function () { + var retval; - // Prefix with default namespace name - if ( this.namespace !== null && mw.Title.newFromText( value, this.namespace ) ) { - value = mw.Title.newFromText( value, this.namespace ).getPrefixedText(); - } + // Prevent programmatic focus from opening the menu + this.setLookupsDisabled( true ); - // Dont send leading ':' to open search - if ( value.charAt( 0 ) === ':' ) { - value = value.slice( 1 ); - } + // Parent method + retval = OO.ui.TextInputWidget.prototype.focus.apply( this, arguments ); - return new mw.Api().get( { - action: 'opensearch', - search: value, - suggest: '' - } ); + this.setLookupsDisabled( false ); + + return retval; }; /** * @inheritdoc */ + mw.widgets.TitleInputWidget.prototype.getLookupRequest = function () { + var req, + widget = this, + promiseAbortObject = { abort: function () { + // Do nothing. This is just so OOUI doesn't break due to abort being undefined. + } }; + + if ( mw.Title.newFromText( this.value ) ) { + return this.interwikiPrefixesPromise.then( function () { + var params, props, + interwiki = widget.value.substring( 0, widget.value.indexOf( ':' ) ); + if ( + interwiki && interwiki !== '' && + widget.interwikiPrefixes.indexOf( interwiki ) !== -1 + ) { + return $.Deferred().resolve( { query: { + pages: [{ + title: widget.value + }] + } } ).promise( promiseAbortObject ); + } else { + params = { + action: 'query', + generator: 'prefixsearch', + gpssearch: widget.value, + gpsnamespace: widget.namespace !== null ? widget.namespace : undefined, + gpslimit: widget.limit, + ppprop: 'disambiguation' + }; + props = [ 'info', 'pageprops' ]; + if ( widget.showRedirectTargets ) { + params.redirects = '1'; + } + if ( widget.showImages ) { + props.push( 'pageimages' ); + params.pithumbsize = 80; + params.pilimit = widget.limit; + } + if ( widget.showDescriptions ) { + props.push( 'pageterms' ); + params.wbptterms = 'description'; + } + params.prop = props.join( '|' ); + req = new mw.Api().get( params ); + promiseAbortObject.abort = req.abort.bind( req ); // todo: ew + return req; + } + } ).promise( promiseAbortObject ); + } else { + // Don't send invalid titles to the API. + // Just pretend it returned nothing so we can show the 'invalid title' section + return $.Deferred().resolve( {} ).promise( promiseAbortObject ); + } + }; + + /** + * Get lookup cache item from server response data. + * + * @method + * @param {Mixed} data Response from server + */ mw.widgets.TitleInputWidget.prototype.getLookupCacheDataFromResponse = function ( data ) { - return data[1] || []; + return data.query || {}; }; /** - * @inheritdoc + * Get list of menu items from a server response. + * + * @param {Object} data Query result + * @returns {OO.ui.MenuOptionWidget[]} Menu items */ mw.widgets.TitleInputWidget.prototype.getLookupMenuOptionsFromData = function ( data ) { - var i, len, title, value, + var i, len, index, pageExists, pageExistsExact, suggestionPage, page, redirect, redirects, items = [], - matchingPages = data; - - // Matching pages - if ( matchingPages && matchingPages.length ) { - for ( i = 0, len = matchingPages.length; i < len; i++ ) { - title = new mw.Title( matchingPages[i] ); - if ( this.namespace !== null ) { - value = title.getRelativeText( this.namespace ); - } else { - value = title.getPrefixedText(); - } - items.push( new OO.ui.MenuOptionWidget( { - data: value, - label: value - } ) ); + titles = [], + titleObj = mw.Title.newFromText( this.value ), + redirectsTo = {}, + pageData = {}; + + if ( data.redirects ) { + for ( i = 0, len = data.redirects.length; i < len; i++ ) { + redirect = data.redirects[i]; + redirectsTo[redirect.to] = redirectsTo[redirect.to] || []; + redirectsTo[redirect.to].push( redirect.from ); + } + } + + for ( index in data.pages ) { + suggestionPage = data.pages[index]; + pageData[suggestionPage.title] = { + missing: suggestionPage.missing !== undefined, + redirect: suggestionPage.redirect !== undefined, + disambiguation: OO.getProp( suggestionPage, 'pageprops', 'disambiguation' ) !== undefined, + imageUrl: OO.getProp( suggestionPage, 'thumbnail', 'source' ), + description: OO.getProp( suggestionPage, 'terms', 'description' ) + }; + titles.push( suggestionPage.title ); + + redirects = redirectsTo[suggestionPage.title] || []; + for ( i = 0, len = redirects.length; i < len; i++ ) { + pageData[redirects[i]] = { + missing: false, + redirect: true, + disambiguation: false, + description: mw.msg( 'mw-widgets-titleinput-description-redirect', suggestionPage.title ) + }; + titles.push( redirects[i] ); } } + // If not found, run value through mw.Title to avoid treating a match as a + // mismatch where normalisation would make them matching (bug 48476) + + pageExistsExact = titles.indexOf( this.value ) !== -1; + pageExists = pageExistsExact || ( + titleObj && titles.indexOf( titleObj.getPrefixedText() ) !== -1 + ); + + if ( !pageExists ) { + pageData[this.value] = { + missing: true, redirect: false, disambiguation: false, + description: mw.msg( 'mw-widgets-titleinput-description-new-page' ) + }; + } + + if ( this.cache ) { + this.cache.set( pageData ); + } + + // Offer the exact text as a suggestion if the page exists + if ( pageExists && !pageExistsExact ) { + titles.unshift( this.value ); + } + // Offer the exact text as a new page if the title is valid + if ( this.showRedlink && !pageExists && titleObj ) { + titles.push( this.value ); + } + for ( i = 0, len = titles.length; i < len; i++ ) { + page = pageData[titles[i]] || {}; + items.push( new mw.widgets.TitleOptionWidget( this.getOptionWidgetData( titles[i], page ) ) ); + } + return items; }; + /** + * Get menu option widget data from the title and page data + * + * @param {mw.Title} title Title object + * @param {Object} data Page data + * @return {Object} Data for option widget + */ + mw.widgets.TitleInputWidget.prototype.getOptionWidgetData = function ( title, data ) { + var mwTitle = new mw.Title( title ); + return { + data: this.namespace !== null ? mwTitle.getRelativeText( this.namespace ) : title, + imageUrl: this.showImages ? data.imageUrl : null, + description: this.showDescriptions ? data.description : null, + missing: data.missing, + redirect: data.redirect, + disambiguation: data.disambiguation, + query: this.value + }; + }; + /** * Get title object corresponding to #getValue *