/*! * VisualEditor UserInterface MWTemplateTitleInputWidget class. * * @copyright 2011-2020 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * Creates an ve.ui.MWTemplateTitleInputWidget object. * * @class * @extends mw.widgets.TitleInputWidget * * @constructor * @param {Object} [config] Configuration options * @cfg {number} [namespace] Namespace to prepend to queries. Defaults to template namespace. */ ve.ui.MWTemplateTitleInputWidget = function VeUiMWTemplateTitleInputWidget( config ) { config = ve.extendObject( {}, { namespace: mw.config.get( 'wgNamespaceIds' ).template }, config ); // Parent constructor ve.ui.MWTemplateTitleInputWidget.super.call( this, config ); this.showTemplateDescriptions = this.showDescriptions; // Clear the showDescriptions flag for subsequent requests as we implement // description fetch ourselves this.showDescriptions = false; // Properties this.descriptions = {}; // Initialization this.$element.addClass( 've-ui-mwTemplateTitleInputWidget' ); }; /* Inheritance */ // FIXME: This should extend mw.widgets.TitleSearchWidget instead OO.inheritClass( ve.ui.MWTemplateTitleInputWidget, mw.widgets.TitleInputWidget ); /* Methods */ // @inheritdoc mw.widgets.TitleWidget ve.ui.MWTemplateTitleInputWidget.prototype.getApiParams = function ( query ) { var params = ve.ui.MWTemplateTitleInputWidget.super.prototype.getApiParams.call( this, query ); // TODO: This should stay as a feature flag for 3rd-parties to fallback to prefixsearch if ( mw.config.get( 'wgVisualEditorConfig' ).cirrusSearchLookup ) { params.generator = 'search'; params.gsrsearch = params.gpssearch; params.gsrsearch += '*'; // params.gsrsort = 'incoming_links_desc'; params.gsrnamespace = params.gpsnamespace; params.gsrlimit = params.gpslimit; delete params.gpssearch; delete params.gpsnamespace; delete params.gpslimit; } return params; }; // @inheritdoc mw.widgets.TitleInputWidget ve.ui.MWTemplateTitleInputWidget.prototype.getLookupRequest = function () { var widget = this, originalResponse, templateDataMessage = mw.message( 'templatedata-doc-subpage' ), templateDataInstalled = templateDataMessage.exists(), templateDocPageFragment = '/' + templateDataMessage.text(), promise = ve.ui.MWTemplateTitleInputWidget.super.prototype.getLookupRequest.call( this ); if ( !this.showTemplateDescriptions ) { return promise; } return promise .then( function ( response ) { var xhr, pageId, redirIndex, redirects = ( response.query && response.query.redirects ) || [], origPages = ( response.query && response.query.pages ) || {}, newPages = [], titles = []; // Build a new array to replace response.query.pages, ensuring everything goes into // the order defined by the page's index key, instead of whatever random order the // browser would let you iterate over the old object in. for ( pageId in origPages ) { if ( 'index' in origPages[ pageId ] ) { newPages[ origPages[ pageId ].index - 1 ] = origPages[ pageId ]; } else { // Watch out for cases where the index is specified on the redirect object // rather than the page object. for ( redirIndex in redirects ) { if ( redirects[ redirIndex ].to === origPages[ pageId ].title ) { newPages[ redirects[ redirIndex ].index - 1 ] = origPages[ pageId ]; break; } } } } // T54448: Filter out matches which end in /doc or as configured on-wiki if ( templateDataInstalled ) { newPages = newPages.filter( function ( page ) { // Can't use String.endsWith() as that's ES6. // page.title.endsWith( templateDocPageFragment ) return page.title.slice( 0 - templateDocPageFragment.length ) !== templateDocPageFragment; } ); } else { // Even if not filtering /doc, collapse the sparse array newPages = newPages.filter( function ( page ) { return page; } ); } titles = newPages.map( function ( page ) { return page.title; } ); ve.setProp( response, 'query', 'pages', newPages ); originalResponse = response; // lie! // Also get descriptions // FIXME: This should go through MWTransclusionModel rather than duplicate. if ( titles.length > 0 ) { xhr = widget.getApi().get( { action: 'templatedata', format: 'json', formatversion: 2, titles: titles, redirects: !!widget.showRedirects, includeMissingTitles: '1', lang: mw.config.get( 'wgUserLanguage' ) } ); return xhr.promise( { abort: xhr.abort } ); } } ) .then( function ( templateDataResponse ) { var index, page, missingTitle, pages = ( templateDataResponse && templateDataResponse.pages ) || {}; // Look for descriptions and cache them for ( index in pages ) { page = pages[ index ]; if ( page.missing ) { // Remmeber templates that don't exist in the link cache // { title: { missing: true|false } missingTitle = {}; missingTitle[ page.title ] = { missing: true }; ve.init.platform.linkCache.setMissing( missingTitle ); } else if ( !page.notemplatedata ) { // Cache descriptions widget.descriptions[ page.title ] = page.description; } } // Return the original response return originalResponse; }, function () { // API request failed; most likely, we're on a wiki which doesn't have TemplateData. return originalResponse; } ) .promise( { abort: function () {} } ); }; // @inheritdoc mw.widgets.TitleInputWidget ve.ui.MWTemplateTitleInputWidget.prototype.getOptionWidgetData = function ( title ) { return ve.extendObject( ve.ui.MWTemplateTitleInputWidget.super.prototype.getOptionWidgetData.apply( this, arguments ), { description: this.descriptions[ title ] } ); };