From e6e728b58e75ce4a5f4d43c9e5495ed3f16046c9 Mon Sep 17 00:00:00 2001 From: Moriel Schottlender Date: Tue, 17 Feb 2015 15:56:44 -0800 Subject: [PATCH] Change global class variables to mw.TemplateData Nest all classes for the templatedata dialog in mw.TemplateData namespace. Change-Id: Ib514378c9fbc0fb993b3cbc2fa48ced920167226 --- .jshintrc | 13 +- TemplateData.php | 1 + modules/ext.templateDataGenerator.data.js | 2119 ++++++++--------- modules/ext.templateDataGenerator.editPage.js | 4 +- modules/ext.templateDataGenerator.js | 1 + modules/ext.templateDataGenerator.ui.js | 6 +- .../ext.templateDataGenerator.ui.tdDialog.js | 1686 +++++++------ ...emplateDataGenerator.dragDropItemWidget.js | 8 +- ...xt.templateDataGenerator.dragDropWidget.js | 12 +- ...plateDataGenerator.languageResultWidget.js | 8 +- ...plateDataGenerator.languageSearchWidget.js | 12 +- ...emplateDataGenerator.optionImportWidget.js | 8 +- .../ext.templateDataGenerator.optionWidget.js | 8 +- tests/ext.templateData.tests.js | 16 +- 14 files changed, 1944 insertions(+), 1958 deletions(-) create mode 100644 modules/ext.templateDataGenerator.js diff --git a/.jshintrc b/.jshintrc index dccd4afb..7ed54dc6 100644 --- a/.jshintrc +++ b/.jshintrc @@ -20,17 +20,6 @@ "globals": { "mw": false, "OO": false, - "QUnit": false, - "unicodeJS": false, - "jQuery": false, - "mediaWiki": false, - "TemplateDataModel": true, - "TemplateDataDialog": true, - "TemplateDataOptionWidget": true, - "TemplateDataOptionImportWidget": true, - "TemplateDataLanguageSearchWidget": true, - "TemplateDataLanguageResultWidget": true, - "TemplateDataDragDropItemWidget": true, - "TemplateDataDragDropWidget": true + "QUnit": false } } diff --git a/TemplateData.php b/TemplateData.php index 3f23cd33..58aeff22 100644 --- a/TemplateData.php +++ b/TemplateData.php @@ -77,6 +77,7 @@ $wgResourceModules['ext.templateDataGenerator.data'] = array( 'localBasePath' => $dir, 'remoteExtPath' => 'TemplateData', 'scripts' => array( + 'modules/ext.templateDataGenerator.js', 'modules/ext.templateDataGenerator.data.js' ), 'dependencies' => array( diff --git a/modules/ext.templateDataGenerator.data.js b/modules/ext.templateDataGenerator.data.js index 53868f29..bd4fa94a 100644 --- a/modules/ext.templateDataGenerator.data.js +++ b/modules/ext.templateDataGenerator.data.js @@ -1,1103 +1,1100 @@ -( function ( $ ) { - /** - * TemplateData Dialog - * @param {Object} config Dialog configuration object - */ - TemplateDataModel = function TemplateDataModel( config ) { - config = config || {}; +/** + * TemplateData Dialog + * @param {Object} config Dialog configuration object + */ +mw.TemplateData.Model = function mwTemplateDataModel( config ) { + config = config || {}; - // Mixin constructors - OO.EventEmitter.call( this ); + // Mixin constructors + OO.EventEmitter.call( this ); - // Config - this.setParentPage( config.parentPage ); - this.setPageSubLevel( config.isPageSubLevel ); + // Config + this.setParentPage( config.parentPage ); + this.setPageSubLevel( config.isPageSubLevel ); - // Properties - this.params = {}; - this.description = {}; - this.paramOrder = []; - this.paramOrderChanged = false; - this.paramIdentifierCounter = 0; - this.setPageSubLevel( !!config.isPageSubLevel ); - this.setFullPageName( config.fullPageName || '' ); + // Properties + this.params = {}; + this.description = {}; + this.paramOrder = []; + this.paramOrderChanged = false; + this.paramIdentifierCounter = 0; + this.setPageSubLevel( !!config.isPageSubLevel ); + this.setFullPageName( config.fullPageName || '' ); - this.originalTemplateDataObject = null; - this.sourceCodeParameters = []; - this.templateSourceCodePromise = null; - }; + this.originalTemplateDataObject = null; + this.sourceCodeParameters = []; + this.templateSourceCodePromise = null; +}; - /* Setup */ - OO.initClass( TemplateDataModel ); - OO.mixinClass( TemplateDataModel, OO.EventEmitter ); +/* Setup */ +OO.initClass( mw.TemplateData.Model ); +OO.mixinClass( mw.TemplateData.Model, OO.EventEmitter ); - /* Events */ +/* Events */ - /** - * @event add-param - * @param {string} key Parameter key - * @param {Object} data Parameter data - */ +/** + * @event add-param + * @param {string} key Parameter key + * @param {Object} data Parameter data + */ - /** - * @event change-description - * @param {string} description New template description - * @param {Object} [language] Description language, if supplied - */ +/** + * @event change-description + * @param {string} description New template description + * @param {Object} [language] Description language, if supplied + */ - /** - * @event change-paramOrder - * @param {string[]} orderArray Parameter key array in order - */ +/** + * @event change-paramOrder + * @param {string[]} orderArray Parameter key array in order + */ - /** - * @event change-property - * @param {string} paramKey Parameter key - * @param {string} prop Property name - * @param {Mixed...} value Property value - */ +/** + * @event change-property + * @param {string} paramKey Parameter key + * @param {string} prop Property name + * @param {Mixed...} value Property value + */ - /* Static Methods */ +/* Static Methods */ - /** - * Get information from the mediaWiki API - * @param {string} page Page name - * @return {jQuery.Promise} API promise - */ - TemplateDataModel.static.getApi = function ( page ) { - var api = new mediaWiki.Api(); - return api.get( { - action: 'query', - prop: 'revisions', - rvprop: 'content', - indexpageids: '1', - titles: page - } ); - }; +/** + * Get information from the mediaWiki API + * @param {string} page Page name + * @return {jQuery.Promise} API promise + */ +mw.TemplateData.Model.static.getApi = function ( page ) { + var api = new mw.Api(); + return api.get( { + action: 'query', + prop: 'revisions', + rvprop: 'content', + indexpageids: '1', + titles: page + } ); +}; - /** - * Compare two objects or strings - * @param {Object|string} obj1 Base object - * @param {Object|string} obj2 Compared object - * @param {boolean} [allowSubset] Allow the second object to be a - * partial object (or a subset) of the first. - * @return {boolean} Objects have equal values - */ - TemplateDataModel.static.compare = function ( obj1, obj2, allowSubset ) { - if ( allowSubset && obj2 === undefined ) { - return true; - } - - // Make sure the objects are of the same type - if ( $.type( obj1 ) !== $.type( obj2 ) ) { - return false; - } - - // Comparing objects or arrays - if ( typeof obj1 === 'object' ) { - return OO.compare( obj2, obj1, allowSubset ); - } - - // Everything else (primitive types, functions, etc) - return obj1 === obj2; - }; - - /** - * Translate obsolete parameter types into the new types - * @param {string} paramType Given type - * @return {string} Normalized non-obsolete type - */ - TemplateDataModel.static.translateObsoleteParamTypes = function ( paramType ) { - switch ( paramType ) { - case 'string/wiki-page-name': - return 'wiki-page-name'; - case 'string/wiki-file-name': - return 'wiki-file-name'; - case 'string/wiki-user-name': - return 'wiki-user-name'; - default: - return paramType; - } - }; - - /** - * Retrieve information about all legal properties for a parameter. - * @param {boolean} getFullData Retrieve full information about each - * parameter. If false, the method will return an array of property - * names only. - * @return {Object|string[]} Legal property names with or without their - * definition data - */ - TemplateDataModel.static.getAllProperties = function ( getFullData ) { - var properties = { - name: { - type: 'string', - // Validation regex - restrict: /[\|=]|}}/ - }, - aliases: { - type: 'array', - delimiter: mw.msg( 'comma-separator' ) - }, - label: { - type: 'string', - allowLanguages: true - }, - description: { - type: 'string', - allowLanguages: true - }, - type: { - type: 'select', - children: [ - 'boolean', - 'content', - 'wiki-file-name', - 'line', - 'number', - 'date', - 'wiki-page-name', - 'string', - 'unbalanced-wikitext', - 'undefined', - 'wiki-user-name' - ], - 'default': 'undefined' - }, - 'default': { - type: 'string', - multiline: true - }, - autovalue: { - type: 'string' - }, - deprecated: { - type: 'boolean' - }, - required: { - type: 'boolean' - }, - suggested: { - type: 'boolean' - } - }; - - if ( !getFullData ) { - return Object.keys( properties ); - } else { - return properties; - } - }; - - /** - * Retrieve the list of property names that allow for multiple languages. - * @return {string[]} Property names - */ - TemplateDataModel.static.getPropertiesWithLanguage = function () { - var prop, - result = [], - propDefinitions = this.getAllProperties( true ); - - for ( prop in propDefinitions ) { - if ( propDefinitions[prop].allowLanguages ) { - result.push( prop ); - } - } - return result; - }; - - /** - * Split a string into an array and clean/trim the values - * @param {string} str String to split - * @param {string} [delim] Delimeter - * @return {string[]} Clean array - */ - TemplateDataModel.static.splitAndTrimArray = function ( str, delim ) { - var arr = []; - delim = delim || mw.msg( 'comma-separator' ); - - $.each( str.split( delim ), function () { - var trimmed = $.trim( this ); - if ( trimmed ) { - arr.push( trimmed ); - } - } ); - - return arr; - }; - - /** - * This is an adjustment of OO.simpleArrayUnion that ignores - * empty values when inserting into the unified array. - * @param {Array...} arrays Arrays to union - * @return {Array} Union of the arrays - */ - TemplateDataModel.static.arrayUnionWithoutEmpty = function () { - var result = OO.simpleArrayUnion.apply( this, arguments ); - - // Trim and filter empty strings - return result.filter( function ( i ) { - return $.trim( i ); - } ); - }; - - /* Methods */ - - /** - * Load the model from the template data string. If no templatedata tags - * are available, the model will be initialized empty. - * - * After loading the model itself, fetch the parameter list from the template - * source code and update existing parameters in the model. - * - * @param {string} templateDataString Current page wikitext - */ - TemplateDataModel.prototype.loadModel = function ( tdString ) { - var original = {}, - deferred = $.Deferred(); - - // Store existing templatedata into the model - this.setOriginalTemplateDataObject( this.getModelFromString( tdString ) ); - original = this.getOriginalTemplateDataObject(); - - // Initialize model - this.params = {}; - - if ( original ) { - // Get parameter list from the template source code - this.getParametersFromTemplateSource( tdString ).done( $.proxy( function ( params ) { - this.sourceCodeParameters = params; - - // Mark existing parameters in the model - if ( original.params ) { - for ( var param in original.params ) { - this.addParam( param, original.params[param] ); - } - } - this.setTemplateDescription( original.description ); - // Override the param order if it exists in the templatedata string - if ( original.paramOrder && original.paramOrder.length > 0 ) { - this.setTemplateParamOrder( original.paramOrder ); - } - - deferred.resolve(); - }, this ) ); - } else { - // Bad syntax for JSON - deferred.reject(); - } - return deferred.promise(); - }; - - /** - * Go over the importable parameters and check if they are - * included in the parameter model. Return the parameter names - * that are not included yet. - * @return {string[]} Parameters that are not yet included in - * the model - */ - TemplateDataModel.prototype.getMissingParams = function () { - var i, - result = [], - allParamNames = this.getAllParamNames(); - - // Check source code params - for ( i = 0; i < this.sourceCodeParameters.length; i++ ) { - if ( $.inArray( this.sourceCodeParameters[i], allParamNames ) === -1 ) { - result.push( this.sourceCodeParameters[i] ); - } - } - return result; - }; - - /** - * Add imported parameters into the model - * @return {Object} Parameters added. -1 for failure. - */ - TemplateDataModel.prototype.importSourceCodeParameters = function () { - var i, paramKey, - allParamNames = this.getAllParamNames(), - existingArray = [], - importedArray = [], - skippedArray = []; - - // Check existing params - for ( i = 0; i < allParamNames.length; i++ ) { - paramKey = allParamNames[i]; - if ( $.inArray( paramKey, this.sourceCodeParameters ) !== -1 ) { - existingArray.push( paramKey ); - } - } - - // Add sourceCodeParameters to the model - for ( i = 0; i < this.sourceCodeParameters.length; i++ ) { - if ( - $.inArray( this.sourceCodeParameters[i], existingArray ) === -1 && - this.addParam( this.sourceCodeParameters[i] ) - ) { - importedArray.push( this.sourceCodeParameters[i]); - } else { - skippedArray.push( this.sourceCodeParameters[i]); - } - } - - return { - imported: importedArray, - skipped: skippedArray, - existing: existingArray - }; - }; - - /** - * Look for a templatedata json string and convert it into - * the and object, if it exists. - * @param {string} templateDataString Wikitext templatedata string - * @return {Object|null} The parsed json string. Empty if no - * templatedata string was found. Null if the json string - * failed to parse. - */ - TemplateDataModel.prototype.getModelFromString = function ( templateDataString ) { - var parts; - - parts = templateDataString.match( - /([\s\S]*?)<\/templatedata>/i - ); - - // Check if exists - if ( parts && parts[1] && $.trim( parts[1] ).length > 0 ) { - // Parse the json string - try { - return $.parseJSON( $.trim( parts[1] ) ); - } catch ( err ) { - return null; - } - } else { - // Return empty model - return { params: {} }; - } - }; - - /** - * Retrieve all existing language codes in the current templatedata model - * @return {string[]} Language codes in use - */ - TemplateDataModel.prototype.getExistingLanguageCodes = function () { - var param, prop, lang, - result = [], - languageProps = this.constructor.static.getPropertiesWithLanguage(); - - // Take languages from the template description - if ( $.isPlainObject( this.description ) ) { - result.concat( Object.keys( this.description ) ); - } - - // Go over description - if ( $.type( this.description ) ) { - for ( lang in this.description ) { - result.push( lang ); - } - } - - // Go over the parameters - for ( param in this.params ) { - // Go over the properties - for ( prop in this.params[param] ) { - if ( $.inArray( prop, languageProps ) !== -1 ) { - result = this.constructor.static.arrayUnionWithoutEmpty( result, Object.keys( this.params[param][prop] ) ); - } - } - } - - return result; - }; - - /** - * Retrieve parameters from the template code from source in this order: - * - * 1. Check if there's a template in the given 'wikitext' parameter. If not, - * 2. Check if there's a template in the current page. If not, - * 3. Check if the page is a subpage and go up a level to check for template code. If none found, - * 4. Repeat until we are in the root of the template - * 5. Save the name of the page where the template is taken from - * - * Cache the templateCodePromise so we don't have to do this all over again on each - * template code request. - * - * @param {string} [wikitext] Optional. Source of the template. - * @returns {jQuery.Promise} Promise resolving into template parameter array - */ - TemplateDataModel.prototype.getParametersFromTemplateSource = function ( templateDataString ) { - var params = []; - - if ( !this.templateSourceCodePromise ) { - // Check given page text first - if ( templateDataString ) { - params = this.extractParametersFromTemplateCode( templateDataString ); - } - - if ( params.length > 0 ) { - // Cache list of parameters found in template source - this.sourceCodeParameters = params; - // There are parameters found; Resolve. - this.templateSourceCodePromise = $.Deferred().resolve( params ); - } else { - // Try to find the template code - this.templateSourceCodePromise = $.Deferred(); - if ( this.isPageSubLevel() && this.getParentPage() ) { - // Get the content of the parent - this.constructor.static.getApi( this.getParentPage() ) - .done( $.proxy( function ( resp ) { - var pageContent = ''; - - // Verify that we have a sane response from the API. - // This is particularly important for unit tests, since the - // requested page from the API is the Qunit module and has no content - if ( - resp.query.pages[resp.query.pageids[0]].revisions && - resp.query.pages[resp.query.pageids[0]].revisions[0] - ) { - pageContent = resp.query.pages[resp.query.pageids[0]].revisions[0]['*']; - // Get the parameters from the code - this.sourceCodeParameters = this.extractParametersFromTemplateCode( pageContent ); - } - this.templateSourceCodePromise.resolve( this.sourceCodeParameters ); - }, this ) ) - .fail( $.proxy( function () { - // Resolve an empty parameters array - return this.templateSourceCodePromise.resolve( [] ); - }, this ) ); - } else { - // No template found. Resolve to empty array of parameters - this.templateSourceCodePromise.resolve( [] ); - } - } - } - - return this.templateSourceCodePromise; - }; - - /** - * Retrieve template parameters from the template code. - * - * Adapted from https://he.wikipedia.org/wiki/MediaWiki:Gadget-TemplateParamWizard.js - * - * @param {string} templateSource Source of the template. - * @returns {jQuery.Promise} A promise that resolves into an - * array of parameters that appear in the template code - */ - TemplateDataModel.prototype.extractParametersFromTemplateCode = function ( templateCode ) { - var matches, - paramNames = [], - paramExtractor = /{{3,}(.*?)[<|}]/mg; - - while ( ( matches = paramExtractor.exec( templateCode ) ) !== null ) { - if ( $.inArray( matches[1], paramNames ) === -1 ) { - paramNames.push( $.trim( matches[1] ) ); - } - } - - return paramNames; - }; - - /** - * Add parameter to the model - * @param {string} key Parameter key - * @param {Object} [data] Parameter data - * @return {boolean} Parameter was added successfully - */ - TemplateDataModel.prototype.addParam = function ( key, paramData ) { - var prop, name, lang, - existingNames = this.getAllParamNames(), - data = $.extend( true, {}, paramData ), - language = this.getDefaultLanguage(), - propertiesWithLanguage = this.constructor.static.getPropertiesWithLanguage(); - - name = key; - // Check that the parameter is not already in the model - if ( this.params[key] || $.inArray( key, existingNames ) !== -1 ) { - // Change parameter key - key = this.getNewValidParameterKey( key ); - } - - // Initialize - this.params[key] = {}; - - // Store the key - this.params[key].name = name; - - // Mark the parameter if it is in the template source - if ( $.inArray( key, this.sourceCodeParameters ) !== -1 ) { - this.params[key].inSource = true; - } - - // Translate types - if ( this.params[key].type !== undefined ) { - this.params[key].normalizedType = this.constructor.static.translateObsoleteParamTypes( this.params[key].type ); - } - - // Go over the rest of the data - if ( data ) { - for ( prop in data ) { - if ( - $.inArray( prop, propertiesWithLanguage ) !== -1 && - $.isPlainObject( data[prop] ) - ) { - // Add all language properties - for ( lang in data[prop] ) { - this.setParamProperty( key, prop, data[prop], lang ); - } - } else { - this.setParamProperty( key, prop, data[prop], language ); - } - } - } - - // Add to paramOrder - this.addKeyTemplateParamOrder( key ); - - // Trigger the add parameter event - this.emit( 'add-param', key, this.params[key] ); +/** + * Compare two objects or strings + * @param {Object|string} obj1 Base object + * @param {Object|string} obj2 Compared object + * @param {boolean} [allowSubset] Allow the second object to be a + * partial object (or a subset) of the first. + * @return {boolean} Objects have equal values + */ +mw.TemplateData.Model.static.compare = function ( obj1, obj2, allowSubset ) { + if ( allowSubset && obj2 === undefined ) { return true; - }; + } - /** - * Retrieve an array of all used parameter names. Note that parameter - * names can be different than their stored keys. - * @return {string[]} Used parameter names - */ - TemplateDataModel.prototype.getAllParamNames = function () { - var param, - result = []; - for ( param in this.params ) { - result.push( this.params[param].name ); - } - - return result; - }; - - /** - * Set the template description - * @param {string} description New template description - * @param {Object} [language] Description language, if supplied. If not given, - * will default to the wiki language. - * @fires change-description - */ - TemplateDataModel.prototype.setTemplateDescription = function ( desc, language ) { - language = language || this.getDefaultLanguage(); - - if ( !this.constructor.static.compare( this.description[language], desc ) ) { - if ( $.type( desc ) === 'object' ) { - $.extend( this.description, desc ); - this.emit( 'change-description', desc[language], language ); - } else { - this.description[language] = desc; - this.emit( 'change-description', desc, language ); - } - } - }; - - /** - * Get the template description. - * @param {string} [language] Optional language key - * @return {string|Object} The template description. If it is set - * as multilanguage object and no language is set, the whole object - * will be returned. - */ - TemplateDataModel.prototype.getTemplateDescription = function ( language ) { - language = language || this.getDefaultLanguage(); - return this.description[language]; - }; - - /** - * Get a specific parameter's description - * @param {string} paramKey Parameter key - * @param {string} [language] Optional language key - * @return {string} Parameter description in given language. - */ - TemplateDataModel.prototype.getParamDescription = function ( paramKey, language ) { - language = language || this.getDefaultLanguage(); - if ( this.params[paramKey] && this.params[paramKey].description ) { - // Return description in this language or fall back - return this.params[paramKey].description[language] || ''; - } - return ''; - }; - - /** - * Get the current wiki language code. Defaults on 'en'. - * @return {string} Wiki language - */ - TemplateDataModel.prototype.getDefaultLanguage = function () { - return mw.config.get( 'wgContentLanguage' ) || 'en'; - }; - - /** - * Set template param order array. - * @param {string[]} orderArray Parameter key array in order - * @fires change-paramOrder - */ - TemplateDataModel.prototype.setTemplateParamOrder = function ( orderArray ) { - orderArray = orderArray || []; - // TODO: Make the compare method verify order of array? - // Copy the array - this.paramOrder = orderArray.slice(); - this.emit( 'change-paramOrder', orderArray ); - }; - - /** - * Add a key to the end of the paramOrder - * @param {string} key New key the add into the paramOrder - * @fires add-paramOrder - */ - TemplateDataModel.prototype.addKeyTemplateParamOrder = function ( key ) { - if ( $.inArray( key, this.paramOrder ) === -1 ) { - this.paramOrder.push( key ); - this.emit( 'add-paramOrder', key ); - } - }; - - TemplateDataModel.prototype.reorderParamOrderKey = function ( key, newIndex ) { - var keyIndex = this.paramOrder.indexOf( key ); - // Move the parameter - this.paramOrder.splice( - newIndex, - 0, - this.paramOrder.splice( keyIndex, 1 )[0] - ); - - this.paramOrderChanged = true; - - // Emit event - this.emit( 'change-paramOrder', this.paramOrder ); - }; - - /** - * Add a key to the end of the paramOrder - * @param {string} key New key the add into the paramOrder - */ - TemplateDataModel.prototype.removeKeyTemplateParamOrder = function ( key ) { - var keyPos = $.inArray( key, this.paramOrder ); - if ( keyPos > -1 ) { - this.paramOrder.splice( keyPos, 1 ); - this.emit( 'change-paramOrder', this.paramOrder ); - } - }; - - /** - * Retrieve the template paramOrder array - * @return {string[]} orderArray Parameter keys in order - */ - TemplateDataModel.prototype.getTemplateParamOrder = function () { - return this.paramOrder; - }; - - /** - * Set a specific parameter's property - * @param {string} paramKey Parameter key - * @param {string} prop Property name - * @param {Mixed...} value Property value - * @param {string} [language] Value language - * @returns {boolean} Operation was successful - * @fires change-property - */ - TemplateDataModel.prototype.setParamProperty = function ( paramKey, prop, value, language ) { - var propertiesWithLanguage = this.constructor.static.getPropertiesWithLanguage(), - allProps = this.constructor.static.getAllProperties( true ); - - language = language || this.getDefaultLanguage(); - - if ( !allProps[prop] ) { - // The property isn't supported yet - return false; - } - - if ( allProps[prop].type === 'array' && $.type( value ) === 'string' ) { - value = this.constructor.static.splitAndTrimArray( value, allProps[prop].delimiter ); - } - - // Check if the property is split by language code - if ( $.inArray( prop, propertiesWithLanguage ) !== -1 ) { - // Initialize property if necessary - if ( !$.isPlainObject( this.params[paramKey][prop] ) ) { - this.params[paramKey][prop] = {}; - } - value = $.isPlainObject( value ) ? value[language] : value; - // Compare with language - if ( !this.constructor.static.compare( this.params[paramKey][prop][language], value ) ) { - this.params[paramKey][prop][language] = value; - this.emit( 'change-property', paramKey, prop, value, language ); - return true; - } - } else { - // Compare without language - if ( !this.constructor.static.compare( this.params[paramKey][prop], value ) ) { - this.params[paramKey][prop] = value; - this.emit( 'change-property', paramKey, prop, value, language ); - return true; - } - } + // Make sure the objects are of the same type + if ( $.type( obj1 ) !== $.type( obj2 ) ) { return false; - }; + } - /** - * Mark a parameter for deletion. - * Don't actually delete the parameter so we can make sure it is removed - * from the final output. - * @param {string} paramKey Parameter key - * @fires delete-param - */ - TemplateDataModel.prototype.deleteParam = function ( paramKey ) { - this.params[paramKey].deleted = true; - // Remove from paramOrder - this.removeKeyTemplateParamOrder( paramKey ); - this.emit( 'delete-param', paramKey ); - }; + // Comparing objects or arrays + if ( typeof obj1 === 'object' ) { + return OO.compare( obj2, obj1, allowSubset ); + } - /** - * Restore parameter by unmarking it as deleted. - * @param {string} paramKey Parameter key - * @fires add-param - */ - TemplateDataModel.prototype.restoreParam = function ( paramKey ) { - if ( this.params[paramKey] ) { - this.params[paramKey].deleted = false; - // Add back to paramOrder - this.addKeyTemplateParamOrder( paramKey ); - this.emit( 'add-param', paramKey, this.params[paramKey] ); + // Everything else (primitive types, functions, etc) + return obj1 === obj2; +}; + +/** + * Translate obsolete parameter types into the new types + * @param {string} paramType Given type + * @return {string} Normalized non-obsolete type + */ +mw.TemplateData.Model.static.translateObsoleteParamTypes = function ( paramType ) { + switch ( paramType ) { + case 'string/wiki-page-name': + return 'wiki-page-name'; + case 'string/wiki-file-name': + return 'wiki-file-name'; + case 'string/wiki-user-name': + return 'wiki-user-name'; + default: + return paramType; + } +}; + +/** + * Retrieve information about all legal properties for a parameter. + * @param {boolean} getFullData Retrieve full information about each + * parameter. If false, the method will return an array of property + * names only. + * @return {Object|string[]} Legal property names with or without their + * definition data + */ +mw.TemplateData.Model.static.getAllProperties = function ( getFullData ) { + var properties = { + name: { + type: 'string', + // Validation regex + restrict: /[\|=]|}}/ + }, + aliases: { + type: 'array', + delimiter: mw.msg( 'comma-separator' ) + }, + label: { + type: 'string', + allowLanguages: true + }, + description: { + type: 'string', + allowLanguages: true + }, + type: { + type: 'select', + children: [ + 'boolean', + 'content', + 'wiki-file-name', + 'line', + 'number', + 'date', + 'wiki-page-name', + 'string', + 'unbalanced-wikitext', + 'undefined', + 'wiki-user-name' + ], + 'default': 'undefined' + }, + 'default': { + type: 'string', + multiline: true + }, + autovalue: { + type: 'string' + }, + deprecated: { + type: 'boolean' + }, + required: { + type: 'boolean' + }, + suggested: { + type: 'boolean' } }; - /** - * Delete all data attached to a parameter - * @param {string} paramKey Parameter key - */ - TemplateDataModel.prototype.emptyParamData = function ( paramKey ) { - if ( this.params[paramKey] ) { - // Delete all data and readd the parameter - delete this.params[paramKey]; - this.addParam( paramKey ); - // Mark this parameter as intentionally emptied - this.params[paramKey].emptied = true; + if ( !getFullData ) { + return Object.keys( properties ); + } else { + return properties; + } +}; + +/** + * Retrieve the list of property names that allow for multiple languages. + * @return {string[]} Property names + */ +mw.TemplateData.Model.static.getPropertiesWithLanguage = function () { + var prop, + result = [], + propDefinitions = this.getAllProperties( true ); + + for ( prop in propDefinitions ) { + if ( propDefinitions[prop].allowLanguages ) { + result.push( prop ); } - }; + } + return result; +}; - /** - * Get a parameter property. - * @param {string} paramKey Parameter key - * @param {string} prop Parameter property - * @return {Mixed...|null} Property value if it exists. Returns null if the - * parameter key itself doesn't exist. - */ - TemplateDataModel.prototype.getParamProperty = function ( paramKey, prop ) { - if ( this.params[paramKey] ) { - return this.params[paramKey][prop]; +/** + * Split a string into an array and clean/trim the values + * @param {string} str String to split + * @param {string} [delim] Delimeter + * @return {string[]} Clean array + */ +mw.TemplateData.Model.static.splitAndTrimArray = function ( str, delim ) { + var arr = []; + delim = delim || mw.msg( 'comma-separator' ); + + $.each( str.split( delim ), function () { + var trimmed = $.trim( this ); + if ( trimmed ) { + arr.push( trimmed ); } - return null; - }; + } ); - /** - * Retrieve a specific parameter data - * @param {string} key Parameter key - * @return {Object} Parameter data - */ - TemplateDataModel.prototype.getParamData = function ( key ) { - return this.params[key]; - }; + return arr; +}; - /** - * Return the complete object of all parameters. - * @return {Object} All parameters and their data - */ - TemplateDataModel.prototype.getParams = function () { - return this.params; - }; +/** + * This is an adjustment of OO.simpleArrayUnion that ignores + * empty values when inserting into the unified array. + * @param {Array...} arrays Arrays to union + * @return {Array} Union of the arrays + */ +mw.TemplateData.Model.static.arrayUnionWithoutEmpty = function () { + var result = OO.simpleArrayUnion.apply( this, arguments ); - TemplateDataModel.prototype.isParamDeleted = function ( key ) { - return this.params[key].deleted === true; - }; + // Trim and filter empty strings + return result.filter( function ( i ) { + return $.trim( i ); + } ); +}; - TemplateDataModel.prototype.isParamExists = function ( key ) { - return $.inArray( key, Object.keys( this.params ) ) > -1; - }; +/* Methods */ - /** - * Set the original templatedata object - * @param {Object} templatedataObj TemplateData object - */ - TemplateDataModel.prototype.setOriginalTemplateDataObject = function ( templatedataObj ) { - this.originalTemplateDataObject = $.extend( true, {}, templatedataObj ); - }; +/** + * Load the model from the template data string. If no templatedata tags + * are available, the model will be initialized empty. + * + * After loading the model itself, fetch the parameter list from the template + * source code and update existing parameters in the model. + * + * @param {string} templateDataString Current page wikitext + */ +mw.TemplateData.Model.prototype.loadModel = function ( tdString ) { + var original = {}, + deferred = $.Deferred(); - /** - * Set is page sublevel - * @param {Boolean} isSubLevel Page is sublevel - */ - TemplateDataModel.prototype.setPageSubLevel = function ( isSubLevel ) { - this.subLevel = isSubLevel; - }; + // Store existing templatedata into the model + this.setOriginalTemplateDataObject( this.getModelFromString( tdString ) ); + original = this.getOriginalTemplateDataObject(); - /** - * Get full page name - * @param {string} pageName Page name - */ - TemplateDataModel.prototype.setFullPageName = function ( pageName ) { - this.fullPageName = pageName; - }; + // Initialize model + this.params = {}; - /** - * Set parent page - * @param {string} Parent page - */ - TemplateDataModel.prototype.setParentPage = function ( parent ) { - this.parentPage = parent; - }; + if ( original ) { + // Get parameter list from the template source code + this.getParametersFromTemplateSource( tdString ).done( $.proxy( function ( params ) { + this.sourceCodeParameters = params; - /** - * Get is page sublevel - * @return {boolean} Page is sublevel - */ - TemplateDataModel.prototype.isPageSubLevel = function () { - return this.subLevel; - }; - - /** - * Get page full name - * @return {string} Page full name - */ - TemplateDataModel.prototype.getFullPageName = function () { - return this.fullPageName; - }; - - /** - * Get parent page - * @return {string} Parent page - */ - TemplateDataModel.prototype.getParentPage = function () { - return this.parentPage; - }; - - /** - * Get original templatedata object - * @return {Object} Templatedata object - */ - TemplateDataModel.prototype.getOriginalTemplateDataObject = function () { - return this.originalTemplateDataObject; - }; - - /** - * Process the current model and output it as a complete templatedata string - * @return {string} Templatedata String - */ - TemplateDataModel.prototype.outputTemplateDataString = function () { - var param, paramKey, key, prop, oldKey, name, compareOrig, normalizedValue, - allProps = this.constructor.static.getAllProperties( true ), - original = this.getOriginalTemplateDataObject(), - result = $.extend( true, {}, this.getOriginalTemplateDataObject() ), - defaultLang = this.getDefaultLanguage(); - - // Template description - if ( this.description[defaultLang] !== undefined ) { - normalizedValue = this.propRemoveUnusedLanguages( this.description ); - if ( this.isOutputInLanguageObject( result.description, normalizedValue ) ) { - result.description = normalizedValue; - } else { - // Store only one language as a string - result.description = normalizedValue[defaultLang]; - } - } else { - // Delete description - delete result.description; - } - - // Param order - if ( original.paramOrder || this.paramOrderChanged ) { - result.paramOrder = this.paramOrder; - } else { - delete result.paramOrder; - } - - // Attach sets as-is for now - // TODO: Work properly with sets - if ( original.sets ) { - result.sets = original.sets; - } - - // Go over parameters in data - for ( paramKey in this.params ) { - key = paramKey; - if ( this.params[key].deleted ) { - delete result.params[key]; - continue; - } - - // If the user intentionally empties a parameter, delete it from - // the result and treat it as a new parameter - if ( this.params[key].emptied ) { - delete result.params[key]; - } - - // Check if name was changed and change the key accordingly - name = this.params[key].name; - oldKey = key; - if ( key !== this.params[key].name ) { - key = this.params[key].name; - // See if the parameters already has something with this new key - if ( this.params[key] ) { - // Change the key to be something else - key += this.getNewValidParameterKey( key ); - } - // Copy param details to new name in the model - this.params[key] = this.params[oldKey]; - // Update the references to the key and param data - param = result.params[name]; - // Delete the old param in both the result and model param - delete result.params[oldKey]; - delete this.params[oldKey]; - } - - // Notice for clarity: - // Whether the parameter name was changed or not the following - // consistency with object keys will be observed: - // * oldKey: original will use oldKey (for comparison to the old value) - // * key: this.params will use key (for storing without conflict) - // * name: result will use name (for valid output) - - // Check if param is new - if ( !result.params[name] ) { - // New param. Initialize it - result.params[name] = {}; - } - - // Go over all properties - for ( prop in allProps ) { - switch ( prop ) { - case 'name': - continue; - case 'type': - // Only include type if the original included type - // or if the current type is not undefined - if ( - original.type !== undefined || - this.params[key].type !== 'undefined' - ) { - result.params[name][prop] = this.params[key].type; - } - break; - case 'deprecated': - case 'required': - case 'suggested': - if ( !this.params[key][prop] ) { - // Only add a literal false value if there was a false - // value before - if ( original.params[oldKey] && original.params[oldKey][prop] === false ) { - result.params[name][prop] = false; - } - } else { - result.params[name][prop] = this.params[key][prop]; - } - break; - case 'aliases': - // Only update the aliases in if the new templatedata has an - // aliases array that isn't empty - if ( - $.type( this.params[key][prop] ) === 'array' && - this.params[key][prop].length > 0 - ) { - result.params[name][prop] = this.params[key][prop]; - } else { - // If the new aliases array is empty, delete it from the original - delete result.params[name][prop]; - } - break; - default: - // Check if there's a value in the model - if ( this.params[key][prop] !== undefined ) { - if ( allProps[prop].allowLanguages ) { - normalizedValue = this.propRemoveUnusedLanguages( this.params[key][prop] ); - // Check if this should be displayed with language object or directly as string - compareOrig = original.params[oldKey] ? original.params[oldKey][prop] : {}; - if ( this.isOutputInLanguageObject( compareOrig, normalizedValue ) ) { - result.params[name][prop] = normalizedValue; - } else { - // Store only one language as a string - result.params[name][prop] = normalizedValue[defaultLang]; - } - } else { - result.params[name][prop] = this.params[key][prop]; - } - } - break; + // Mark existing parameters in the model + if ( original.params ) { + for ( var param in original.params ) { + this.addParam( param, original.params[param] ); } } - } - return JSON.stringify( result, null, '\t' ); - }; - - /** - * Check the key if it already exists in the parameter list. If it does, - * find a new key that doesn't, and return it. - * @param {string} key New parameter key - * @return {string} Valid new parameter key - */ - TemplateDataModel.prototype.getNewValidParameterKey = function ( key ) { - var allParamNames = this.getAllParamNames(); - if ( this.params[key] || $.inArray( key, allParamNames ) !== -1 ) { - // Change the key to be something else - key += this.paramIdentifierCounter; - this.paramIdentifierCounter++; - this.getNewValidParameterKey( key ); - } else { - return key; - } - }; - /** - * Go over a language property and remove empty language key values - * @return {Object} Property data with only used language keys - */ - TemplateDataModel.prototype.propRemoveUnusedLanguages = function ( propData ) { - var key, - result = {}; - if ( $.isPlainObject( propData ) ) { - for ( key in propData ) { - if ( propData[key] ) { - result[key] = propData[key]; - } + this.setTemplateDescription( original.description ); + // Override the param order if it exists in the templatedata string + if ( original.paramOrder && original.paramOrder.length > 0 ) { + this.setTemplateParamOrder( original.paramOrder ); } - } - return result; - }; - /** - * Check whether the output of the current parameter property should be - * outputted in full language mode (object) or a simple string. - * @param {string} paramKey Parameter key - * @param {string} prop Param property - * @return {boolean} Output should be a full language object - */ - TemplateDataModel.prototype.isOutputInLanguageObject = function ( originalPropValue, newPropValue ) { + deferred.resolve(); + }, this ) ); + } else { + // Bad syntax for JSON + deferred.reject(); + } + return deferred.promise(); +}; + +/** + * Go over the importable parameters and check if they are + * included in the parameter model. Return the parameter names + * that are not included yet. + * @return {string[]} Parameters that are not yet included in + * the model + */ +mw.TemplateData.Model.prototype.getMissingParams = function () { + var i, + result = [], + allParamNames = this.getAllParamNames(); + + // Check source code params + for ( i = 0; i < this.sourceCodeParameters.length; i++ ) { + if ( $.inArray( this.sourceCodeParameters[i], allParamNames ) === -1 ) { + result.push( this.sourceCodeParameters[i] ); + } + } + return result; +}; + +/** + * Add imported parameters into the model + * @return {Object} Parameters added. -1 for failure. + */ +mw.TemplateData.Model.prototype.importSourceCodeParameters = function () { + var i, paramKey, + allParamNames = this.getAllParamNames(), + existingArray = [], + importedArray = [], + skippedArray = []; + + // Check existing params + for ( i = 0; i < allParamNames.length; i++ ) { + paramKey = allParamNames[i]; + if ( $.inArray( paramKey, this.sourceCodeParameters ) !== -1 ) { + existingArray.push( paramKey ); + } + } + + // Add sourceCodeParameters to the model + for ( i = 0; i < this.sourceCodeParameters.length; i++ ) { if ( - ( - // The original was already split to languages - $.type( originalPropValue ) === 'object' && - // Original was not an empty object - !$.isEmptyObject( originalPropValue ) - ) || - ( - // The new value is split to languages - $.type( newPropValue ) === 'object' && - // New object is not empty - !$.isEmptyObject( newPropValue ) && - ( - // The new value doesn't have the default language - newPropValue[this.getDefaultLanguage()] === undefined || - // There is more than just one language in the new property - Object.keys( newPropValue ).length > 1 - ) - ) + $.inArray( this.sourceCodeParameters[i], existingArray ) === -1 && + this.addParam( this.sourceCodeParameters[i] ) ) { + importedArray.push( this.sourceCodeParameters[i]); + } else { + skippedArray.push( this.sourceCodeParameters[i]); + } + } + + return { + imported: importedArray, + skipped: skippedArray, + existing: existingArray + }; +}; + +/** + * Look for a templatedata json string and convert it into + * the and object, if it exists. + * @param {string} templateDataString Wikitext templatedata string + * @return {Object|null} The parsed json string. Empty if no + * templatedata string was found. Null if the json string + * failed to parse. + */ +mw.TemplateData.Model.prototype.getModelFromString = function ( templateDataString ) { + var parts; + + parts = templateDataString.match( + /([\s\S]*?)<\/templatedata>/i + ); + + // Check if exists + if ( parts && parts[1] && $.trim( parts[1] ).length > 0 ) { + // Parse the json string + try { + return $.parseJSON( $.trim( parts[1] ) ); + } catch ( err ) { + return null; + } + } else { + // Return empty model + return { params: {} }; + } +}; + +/** + * Retrieve all existing language codes in the current templatedata model + * @return {string[]} Language codes in use + */ +mw.TemplateData.Model.prototype.getExistingLanguageCodes = function () { + var param, prop, lang, + result = [], + languageProps = this.constructor.static.getPropertiesWithLanguage(); + + // Take languages from the template description + if ( $.isPlainObject( this.description ) ) { + result.concat( Object.keys( this.description ) ); + } + + // Go over description + if ( $.type( this.description ) ) { + for ( lang in this.description ) { + result.push( lang ); + } + } + + // Go over the parameters + for ( param in this.params ) { + // Go over the properties + for ( prop in this.params[param] ) { + if ( $.inArray( prop, languageProps ) !== -1 ) { + result = this.constructor.static.arrayUnionWithoutEmpty( result, Object.keys( this.params[param][prop] ) ); + } + } + } + + return result; +}; + +/** + * Retrieve parameters from the template code from source in this order: + * + * 1. Check if there's a template in the given 'wikitext' parameter. If not, + * 2. Check if there's a template in the current page. If not, + * 3. Check if the page is a subpage and go up a level to check for template code. If none found, + * 4. Repeat until we are in the root of the template + * 5. Save the name of the page where the template is taken from + * + * Cache the templateCodePromise so we don't have to do this all over again on each + * template code request. + * + * @param {string} [wikitext] Optional. Source of the template. + * @returns {jQuery.Promise} Promise resolving into template parameter array + */ +mw.TemplateData.Model.prototype.getParametersFromTemplateSource = function ( templateDataString ) { + var params = []; + + if ( !this.templateSourceCodePromise ) { + // Check given page text first + if ( templateDataString ) { + params = this.extractParametersFromTemplateCode( templateDataString ); + } + + if ( params.length > 0 ) { + // Cache list of parameters found in template source + this.sourceCodeParameters = params; + // There are parameters found; Resolve. + this.templateSourceCodePromise = $.Deferred().resolve( params ); + } else { + // Try to find the template code + this.templateSourceCodePromise = $.Deferred(); + if ( this.isPageSubLevel() && this.getParentPage() ) { + // Get the content of the parent + this.constructor.static.getApi( this.getParentPage() ) + .done( $.proxy( function ( resp ) { + var pageContent = ''; + + // Verify that we have a sane response from the API. + // This is particularly important for unit tests, since the + // requested page from the API is the Qunit module and has no content + if ( + resp.query.pages[resp.query.pageids[0]].revisions && + resp.query.pages[resp.query.pageids[0]].revisions[0] + ) { + pageContent = resp.query.pages[resp.query.pageids[0]].revisions[0]['*']; + // Get the parameters from the code + this.sourceCodeParameters = this.extractParametersFromTemplateCode( pageContent ); + } + this.templateSourceCodePromise.resolve( this.sourceCodeParameters ); + }, this ) ) + .fail( $.proxy( function () { + // Resolve an empty parameters array + return this.templateSourceCodePromise.resolve( [] ); + }, this ) ); + } else { + // No template found. Resolve to empty array of parameters + this.templateSourceCodePromise.resolve( [] ); + } + } + } + + return this.templateSourceCodePromise; +}; + +/** + * Retrieve template parameters from the template code. + * + * Adapted from https://he.wikipedia.org/wiki/MediaWiki:Gadget-TemplateParamWizard.js + * + * @param {string} templateSource Source of the template. + * @returns {jQuery.Promise} A promise that resolves into an + * array of parameters that appear in the template code + */ +mw.TemplateData.Model.prototype.extractParametersFromTemplateCode = function ( templateCode ) { + var matches, + paramNames = [], + paramExtractor = /{{3,}(.*?)[<|}]/mg; + + while ( ( matches = paramExtractor.exec( templateCode ) ) !== null ) { + if ( $.inArray( matches[1], paramNames ) === -1 ) { + paramNames.push( $.trim( matches[1] ) ); + } + } + + return paramNames; +}; + +/** + * Add parameter to the model + * @param {string} key Parameter key + * @param {Object} [data] Parameter data + * @return {boolean} Parameter was added successfully + */ +mw.TemplateData.Model.prototype.addParam = function ( key, paramData ) { + var prop, name, lang, + existingNames = this.getAllParamNames(), + data = $.extend( true, {}, paramData ), + language = this.getDefaultLanguage(), + propertiesWithLanguage = this.constructor.static.getPropertiesWithLanguage(); + + name = key; + // Check that the parameter is not already in the model + if ( this.params[key] || $.inArray( key, existingNames ) !== -1 ) { + // Change parameter key + key = this.getNewValidParameterKey( key ); + } + + // Initialize + this.params[key] = {}; + + // Store the key + this.params[key].name = name; + + // Mark the parameter if it is in the template source + if ( $.inArray( key, this.sourceCodeParameters ) !== -1 ) { + this.params[key].inSource = true; + } + + // Translate types + if ( this.params[key].type !== undefined ) { + this.params[key].normalizedType = this.constructor.static.translateObsoleteParamTypes( this.params[key].type ); + } + + // Go over the rest of the data + if ( data ) { + for ( prop in data ) { + if ( + $.inArray( prop, propertiesWithLanguage ) !== -1 && + $.isPlainObject( data[prop] ) + ) { + // Add all language properties + for ( lang in data[prop] ) { + this.setParamProperty( key, prop, data[prop], lang ); + } + } else { + this.setParamProperty( key, prop, data[prop], language ); + } + } + } + + // Add to paramOrder + this.addKeyTemplateParamOrder( key ); + + // Trigger the add parameter event + this.emit( 'add-param', key, this.params[key] ); + return true; +}; + +/** + * Retrieve an array of all used parameter names. Note that parameter + * names can be different than their stored keys. + * @return {string[]} Used parameter names + */ +mw.TemplateData.Model.prototype.getAllParamNames = function () { + var param, + result = []; + for ( param in this.params ) { + result.push( this.params[param].name ); + } + + return result; +}; + +/** + * Set the template description + * @param {string} description New template description + * @param {Object} [language] Description language, if supplied. If not given, + * will default to the wiki language. + * @fires change-description + */ +mw.TemplateData.Model.prototype.setTemplateDescription = function ( desc, language ) { + language = language || this.getDefaultLanguage(); + + if ( !this.constructor.static.compare( this.description[language], desc ) ) { + if ( $.type( desc ) === 'object' ) { + $.extend( this.description, desc ); + this.emit( 'change-description', desc[language], language ); + } else { + this.description[language] = desc; + this.emit( 'change-description', desc, language ); + } + } +}; + +/** + * Get the template description. + * @param {string} [language] Optional language key + * @return {string|Object} The template description. If it is set + * as multilanguage object and no language is set, the whole object + * will be returned. + */ +mw.TemplateData.Model.prototype.getTemplateDescription = function ( language ) { + language = language || this.getDefaultLanguage(); + return this.description[language]; +}; + +/** + * Get a specific parameter's description + * @param {string} paramKey Parameter key + * @param {string} [language] Optional language key + * @return {string} Parameter description in given language. + */ +mw.TemplateData.Model.prototype.getParamDescription = function ( paramKey, language ) { + language = language || this.getDefaultLanguage(); + if ( this.params[paramKey] && this.params[paramKey].description ) { + // Return description in this language or fall back + return this.params[paramKey].description[language] || ''; + } + return ''; +}; + +/** + * Get the current wiki language code. Defaults on 'en'. + * @return {string} Wiki language + */ +mw.TemplateData.Model.prototype.getDefaultLanguage = function () { + return mw.config.get( 'wgContentLanguage' ) || 'en'; +}; + +/** + * Set template param order array. + * @param {string[]} orderArray Parameter key array in order + * @fires change-paramOrder + */ +mw.TemplateData.Model.prototype.setTemplateParamOrder = function ( orderArray ) { + orderArray = orderArray || []; + // TODO: Make the compare method verify order of array? + // Copy the array + this.paramOrder = orderArray.slice(); + this.emit( 'change-paramOrder', orderArray ); +}; + +/** + * Add a key to the end of the paramOrder + * @param {string} key New key the add into the paramOrder + * @fires add-paramOrder + */ +mw.TemplateData.Model.prototype.addKeyTemplateParamOrder = function ( key ) { + if ( $.inArray( key, this.paramOrder ) === -1 ) { + this.paramOrder.push( key ); + this.emit( 'add-paramOrder', key ); + } +}; + +mw.TemplateData.Model.prototype.reorderParamOrderKey = function ( key, newIndex ) { + var keyIndex = this.paramOrder.indexOf( key ); + // Move the parameter + this.paramOrder.splice( + newIndex, + 0, + this.paramOrder.splice( keyIndex, 1 )[0] + ); + + this.paramOrderChanged = true; + + // Emit event + this.emit( 'change-paramOrder', this.paramOrder ); +}; + +/** + * Add a key to the end of the paramOrder + * @param {string} key New key the add into the paramOrder + */ +mw.TemplateData.Model.prototype.removeKeyTemplateParamOrder = function ( key ) { + var keyPos = $.inArray( key, this.paramOrder ); + if ( keyPos > -1 ) { + this.paramOrder.splice( keyPos, 1 ); + this.emit( 'change-paramOrder', this.paramOrder ); + } +}; + +/** + * Retrieve the template paramOrder array + * @return {string[]} orderArray Parameter keys in order + */ +mw.TemplateData.Model.prototype.getTemplateParamOrder = function () { + return this.paramOrder; +}; + +/** + * Set a specific parameter's property + * @param {string} paramKey Parameter key + * @param {string} prop Property name + * @param {Mixed...} value Property value + * @param {string} [language] Value language + * @returns {boolean} Operation was successful + * @fires change-property + */ +mw.TemplateData.Model.prototype.setParamProperty = function ( paramKey, prop, value, language ) { + var propertiesWithLanguage = this.constructor.static.getPropertiesWithLanguage(), + allProps = this.constructor.static.getAllProperties( true ); + + language = language || this.getDefaultLanguage(); + + if ( !allProps[prop] ) { + // The property isn't supported yet + return false; + } + + if ( allProps[prop].type === 'array' && $.type( value ) === 'string' ) { + value = this.constructor.static.splitAndTrimArray( value, allProps[prop].delimiter ); + } + + // Check if the property is split by language code + if ( $.inArray( prop, propertiesWithLanguage ) !== -1 ) { + // Initialize property if necessary + if ( !$.isPlainObject( this.params[paramKey][prop] ) ) { + this.params[paramKey][prop] = {}; + } + value = $.isPlainObject( value ) ? value[language] : value; + // Compare with language + if ( !this.constructor.static.compare( this.params[paramKey][prop][language], value ) ) { + this.params[paramKey][prop][language] = value; + this.emit( 'change-property', paramKey, prop, value, language ); return true; } - return false; - }; + } else { + // Compare without language + if ( !this.constructor.static.compare( this.params[paramKey][prop], value ) ) { + this.params[paramKey][prop] = value; + this.emit( 'change-property', paramKey, prop, value, language ); + return true; + } + } + return false; +}; -}( jQuery ) ); +/** + * Mark a parameter for deletion. + * Don't actually delete the parameter so we can make sure it is removed + * from the final output. + * @param {string} paramKey Parameter key + * @fires delete-param + */ +mw.TemplateData.Model.prototype.deleteParam = function ( paramKey ) { + this.params[paramKey].deleted = true; + // Remove from paramOrder + this.removeKeyTemplateParamOrder( paramKey ); + this.emit( 'delete-param', paramKey ); +}; + +/** + * Restore parameter by unmarking it as deleted. + * @param {string} paramKey Parameter key + * @fires add-param + */ +mw.TemplateData.Model.prototype.restoreParam = function ( paramKey ) { + if ( this.params[paramKey] ) { + this.params[paramKey].deleted = false; + // Add back to paramOrder + this.addKeyTemplateParamOrder( paramKey ); + this.emit( 'add-param', paramKey, this.params[paramKey] ); + } +}; + +/** + * Delete all data attached to a parameter + * @param {string} paramKey Parameter key + */ +mw.TemplateData.Model.prototype.emptyParamData = function ( paramKey ) { + if ( this.params[paramKey] ) { + // Delete all data and readd the parameter + delete this.params[paramKey]; + this.addParam( paramKey ); + // Mark this parameter as intentionally emptied + this.params[paramKey].emptied = true; + } +}; + +/** + * Get a parameter property. + * @param {string} paramKey Parameter key + * @param {string} prop Parameter property + * @return {Mixed...|null} Property value if it exists. Returns null if the + * parameter key itself doesn't exist. + */ +mw.TemplateData.Model.prototype.getParamProperty = function ( paramKey, prop ) { + if ( this.params[paramKey] ) { + return this.params[paramKey][prop]; + } + return null; +}; + +/** + * Retrieve a specific parameter data + * @param {string} key Parameter key + * @return {Object} Parameter data + */ +mw.TemplateData.Model.prototype.getParamData = function ( key ) { + return this.params[key]; +}; + +/** + * Return the complete object of all parameters. + * @return {Object} All parameters and their data + */ +mw.TemplateData.Model.prototype.getParams = function () { + return this.params; +}; + +mw.TemplateData.Model.prototype.isParamDeleted = function ( key ) { + return this.params[key].deleted === true; +}; + +mw.TemplateData.Model.prototype.isParamExists = function ( key ) { + return $.inArray( key, Object.keys( this.params ) ) > -1; +}; + +/** + * Set the original templatedata object + * @param {Object} templatedataObj TemplateData object + */ +mw.TemplateData.Model.prototype.setOriginalTemplateDataObject = function ( templatedataObj ) { + this.originalTemplateDataObject = $.extend( true, {}, templatedataObj ); +}; + +/** + * Set is page sublevel + * @param {Boolean} isSubLevel Page is sublevel + */ +mw.TemplateData.Model.prototype.setPageSubLevel = function ( isSubLevel ) { + this.subLevel = isSubLevel; +}; + +/** + * Get full page name + * @param {string} pageName Page name + */ +mw.TemplateData.Model.prototype.setFullPageName = function ( pageName ) { + this.fullPageName = pageName; +}; + +/** + * Set parent page + * @param {string} Parent page + */ +mw.TemplateData.Model.prototype.setParentPage = function ( parent ) { + this.parentPage = parent; +}; + +/** + * Get is page sublevel + * @return {boolean} Page is sublevel + */ +mw.TemplateData.Model.prototype.isPageSubLevel = function () { + return this.subLevel; +}; + +/** + * Get page full name + * @return {string} Page full name + */ +mw.TemplateData.Model.prototype.getFullPageName = function () { + return this.fullPageName; +}; + +/** + * Get parent page + * @return {string} Parent page + */ +mw.TemplateData.Model.prototype.getParentPage = function () { + return this.parentPage; +}; + +/** + * Get original templatedata object + * @return {Object} Templatedata object + */ +mw.TemplateData.Model.prototype.getOriginalTemplateDataObject = function () { + return this.originalTemplateDataObject; +}; + +/** + * Process the current model and output it as a complete templatedata string + * @return {string} Templatedata String + */ +mw.TemplateData.Model.prototype.outputTemplateDataString = function () { + var param, paramKey, key, prop, oldKey, name, compareOrig, normalizedValue, + allProps = this.constructor.static.getAllProperties( true ), + original = this.getOriginalTemplateDataObject(), + result = $.extend( true, {}, this.getOriginalTemplateDataObject() ), + defaultLang = this.getDefaultLanguage(); + + // Template description + if ( this.description[defaultLang] !== undefined ) { + normalizedValue = this.propRemoveUnusedLanguages( this.description ); + if ( this.isOutputInLanguageObject( result.description, normalizedValue ) ) { + result.description = normalizedValue; + } else { + // Store only one language as a string + result.description = normalizedValue[defaultLang]; + } + } else { + // Delete description + delete result.description; + } + + // Param order + if ( original.paramOrder || this.paramOrderChanged ) { + result.paramOrder = this.paramOrder; + } else { + delete result.paramOrder; + } + + // Attach sets as-is for now + // TODO: Work properly with sets + if ( original.sets ) { + result.sets = original.sets; + } + + // Go over parameters in data + for ( paramKey in this.params ) { + key = paramKey; + if ( this.params[key].deleted ) { + delete result.params[key]; + continue; + } + + // If the user intentionally empties a parameter, delete it from + // the result and treat it as a new parameter + if ( this.params[key].emptied ) { + delete result.params[key]; + } + + // Check if name was changed and change the key accordingly + name = this.params[key].name; + oldKey = key; + if ( key !== this.params[key].name ) { + key = this.params[key].name; + // See if the parameters already has something with this new key + if ( this.params[key] ) { + // Change the key to be something else + key += this.getNewValidParameterKey( key ); + } + // Copy param details to new name in the model + this.params[key] = this.params[oldKey]; + // Update the references to the key and param data + param = result.params[name]; + // Delete the old param in both the result and model param + delete result.params[oldKey]; + delete this.params[oldKey]; + } + + // Notice for clarity: + // Whether the parameter name was changed or not the following + // consistency with object keys will be observed: + // * oldKey: original will use oldKey (for comparison to the old value) + // * key: this.params will use key (for storing without conflict) + // * name: result will use name (for valid output) + + // Check if param is new + if ( !result.params[name] ) { + // New param. Initialize it + result.params[name] = {}; + } + + // Go over all properties + for ( prop in allProps ) { + switch ( prop ) { + case 'name': + continue; + case 'type': + // Only include type if the original included type + // or if the current type is not undefined + if ( + original.type !== undefined || + this.params[key].type !== 'undefined' + ) { + result.params[name][prop] = this.params[key].type; + } + break; + case 'deprecated': + case 'required': + case 'suggested': + if ( !this.params[key][prop] ) { + // Only add a literal false value if there was a false + // value before + if ( original.params[oldKey] && original.params[oldKey][prop] === false ) { + result.params[name][prop] = false; + } + } else { + result.params[name][prop] = this.params[key][prop]; + } + break; + case 'aliases': + // Only update the aliases in if the new templatedata has an + // aliases array that isn't empty + if ( + $.type( this.params[key][prop] ) === 'array' && + this.params[key][prop].length > 0 + ) { + result.params[name][prop] = this.params[key][prop]; + } else { + // If the new aliases array is empty, delete it from the original + delete result.params[name][prop]; + } + break; + default: + // Check if there's a value in the model + if ( this.params[key][prop] !== undefined ) { + if ( allProps[prop].allowLanguages ) { + normalizedValue = this.propRemoveUnusedLanguages( this.params[key][prop] ); + // Check if this should be displayed with language object or directly as string + compareOrig = original.params[oldKey] ? original.params[oldKey][prop] : {}; + if ( this.isOutputInLanguageObject( compareOrig, normalizedValue ) ) { + result.params[name][prop] = normalizedValue; + } else { + // Store only one language as a string + result.params[name][prop] = normalizedValue[defaultLang]; + } + } else { + result.params[name][prop] = this.params[key][prop]; + } + } + break; + } + } + } + return JSON.stringify( result, null, '\t' ); +}; + +/** + * Check the key if it already exists in the parameter list. If it does, + * find a new key that doesn't, and return it. + * @param {string} key New parameter key + * @return {string} Valid new parameter key + */ +mw.TemplateData.Model.prototype.getNewValidParameterKey = function ( key ) { + var allParamNames = this.getAllParamNames(); + if ( this.params[key] || $.inArray( key, allParamNames ) !== -1 ) { + // Change the key to be something else + key += this.paramIdentifierCounter; + this.paramIdentifierCounter++; + this.getNewValidParameterKey( key ); + } else { + return key; + } +}; +/** + * Go over a language property and remove empty language key values + * @return {Object} Property data with only used language keys + */ +mw.TemplateData.Model.prototype.propRemoveUnusedLanguages = function ( propData ) { + var key, + result = {}; + if ( $.isPlainObject( propData ) ) { + for ( key in propData ) { + if ( propData[key] ) { + result[key] = propData[key]; + } + } + } + return result; +}; + +/** + * Check whether the output of the current parameter property should be + * outputted in full language mode (object) or a simple string. + * @param {string} paramKey Parameter key + * @param {string} prop Param property + * @return {boolean} Output should be a full language object + */ +mw.TemplateData.Model.prototype.isOutputInLanguageObject = function ( originalPropValue, newPropValue ) { + if ( + ( + // The original was already split to languages + $.type( originalPropValue ) === 'object' && + // Original was not an empty object + !$.isEmptyObject( originalPropValue ) + ) || + ( + // The new value is split to languages + $.type( newPropValue ) === 'object' && + // New object is not empty + !$.isEmptyObject( newPropValue ) && + ( + // The new value doesn't have the default language + newPropValue[this.getDefaultLanguage()] === undefined || + // There is more than just one language in the new property + Object.keys( newPropValue ).length > 1 + ) + ) + ) { + return true; + } + return false; +}; diff --git a/modules/ext.templateDataGenerator.editPage.js b/modules/ext.templateDataGenerator.editPage.js index 76199c0f..6703b643 100644 --- a/modules/ext.templateDataGenerator.editPage.js +++ b/modules/ext.templateDataGenerator.editPage.js @@ -1,4 +1,4 @@ -( function ( $, mw ) { +( function () { /** * TemplateData Generator button fixture * The button will appear on Template namespaces only, above the edit textbox @@ -27,4 +27,4 @@ } ); -}( jQuery, mediaWiki ) ); +}() ); diff --git a/modules/ext.templateDataGenerator.js b/modules/ext.templateDataGenerator.js new file mode 100644 index 00000000..8a134c4c --- /dev/null +++ b/modules/ext.templateDataGenerator.js @@ -0,0 +1 @@ +mw.TemplateData = {}; diff --git a/modules/ext.templateDataGenerator.ui.js b/modules/ext.templateDataGenerator.ui.js index 5bb4667a..5ca52519 100644 --- a/modules/ext.templateDataGenerator.ui.js +++ b/modules/ext.templateDataGenerator.ui.js @@ -1,4 +1,4 @@ -( function ( $, mw ) { +( function () { /** * TemplateData Generator data model. * This singleton is independent of any UI; it expects @@ -141,7 +141,7 @@ // Dialog windowManager = new OO.ui.WindowManager(); - tdgDialog = new TemplateDataDialog( config ); + tdgDialog = new mw.TemplateData.Dialog( config ); windowManager.addWindows( [ tdgDialog ] ); // Prepend to container @@ -167,4 +167,4 @@ } }; }() ); -}( jQuery, mediaWiki ) ); +}() ); diff --git a/modules/ext.templateDataGenerator.ui.tdDialog.js b/modules/ext.templateDataGenerator.ui.tdDialog.js index 71e07f20..ddd794a8 100644 --- a/modules/ext.templateDataGenerator.ui.tdDialog.js +++ b/modules/ext.templateDataGenerator.ui.tdDialog.js @@ -1,901 +1,899 @@ - ( function ( mw ) { - /** - * TemplateData Dialog - * @param {Object} config Dialog configuration object - */ - TemplateDataDialog = function TemplateDataDialog( config ) { - // Parent constructor - TemplateDataDialog.super.call( this, config ); +/** + * TemplateData Dialog + * @param {Object} config Dialog configuration object + */ +mw.TemplateData.Dialog = function mwTemplateDataDialog( config ) { + // Parent constructor + mw.TemplateData.Dialog.super.call( this, config ); - this.model = null; - this.language = null; - this.availableLanguages = []; - this.selectedParamKey = ''; - this.propInputs = {}; - this.propFieldLayout = {}; + this.model = null; + this.language = null; + this.availableLanguages = []; + this.selectedParamKey = ''; + this.propInputs = {}; + this.propFieldLayout = {}; - // Initialize - this.$element.addClass( 'tdg-TemplateDataDialog' ); - }; + // Initialize + this.$element.addClass( 'tdg-TemplateDataDialog' ); +}; - OO.inheritClass( TemplateDataDialog, OO.ui.ProcessDialog ); +OO.inheritClass( mw.TemplateData.Dialog, OO.ui.ProcessDialog ); - /* Static properties */ - TemplateDataDialog.static.name = 'TemplateDataDialog'; - TemplateDataDialog.static.title = mw.msg( 'templatedata-modal-title' ); - TemplateDataDialog.static.size = 'large'; - TemplateDataDialog.static.actions = [ +/* Static properties */ +mw.TemplateData.Dialog.static.name = 'TemplateDataDialog'; +mw.TemplateData.Dialog.static.title = mw.msg( 'templatedata-modal-title' ); +mw.TemplateData.Dialog.static.size = 'large'; +mw.TemplateData.Dialog.static.actions = [ + { + action: 'apply', + label: mw.msg( 'templatedata-modal-button-apply' ), + flags: [ 'primary', 'constructive' ], + modes: 'list' + }, + { + action: 'add', + label: mw.msg( 'templatedata-modal-button-addparam' ), + flags: [ 'constructive' ], + modes: 'list' + }, + { + action: 'delete', + label: mw.msg( 'templatedata-modal-button-delparam' ), + modes: 'edit', + flags: 'destructive' + }, + { + label: mw.msg( 'templatedata-modal-button-cancel' ), + flags: 'safe', + modes: [ 'list', 'error' ] + }, + { + action: 'back', + label: mw.msg( 'templatedata-modal-button-back' ), + flags: 'safe', + modes: [ 'edit', 'language', 'add' ] + } +]; + +/** + * @inheritDoc + */ +mw.TemplateData.Dialog.prototype.initialize = function () { + var templateParamsFieldset, addParamFieldlayout, languageActionFieldLayout, + paramOrderFieldset; + + // Parent method + mw.TemplateData.Dialog.super.prototype.initialize.call( this ); + + this.$spinner = this.$( '
' ).addClass( 'tdg-spinner' ).text( 'working...' ); + this.$body.append( this.$spinner ); + + this.noticeLabel = new OO.ui.LabelWidget( { $: this.$ } ); + this.noticeLabel.$element.hide(); + + this.panels = new OO.ui.StackLayout( { $: this.$, continuous: false } ); + + this.listParamsPanel = new OO.ui.PanelLayout( { + $: this.$, + scrollable: true + } ); + this.editParamPanel = new OO.ui.PanelLayout( { + $: this.$ + } ); + this.languagePanel = new OO.ui.PanelLayout( { + $: this.$ + } ); + this.addParamPanel = new OO.ui.PanelLayout( { + $: this.$ + } ); + + // Language panel + this.newLanguageSearchWidget = new mw.TemplateData.LanguageSearchWidget( { + $: this.$ + } ); + + // Add parameter panel + this.newParamInput = new OO.ui.TextInputWidget( { + $: this.$, + placeholder: mw.msg( 'templatedata-modal-placeholder-paramkey' ) + } ); + this.addParamButton = new OO.ui.ButtonWidget( { + $: this.$, + label: mw.msg( 'templatedata-modal-button-addparam' ) + } ); + addParamFieldlayout = new OO.ui.FieldsetLayout( { + $: this.$, + label: mw.msg( 'templatedata-modal-title-addparam' ), + items: [ this.newParamInput, this.addParamButton ] + } ); + + // Param list panel (main) + this.languageDropdownWidget = new OO.ui.DropdownWidget( { $: this.$ } ); + this.languagePanelButton = new OO.ui.ButtonWidget( { + $: this.$, + label: mw.msg( 'templatedata-modal-button-add-language' ) + } ); + languageActionFieldLayout = new OO.ui.ActionFieldLayout( + this.languageDropdownWidget, + this.languagePanelButton, { - action: 'apply', - label: mw.msg( 'templatedata-modal-button-apply' ), - flags: [ 'primary', 'constructive' ], - modes: 'list' - }, - { - action: 'add', - label: mw.msg( 'templatedata-modal-button-addparam' ), - flags: [ 'constructive' ], - modes: 'list' - }, - { - action: 'delete', - label: mw.msg( 'templatedata-modal-button-delparam' ), - modes: 'edit', - flags: 'destructive' - }, - { - label: mw.msg( 'templatedata-modal-button-cancel' ), - flags: 'safe', - modes: [ 'list', 'error' ] - }, - { - action: 'back', - label: mw.msg( 'templatedata-modal-button-back' ), - flags: 'safe', - modes: [ 'edit', 'language', 'add' ] + $: this.$, + align: 'left', + label: mw.msg( 'templatedata-modal-title-language' ) } - ]; + ); - /** - * @inheritDoc - */ - TemplateDataDialog.prototype.initialize = function () { - var templateParamsFieldset, addParamFieldlayout, languageActionFieldLayout, - paramOrderFieldset; + // ParamOrder + this.paramOrderWidget = new mw.TemplateData.DragDropWidget( { + $: this.$, + orientation: 'horizontal' + } ); + paramOrderFieldset = new OO.ui.FieldsetLayout( { + $: this.$, + label: mw.msg( 'templatedata-modal-title-paramorder' ), + items: [ this.paramOrderWidget ] + } ); - // Parent method - TemplateDataDialog.super.prototype.initialize.call( this ); + this.descriptionInput = new OO.ui.TextInputWidget( { + $: this.$, + multiline: true, + autosize: true + } ); + this.templateDescriptionFieldset = new OO.ui.FieldsetLayout( { + $: this.$, + items: [ this.descriptionInput ] + } ); + this.paramListNoticeLabel = new OO.ui.LabelWidget( { $: this.$ } ); + this.paramListNoticeLabel.$element.hide(); - this.$spinner = this.$( '
' ).addClass( 'tdg-spinner' ).text( 'working...' ); - this.$body.append( this.$spinner ); + this.paramSelectWidget = new OO.ui.SelectWidget(); + templateParamsFieldset = new OO.ui.FieldsetLayout( { + $: this.$, + label: mw.msg( 'templatedata-modal-title-templateparams' ) + } ); + templateParamsFieldset.$element.append( this.paramSelectWidget.$element ); - this.noticeLabel = new OO.ui.LabelWidget( { $: this.$ } ); - this.noticeLabel.$element.hide(); + // Param details panel + this.$paramDetailsContainer = this.$( '
' ) + .addClass( 'tdg-TemplateDataDialog-paramDetails' ); - this.panels = new OO.ui.StackLayout( { $: this.$, continuous: false } ); + this.listParamsPanel.$element + .addClass( 'tdg-templateDataDialog-listParamsPanel' ) + .append( + this.paramListNoticeLabel.$element, + languageActionFieldLayout.$element, + this.templateDescriptionFieldset.$element, + paramOrderFieldset.$element, + templateParamsFieldset.$element + ); + this.paramEditNoticeLabel = new OO.ui.LabelWidget( { $: this.$ } ); + this.paramEditNoticeLabel.$element.hide(); + // Edit panel + this.editParamPanel.$element + .addClass( 'tdg-templateDataDialog-editParamPanel' ) + .append( + this.paramEditNoticeLabel.$element, + this.$paramDetailsContainer + ); + // Language panel + this.languagePanel.$element + .addClass( 'tdg-templateDataDialog-languagePanel' ) + .append( + this.newLanguageSearchWidget.$element + ); + this.addParamPanel.$element + .addClass( 'tdg-templateDataDialog-addParamPanel' ) + .append( addParamFieldlayout.$element ); - this.listParamsPanel = new OO.ui.PanelLayout( { - $: this.$, - scrollable: true - } ); - this.editParamPanel = new OO.ui.PanelLayout( { - $: this.$ - } ); - this.languagePanel = new OO.ui.PanelLayout( { - $: this.$ - } ); - this.addParamPanel = new OO.ui.PanelLayout( { - $: this.$ - } ); + this.panels.addItems( [ + this.listParamsPanel, + this.editParamPanel, + this.languagePanel, + this.addParamPanel + ] ); + this.panels.setItem( this.listParamsPanel ); + this.panels.$element.addClass( 'tdg-TemplateDataDialog-panels' ); - // Language panel - this.newLanguageSearchWidget = new TemplateDataLanguageSearchWidget( { - $: this.$ - } ); + // Build param details panel + this.$paramDetailsContainer.append( this.createParamDetails() ); - // Add parameter panel - this.newParamInput = new OO.ui.TextInputWidget( { - $: this.$, - placeholder: mw.msg( 'templatedata-modal-placeholder-paramkey' ) - } ); - this.addParamButton = new OO.ui.ButtonWidget( { - $: this.$, - label: mw.msg( 'templatedata-modal-button-addparam' ) - } ); - addParamFieldlayout = new OO.ui.FieldsetLayout( { - $: this.$, - label: mw.msg( 'templatedata-modal-title-addparam' ), - items: [ this.newParamInput, this.addParamButton ] - } ); + // Initialization + this.$body.append( + this.noticeLabel.$element, + this.panels.$element + ); - // Param list panel (main) - this.languageDropdownWidget = new OO.ui.DropdownWidget( { $: this.$ } ); - this.languagePanelButton = new OO.ui.ButtonWidget( { - $: this.$, - label: mw.msg( 'templatedata-modal-button-add-language' ) - } ); - languageActionFieldLayout = new OO.ui.ActionFieldLayout( - this.languageDropdownWidget, - this.languagePanelButton, - { + // Events + this.newLanguageSearchWidget.connect( this, { select: 'newLanguageSearchWidgetSelect' } ); + this.newParamInput.connect( this, { change: 'onAddParamInputChange' } ); + this.addParamButton.connect( this, { click: 'onAddParamButtonClick' } ); + this.descriptionInput.connect( this, { change: 'onDescriptionInputChange' } ); + this.paramOrderWidget.connect( this, { reorder: 'onParamOrderWidgetReorder' } ); + this.languagePanelButton.connect( this, { click: 'onLanguagePanelButton' } ); + this.languageDropdownWidget.getMenu().connect( this, { choose: 'onLanguageDropdownWidgetChoose' } ); + this.paramSelectWidget.connect( this, { choose: 'onParamSelectWidgetChoose' } ); +}; + +/** + * Respond to model change of description event + * @param {jQuery.event} event Event details + * @param {string} description New description + */ +mw.TemplateData.Dialog.prototype.onModelChangeDescription = function ( description ) { + this.descriptionInput.setValue( description ); +}; + +mw.TemplateData.Dialog.prototype.onAddParamInputChange = function ( value ) { + if ( this.model.isParamExists( value ) && !this.model.isParamDeleted( value ) ) { + // Disable the add button + this.addParamButton.setDisabled( true ); + } else { + this.addParamButton.setDisabled( false ); + } +}; + +/** + * Respond to change of paramOrder from the model + * @param {string[]} paramOrderArray The array of keys in order + */ +mw.TemplateData.Dialog.prototype.onModelChangeParamOrder = function ( paramOrderArray ) { + var i, + items = []; + + this.paramOrderWidget.clearItems(); + for ( i = 0; i < paramOrderArray.length; i++ ) { + items.push( + new mw.TemplateData.DragDropItemWidget( { $: this.$, - align: 'left', - label: mw.msg( 'templatedata-modal-title-language' ) - } + data: paramOrderArray[i], + label: paramOrderArray[i] + } ) ); + } + this.paramOrderWidget.addItems( items ); - // ParamOrder - this.paramOrderWidget = new TemplateDataDragDropWidget( { - $: this.$, - orientation: 'horizontal' - } ); - paramOrderFieldset = new OO.ui.FieldsetLayout( { - $: this.$, - label: mw.msg( 'templatedata-modal-title-paramorder' ), - items: [ this.paramOrderWidget ] - } ); + // Refresh the parameter widget + this.repopulateParamSelectWidget(); +}; - this.descriptionInput = new OO.ui.TextInputWidget( { - $: this.$, - multiline: true, - autosize: true - } ); - this.templateDescriptionFieldset = new OO.ui.FieldsetLayout( { - $: this.$, - items: [ this.descriptionInput ] - } ); - this.paramListNoticeLabel = new OO.ui.LabelWidget( { $: this.$ } ); - this.paramListNoticeLabel.$element.hide(); +/** + * Respond to an addition of a key to the model paramOrder + * @param {string} key Added key + */ +mw.TemplateData.Dialog.prototype.onModelAddKeyParamOrder = function ( key ) { + var dragItem = new mw.TemplateData.DragDropItemWidget( { + $: this.$, + data: key, + label: key + } ); - this.paramSelectWidget = new OO.ui.SelectWidget(); - templateParamsFieldset = new OO.ui.FieldsetLayout( { - $: this.$, - label: mw.msg( 'templatedata-modal-title-templateparams' ) - } ); - templateParamsFieldset.$element.append( this.paramSelectWidget.$element ); + this.paramOrderWidget.addItems( [ dragItem ] ); +}; - // Param details panel - this.$paramDetailsContainer = this.$( '
' ) - .addClass( 'tdg-TemplateDataDialog-paramDetails' ); +mw.TemplateData.Dialog.prototype.onParamOrderWidgetReorder = function ( item, newIndex ) { + this.model.reorderParamOrderKey( item.getData(), newIndex ); +}; - this.listParamsPanel.$element - .addClass( 'tdg-templateDataDialog-listParamsPanel' ) - .append( - this.paramListNoticeLabel.$element, - languageActionFieldLayout.$element, - this.templateDescriptionFieldset.$element, - paramOrderFieldset.$element, - templateParamsFieldset.$element - ); - this.paramEditNoticeLabel = new OO.ui.LabelWidget( { $: this.$ } ); - this.paramEditNoticeLabel.$element.hide(); - // Edit panel - this.editParamPanel.$element - .addClass( 'tdg-templateDataDialog-editParamPanel' ) - .append( - this.paramEditNoticeLabel.$element, - this.$paramDetailsContainer - ); - // Language panel - this.languagePanel.$element - .addClass( 'tdg-templateDataDialog-languagePanel' ) - .append( - this.newLanguageSearchWidget.$element - ); - this.addParamPanel.$element - .addClass( 'tdg-templateDataDialog-addParamPanel' ) - .append( addParamFieldlayout.$element ); +/** + * Respond to description input change event + */ +mw.TemplateData.Dialog.prototype.onDescriptionInputChange = function ( value ) { + if ( this.model.getTemplateDescription() !== value ) { + this.model.setTemplateDescription( value, this.language ); + } +}; - this.panels.addItems( [ - this.listParamsPanel, - this.editParamPanel, - this.languagePanel, - this.addParamPanel - ] ); - this.panels.setItem( this.listParamsPanel ); - this.panels.$element.addClass( 'tdg-TemplateDataDialog-panels' ); +/** + * Respond to add language button click + */ +mw.TemplateData.Dialog.prototype.onLanguagePanelButton = function () { + this.switchPanels( 'language' ); +}; - // Build param details panel - this.$paramDetailsContainer.append( this.createParamDetails() ); +/** + * Respond to language select widget choose event + * @param {OO.ui.OptionWidget} item Chosen item + */ +mw.TemplateData.Dialog.prototype.onLanguageDropdownWidgetChoose = function ( item ) { + var language = item ? item.getData() : this.language; - // Initialization - this.$body.append( - this.noticeLabel.$element, - this.panels.$element - ); + // Change current language + if ( language !== this.language ) { + this.language = language; - // Events - this.newLanguageSearchWidget.connect( this, { select: 'newLanguageSearchWidgetSelect' } ); - this.newParamInput.connect( this, { change: 'onAddParamInputChange' } ); - this.addParamButton.connect( this, { click: 'onAddParamButtonClick' } ); - this.descriptionInput.connect( this, { change: 'onDescriptionInputChange' } ); - this.paramOrderWidget.connect( this, { reorder: 'onParamOrderWidgetReorder' } ); - this.languagePanelButton.connect( this, { click: 'onLanguagePanelButton' } ); - this.languageDropdownWidget.getMenu().connect( this, { choose: 'onLanguageDropdownWidgetChoose' } ); - this.paramSelectWidget.connect( this, { choose: 'onParamSelectWidgetChoose' } ); - }; + // Update description label + this.templateDescriptionFieldset.setLabel( mw.msg( 'templatedata-modal-title-templatedesc', this.language ) ); - /** - * Respond to model change of description event - * @param {jQuery.event} event Event details - * @param {string} description New description - */ - TemplateDataDialog.prototype.onModelChangeDescription = function ( description ) { - this.descriptionInput.setValue( description ); - }; - - TemplateDataDialog.prototype.onAddParamInputChange = function ( value ) { - if ( this.model.isParamExists( value ) && !this.model.isParamDeleted( value ) ) { - // Disable the add button - this.addParamButton.setDisabled( true ); - } else { - this.addParamButton.setDisabled( false ); - } - }; - - /** - * Respond to change of paramOrder from the model - * @param {string[]} paramOrderArray The array of keys in order - */ - TemplateDataDialog.prototype.onModelChangeParamOrder = function ( paramOrderArray ) { - var i, - items = []; - - this.paramOrderWidget.clearItems(); - for ( i = 0; i < paramOrderArray.length; i++ ) { - items.push( - new TemplateDataDragDropItemWidget( { - $: this.$, - data: paramOrderArray[i], - label: paramOrderArray[i] - } ) - ); - } - this.paramOrderWidget.addItems( items ); - - // Refresh the parameter widget - this.repopulateParamSelectWidget(); - }; - - /** - * Respond to an addition of a key to the model paramOrder - * @param {string} key Added key - */ - TemplateDataDialog.prototype.onModelAddKeyParamOrder = function ( key ) { - var dragItem = new TemplateDataDragDropItemWidget( { - $: this.$, - data: key, - label: key - } ); - - this.paramOrderWidget.addItems( [ dragItem ] ); - }; - - TemplateDataDialog.prototype.onParamOrderWidgetReorder = function ( item, newIndex ) { - this.model.reorderParamOrderKey( item.getData(), newIndex ); - }; - - /** - * Respond to description input change event - */ - TemplateDataDialog.prototype.onDescriptionInputChange = function ( value ) { - if ( this.model.getTemplateDescription() !== value ) { - this.model.setTemplateDescription( value, this.language ); - } - }; - - /** - * Respond to add language button click - */ - TemplateDataDialog.prototype.onLanguagePanelButton = function () { - this.switchPanels( 'language' ); - }; - - /** - * Respond to language select widget choose event - * @param {OO.ui.OptionWidget} item Chosen item - */ - TemplateDataDialog.prototype.onLanguageDropdownWidgetChoose = function ( item ) { - var language = item ? item.getData() : this.language; - - // Change current language - if ( language !== this.language ) { - this.language = language; - - // Update description label - this.templateDescriptionFieldset.setLabel( mw.msg( 'templatedata-modal-title-templatedesc', this.language ) ); - - // Update description value - this.descriptionInput.setValue( this.model.getTemplateDescription( language ) ); - - // Update all param descriptions in the param select widget - this.repopulateParamSelectWidget(); - - // Update the parameter detail page - this.updateParamDetailsLanguage( this.language ); - - this.emit( 'change-language', this.language ); - } - }; - - /** - * Respond to add language button - * @param {Object} data Data from the selected option widget - */ - TemplateDataDialog.prototype.newLanguageSearchWidgetSelect = function ( data ) { - var languageButton, - newLanguage = data.code; - - if ( newLanguage ) { - if ( $.inArray( newLanguage, this.availableLanguages ) === -1 ) { - // Add new language - this.availableLanguages.push( newLanguage ); - languageButton = new OO.ui.OptionWidget( { - data: newLanguage, - $: this.$, - label: $.uls.data.getAutonym( newLanguage ) - } ); - this.languageDropdownWidget.getMenu().addItems( [ languageButton ] ); - } - - // Select the new item - this.languageDropdownWidget.getMenu().chooseItem( - this.languageDropdownWidget.getMenu().getItemFromData( newLanguage ) - ); - } - - // Go to the main panel - this.switchPanels( 'listParams' ); - }; - - /** - * Respond to add parameter button - */ - TemplateDataDialog.prototype.onAddParamButtonClick = function () { - var newParamKey = this.newParamInput.getValue(), - allProps = TemplateDataModel.static.getAllProperties( true ); - - // Validate parameter - if ( - !newParamKey.match( allProps.name.restrict ) && - this.model.isParamExists( newParamKey ) - ) { - if ( this.model.isParamDeleted( newParamKey ) ) { - // Empty param - this.model.emptyParamData( newParamKey ); - } else { - // Add to model - if ( this.model.addParam( newParamKey ) ) { - // Add parameter to list - this.addParamToSelectWidget( newParamKey ); - } - } - } - // Reset the input - this.newParamInput.setValue( '' ); - - // Go back to list - this.switchPanels( 'listParams' ); - }; - - /** - * Respond to choose event from the param select widget - * @param {OO.ui.OptionWidget} item Parameter item - */ - TemplateDataDialog.prototype.onParamSelectWidgetChoose = function ( item ) { - var paramKey = item.getData(); - - if ( paramKey === 'tdg-importParameters' ) { - // Import - this.importParametersFromTemplateCode(); - } else { - this.selectedParamKey = paramKey; - - // Fill in parameter detail - this.getParameterDetails( paramKey ); - this.switchPanels( 'editParam' ); - } - }; - - TemplateDataDialog.prototype.onParamPropertyInputChange = function ( property, value ) { - var err = false, - anyInputError = false, - allProps = TemplateDataModel.static.getAllProperties( true ); - - if ( property === 'type' ) { - value = this.propInputs[property].getMenu().getSelectedItem() ? this.propInputs[property].getMenu().getSelectedItem().getData() : 'undefined'; - } - - // TODO: Validate the name - if ( allProps[property].restrict ) { - if ( value.match( allProps[property].restrict ) ) { - // Error! Don't fix the model - err = true; - this.toggleNoticeMessage( 'edit', true, 'error', mw.msg( 'templatedata-modal-errormsg', '|', '=', '}}' ) ); - } else { - this.toggleNoticeMessage( 'edit', false ); - } - } - - this.propInputs[property].$element.toggleClass( 'tdg-editscreen-input-error', err ); - - // Validate - $( '.tdg-TemplateDataDialog-paramInput' ).each( function () { - if ( $( this ).hasClass( 'tdg-editscreen-input-error' ) ) { - anyInputError = true; - } - } ); - // Disable the 'back' button if there are any errors in the inputs - this.actions.setAbilities( { back: !anyInputError } ); - if ( !err ) { - this.model.setParamProperty( this.selectedParamKey, property, value, this.language ); - } - }; - - /** - * Set the parameter details in the detail panel. - * @param {Object} paramKey Parameter details - */ - TemplateDataDialog.prototype.getParameterDetails = function ( paramKey ) { - var prop, - paramData = this.model.getParamData( paramKey ); - - for ( prop in this.propInputs ) { - this.changeParamPropertyInput( paramKey, prop, paramData[prop], this.language ); - } - }; - - /** - * Reset contents on reload - */ - TemplateDataDialog.prototype.reset = function () { - this.language = null; - this.availableLanguages = []; - if ( this.paramSelectWidget ) { - this.paramSelectWidget.clearItems(); - this.selectedParamKey = ''; - } - - if ( this.languageDropdownWidget ) { - this.languageDropdownWidget.getMenu().clearItems(); - } - - if ( this.paramOrderWidget ) { - this.paramOrderWidget.clearItems(); - } - }; - - /** - * Empty and repopulate the parameter select widget. - */ - TemplateDataDialog.prototype.repopulateParamSelectWidget = function () { - var i, paramKey, - missingParams = this.model.getMissingParams(), - paramList = this.model.getParams(), - paramOrder = this.model.getTemplateParamOrder(); - - this.paramSelectWidget.clearItems(); + // Update description value + this.descriptionInput.setValue( this.model.getTemplateDescription( language ) ); // Update all param descriptions in the param select widget - for ( i in paramOrder ) { - paramKey = paramList[paramOrder[i]]; - if ( paramKey && !paramKey.deleted ) { - this.addParamToSelectWidget( paramOrder[i] ); - } - } + this.repopulateParamSelectWidget(); - // Check if there are potential parameters to add - // from the template source code - if ( missingParams.length > 0 ) { - // Add a final option - this.paramSelectWidget.addItems( [ - new TemplateDataOptionImportWidget( { - data: 'tdg-importParameters', - $: this.$, - params: missingParams - } ) - ] ); - } - }; - - /** - * Change parameter property - * @param {string} paramKey Parameter key - * @param {string} propName Property name - * @param {string} propVal Property value - * @param {string} [lang] Language - */ - TemplateDataDialog.prototype.changeParamPropertyInput = function ( paramKey, propName, value, lang ) { - var languageProps = TemplateDataModel.static.getPropertiesWithLanguage(), - allProps = TemplateDataModel.static.getAllProperties( true ), - prop = allProps[propName], - propInput = typeof this.propInputs[propName].getMenu === 'function' ? - this.propInputs[propName].getMenu() : this.propInputs[propName]; - - lang = lang || this.language; - - if ( value !== undefined ) { - // Change the actual input - if ( prop.type === 'select' ) { - propInput.selectItem( propInput.getItemFromData( value ) ); - } else if ( prop.type === 'boolean' ) { - propInput.setValue( !!value ); - } else { - if ( $.inArray( propName, languageProps ) !== -1 ) { - propInput.setValue( value[lang] ); - } else { - if ( prop.type === 'array' && $.type( value ) === 'array' ) { - value = value.join( prop.delimiter ); - } - propInput.setValue( value ); - } - } - } else { - // Empty the input - if ( prop.type === 'select' ) { - propInput.selectItem( propInput.getItemFromData( prop['default'] ) ); - } else { - propInput.setValue( '' ); - } - } - }; - - /** - * Add parameter to the list - * @param {string} paramKey Parameter key in the model - * @param {Object} paramData Parameter data - */ - TemplateDataDialog.prototype.addParamToSelectWidget = function ( paramKey ) { - var paramItem, - data = this.model.getParamData( paramKey ); - - paramItem = new TemplateDataOptionWidget( { - data: { - key: paramKey, - name: data.name, - aliases: data.aliases, - description: this.model.getParamDescription( paramKey, this.language ) - }, - $: this.$ - } ); - - this.paramSelectWidget.addItems( [ paramItem ] ); - }; - - /** - * Create the information page about individual parameters - * @returns {jQuery} Editable details page for the parameter - */ - TemplateDataDialog.prototype.createParamDetails = function () { - var props, type, propInput, config, paramProperties, - paramFieldset, - typeItemArray = []; - - paramProperties = TemplateDataModel.static.getAllProperties( true ); - - // Fieldset - paramFieldset = new OO.ui.FieldsetLayout( { - $: this.$ - } ); - - for ( props in paramProperties ) { - config = { - $: this.$, - multiline: paramProperties[props].multiline - }; - if ( paramProperties[props].multiline ) { - config.autosize = true; - } - // Create the property inputs - switch ( props ) { - case 'type': - propInput = new OO.ui.DropdownWidget( config ); - for ( type in paramProperties[props].children ) { - typeItemArray.push( new OO.ui.OptionWidget( { - data: paramProperties[props].children[type], - $: this.$, - label: mw.msg( 'templatedata-modal-table-param-type-' + paramProperties[props].children[type] ) - } ) ); - } - propInput.getMenu().addItems( typeItemArray ); - break; - case 'deprecated': - case 'required': - case 'suggested': - propInput = new OO.ui.ToggleSwitchWidget( config ); - break; - default: - propInput = new OO.ui.TextInputWidget( config ); - break; - } - - this.propInputs[props] = propInput; - - propInput.$element - .addClass( 'tdg-TemplateDataDialog-paramInput tdg-TemplateDataDialog-paramList-' + props ); - - this.propFieldLayout[props] = new OO.ui.FieldLayout( propInput, { - align: 'left', - label: mw.msg( 'templatedata-modal-table-param-' + props ) - } ); - // Event - if ( props === 'type' ) { - propInput.getMenu().connect( this, { choose: [ 'onParamPropertyInputChange', props ] } ); - } else { - propInput.connect( this, { change: [ 'onParamPropertyInputChange', props ] } ); - } - // Append to parameter section - paramFieldset.$element.append( this.propFieldLayout[props].$element ); - } - // Update parameter property fields with languages + // Update the parameter detail page this.updateParamDetailsLanguage( this.language ); - return paramFieldset.$element; - }; - /** - * Update the labels for parameter property inputs that include language, so - * they show the currently used language. - * @param {string} [lang] Language. If not used, will use currently defined - * language. - */ - TemplateDataDialog.prototype.updateParamDetailsLanguage = function ( lang ) { - var i, prop, label, - languageProps = TemplateDataModel.static.getPropertiesWithLanguage(); - lang = lang || this.language; + this.emit( 'change-language', this.language ); + } +}; - for ( i = 0; i < languageProps.length; i++ ) { - prop = languageProps[i]; - label = mw.msg( 'templatedata-modal-table-param-' + prop, lang ); - this.propFieldLayout[prop].setLabel( label ); - } - }; +/** + * Respond to add language button + * @param {Object} data Data from the selected option widget + */ +mw.TemplateData.Dialog.prototype.newLanguageSearchWidgetSelect = function ( data ) { + var languageButton, + newLanguage = data.code; - /** - * Override getBodyHeight to create a tall dialog relative to the screen. - * @return {number} Body height - */ - TemplateDataDialog.prototype.getBodyHeight = function () { - return window.innerHeight - 200; - }; - - /** - * Show or hide the notice message in the dialog with a set message. - * @param {string} type Which notice label to show: 'list' or 'global' - * @param {Boolean} isShowing Show or hide the message - * @param {string} status Message status 'error' or 'success' - * @param {string|string[]} noticeMessage The message to display - */ - TemplateDataDialog.prototype.toggleNoticeMessage = function ( type, isShowing, status, noticeMessage ) { - var noticeReference, - $message; - - type = type || 'list'; - - // Hide all - this.noticeLabel.$element.hide(); - this.paramEditNoticeLabel.$element.hide(); - this.paramListNoticeLabel.$element.hide(); - - if ( noticeMessage ) { - // See which error to display - if ( type === 'global' ) { - noticeReference = this.noticeLabel; - } else if ( type === 'edit' ) { - noticeReference = this.paramEditNoticeLabel; - } else { - noticeReference = this.paramListNoticeLabel; - } - isShowing = isShowing || !noticeReference.$element.is( ':visible' ); - - if ( $.type( noticeMessage ) === 'array' ) { - $message = $( '
' ); - $.each( noticeMessage, function ( i, msg ) { - $message.append( $( '

' ).text( msg ) ); - } ); - noticeReference.setLabel( $message ); - } else { - noticeReference.setLabel( noticeMessage ); - } - noticeReference.$element - .toggle( isShowing ) - .toggleClass( 'errorbox', status === 'error' ) - .toggleClass( 'successbox', status === 'success' ); - } - }; - - /** - * Import parameters from the source code. - */ - TemplateDataDialog.prototype.importParametersFromTemplateCode = function () { - var combinedMessage = [], - state = 'success', - response = this.model.importSourceCodeParameters(); - // Repopulate the list - this.repopulateParamSelectWidget(); - - if ( response.existing.length > 0 ) { - combinedMessage.push( mw.msg( 'templatedata-modal-errormsg-import-paramsalreadyexist', response.existing.join( mw.msg( 'comma-separator' ) ) ) ); + if ( newLanguage ) { + if ( $.inArray( newLanguage, this.availableLanguages ) === -1 ) { + // Add new language + this.availableLanguages.push( newLanguage ); + languageButton = new OO.ui.OptionWidget( { + data: newLanguage, + $: this.$, + label: $.uls.data.getAutonym( newLanguage ) + } ); + this.languageDropdownWidget.getMenu().addItems( [ languageButton ] ); } - if ( response.imported.length === 0 ) { - combinedMessage.push( mw.msg( 'templatedata-modal-errormsg-import-noparams' ) ); - state = 'error'; + // Select the new item + this.languageDropdownWidget.getMenu().chooseItem( + this.languageDropdownWidget.getMenu().getItemFromData( newLanguage ) + ); + } + + // Go to the main panel + this.switchPanels( 'listParams' ); +}; + +/** + * Respond to add parameter button + */ +mw.TemplateData.Dialog.prototype.onAddParamButtonClick = function () { + var newParamKey = this.newParamInput.getValue(), + allProps = mw.TemplateData.Model.static.getAllProperties( true ); + + // Validate parameter + if ( + !newParamKey.match( allProps.name.restrict ) && + this.model.isParamExists( newParamKey ) + ) { + if ( this.model.isParamDeleted( newParamKey ) ) { + // Empty param + this.model.emptyParamData( newParamKey ); } else { - combinedMessage.push( mw.msg( 'templatedata-modal-notice-import-numparams', response.imported.length, response.imported.join( mw.msg( 'comma-separator' ) ) ) ); + // Add to model + if ( this.model.addParam( newParamKey ) ) { + // Add parameter to list + this.addParamToSelectWidget( newParamKey ); + } } + } + // Reset the input + this.newParamInput.setValue( '' ); - this.toggleNoticeMessage( 'list', true, state, combinedMessage ); - }; + // Go back to list + this.switchPanels( 'listParams' ); +}; - /** - * @inheritDoc - */ - TemplateDataDialog.prototype.getSetupProcess = function ( data ) { - return TemplateDataDialog.super.prototype.getSetupProcess.call( this, data ) - .next( function () { - this.reset(); - // Hide the panels and display a spinner - this.$spinner.show(); - this.panels.$element.hide(); - this.toggleNoticeMessage( 'global', false ); - this.toggleNoticeMessage( 'list', false ); +/** + * Respond to choose event from the param select widget + * @param {OO.ui.OptionWidget} item Parameter item + */ +mw.TemplateData.Dialog.prototype.onParamSelectWidgetChoose = function ( item ) { + var paramKey = item.getData(); - // Start with parameter list - this.switchPanels( 'listParams' ); - this.model = new TemplateDataModel( data.config ); + if ( paramKey === 'tdg-importParameters' ) { + // Import + this.importParametersFromTemplateCode(); + } else { + this.selectedParamKey = paramKey; - // Events - this.model.connect( this, { - 'change-description': 'onModelChangeDescription', - 'change-paramOrder': 'onModelChangeParamOrder', - 'add-paramOrder': 'onModelAddKeyParamOrder' - } ); + // Fill in parameter detail + this.getParameterDetails( paramKey ); + this.switchPanels( 'editParam' ); + } +}; - // Load the model according to the string - this.model.loadModel( data.wikitext ) - .done( $.proxy( function () { - var i, - language = this.model.getDefaultLanguage(), - languageItems = [], - languages = this.model.getExistingLanguageCodes(); +mw.TemplateData.Dialog.prototype.onParamPropertyInputChange = function ( property, value ) { + var err = false, + anyInputError = false, + allProps = mw.TemplateData.Model.static.getAllProperties( true ); - this.setupDetailsFromModel(); + if ( property === 'type' ) { + value = this.propInputs[property].getMenu().getSelectedItem() ? this.propInputs[property].getMenu().getSelectedItem().getData() : 'undefined'; + } - // Fill up the language selection - if ( - languages.length === 0 || - $.inArray( language, languages ) === -1 - ) { - // Add the default language - languageItems.push( new OO.ui.OptionWidget( { - data: language, - $: this.$, - label: $.uls.data.getAutonym( language ) - } ) ); - this.availableLanguages.push( language ); - } + // TODO: Validate the name + if ( allProps[property].restrict ) { + if ( value.match( allProps[property].restrict ) ) { + // Error! Don't fix the model + err = true; + this.toggleNoticeMessage( 'edit', true, 'error', mw.msg( 'templatedata-modal-errormsg', '|', '=', '}}' ) ); + } else { + this.toggleNoticeMessage( 'edit', false ); + } + } - // Add all available languages - for ( i = 0; i < languages.length; i++ ) { - languageItems.push( new OO.ui.OptionWidget( { - data: languages[i], - $: this.$, - label: $.uls.data.getAutonym( languages[i] ) - } ) ); - // Store available languages - this.availableLanguages.push( languages[i] ); - } - this.languageDropdownWidget.getMenu().addItems( languageItems ); - // Trigger the initial language choice - this.languageDropdownWidget.getMenu().chooseItem( this.languageDropdownWidget.getMenu().getItemFromData( language ) ); + this.propInputs[property].$element.toggleClass( 'tdg-editscreen-input-error', err ); - // Show the panel - this.$spinner.hide(); - this.panels.$element.show(); - }, this ) ) - .fail( $.proxy( function () { - // Show error - this.actions.setMode( 'error' ); - this.$spinner.hide(); - this.toggleNoticeMessage( 'global', true, 'error', mw.msg( 'templatedata-errormsg-jsonbadformat' ) ); - }, this ) ); + // Validate + $( '.tdg-TemplateDataDialog-paramInput' ).each( function () { + if ( $( this ).hasClass( 'tdg-editscreen-input-error' ) ) { + anyInputError = true; + } + } ); + // Disable the 'back' button if there are any errors in the inputs + this.actions.setAbilities( { back: !anyInputError } ); + if ( !err ) { + this.model.setParamProperty( this.selectedParamKey, property, value, this.language ); + } +}; - }, this ); - }; +/** + * Set the parameter details in the detail panel. + * @param {Object} paramKey Parameter details + */ +mw.TemplateData.Dialog.prototype.getParameterDetails = function ( paramKey ) { + var prop, + paramData = this.model.getParamData( paramKey ); - /** - * Set up the list of parameters from the model. This should happen - * after initialization of the model. - */ - TemplateDataDialog.prototype.setupDetailsFromModel = function () { - // Set up description - this.descriptionInput.setValue( this.model.getTemplateDescription( this.language ) ); - // Repopulate the parameter list - this.repopulateParamSelectWidget(); - }; + for ( prop in this.propInputs ) { + this.changeParamPropertyInput( paramKey, prop, paramData[prop], this.language ); + } +}; - /** - * Switch between stack layout panels - * @param {string} panel Panel key to switch to - */ - TemplateDataDialog.prototype.switchPanels = function ( panel ) { - switch ( panel ) { - case 'listParams': - this.actions.setMode( 'list' ); - this.panels.setItem( this.listParamsPanel ); - // Reset message - this.toggleNoticeMessage( 'list', false ); - // Deselect parameter - this.paramSelectWidget.selectItem( null ); - // Repopulate the list to account for any changes - if ( this.model ) { - this.repopulateParamSelectWidget(); +/** + * Reset contents on reload + */ +mw.TemplateData.Dialog.prototype.reset = function () { + this.language = null; + this.availableLanguages = []; + if ( this.paramSelectWidget ) { + this.paramSelectWidget.clearItems(); + this.selectedParamKey = ''; + } + + if ( this.languageDropdownWidget ) { + this.languageDropdownWidget.getMenu().clearItems(); + } + + if ( this.paramOrderWidget ) { + this.paramOrderWidget.clearItems(); + } +}; + +/** + * Empty and repopulate the parameter select widget. + */ +mw.TemplateData.Dialog.prototype.repopulateParamSelectWidget = function () { + var i, paramKey, + missingParams = this.model.getMissingParams(), + paramList = this.model.getParams(), + paramOrder = this.model.getTemplateParamOrder(); + + this.paramSelectWidget.clearItems(); + + // Update all param descriptions in the param select widget + for ( i in paramOrder ) { + paramKey = paramList[paramOrder[i]]; + if ( paramKey && !paramKey.deleted ) { + this.addParamToSelectWidget( paramOrder[i] ); + } + } + + // Check if there are potential parameters to add + // from the template source code + if ( missingParams.length > 0 ) { + // Add a final option + this.paramSelectWidget.addItems( [ + new mw.TemplateData.OptionImportWidget( { + data: 'tdg-importParameters', + $: this.$, + params: missingParams + } ) + ] ); + } +}; + +/** + * Change parameter property + * @param {string} paramKey Parameter key + * @param {string} propName Property name + * @param {string} propVal Property value + * @param {string} [lang] Language + */ +mw.TemplateData.Dialog.prototype.changeParamPropertyInput = function ( paramKey, propName, value, lang ) { + var languageProps = mw.TemplateData.Model.static.getPropertiesWithLanguage(), + allProps = mw.TemplateData.Model.static.getAllProperties( true ), + prop = allProps[propName], + propInput = typeof this.propInputs[propName].getMenu === 'function' ? + this.propInputs[propName].getMenu() : this.propInputs[propName]; + + lang = lang || this.language; + + if ( value !== undefined ) { + // Change the actual input + if ( prop.type === 'select' ) { + propInput.selectItem( propInput.getItemFromData( value ) ); + } else if ( prop.type === 'boolean' ) { + propInput.setValue( !!value ); + } else { + if ( $.inArray( propName, languageProps ) !== -1 ) { + propInput.setValue( value[lang] ); + } else { + if ( prop.type === 'array' && $.type( value ) === 'array' ) { + value = value.join( prop.delimiter ); } - // Hide/show panels - this.listParamsPanel.$element.show(); - this.editParamPanel.$element.hide(); - this.addParamPanel.$element.hide(); - this.languagePanel.$element.hide(); - break; - case 'editParam': - this.actions.setMode( 'edit' ); - this.panels.setItem( this.editParamPanel ); - // Deselect parameter - this.paramSelectWidget.selectItem( null ); - // Hide/show panels - this.listParamsPanel.$element.hide(); - this.languagePanel.$element.hide(); - this.addParamPanel.$element.hide(); - this.editParamPanel.$element.show(); - break; - case 'addParam': - this.actions.setMode( 'add' ); - this.panels.setItem( this.addParamPanel ); - // Hide/show panels - this.listParamsPanel.$element.hide(); - this.editParamPanel.$element.hide(); - this.languagePanel.$element.hide(); - this.addParamPanel.$element.show(); - break; - case 'language': - this.actions.setMode( 'language' ); - this.panels.setItem( this.languagePanel ); - // Hide/show panels - this.listParamsPanel.$element.hide(); - this.editParamPanel.$element.hide(); - this.addParamPanel.$element.hide(); - this.languagePanel.$element.show(); - break; + propInput.setValue( value ); + } } - }; + } else { + // Empty the input + if ( prop.type === 'select' ) { + propInput.selectItem( propInput.getItemFromData( prop['default'] ) ); + } else { + propInput.setValue( '' ); + } + } +}; - /** - * @inheritDoc - */ - TemplateDataDialog.prototype.getActionProcess = function ( action ) { - if ( action === 'back' ) { - return new OO.ui.Process( function () { - this.switchPanels( 'listParams' ); - }, this ); +/** + * Add parameter to the list + * @param {string} paramKey Parameter key in the model + * @param {Object} paramData Parameter data + */ +mw.TemplateData.Dialog.prototype.addParamToSelectWidget = function ( paramKey ) { + var paramItem, + data = this.model.getParamData( paramKey ); + + paramItem = new mw.TemplateData.OptionWidget( { + data: { + key: paramKey, + name: data.name, + aliases: data.aliases, + description: this.model.getParamDescription( paramKey, this.language ) + }, + $: this.$ + } ); + + this.paramSelectWidget.addItems( [ paramItem ] ); +}; + +/** + * Create the information page about individual parameters + * @returns {jQuery} Editable details page for the parameter + */ +mw.TemplateData.Dialog.prototype.createParamDetails = function () { + var props, type, propInput, config, paramProperties, + paramFieldset, + typeItemArray = []; + + paramProperties = mw.TemplateData.Model.static.getAllProperties( true ); + + // Fieldset + paramFieldset = new OO.ui.FieldsetLayout( { + $: this.$ + } ); + + for ( props in paramProperties ) { + config = { + $: this.$, + multiline: paramProperties[props].multiline + }; + if ( paramProperties[props].multiline ) { + config.autosize = true; } - if ( action === 'add' ) { - return new OO.ui.Process( function () { - this.switchPanels( 'addParam' ); - }, this ); + // Create the property inputs + switch ( props ) { + case 'type': + propInput = new OO.ui.DropdownWidget( config ); + for ( type in paramProperties[props].children ) { + typeItemArray.push( new OO.ui.OptionWidget( { + data: paramProperties[props].children[type], + $: this.$, + label: mw.msg( 'templatedata-modal-table-param-type-' + paramProperties[props].children[type] ) + } ) ); + } + propInput.getMenu().addItems( typeItemArray ); + break; + case 'deprecated': + case 'required': + case 'suggested': + propInput = new OO.ui.ToggleSwitchWidget( config ); + break; + default: + propInput = new OO.ui.TextInputWidget( config ); + break; } - if ( action === 'delete' ) { - return new OO.ui.Process( function () { - this.model.deleteParam( this.selectedParamKey ); - this.switchPanels( 'listParams' ); - }, this ); + + this.propInputs[props] = propInput; + + propInput.$element + .addClass( 'tdg-TemplateDataDialog-paramInput tdg-TemplateDataDialog-paramList-' + props ); + + this.propFieldLayout[props] = new OO.ui.FieldLayout( propInput, { + align: 'left', + label: mw.msg( 'templatedata-modal-table-param-' + props ) + } ); + // Event + if ( props === 'type' ) { + propInput.getMenu().connect( this, { choose: [ 'onParamPropertyInputChange', props ] } ); + } else { + propInput.connect( this, { change: [ 'onParamPropertyInputChange', props ] } ); } - if ( action === 'apply' ) { - return new OO.ui.Process( function () { - this.emit( 'apply', this.model.outputTemplateDataString() ); - this.close( { action: action } ); - }, this ); + // Append to parameter section + paramFieldset.$element.append( this.propFieldLayout[props].$element ); + } + // Update parameter property fields with languages + this.updateParamDetailsLanguage( this.language ); + return paramFieldset.$element; +}; + +/** + * Update the labels for parameter property inputs that include language, so + * they show the currently used language. + * @param {string} [lang] Language. If not used, will use currently defined + * language. + */ +mw.TemplateData.Dialog.prototype.updateParamDetailsLanguage = function ( lang ) { + var i, prop, label, + languageProps = mw.TemplateData.Model.static.getPropertiesWithLanguage(); + lang = lang || this.language; + + for ( i = 0; i < languageProps.length; i++ ) { + prop = languageProps[i]; + label = mw.msg( 'templatedata-modal-table-param-' + prop, lang ); + this.propFieldLayout[prop].setLabel( label ); + } +}; + +/** + * Override getBodyHeight to create a tall dialog relative to the screen. + * @return {number} Body height + */ +mw.TemplateData.Dialog.prototype.getBodyHeight = function () { + return window.innerHeight - 200; +}; + +/** + * Show or hide the notice message in the dialog with a set message. + * @param {string} type Which notice label to show: 'list' or 'global' + * @param {Boolean} isShowing Show or hide the message + * @param {string} status Message status 'error' or 'success' + * @param {string|string[]} noticeMessage The message to display + */ +mw.TemplateData.Dialog.prototype.toggleNoticeMessage = function ( type, isShowing, status, noticeMessage ) { + var noticeReference, + $message; + + type = type || 'list'; + + // Hide all + this.noticeLabel.$element.hide(); + this.paramEditNoticeLabel.$element.hide(); + this.paramListNoticeLabel.$element.hide(); + + if ( noticeMessage ) { + // See which error to display + if ( type === 'global' ) { + noticeReference = this.noticeLabel; + } else if ( type === 'edit' ) { + noticeReference = this.paramEditNoticeLabel; + } else { + noticeReference = this.paramListNoticeLabel; } - // Fallback to parent handler - return TemplateDataDialog.super.prototype.getActionProcess.call( this, action ); - }; -}( mediaWiki ) ); + isShowing = isShowing || !noticeReference.$element.is( ':visible' ); + + if ( $.type( noticeMessage ) === 'array' ) { + $message = $( '

' ); + $.each( noticeMessage, function ( i, msg ) { + $message.append( $( '

' ).text( msg ) ); + } ); + noticeReference.setLabel( $message ); + } else { + noticeReference.setLabel( noticeMessage ); + } + noticeReference.$element + .toggle( isShowing ) + .toggleClass( 'errorbox', status === 'error' ) + .toggleClass( 'successbox', status === 'success' ); + } +}; + +/** + * Import parameters from the source code. + */ +mw.TemplateData.Dialog.prototype.importParametersFromTemplateCode = function () { + var combinedMessage = [], + state = 'success', + response = this.model.importSourceCodeParameters(); + // Repopulate the list + this.repopulateParamSelectWidget(); + + if ( response.existing.length > 0 ) { + combinedMessage.push( mw.msg( 'templatedata-modal-errormsg-import-paramsalreadyexist', response.existing.join( mw.msg( 'comma-separator' ) ) ) ); + } + + if ( response.imported.length === 0 ) { + combinedMessage.push( mw.msg( 'templatedata-modal-errormsg-import-noparams' ) ); + state = 'error'; + } else { + combinedMessage.push( mw.msg( 'templatedata-modal-notice-import-numparams', response.imported.length, response.imported.join( mw.msg( 'comma-separator' ) ) ) ); + } + + this.toggleNoticeMessage( 'list', true, state, combinedMessage ); +}; + +/** + * @inheritDoc + */ +mw.TemplateData.Dialog.prototype.getSetupProcess = function ( data ) { + return mw.TemplateData.Dialog.super.prototype.getSetupProcess.call( this, data ) + .next( function () { + this.reset(); + // Hide the panels and display a spinner + this.$spinner.show(); + this.panels.$element.hide(); + this.toggleNoticeMessage( 'global', false ); + this.toggleNoticeMessage( 'list', false ); + + // Start with parameter list + this.switchPanels( 'listParams' ); + this.model = new mw.TemplateData.Model( data.config ); + + // Events + this.model.connect( this, { + 'change-description': 'onModelChangeDescription', + 'change-paramOrder': 'onModelChangeParamOrder', + 'add-paramOrder': 'onModelAddKeyParamOrder' + } ); + + // Load the model according to the string + this.model.loadModel( data.wikitext ) + .done( $.proxy( function () { + var i, + language = this.model.getDefaultLanguage(), + languageItems = [], + languages = this.model.getExistingLanguageCodes(); + + this.setupDetailsFromModel(); + + // Fill up the language selection + if ( + languages.length === 0 || + $.inArray( language, languages ) === -1 + ) { + // Add the default language + languageItems.push( new OO.ui.OptionWidget( { + data: language, + $: this.$, + label: $.uls.data.getAutonym( language ) + } ) ); + this.availableLanguages.push( language ); + } + + // Add all available languages + for ( i = 0; i < languages.length; i++ ) { + languageItems.push( new OO.ui.OptionWidget( { + data: languages[i], + $: this.$, + label: $.uls.data.getAutonym( languages[i] ) + } ) ); + // Store available languages + this.availableLanguages.push( languages[i] ); + } + this.languageDropdownWidget.getMenu().addItems( languageItems ); + // Trigger the initial language choice + this.languageDropdownWidget.getMenu().chooseItem( this.languageDropdownWidget.getMenu().getItemFromData( language ) ); + + // Show the panel + this.$spinner.hide(); + this.panels.$element.show(); + }, this ) ) + .fail( $.proxy( function () { + // Show error + this.actions.setMode( 'error' ); + this.$spinner.hide(); + this.toggleNoticeMessage( 'global', true, 'error', mw.msg( 'templatedata-errormsg-jsonbadformat' ) ); + }, this ) ); + + }, this ); +}; + +/** + * Set up the list of parameters from the model. This should happen + * after initialization of the model. + */ +mw.TemplateData.Dialog.prototype.setupDetailsFromModel = function () { + // Set up description + this.descriptionInput.setValue( this.model.getTemplateDescription( this.language ) ); + // Repopulate the parameter list + this.repopulateParamSelectWidget(); +}; + +/** + * Switch between stack layout panels + * @param {string} panel Panel key to switch to + */ +mw.TemplateData.Dialog.prototype.switchPanels = function ( panel ) { + switch ( panel ) { + case 'listParams': + this.actions.setMode( 'list' ); + this.panels.setItem( this.listParamsPanel ); + // Reset message + this.toggleNoticeMessage( 'list', false ); + // Deselect parameter + this.paramSelectWidget.selectItem( null ); + // Repopulate the list to account for any changes + if ( this.model ) { + this.repopulateParamSelectWidget(); + } + // Hide/show panels + this.listParamsPanel.$element.show(); + this.editParamPanel.$element.hide(); + this.addParamPanel.$element.hide(); + this.languagePanel.$element.hide(); + break; + case 'editParam': + this.actions.setMode( 'edit' ); + this.panels.setItem( this.editParamPanel ); + // Deselect parameter + this.paramSelectWidget.selectItem( null ); + // Hide/show panels + this.listParamsPanel.$element.hide(); + this.languagePanel.$element.hide(); + this.addParamPanel.$element.hide(); + this.editParamPanel.$element.show(); + break; + case 'addParam': + this.actions.setMode( 'add' ); + this.panels.setItem( this.addParamPanel ); + // Hide/show panels + this.listParamsPanel.$element.hide(); + this.editParamPanel.$element.hide(); + this.languagePanel.$element.hide(); + this.addParamPanel.$element.show(); + break; + case 'language': + this.actions.setMode( 'language' ); + this.panels.setItem( this.languagePanel ); + // Hide/show panels + this.listParamsPanel.$element.hide(); + this.editParamPanel.$element.hide(); + this.addParamPanel.$element.hide(); + this.languagePanel.$element.show(); + break; + } +}; + +/** + * @inheritDoc + */ +mw.TemplateData.Dialog.prototype.getActionProcess = function ( action ) { + if ( action === 'back' ) { + return new OO.ui.Process( function () { + this.switchPanels( 'listParams' ); + }, this ); + } + if ( action === 'add' ) { + return new OO.ui.Process( function () { + this.switchPanels( 'addParam' ); + }, this ); + } + if ( action === 'delete' ) { + return new OO.ui.Process( function () { + this.model.deleteParam( this.selectedParamKey ); + this.switchPanels( 'listParams' ); + }, this ); + } + if ( action === 'apply' ) { + return new OO.ui.Process( function () { + this.emit( 'apply', this.model.outputTemplateDataString() ); + this.close( { action: action } ); + }, this ); + } + // Fallback to parent handler + return mw.TemplateData.Dialog.super.prototype.getActionProcess.call( this, action ); +}; diff --git a/modules/widgets/ext.templateDataGenerator.dragDropItemWidget.js b/modules/widgets/ext.templateDataGenerator.dragDropItemWidget.js index 612e6f08..e2a4c188 100644 --- a/modules/widgets/ext.templateDataGenerator.dragDropItemWidget.js +++ b/modules/widgets/ext.templateDataGenerator.dragDropItemWidget.js @@ -9,12 +9,12 @@ * @param {Mixed} data Option data * @param {Object} [config] Configuration options */ -TemplateDataDragDropItemWidget = function TemplateDataDragDropItemWidget( config ) { +mw.TemplateData.DragDropItemWidget = function mwTemplateDataDragDropItemWidget( config ) { // Configuration initialization config = config || {}; // Parent constructor - TemplateDataDragDropItemWidget.super.call( this, $.extend( {}, { icon: 'parameter' }, config ) ); + mw.TemplateData.DragDropItemWidget.super.call( this, $.extend( {}, { icon: 'parameter' }, config ) ); // Mixin constructors OO.ui.DraggableElement.call( this, config ); @@ -26,5 +26,5 @@ TemplateDataDragDropItemWidget = function TemplateDataDragDropItemWidget( config /* Setup */ -OO.inheritClass( TemplateDataDragDropItemWidget, OO.ui.DecoratedOptionWidget ); -OO.mixinClass( TemplateDataDragDropItemWidget, OO.ui.DraggableElement ); +OO.inheritClass( mw.TemplateData.DragDropItemWidget, OO.ui.DecoratedOptionWidget ); +OO.mixinClass( mw.TemplateData.DragDropItemWidget, OO.ui.DraggableElement ); diff --git a/modules/widgets/ext.templateDataGenerator.dragDropWidget.js b/modules/widgets/ext.templateDataGenerator.dragDropWidget.js index c48af7f7..b00bf45e 100644 --- a/modules/widgets/ext.templateDataGenerator.dragDropWidget.js +++ b/modules/widgets/ext.templateDataGenerator.dragDropWidget.js @@ -11,12 +11,12 @@ * @param {Object} [config] Configuration options * @cfg {OO.ui.OptionWidget[]} [items] Options to add */ -TemplateDataDragDropWidget = function TemplateDataDragDropWidget( config ) { +mw.TemplateData.DragDropWidget = function mwTemplateDataDragDropWidget( config ) { // Configuration initialization config = config || {}; // Parent constructor - TemplateDataDragDropWidget.super.call( this, config ); + mw.TemplateData.DragDropWidget.super.call( this, config ); // Mixin constructors OO.ui.DraggableGroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) ); @@ -27,14 +27,14 @@ TemplateDataDragDropWidget = function TemplateDataDragDropWidget( config ) { /* Setup */ -OO.inheritClass( TemplateDataDragDropWidget, OO.ui.Widget ); -OO.mixinClass( TemplateDataDragDropWidget, OO.ui.DraggableGroupElement ); +OO.inheritClass( mw.TemplateData.DragDropWidget, OO.ui.Widget ); +OO.mixinClass( mw.TemplateData.DragDropWidget, OO.ui.DraggableGroupElement ); /** * Get an array of keys based on the current items, in order * @return {string[]} Array of keys */ -TemplateDataDragDropWidget.prototype.getKeyArray = function () { +mw.TemplateData.DragDropWidget.prototype.getKeyArray = function () { var i, len, arr = []; @@ -51,7 +51,7 @@ TemplateDataDragDropWidget.prototype.getKeyArray = function () { * @param {string} key Unique key * @param {[type]} newIndex New index */ -TemplateDataDragDropWidget.prototype.reorderKey = function ( key, newIndex ) { +mw.TemplateData.DragDropWidget.prototype.reorderKey = function ( key, newIndex ) { var i, len, item; // Get the item that belongs to this key diff --git a/modules/widgets/ext.templateDataGenerator.languageResultWidget.js b/modules/widgets/ext.templateDataGenerator.languageResultWidget.js index 9f889faa..3e0dcbf1 100644 --- a/modules/widgets/ext.templateDataGenerator.languageResultWidget.js +++ b/modules/widgets/ext.templateDataGenerator.languageResultWidget.js @@ -8,7 +8,7 @@ * @constructor * @param {Object} [config] Configuration options */ -TemplateDataLanguageResultWidget = function TemplateDataLanguageResultWidget( config ) { +mw.TemplateData.LanguageResultWidget = function mwTemplateDataLanguageResultWidget( config ) { // Parent constructor OO.ui.OptionWidget.call( this, config ); @@ -21,7 +21,7 @@ TemplateDataLanguageResultWidget = function TemplateDataLanguageResultWidget( co /* Inheritance */ -OO.inheritClass( TemplateDataLanguageResultWidget, OO.ui.OptionWidget ); +OO.inheritClass( mw.TemplateData.LanguageResultWidget, OO.ui.OptionWidget ); /* Methods */ @@ -32,7 +32,7 @@ OO.inheritClass( TemplateDataLanguageResultWidget, OO.ui.OptionWidget ); * @param {string} [matchedProperty] Data property which matched the query text * @chainable */ -TemplateDataLanguageResultWidget.prototype.updateLabel = function ( query, matchedProperty ) { +mw.TemplateData.LanguageResultWidget.prototype.updateLabel = function ( query, matchedProperty ) { var $highlighted, data = this.getData(); // Reset text @@ -59,7 +59,7 @@ TemplateDataLanguageResultWidget.prototype.updateLabel = function ( query, match * @param {string} query Query to find * @returns {jQuery} Text with query substring wrapped in highlighted span */ -TemplateDataLanguageResultWidget.prototype.highlightQuery = function ( text, query ) { +mw.TemplateData.LanguageResultWidget.prototype.highlightQuery = function ( text, query ) { var $result = this.$( '' ), offset = text.toLowerCase().indexOf( query.toLowerCase() ); diff --git a/modules/widgets/ext.templateDataGenerator.languageSearchWidget.js b/modules/widgets/ext.templateDataGenerator.languageSearchWidget.js index c692dae5..94ab009f 100644 --- a/modules/widgets/ext.templateDataGenerator.languageSearchWidget.js +++ b/modules/widgets/ext.templateDataGenerator.languageSearchWidget.js @@ -8,7 +8,7 @@ * @constructor * @param {Object} [config] Configuration options */ -TemplateDataLanguageSearchWidget = function TemplateDataLanguageSearchWidget( config ) { +mw.TemplateData.LanguageSearchWidget = function mwTemplateDataLanguageSearchWidget( config ) { // Configuration intialization config = $.extend( { placeholder: mw.msg( 'templatedata-modal-search-input-placeholder' ) @@ -26,7 +26,7 @@ TemplateDataLanguageSearchWidget = function TemplateDataLanguageSearchWidget( co for ( i = 0, l = languageCodes.length; i < l; i++ ) { languageCode = languageCodes[i]; this.languageResultWidgets.push( - new TemplateDataLanguageResultWidget( { + new mw.TemplateData.LanguageResultWidget( { data: { code: languageCode, name: $.uls.data.getAutonym( languageCode ), @@ -43,14 +43,14 @@ TemplateDataLanguageSearchWidget = function TemplateDataLanguageSearchWidget( co /* Inheritance */ -OO.inheritClass( TemplateDataLanguageSearchWidget, OO.ui.SearchWidget ); +OO.inheritClass( mw.TemplateData.LanguageSearchWidget, OO.ui.SearchWidget ); /* Methods */ /** * @inheritdoc */ -TemplateDataLanguageSearchWidget.prototype.onQueryChange = function () { +mw.TemplateData.LanguageSearchWidget.prototype.onQueryChange = function () { // Parent method OO.ui.SearchWidget.prototype.onQueryChange.call( this ); @@ -61,7 +61,7 @@ TemplateDataLanguageSearchWidget.prototype.onQueryChange = function () { /** * Update search results from current query */ -TemplateDataLanguageSearchWidget.prototype.addResults = function () { +mw.TemplateData.LanguageSearchWidget.prototype.addResults = function () { var i, iLen, j, jLen, languageResult, data, matchedProperty, matchProperties = ['name', 'autonym', 'code'], query = this.query.getValue().trim(), @@ -107,6 +107,6 @@ TemplateDataLanguageSearchWidget.prototype.addResults = function () { * @param {string} value Text * @returns {string} Text escaped for use in regex */ -TemplateDataLanguageSearchWidget.static.escapeRegex = function ( value ) { +mw.TemplateData.LanguageSearchWidget.static.escapeRegex = function ( value ) { return value.replace( /[\-\[\]{}()*+?.,\\\^$\|#\s]/g, '\\$&' ); }; diff --git a/modules/widgets/ext.templateDataGenerator.optionImportWidget.js b/modules/widgets/ext.templateDataGenerator.optionImportWidget.js index 91939d95..1d8a526f 100644 --- a/modules/widgets/ext.templateDataGenerator.optionImportWidget.js +++ b/modules/widgets/ext.templateDataGenerator.optionImportWidget.js @@ -3,11 +3,11 @@ * @extends {OO.ui.DecoratedOptionWidget} * @param {Object} config Dialog configuration object */ -TemplateDataOptionImportWidget = function TemplateDataOptionImportWidget( config ) { +mw.TemplateData.OptionImportWidget = function mwTemplateDataOptionImportWidget( config ) { config = config || {}; // Parent constructor - TemplateDataOptionImportWidget.super.call( this, $.extend( {}, config, { icon: 'parameter-set' } ) ); + mw.TemplateData.OptionImportWidget.super.call( this, $.extend( {}, config, { icon: 'parameter-set' } ) ); this.params = config.params; @@ -16,14 +16,14 @@ TemplateDataOptionImportWidget = function TemplateDataOptionImportWidget( config this.buildParamLabel(); }; -OO.inheritClass( TemplateDataOptionImportWidget, OO.ui.DecoratedOptionWidget ); +OO.inheritClass( mw.TemplateData.OptionImportWidget, OO.ui.DecoratedOptionWidget ); /** * Build the parameter label in the parameter select widget * @param {Object} paramData Parameter data * @return {jQuery} Label element */ -TemplateDataOptionImportWidget.prototype.buildParamLabel = function () { +mw.TemplateData.OptionImportWidget.prototype.buildParamLabel = function () { var paramNames = this.params.slice( 0, 9 ).join( mw.msg( 'comma-separator' ) ), $paramName = this.$( '

' ) .addClass( 'tdg-TemplateDataOptionWidget-param-name' ), diff --git a/modules/widgets/ext.templateDataGenerator.optionWidget.js b/modules/widgets/ext.templateDataGenerator.optionWidget.js index ab4d795e..6ddfede7 100644 --- a/modules/widgets/ext.templateDataGenerator.optionWidget.js +++ b/modules/widgets/ext.templateDataGenerator.optionWidget.js @@ -3,13 +3,13 @@ * @extends {OO.ui.DecoratedOptionWidget} * @param {Object} config Dialog configuration object */ -TemplateDataOptionWidget = function TemplateDataOptionWidget( config ) { +mw.TemplateData.OptionWidget = function mwTemplateDataOptionWidget( config ) { var data; config = config || {}; data = config.data || {}; // Parent constructor - TemplateDataOptionWidget.super.call( this, $.extend( {}, config, { data: data.key, icon: 'parameter' } ) ); + mw.TemplateData.OptionWidget.super.call( this, $.extend( {}, config, { data: data.key, icon: 'parameter' } ) ); this.key = data.key; this.name = data.name; @@ -21,14 +21,14 @@ TemplateDataOptionWidget = function TemplateDataOptionWidget( config ) { this.buildParamLabel(); }; -OO.inheritClass( TemplateDataOptionWidget, OO.ui.DecoratedOptionWidget ); +OO.inheritClass( mw.TemplateData.OptionWidget, OO.ui.DecoratedOptionWidget ); /** * Build the parameter label in the parameter select widget * @param {Object} paramData Parameter data * @return {jQuery} Label element */ -TemplateDataOptionWidget.prototype.buildParamLabel = function () { +mw.TemplateData.OptionWidget.prototype.buildParamLabel = function () { var i, len, $paramName = this.$( '
' ) .addClass( 'tdg-TemplateDataOptionWidget-param-name' ), diff --git a/tests/ext.templateData.tests.js b/tests/ext.templateData.tests.js index ad374002..2853985f 100644 --- a/tests/ext.templateData.tests.js +++ b/tests/ext.templateData.tests.js @@ -2,7 +2,7 @@ * TemplateData Generator GUI Unit Tests */ -( function ( $ ) { +( function () { 'use strict'; QUnit.module( 'ext.templateData', QUnit.newMwEnvironment() ); @@ -12,7 +12,7 @@ resultDescMockLang = {}, resultDescBothLang = {}, currLanguage = mw.config.get( 'wgContentLanguage' ) || 'en', - model = new TemplateDataModel(), + model = new mw.TemplateData.Model(), originalWikitext = 'Some text here that is not templatedata information.' + '' + '{' + @@ -365,7 +365,7 @@ for ( i = 0; i < tests.compare.length; i++ ) { testVars = tests.compare[i]; assert.equal( - TemplateDataModel.static.compare( testVars.obj1, testVars.obj2, testVars.allowSubset ), + mw.TemplateData.Model.static.compare( testVars.obj1, testVars.obj2, testVars.allowSubset ), testVars.result, testVars.msg ); @@ -375,7 +375,7 @@ for ( i = 0; i < tests.splitAndTrimArray.length; i++ ) { testVars = tests.splitAndTrimArray[i]; assert.deepEqual( - TemplateDataModel.static.splitAndTrimArray( testVars.string, testVars.delim ), + mw.TemplateData.Model.static.splitAndTrimArray( testVars.string, testVars.delim ), testVars.result, testVars.msg ); @@ -385,7 +385,7 @@ for ( i = 0; i < tests.arrayUnionWithoutEmpty.length; i++ ) { testVars = tests.arrayUnionWithoutEmpty[i]; assert.deepEqual( - TemplateDataModel.static.arrayUnionWithoutEmpty.apply( testVars, testVars.arrays ), + mw.TemplateData.Model.static.arrayUnionWithoutEmpty.apply( testVars, testVars.arrays ), testVars.result, testVars.msg ); @@ -393,12 +393,12 @@ // Props assert.deepEqual( - TemplateDataModel.static.getAllProperties( false ), + mw.TemplateData.Model.static.getAllProperties( false ), tests.props.all, 'All properties' ); assert.deepEqual( - TemplateDataModel.static.getPropertiesWithLanguage(), + mw.TemplateData.Model.static.getPropertiesWithLanguage(), tests.props.language, 'Language properties' ); @@ -571,4 +571,4 @@ } ); } ); -}( jQuery ) ); +}() );