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