summaryrefslogtreecommitdiff
path: root/www/wiki/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js')
-rw-r--r--www/wiki/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js375
1 files changed, 375 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js b/www/wiki/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
new file mode 100644
index 00000000..2bbeabf5
--- /dev/null
+++ b/www/wiki/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js
@@ -0,0 +1,375 @@
+/*!
+ * MediaWiki Widgets - TitleWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+ var hasOwn = Object.prototype.hasOwnProperty;
+
+ /**
+ * Mixin for title widgets
+ *
+ * @class
+ * @abstract
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number} [limit=10] Number of results to show
+ * @cfg {number} [namespace] Namespace to prepend to queries
+ * @cfg {number} [maxLength=255] Maximum query length
+ * @cfg {boolean} [relative=true] If a namespace is set, display titles relative to it
+ * @cfg {boolean} [suggestions=true] Display search suggestions
+ * @cfg {boolean} [showRedirectTargets=true] Show the targets of redirects
+ * @cfg {boolean} [showImages] Show page images
+ * @cfg {boolean} [showDescriptions] Show page descriptions
+ * @cfg {boolean} [showMissing=true] Show missing pages
+ * @cfg {boolean} [addQueryInput=true] Add exact user's input query to results
+ * @cfg {boolean} [excludeCurrentPage] Exclude the current page from suggestions
+ * @cfg {boolean} [validateTitle=true] Whether the input must be a valid title (if set to true,
+ * the widget will marks itself red for invalid inputs, including an empty query).
+ * @cfg {Object} [cache] Result cache which implements a 'set' method, taking keyed values as an argument
+ * @cfg {mw.Api} [api] API object to use, creates a default mw.Api instance if not specified
+ */
+ mw.widgets.TitleWidget = function MwWidgetsTitleWidget( config ) {
+ // Config initialization
+ config = $.extend( {
+ maxLength: 255,
+ limit: 10
+ }, config );
+
+ // Properties
+ this.limit = config.limit;
+ this.maxLength = config.maxLength;
+ this.namespace = config.namespace !== undefined ? config.namespace : null;
+ this.relative = config.relative !== undefined ? config.relative : true;
+ this.suggestions = config.suggestions !== undefined ? config.suggestions : true;
+ this.showRedirectTargets = config.showRedirectTargets !== false;
+ this.showImages = !!config.showImages;
+ this.showDescriptions = !!config.showDescriptions;
+ this.showMissing = config.showMissing !== false;
+ this.addQueryInput = config.addQueryInput !== false;
+ this.excludeCurrentPage = !!config.excludeCurrentPage;
+ this.validateTitle = config.validateTitle !== undefined ? config.validateTitle : true;
+ this.cache = config.cache;
+ this.api = config.api || new mw.Api();
+ // Supports: IE10, FF28, Chrome23
+ this.compare = window.Intl && Intl.Collator ?
+ new Intl.Collator( mw.config.get( 'wgContentLanguage' ), { sensitivity: 'base' } ).compare :
+ null;
+
+ // Initialization
+ this.$element.addClass( 'mw-widget-titleWidget' );
+ };
+
+ /* Setup */
+
+ OO.initClass( mw.widgets.TitleWidget );
+
+ /* Static properties */
+
+ mw.widgets.TitleWidget.static.interwikiPrefixesPromiseCache = {};
+
+ /* Methods */
+
+ /**
+ * Get the current value of the search query
+ *
+ * @abstract
+ * @return {string} Search query
+ */
+ mw.widgets.TitleWidget.prototype.getQueryValue = null;
+
+ /**
+ * Get the namespace to prepend to titles in suggestions, if any.
+ *
+ * @return {number|null} Namespace number
+ */
+ mw.widgets.TitleWidget.prototype.getNamespace = function () {
+ return this.namespace;
+ };
+
+ /**
+ * Set the namespace to prepend to titles in suggestions, if any.
+ *
+ * @param {number|null} namespace Namespace number
+ */
+ mw.widgets.TitleWidget.prototype.setNamespace = function ( namespace ) {
+ this.namespace = namespace;
+ };
+
+ mw.widgets.TitleWidget.prototype.getInterwikiPrefixesPromise = function () {
+ var api = this.getApi(),
+ cache = this.constructor.static.interwikiPrefixesPromiseCache,
+ key = api.defaults.ajax.url;
+ if ( !cache.hasOwnProperty( key ) ) {
+ cache[ key ] = api.get( {
+ action: 'query',
+ meta: 'siteinfo',
+ siprop: 'interwikimap',
+ // Cache client-side for a day since this info is mostly static
+ maxage: 60 * 60 * 24,
+ smaxage: 60 * 60 * 24,
+ // Workaround T97096 by setting uselang=content
+ uselang: 'content'
+ } ).then( function ( data ) {
+ return $.map( data.query.interwikimap, function ( interwiki ) {
+ return interwiki.prefix;
+ } );
+ } );
+ }
+ return cache[ key ];
+ };
+
+ /**
+ * Get a promise which resolves with an API repsonse for suggested
+ * links for the current query.
+ *
+ * @return {jQuery.Promise} Suggestions promise
+ */
+ mw.widgets.TitleWidget.prototype.getSuggestionsPromise = function () {
+ var req,
+ api = this.getApi(),
+ query = this.getQueryValue(),
+ widget = this,
+ promiseAbortObject = { abort: function () {
+ // Do nothing. This is just so OOUI doesn't break due to abort being undefined.
+ } };
+
+ if ( !mw.Title.newFromText( query ) ) {
+ // 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 );
+ }
+
+ return this.getInterwikiPrefixesPromise().then( function ( interwikiPrefixes ) {
+ var interwiki = query.substring( 0, query.indexOf( ':' ) );
+ if (
+ interwiki && interwiki !== '' &&
+ interwikiPrefixes.indexOf( interwiki ) !== -1
+ ) {
+ return $.Deferred().resolve( { query: {
+ pages: [ {
+ title: query
+ } ]
+ } } ).promise( promiseAbortObject );
+ } else {
+ req = api.get( widget.getApiParams( query ) );
+ promiseAbortObject.abort = req.abort.bind( req ); // TODO ew
+ return req.then( function ( ret ) {
+ if ( widget.showMissing && ret.query === undefined ) {
+ ret = api.get( { action: 'query', titles: query } );
+ promiseAbortObject.abort = ret.abort.bind( ret );
+ }
+ return ret;
+ } );
+ }
+ } ).promise( promiseAbortObject );
+ };
+
+ /**
+ * Get API params for a given query
+ *
+ * @param {string} query User query
+ * @return {Object} API params
+ */
+ mw.widgets.TitleWidget.prototype.getApiParams = function ( query ) {
+ var params = {
+ action: 'query',
+ prop: [ 'info', 'pageprops' ],
+ generator: 'prefixsearch',
+ gpssearch: query,
+ gpsnamespace: this.namespace !== null ? this.namespace : undefined,
+ gpslimit: this.limit,
+ ppprop: 'disambiguation'
+ };
+ if ( this.showRedirectTargets ) {
+ params.redirects = true;
+ }
+ if ( this.showImages ) {
+ params.prop.push( 'pageimages' );
+ params.pithumbsize = 80;
+ params.pilimit = this.limit;
+ }
+ if ( this.showDescriptions ) {
+ params.prop.push( 'description' );
+ }
+ return params;
+ };
+
+ /**
+ * Get the API object for title requests
+ *
+ * @return {mw.Api} MediaWiki API
+ */
+ mw.widgets.TitleWidget.prototype.getApi = function () {
+ return this.api;
+ };
+
+ /**
+ * Get option widgets from the server response
+ *
+ * @param {Object} data Query result
+ * @return {OO.ui.OptionWidget[]} Menu items
+ */
+ mw.widgets.TitleWidget.prototype.getOptionsFromData = function ( data ) {
+ var i, len, index, pageExists, pageExistsExact, suggestionPage, page, redirect, redirects,
+ currentPageName = new mw.Title( mw.config.get( 'wgRelevantPageName' ) ).getPrefixedText(),
+ items = [],
+ titles = [],
+ titleObj = mw.Title.newFromText( this.getQueryValue() ),
+ 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 ];
+
+ // When excludeCurrentPage is set, don't list the current page unless the user has type the full title
+ if ( this.excludeCurrentPage && suggestionPage.title === currentPageName && suggestionPage.title !== titleObj.getPrefixedText() ) {
+ continue;
+ }
+ pageData[ suggestionPage.title ] = {
+ known: suggestionPage.known !== undefined,
+ missing: suggestionPage.missing !== undefined,
+ redirect: suggestionPage.redirect !== undefined,
+ disambiguation: OO.getProp( suggestionPage, 'pageprops', 'disambiguation' ) !== undefined,
+ imageUrl: OO.getProp( suggestionPage, 'thumbnail', 'source' ),
+ description: suggestionPage.description,
+ // Sort index
+ index: suggestionPage.index,
+ originalData: suggestionPage
+ };
+
+ // Throw away pages from wrong namespaces. This can happen when 'showRedirectTargets' is true
+ // and we encounter a cross-namespace redirect.
+ if ( this.namespace === null || this.namespace === suggestionPage.ns ) {
+ titles.push( suggestionPage.title );
+ }
+
+ redirects = hasOwn.call( redirectsTo, suggestionPage.title ) ? redirectsTo[ suggestionPage.title ] : [];
+ for ( i = 0, len = redirects.length; i < len; i++ ) {
+ pageData[ redirects[ i ] ] = {
+ missing: false,
+ known: true,
+ redirect: true,
+ disambiguation: false,
+ description: mw.msg( 'mw-widgets-titleinput-description-redirect', suggestionPage.title ),
+ // Sort index, just below its target
+ index: suggestionPage.index + 0.5,
+ originalData: suggestionPage
+ };
+ titles.push( redirects[ i ] );
+ }
+ }
+
+ titles.sort( function ( a, b ) {
+ return pageData[ a ].index - pageData[ b ].index;
+ } );
+
+ // If not found, run value through mw.Title to avoid treating a match as a
+ // mismatch where normalisation would make them matching (T50476)
+
+ pageExistsExact = (
+ hasOwn.call( pageData, this.getQueryValue() ) &&
+ (
+ !pageData[ this.getQueryValue() ].missing ||
+ pageData[ this.getQueryValue() ].known
+ )
+ );
+ pageExists = pageExistsExact || (
+ titleObj &&
+ hasOwn.call( pageData, titleObj.getPrefixedText() ) &&
+ (
+ !pageData[ titleObj.getPrefixedText() ].missing ||
+ pageData[ titleObj.getPrefixedText() ].known
+ )
+ );
+
+ if ( this.cache ) {
+ this.cache.set( pageData );
+ }
+
+ // Offer the exact text as a suggestion if the page exists
+ if ( this.addQueryInput && pageExists && !pageExistsExact ) {
+ titles.unshift( this.getQueryValue() );
+ }
+
+ for ( i = 0, len = titles.length; i < len; i++ ) {
+ page = hasOwn.call( pageData, titles[ i ] ) ? pageData[ titles[ i ] ] : {};
+ items.push( this.createOptionWidget( this.getOptionWidgetData( titles[ i ], page ) ) );
+ }
+
+ return items;
+ };
+
+ /**
+ * Create a menu option widget with specified data
+ *
+ * @param {Object} data Data for option widget
+ * @return {OO.ui.MenuOptionWidget} Data for option widget
+ */
+ mw.widgets.TitleWidget.prototype.createOptionWidget = function ( data ) {
+ return new mw.widgets.TitleOptionWidget( data );
+ };
+
+ /**
+ * Get menu option widget data from the title and page data
+ *
+ * @param {string} title Title object
+ * @param {Object} data Page data
+ * @return {Object} Data for option widget
+ */
+ mw.widgets.TitleWidget.prototype.getOptionWidgetData = function ( title, data ) {
+ var mwTitle = new mw.Title( title ),
+ description = data.description;
+ if ( data.missing && !description ) {
+ description = mw.msg( 'mw-widgets-titleinput-description-new-page' );
+ }
+ return {
+ data: this.namespace !== null && this.relative ?
+ mwTitle.getRelativeText( this.namespace ) :
+ title,
+ url: mwTitle.getUrl(),
+ showImages: this.showImages,
+ imageUrl: this.showImages ? data.imageUrl : null,
+ description: this.showDescriptions ? description : null,
+ missing: data.missing,
+ redirect: data.redirect,
+ disambiguation: data.disambiguation,
+ query: this.getQueryValue(),
+ compare: this.compare
+ };
+ };
+
+ /**
+ * Get title object corresponding to given value, or #getQueryValue if not given.
+ *
+ * @param {string} [value] Value to get a title for
+ * @return {mw.Title|null} Title object, or null if value is invalid
+ */
+ mw.widgets.TitleWidget.prototype.getMWTitle = function ( value ) {
+ var title = value !== undefined ? value : this.getQueryValue(),
+ // mw.Title doesn't handle null well
+ titleObj = mw.Title.newFromText( title, this.namespace !== null ? this.namespace : undefined );
+
+ return titleObj;
+ };
+
+ /**
+ * Check if the query is valid
+ *
+ * @return {boolean} The query is valid
+ */
+ mw.widgets.TitleWidget.prototype.isQueryValid = function () {
+ return this.validateTitle ? !!this.getMWTitle() : true;
+ };
+
+}( jQuery, mediaWiki ) );