Trevor Parscal has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/67573


Change subject: Template refactor
......................................................................

Template refactor

Objectives:

* Make a proper data structure for template collections
* Abstract away template data

Changes:

ve.ui.MWTemplateDialogs.js
* Move template spec loading into template collection class
* Add remove button for template parameters
* Add description for templates

ve.dm.*
* Add new template data structures

*.php
* Add links to new files

Change-Id: I3bcf924a3e179cb65f19e833277a39dfd3dad8bd
---
M VisualEditor.php
A modules/ve/dm/templates/ve.dm.MWTemplateCollection.js
A modules/ve/dm/templates/ve.dm.MWTemplateContent.js
A modules/ve/dm/templates/ve.dm.MWTemplateInvocation.js
A modules/ve/dm/templates/ve.dm.MWTemplateParameter.js
A modules/ve/dm/templates/ve.dm.MWTemplatePart.js
A modules/ve/dm/templates/ve.dm.MWTemplateSpec.js
M modules/ve/ui/dialogs/ve.ui.MWTemplateDialog.js
8 files changed, 759 insertions(+), 280 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/73/67573/1

diff --git a/VisualEditor.php b/VisualEditor.php
index 1ba644c..2a25d11 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -557,6 +557,13 @@
                        've/dm/nodes/ve.dm.MWReferenceListNode.js',
                        've/dm/nodes/ve.dm.MWReferenceNode.js',
 
+                       've/dm/templates/ve.dm.MWTemplateSpec.js',
+                       've/dm/templates/ve.dm.MWTemplateCollection.js',
+                       've/dm/templates/ve.dm.MWTemplatePart.js',
+                       've/dm/templates/ve.dm.MWTemplateContent.js',
+                       've/dm/templates/ve.dm.MWTemplateInvocation.js',
+                       've/dm/templates/ve.dm.MWTemplateParameter.js',
+
                        've/ce/nodes/ve.ce.MWInlineImageNode.js',
                        've/ce/nodes/ve.ce.MWBlockImageNode.js',
                        've/ce/nodes/ve.ce.MWImageCaptionNode.js',
diff --git a/modules/ve/dm/templates/ve.dm.MWTemplateCollection.js 
b/modules/ve/dm/templates/ve.dm.MWTemplateCollection.js
new file mode 100644
index 0000000..43f2e53
--- /dev/null
+++ b/modules/ve/dm/templates/ve.dm.MWTemplateCollection.js
@@ -0,0 +1,227 @@
+/*!
+ * VisualEditor DataModel MWTemplateCollection class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/*global mw */
+
+/**
+ * MediaWiki template collection.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.dm.MWTemplateCollection = function () {
+       // Properties
+       this.parts = [];
+       this.specs = {};
+       this.uid = 0;
+};
+
+/* Methods */
+
+/**
+ * Load from collection data, and fetch spec from server.
+ *
+ * @method
+ * @param {Object} data Collection data
+ * @returns {jQuery.Deferred} Deferred object, resolved when spec is loaded
+ */
+ve.dm.MWTemplateCollection.prototype.load = function ( data ) {
+       var i, len, key, part, invocation, template, title,
+               invocations = {};
+
+       // Convert single template format to multiple template format
+       if ( data.params && data.target ) {
+               data = { 'parts': [ { 'template': data } ] };
+       }
+
+       if ( ve.isArray( data.parts ) ) {
+               for ( i = 0, len = data.parts.length; i < len; i++ ) {
+                       part = data.parts[i];
+                       if ( part.template ) {
+                               template = part.template;
+                               invocation = this.addInvocation( 
template.target );
+                               for ( key in template.params ) {
+                                       invocation.addParameter( key, 
template.params[key].wt );
+                               }
+                               if ( template.target.href ) {
+                                       title = template.target.href.replace( 
/^(\.\.?\/)*/, '' );
+                                       if ( !this.specs[title] ) {
+                                               invocations[title] = invocation;
+                                       }
+                               }
+                       } else if ( typeof part === 'string' ) {
+                               this.addContent( part );
+                       }
+               }
+       }
+       return this.fetchSpecs( invocations ).done( ve.bind( ve.extendObject, 
this, this.specs ) );
+};
+
+/**
+ * Fetch template specifications from server.
+ *
+ * @param {Object} invocations List of invocations keyed by title
+ * @returns {jQuery.Deferred} Deferred object, resolved when spec is loaded
+ */
+ve.dm.MWTemplateCollection.prototype.fetchSpecs = function ( invocations ) {
+       var d = $.Deferred(),
+               specs = {},
+               titles = ve.getObjectKeys( invocations );
+
+       // Optimise for empty lists
+       if ( !titles.length ) {
+               setTimeout( d.reject );
+               return d.promise();
+       }
+
+       // Request template data from server
+       $.ajax( {
+               'url': mw.util.wikiScript( 'api' ),
+               'dataType': 'json',
+               'data': {
+                       'format': 'json',
+                       'action': 'templatedata',
+                       'titles': titles.join( '|' )
+               }
+       } )
+               .done( function ( data ) {
+                       var i, len, id, title, spec;
+
+                       if ( data && data.pages ) {
+                               for ( id in data.pages ) {
+                                       spec = new ve.dm.MWTemplateSpec();
+                                       spec.load( data.pages[id] );
+                                       specs[data.pages[id].title] = spec;
+                               }
+                               if ( data.normalized ) {
+                                       for ( i = 0, len = 
data.normalized.length; i < len; i++ ) {
+                                               specs[data.normalized[i].from] 
= specs[data.normalized[i].to];
+                                       }
+                               }
+                               for ( title in invocations ) {
+                                       if ( !specs[title] ) {
+                                               spec = new 
ve.dm.MWTemplateSpec();
+                                               spec.fill( invocations[title] );
+                                               specs[title] = spec;
+                                       }
+                               }
+                               d.resolve( specs );
+                       } else {
+                               d.reject( 'unavailable', arguments );
+                       }
+               } )
+               .fail( function () {
+                       d.reject( 'http', arguments );
+               } );
+
+       return d.promise();
+};
+
+/**
+ * Get plain object represntation of template collection.
+ *
+ * @method
+ * @returns {Object} Plain object representation
+ */
+ve.dm.MWTemplateCollection.prototype.getPlainObject = function () {
+       var i, len, part, template, name, params,
+               obj = { 'parts': [] };
+
+       for ( i = 0, len = this.parts.length; i < len; i++ ) {
+               part = this.parts[i];
+               if ( part instanceof ve.dm.MWTemplateInvocation ) {
+                       template = { 'target': part.getTarget(), 'params': {} };
+                       params = part.getParameters();
+                       for ( name in params ) {
+                               template.params[name] = { 'wt': 
params[name].getValue() };
+                       }
+                       obj.parts.push( { 'template': template } );
+               } else if ( part instanceof ve.dm.MWTemplateContent ) {
+                       obj.parts.push( part.getValue() );
+               }
+       }
+
+       // Use single-part format when possible
+       if ( obj.parts.length === 1 ) {
+               obj = this.content.parts[0].template;
+       }
+
+       return obj;
+};
+
+/**
+ * Get a unique ID for the collection.
+ *
+ * @method
+ * @returns {number} Unique ID
+ */
+ve.dm.MWTemplateCollection.prototype.getUniqueId = function () {
+       return this.uid++;
+};
+
+/**
+ * Add content part.
+ *
+ * @method
+ * @param {string} value Content value
+ * @param {number} [index] Specific index to add content at
+ * @returns {ve.dm.MWTemplateContent} Added content part
+ */
+ve.dm.MWTemplateCollection.prototype.addContent = function ( value, index ) {
+       var part = new ve.dm.MWTemplateContent( this, value );
+       this.parts.splice( index === undefined ? this.parts.length : index, 0, 
part );
+       return part;
+};
+
+/**
+ * Add invocation part.
+ *
+ * @method
+ * @param {string} name Invocation name
+ * @param {number} [index] Specific index to add content at
+ * @returns {ve.dm.MWTemplateInvocation} Added invocation part
+ */
+ve.dm.MWTemplateCollection.prototype.addInvocation = function ( name, index ) {
+       var part = new ve.dm.MWTemplateInvocation( this, name );
+       this.parts.splice( index === undefined ? this.parts.length : index, 0, 
part );
+       return part;
+};
+
+/**
+ * Remove a part.
+ *
+ * @method
+ * @param {ve.dm.MWTemplatePart} part Template part
+ */
+ve.dm.MWTemplateCollection.prototype.removePart = function ( part ) {
+       var index = this.parts.indexOf( part );
+       if ( index !== -1 ) {
+               this.parts.splice( index, 1 );
+       }
+};
+
+/**
+ * Get all parts.
+ *
+ * @method
+ * @returns {ve.dm.MWTemplatePart[]} Parts in collection
+ */
+ve.dm.MWTemplateCollection.prototype.getParts = function () {
+       return this.parts;
+};
+
+/**
+ * Get a template specification.
+ *
+ * @method
+ * @param {string} name Template name
+ * @return {ve.dm.MWTemplateSpec} Template spec
+ */
+ve.dm.MWTemplateCollection.prototype.getTemplateSpec = function ( name ) {
+       return this.specs[name];
+};
diff --git a/modules/ve/dm/templates/ve.dm.MWTemplateContent.js 
b/modules/ve/dm/templates/ve.dm.MWTemplateContent.js
new file mode 100644
index 0000000..02f3497
--- /dev/null
+++ b/modules/ve/dm/templates/ve.dm.MWTemplateContent.js
@@ -0,0 +1,47 @@
+/*!
+ * VisualEditor DataModel MWTemplateContent class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * MediaWiki template content.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.dm.MWTemplateContent = function ( collection, value ) {
+       // Parent constructor
+       ve.dm.MWTemplatePart.call( this, collection );
+
+       // Properties
+       this.value = value || '';
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.dm.MWTemplateContent, ve.dm.MWTemplatePart );
+
+/* Methods */
+
+/**
+ * Get content value.
+ *
+ * @method
+ * @returns {string} Content value
+ */
+ve.dm.MWTemplateContent.prototype.getValue = function () {
+       return this.value;
+};
+
+/**
+ * Set content value.
+ *
+ * @method
+ * @param {string} value Content value
+ */
+ve.dm.MWTemplateContent.prototype.setValue = function ( value ) {
+       this.value = value;
+};
diff --git a/modules/ve/dm/templates/ve.dm.MWTemplateInvocation.js 
b/modules/ve/dm/templates/ve.dm.MWTemplateInvocation.js
new file mode 100644
index 0000000..7d51e3d
--- /dev/null
+++ b/modules/ve/dm/templates/ve.dm.MWTemplateInvocation.js
@@ -0,0 +1,153 @@
+/*!
+ * VisualEditor DataModel MWTemplateInvocation class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * MediaWiki template invocation.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.dm.MWTemplateInvocation = function ( collection, target ) {
+       // Parent constructor
+       ve.dm.MWTemplatePart.call( this, collection );
+
+       // Properties
+       this.target = target;
+       this.name = ( target.href && target.href.replace( /^(\.\.?\/)*/, '' ) ) 
||
+               ( '#!/part/' + collection.getUniqueId() );
+       this.sequence = null;
+       this.parameters = {};
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.dm.MWTemplateInvocation, ve.dm.MWTemplatePart );
+
+/* Methods */
+
+/**
+ * Get invocation target.
+ *
+ * @method
+ * @returns {Object} Invocation target
+ */
+ve.dm.MWTemplateInvocation.prototype.getTarget = function () {
+       return this.target;
+};
+
+/**
+ * Get invocation name.
+ *
+ * @method
+ * @returns {string} Invocation name
+ */
+ve.dm.MWTemplateInvocation.prototype.getName = function () {
+       return this.name;
+};
+
+/**
+ * Get invocation label.
+ *
+ * @method
+ * @returns {string} Invocation label
+ */
+ve.dm.MWTemplateInvocation.prototype.getLabel = function () {
+       return ( this.target.href && this.target.href.replace( /^(\.\.?\/)*/, 
'' ) ) || this.target.wt;
+};
+
+/**
+ * Get template specification.
+ *
+ * @method
+ * @returns {ve.dm.MWTemplateSpec} Template specification
+ */
+ve.dm.MWTemplateInvocation.prototype.getSpec = function () {
+       return this.collection.getTemplateSpec( this.name );
+};
+
+/**
+ * Get template description.
+ *
+ * @method
+ * @returns {string} Template description
+ */
+ve.dm.MWTemplateInvocation.prototype.getDescription = function () {
+       var spec = this.getSpec();
+       return spec.description ? spec.description.en : null;
+};
+
+/**
+ * Get all parameters.
+ *
+ * @method
+ * @returns {Object<string,ve.dm.MWTemplateParameter>} Parameters keyed by name
+ */
+ve.dm.MWTemplateInvocation.prototype.getParameters = function () {
+       return this.parameters;
+};
+
+/**
+ * Get a parameter.
+ *
+ * @method
+ * @param {string} name Parameter name
+ * @returns {ve.dm.MWTemplateParameter} Parameter
+ */
+ve.dm.MWTemplateInvocation.prototype.getParameter = function ( name ) {
+       return this.parameters[name];
+};
+
+/**
+ * Get ordered list of parameter names.
+ *
+ * @method
+ * @returns {string[]} List of parameter names
+ */
+ve.dm.MWTemplateInvocation.prototype.getParameterNames = function () {
+       if ( !this.sequence ) {
+               this.sequence = ve.getObjectKeys( this.parameters ).sort( 
function ( a, b ) {
+                       if ( isNaN( a ) && isNaN( b ) ) {
+                               // Two strings
+                               return a < b ? -1 : a === b ? 0 : 1;
+                       } else if ( isNaN( a ) ) {
+                               // A is a string
+                               return 1;
+                       } else if ( isNaN( b ) ) {
+                               // B is a string
+                               return -1;
+                       } else {
+                               // Two numbers
+                               return a - b;
+                       }
+               } );
+       }
+       return this.sequence;
+};
+
+/**
+ * Add a parameter to invocation.
+ *
+ * @method
+ * @param {string} name Parameter name
+ * @param {string} value Parameter value
+ */
+ve.dm.MWTemplateInvocation.prototype.addParameter = function ( name, value ) {
+       this.sequence = null;
+       this.parameters[name] = new ve.dm.MWTemplateParameter( this, name, 
value );
+};
+
+/**
+ * Remove parameter from invocation.
+ *
+ * @method
+ * @param {string} name Parameter name
+ */
+ve.dm.MWTemplateInvocation.prototype.removeParameter = function ( name ) {
+       this.sequence = null;
+       delete this.parameters[name];
+};
diff --git a/modules/ve/dm/templates/ve.dm.MWTemplateParameter.js 
b/modules/ve/dm/templates/ve.dm.MWTemplateParameter.js
new file mode 100644
index 0000000..05b166c
--- /dev/null
+++ b/modules/ve/dm/templates/ve.dm.MWTemplateParameter.js
@@ -0,0 +1,104 @@
+/*!
+ * VisualEditor DataModel MWTemplateParameter class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * MediaWiki template parameter.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.dm.MWTemplateParameter = function ( invocation, name, value ) {
+       // Properties
+       this.invocation = invocation;
+       this.name = name;
+       this.value = value || '';
+};
+
+/* Methods */
+
+/**
+ * Get invocation parameter is part of.
+ *
+ * @method
+ * @returns {ve.dm.MWTemplateInvocation} Invocation
+ */
+ve.dm.MWTemplateParameter.prototype.getInvocation = function () {
+       return this.invocation;
+};
+
+/**
+ * Get parameter name.
+ *
+ * @method
+ * @returns {string} Parameter name
+ */
+ve.dm.MWTemplateParameter.prototype.getName = function () {
+       return this.name;
+};
+
+/**
+ * Get parameter specification.
+ *
+ * @method
+ * @returns {Object} Parameter specification
+ */
+ve.dm.MWTemplateParameter.prototype.getSpec = function () {
+       return this.invocation.getSpec().getParameter( this.name );
+};
+
+/**
+ * Get parameter label.
+ *
+ * @method
+ * @returns {string} Parameter label
+ */
+ve.dm.MWTemplateParameter.prototype.getLabel = function () {
+       var spec = this.getSpec();
+       // HACK: Hardcoding the language is evil
+       return spec.label ? spec.label.en : this.name;
+};
+
+/**
+ * Get parameter description.
+ *
+ * @method
+ * @returns {string} Parameter description
+ */
+ve.dm.MWTemplateParameter.prototype.getDescription = function () {
+       var spec = this.getSpec();
+       return spec.description ? spec.description.en : null;
+};
+
+/**
+ * Get parameter value.
+ *
+ * @method
+ * @returns {string} Parameter value
+ */
+ve.dm.MWTemplateParameter.prototype.getValue = function () {
+       return this.value;
+};
+
+/**
+ * Set parameter value.
+ *
+ * @method
+ * @param {string} value Parameter value
+ */
+ve.dm.MWTemplateParameter.prototype.setValue = function ( value ) {
+       this.value = value;
+};
+
+/**
+ * Remove parameter from invocation.
+ *
+ * @method
+ */
+ve.dm.MWTemplateParameter.prototype.remove = function () {
+       this.invocation.removeParameter( this.name );
+};
diff --git a/modules/ve/dm/templates/ve.dm.MWTemplatePart.js 
b/modules/ve/dm/templates/ve.dm.MWTemplatePart.js
new file mode 100644
index 0000000..f8467a2
--- /dev/null
+++ b/modules/ve/dm/templates/ve.dm.MWTemplatePart.js
@@ -0,0 +1,39 @@
+/*!
+ * VisualEditor DataModel MWTemplatePart class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * MediaWiki template part.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.dm.MWTemplatePart = function ( collection ) {
+       // Properties
+       this.collection = collection;
+};
+
+/* Methods */
+
+/**
+ * Get collection part is in.
+ *
+ * @method
+ * @returns {ve.dm.MWTemplateCollection} Collection
+ */
+ve.dm.MWTemplatePart.prototype.getCollection = function () {
+       return this.collection;
+};
+
+/**
+ * Remove part from collection.
+ *
+ * @method
+ */
+ve.dm.MWTemplatePart.prototype.remove = function () {
+       this.collection.removePart( this );
+};
diff --git a/modules/ve/dm/templates/ve.dm.MWTemplateSpec.js 
b/modules/ve/dm/templates/ve.dm.MWTemplateSpec.js
new file mode 100644
index 0000000..702e666
--- /dev/null
+++ b/modules/ve/dm/templates/ve.dm.MWTemplateSpec.js
@@ -0,0 +1,105 @@
+/*!
+ * VisualEditor DataModel MWTemplateSpec class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * MediaWiki template specification.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.dm.MWTemplateSpec = function () {
+       this.description = null;
+       this.params = {};
+       this.sets = [];
+};
+
+/* Methods */
+
+/**
+ * Load from template spec data.
+ *
+ * @method
+ * @param {Object} spec Template spec data
+ */
+ve.dm.MWTemplateSpec.prototype.load = function ( spec ) {
+       var key;
+
+       if ( spec.description ) {
+               this.description = spec.description;
+       }
+       if ( spec.params ) {
+               for ( key in spec.params ) {
+                       this.params[key] = ve.extendObject( true, {
+                               'label': { 'en': key },
+                               'required': false,
+                               'description': null,
+                               'deprecated': false,
+                               'aliases': [],
+                               'default': '',
+                               'type': 'string'
+                       }, spec.params[key] );
+               }
+       }
+       if ( spec.sets ) {
+               this.sets = spec.sets;
+       }
+};
+
+/**
+ * Fill from template invocation.
+ *
+ * @method
+ * @param {ve.dm.MWTemplateInvocation} invocation Template invocation
+ */
+ve.dm.MWTemplateSpec.prototype.fill = function ( invocation ) {
+       var key;
+
+       this.description = invocation.getName();
+       for ( key in invocation.getParameters() ) {
+               this.params[key] = {
+                       'label': { 'en': key },
+                       'required': false,
+                       'description': null,
+                       'deprecated': false,
+                       'aliases': [],
+                       'default': '',
+                       'type': 'string'
+               };
+       }
+};
+
+/**
+ * Get template description.
+ *
+ * @method
+ * @returns {string|null} Template description or null if not available
+ */
+ve.dm.MWTemplateSpec.prototype.getDescription = function () {
+       return this.description;
+};
+
+/**
+ * Get a parameter specification.
+ *
+ * @method
+ * @param {string} name Parameter name
+ * @returns {Object} Parameter specification
+ */
+ve.dm.MWTemplateSpec.prototype.getParameter = function ( name ) {
+       return this.params[name];
+};
+
+/**
+ * Get all parameter specifications.
+ *
+ * @method
+ * @returns {Object} Parameter specifications, keyed by parameter name
+ */
+ve.dm.MWTemplateSpec.prototype.getParameters = function () {
+       return this.params;
+};
diff --git a/modules/ve/ui/dialogs/ve.ui.MWTemplateDialog.js 
b/modules/ve/ui/dialogs/ve.ui.MWTemplateDialog.js
index 2fe0ad0..639e2fd 100644
--- a/modules/ve/ui/dialogs/ve.ui.MWTemplateDialog.js
+++ b/modules/ve/ui/dialogs/ve.ui.MWTemplateDialog.js
@@ -26,10 +26,7 @@
 
        // Properties
        this.node = null;
-       this.content = null;
-       // Buffer for getTemplateSpecs
-       this.fetchQueue = [];
-       this.fetchCallbacks = $.Callbacks();
+       this.collection = null;
 };
 
 /* Inheritance */
@@ -52,65 +49,22 @@
  * @method
  */
 ve.ui.MWTemplateDialog.prototype.onOpen = function () {
-       var i, progress, len, template,
-               dialog = this;
+       // Parent method
+       ve.ui.PagedDialog.prototype.onOpen.call( this );
 
-       function increaseProgress() {
-               progress++;
-               if ( progress === len ) {
-                       dialog.setupPages();
-               }
+       // Sanity check
+       this.node = this.surface.getView().getFocusedNode();
+       if ( !this.node ) {
+               throw new Error( 'No node to edit' );
+       }
+       if ( !( this.node instanceof ve.ce.MWTemplateNode ) ) {
+               throw new Error( 'Focused node is not a template' );
        }
 
-       function makeStoreTemplateSpec( template ) {
-               return function ( specs ) {
-                       template.spec = specs[template.specId];
-                       increaseProgress();
-               };
-       }
-
-       dialog.node = dialog.surface.getView().getFocusedNode();
-       if ( !dialog.node ) {
-               throw new Error( 'No focused node to edit' );
-       }
-
-       // Get content values and copy it so we can safely change it to our 
liking
-       dialog.content = ve.copyObject( dialog.node.getModel().getAttribute( 
'mw' ) );
-
-       // Convert single template format to multiple template format
-       if ( dialog.content.params ) {
-               dialog.content = {
-                       'parts': [
-                               {
-                                       'template': dialog.content
-                               }
-                       ]
-               };
-       }
-
-       progress = -1;
-       len = dialog.content.parts.length;
-
-       // Get all template specs asynchronously
-       for ( i = 0; i < len; i++ ) {
-               template = dialog.content.parts[i].template;
-               if ( template ) {
-                       // Method #getTemplateSpecs will use the part id 
instead of `target.href`
-                       // if the target has no url property (which Parsoid 
omits if the target is
-                       // dynamically generated from wikitext). In that case 
we want each template
-                       // invocation to have its own inferred template spec.
-                       // FIXME centralize regex
-                       template.specId = ( template.target.href && 
template.target.href.replace( /^(\.\.?\/)*/, '' ) ) || ( '#!/part/' + i );
-                       dialog.getTemplateSpecs( template, 
makeStoreTemplateSpec( template ) );
-               } else {
-                       // This is a raw wikitext part (between two associated 
template invocations),
-                       // wrap in object so editor has something to reference
-                       dialog.content.parts[i] = { 'wt': 
dialog.content.parts[i] };
-                       increaseProgress();
-               }
-       }
-
-       increaseProgress();
+       // Initialization
+       this.collection = new ve.dm.MWTemplateCollection();
+       this.collection.load( ve.copyObject( this.node.getModel().getAttribute( 
'mw' ) ) )
+               .always( ve.bind( this.setupPages, this ) );
 };
 
 /**
@@ -119,37 +73,10 @@
  * @param {string} action Action that caused the window to be closed
  */
 ve.ui.MWTemplateDialog.prototype.onClose = function ( action ) {
-       var i, len, parts,
-               surfaceModel = this.surface.getModel();
+       var surfaceModel = this.surface.getModel();
 
        // Save changes
        if ( action === 'apply' ) {
-
-               // Undo non-standard changes we made to the content model in 
#onOpen
-               parts = this.content.parts;
-
-               for ( i = 0, len = parts.length; i < len; i++ ) {
-
-                       // Convert object part with wt property back to string 
part
-                       if ( typeof parts[i].wt === 'string' ) {
-                               parts[i] = parts[i].wt;
-                       }
-
-                       // Remove the properties #onOpen put here
-                       if ( parts[i].template ) {
-                               if ( parts[i].template.spec ) {
-                                       delete parts[i].template.spec;
-                               }
-                               if ( parts[i].template.specId ) {
-                                       delete parts[i].template.specId;
-                               }
-                       }
-               }
-
-               // Restore single template format
-               if ( this.content.parts.length === 1 ) {
-                       this.content = this.content.parts[0].template;
-               }
 
                // TODO: Wrap attribute changes in ve.dm.SurfaceFragment
                surfaceModel.change(
@@ -157,7 +84,7 @@
                                surfaceModel.getDocument(),
                                this.node.getOffset(),
                                'mw',
-                               this.content
+                               this.collection.getPlainObject()
                        )
                );
        }
@@ -177,220 +104,77 @@
  */
 ve.ui.MWTemplateDialog.prototype.setupPages = function () {
        // Build pages from parts
-       var i, len, template, spec, param,
-               parts = this.content.parts;
+       var i, iLen, j, jLen, names,
+               parts = this.collection.getParts();
 
-       // Parent method
-       ve.ui.PagedDialog.prototype.onOpen.call( this );
+       this.clearPages();
 
        // Populate pages
-       for ( i = 0, len = parts.length; i < len; i++ ) {
-               if ( parts[i].template ) {
-                       template = parts[i].template;
-                       spec = template.spec;
+       for ( i = 0, iLen = parts.length; i < iLen; i++ ) {
+               if ( parts[i] instanceof ve.dm.MWTemplateInvocation ) {
                        // Add template page
-                       this.addTemplatePage( 'part_' + i, template );
+                       this.addInvocationPage( 'part/' + i, parts[i] );
                        // Add parameter pages
-                       for ( param in template.params ) {
+                       names = parts[i].getParameterNames();
+                       for ( j = 0, jLen = names.length; j < jLen; j++ ) {
                                this.addParameterPage(
-                                       'part_' + i + '_param_' + param,
-                                       param,
-                                       template.params[param],
-                                       spec.params[param]
+                                       'part/' + i + '/param/' + j, 
parts[i].getParameter( names[j] )
                                );
                        }
-               } else if ( parts[i].wt ) {
+               } else if ( parts[i] instanceof ve.dm.MWTemplateContent ) {
                        // Add wikitext page
-                       this.addWikitextPage( 'part_' + i, parts[i] );
+                       this.addContentPage( 'part/' + i, parts[i] );
                }
        }
 };
 
 /**
- * Backfill missing template data based on template invocation.
- * @param {Object} template Template invocation description
- * @return {Object} Template data blob
- */
-ve.ui.MWTemplateDialog.static.makeTemplateSpec = function ( params ) {
-       var key, blob;
-
-       blob = {
-               description: null,
-               params: {},
-               sets: []
-       };
-       for ( key in params ) {
-               blob.params[key] = {
-                       'label': {
-                               en: key
-                       },
-                       'required': false,
-                       'description': null,
-                       'deprecated': false,
-                       'aliases': [],
-                       'default': '',
-                       'type': 'string'
-
-               };
-       }
-       return blob;
-};
-
-/**
- * Get template specs for one or more templates in the content model.
- *
- * @param {Object[]|undefined} templates List of template invocation 
descriptions. Contains `title` and
- * `params` properties. Or undefined to handle the queue built so far.
- * @param {Function} callback
- * @param {Object} callback.blobs Object containing template data blobs keyed 
by page title.
- */
-ve.ui.MWTemplateDialog.prototype.getTemplateSpecs = function ( templates, 
callback ) {
-       var fillTemplateSpecs,
-               dialog = this;
-
-       // Yield once with setTimeout before fetching to allow batching
-       if ( callback ) {
-               dialog.fetchCallbacks.add( callback );
-       }
-       if ( templates ) {
-               templates = ve.isArray( templates ) ? templates : [ templates ];
-               // Push into the queue
-               dialog.fetchQueue.push.apply( dialog.fetchQueue, templates );
-               setTimeout( function () {
-                       dialog.getTemplateSpecs();
-               } );
-               return;
-       } else if ( dialog.fetchQueue.length ) {
-               // Handle batch queue
-               templates = dialog.fetchQueue.slice();
-               dialog.fetchQueue.length = 0;
-       } else {
-               // This a delayed call but a previous delayed call already
-               // cleared the queue for us. This call has become redundant.
-               return;
-       }
-
-       fillTemplateSpecs = function ( specs ) {
-               var i, len, template, specId;
-               for ( i = 0, len = templates.length; i < len; i++ ) {
-                       template = templates[i];
-                       specId = template.specId;
-                       if ( !specs[specId] ) {
-                               specs[specId] = 
dialog.constructor.static.makeTemplateSpec( template );
-                       }
-               }
-               dialog.fetchCallbacks.fireWith( null, [ specs ] );
-       };
-
-       dialog.fetchTemplateSpecs( templates )
-               .done( fillTemplateSpecs )
-               .fail( function () {
-                       fillTemplateSpecs( {} );
-               } );
-};
-
-/**
- * Fetch template data from the TemplateData API.
- *
- * @param {Object[]} templates List of template invocation descriptions
- * @return {jQuery.Promise}
- */
-ve.ui.MWTemplateDialog.prototype.fetchTemplateSpecs = function ( templates ) {
-       var i, len,
-               d = $.Deferred(),
-               titles = [],
-               specs = {};
-
-       // Collect all titles
-       for ( i = 0, len = templates.length; i < len; i++ ) {
-               if ( templates[i].target.href ) {
-                       // FIXME centralize regex
-                       titles.push( templates[i].target.href.replace( 
/^(\.\.?\/)*/, '' ) );
-               }
-       }
-
-       // Optimise for empty lists
-       if ( !templates.length ) {
-               setTimeout( d.reject );
-               return d.promise();
-       }
-
-       // Request template data from server
-       $.ajax( {
-               'url': mw.util.wikiScript( 'api' ),
-               'dataType': 'json',
-               'data': {
-                       'format': 'json',
-                       'action': 'templatedata',
-                       'titles': titles.join( '|' )
-               }
-       } )
-               .done( function ( data ) {
-                       var i, len, id;
-                       if ( data && data.pages ) {
-                               for ( id in data.pages ) {
-                                       specs[data.pages[id].title] = 
data.pages[id];
-                               }
-                               if ( data.normalized ) {
-                                       for ( i = 0, len = 
data.normalized.length; i < len; i++ ) {
-                                               specs[ data.normalized[i].from 
] = specs[ data.normalized[i].to ];
-                                       }
-                               }
-                               d.resolve( specs );
-                       } else {
-                               d.reject( 'unavailable', arguments );
-                       }
-               } )
-               .fail( function () {
-                       d.reject( 'http', arguments );
-               } );
-
-       return d.promise();
-};
-
-/**
- * Add page for wikitext.
+ * Add page for inter-template content.
  *
  * @param {string} page Unique page name
- * @param {Object} value Parameter value
+ * @param {ve.dm.MWTemplateContent} content Content model
  */
-ve.ui.MWTemplateDialog.prototype.addWikitextPage = function ( page, value ) {
-       var fieldset, textInput;
+ve.ui.MWTemplateDialog.prototype.addContentPage = function ( page, content ) {
+       var valueFieldset, textInput;
 
-       fieldset = new ve.ui.FieldsetLayout( {
+       valueFieldset = new ve.ui.FieldsetLayout( {
                '$$': this.frame.$$,
                'label': 'Content',
                'icon': 'source'
        } );
 
        textInput = new ve.ui.TextInputWidget( { '$$': this.frame.$$, 
'multiline': true } );
-       textInput.setValue( value.wt );
+       textInput.setValue( content.getValue() );
        textInput.on( 'change', function () {
-               value.wt = textInput.getValue();
+               content.setValue( textInput.getValue() );
        } );
        textInput.$.addClass( 've-ui-mwTemplateDialog-input' );
-       fieldset.$.append( textInput.$ );
+       valueFieldset.$.append( textInput.$ );
 
        this.addPage( page, { 'label': 'Content', 'icon': 'source' } );
-       this.pages[page].$.append( fieldset.$ );
+       this.pages[page].$.append( valueFieldset.$ );
 };
 
 /**
  * Add page for a template.
  *
  * @param {string} page Unique page name
- * @param {Object} template Template info
+ * @param {ve.dm.MWTemplateInvocation} invocation Invocation model
  */
-ve.ui.MWTemplateDialog.prototype.addTemplatePage = function ( page, template ) 
{
+ve.ui.MWTemplateDialog.prototype.addInvocationPage = function ( page, 
invocation ) {
        var fieldset,
-               // FIXME centralize regex
-               label = ( template.target.href && template.target.href.replace( 
/^(\.\.?\/)*/, '' ) ) || template.target.wt;
+               label = invocation.getLabel(),
+               description = invocation.getDescription();
 
        fieldset = new ve.ui.FieldsetLayout( {
                '$$': this.frame.$$,
                'label': label,
                'icon': 'template'
        } );
+
+       if ( description ) {
+               fieldset.$.append( $( '<div>' ).text( description ) );
+       }
 
        this.addPage( page, { 'label': label, 'icon': 'template' } );
        this.pages[page].$.append( fieldset.$ );
@@ -400,37 +184,50 @@
  * Add page for a parameter.
  *
  * @param {string} page Unique page name
- * @param {string} name Parameter name
- * @param {Object} value Parameter value
- * @param {Object} spec Parameter specification
+ * @param {ve.dm.MWTemplateParameter} parameter Parameter model
  */
-ve.ui.MWTemplateDialog.prototype.addParameterPage = function ( page, name, 
value, spec ) {
-       var fieldset, textInput, inputLabel,
-               label = spec && spec.label ? spec.label.en : name,
-               description = spec && spec.description && spec.description.en;
+ve.ui.MWTemplateDialog.prototype.addParameterPage = function ( page, parameter 
) {
+       var valueFieldset, optionsFieldset, textInput, inputLabel, removeButton,
+               label = parameter.getLabel(),
+               description = parameter.getDescription();
 
-       fieldset = new ve.ui.FieldsetLayout( {
+       valueFieldset = new ve.ui.FieldsetLayout( {
                '$$': this.frame.$$,
-               'label': label,
+               'label': 'Value',
                'icon': 'parameter'
        } );
 
-       textInput = new ve.ui.TextInputWidget( { '$$': this.frame.$$, 
'multiline': true } );
-       textInput.setValue( value.wt );
-       textInput.on( 'change', function () {
-               value.wt = textInput.getValue();
-       } );
-       textInput.$.addClass( 've-ui-mwTemplateDialog-input' );
-       fieldset.$.append( textInput.$ );
-
-       if ( description  ) {
+       if ( description ) {
                inputLabel = new ve.ui.InputLabelWidget( {
                        '$$': this.frame.$$,
                        'input': textInput,
                        'label': description
                } );
-               fieldset.$.append( inputLabel.$ );
+               valueFieldset.$.append( inputLabel.$ );
        }
+
+       textInput = new ve.ui.TextInputWidget( { '$$': this.frame.$$, 
'multiline': true } );
+       textInput.setValue( parameter.getValue() );
+       textInput.on( 'change', function () {
+               parameter.setValue( textInput.getValue() );
+       } );
+       textInput.$.addClass( 've-ui-mwTemplateDialog-input' );
+       valueFieldset.$.append( textInput.$ );
+
+       optionsFieldset = new ve.ui.FieldsetLayout( {
+               '$$': this.frame.$$,
+               'label': 'Options',
+               'icon': 'settings'
+       } );
+
+       removeButton = new ve.ui.ButtonWidget( {
+               '$$': this.frame.$$, 'label': 'Remove parameter', 'flags': 
['destructive']
+       } );
+       removeButton.on( 'click', ve.bind( function () {
+               parameter.remove();
+               this.setupPages();
+       }, this ) );
+       optionsFieldset.$.append( removeButton.$ );
 
        // TODO: Use spec.required
        // TODO: Use spec.deprecation
@@ -438,7 +235,7 @@
        // TODO: Use spec.type
 
        this.addPage( page, { 'label': label, 'icon': 'parameter', 'level': 1 } 
);
-       this.pages[page].$.append( fieldset.$ );
+       this.pages[page].$.append( valueFieldset.$, optionsFieldset.$ );
 };
 
 /* Registration */

-- 
To view, visit https://gerrit.wikimedia.org/r/67573
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I3bcf924a3e179cb65f19e833277a39dfd3dad8bd
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to