mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 18:39:52 +00:00
Merge "ve.ui.MWTemplateDialog: Implement inferring of template data"
This commit is contained in:
commit
09ed510659
|
@ -27,7 +27,9 @@ ve.ui.MWTemplateDialog = function VeUiMWTemplateDialog( surface, config ) {
|
||||||
// Properties
|
// Properties
|
||||||
this.node = null;
|
this.node = null;
|
||||||
this.content = null;
|
this.content = null;
|
||||||
this.specs = {};
|
// Buffer for getTemplateSpecs
|
||||||
|
this.fetchQueue = [];
|
||||||
|
this.fetchCallbacks = $.Callbacks();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Inheritance */
|
/* Inheritance */
|
||||||
|
@ -50,77 +52,111 @@ ve.ui.MWTemplateDialog.static.modelClasses = [ ve.dm.MWTemplateNode ];
|
||||||
* @method
|
* @method
|
||||||
*/
|
*/
|
||||||
ve.ui.MWTemplateDialog.prototype.onOpen = function () {
|
ve.ui.MWTemplateDialog.prototype.onOpen = function () {
|
||||||
var i, len, template, title,
|
var i, progress, len, template,
|
||||||
templates = [];
|
dialog = this;
|
||||||
|
|
||||||
this.node = this.surface.getView().getFocusedNode();
|
function increaseProgress() {
|
||||||
if ( !this.node ) {
|
progress++;
|
||||||
|
if ( progress === len ) {
|
||||||
|
dialog.setupPages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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' );
|
throw new Error( 'No focused node to edit' );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get content values
|
// Get content values and copy it so we can safely change it to our liking
|
||||||
this.content = ve.copyObject( this.node.getModel().getAttribute( 'mw' ) );
|
dialog.content = ve.copyObject( dialog.node.getModel().getAttribute( 'mw' ) );
|
||||||
|
|
||||||
// Convert single template format to multiple template format
|
// Convert single template format to multiple template format
|
||||||
if ( this.content.params ) {
|
if ( dialog.content.params ) {
|
||||||
this.content = { 'parts': [ { 'template': this.content } ] };
|
dialog.content = {
|
||||||
|
'parts': [
|
||||||
|
{
|
||||||
|
'template': dialog.content
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// Get all template data asynchronously
|
|
||||||
for ( i = 0, len = this.content.parts.length; i < len; i++ ) {
|
progress = -1;
|
||||||
template = this.content.parts[i].template;
|
len = dialog.content.parts.length;
|
||||||
|
|
||||||
|
// Get all template specs asynchronously
|
||||||
|
for ( i = 0; i < len; i++ ) {
|
||||||
|
template = dialog.content.parts[i].template;
|
||||||
if ( template ) {
|
if ( template ) {
|
||||||
if ( template.target.url ) {
|
// Method #getTemplateSpecs will use the part id instead of `target.url`
|
||||||
try {
|
// if the target has no url property (which Parsoid omits if the target is
|
||||||
title = new mw.Title( template.target.url );
|
// dynamically generated from wikitext). In that case we want each template
|
||||||
templates.push( {
|
// invocation to have its own inferred template spec.
|
||||||
'title': title.toString(),
|
template.specId = template.target.url || ( '#!/part/' + i );
|
||||||
'params': template.params
|
dialog.getTemplateSpecs( template, makeStoreTemplateSpec( template ) );
|
||||||
} );
|
|
||||||
} catch ( e ) {}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Wrap plain wikitext in object so editor has something to reference
|
// This is a raw wikitext part (between two associated template invocations),
|
||||||
this.content.parts[i] = { 'wt': this.content.parts[i] };
|
// wrap in object so editor has something to reference
|
||||||
|
dialog.content.parts[i] = { 'wt': dialog.content.parts[i] };
|
||||||
|
increaseProgress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( templates.length ) {
|
|
||||||
this.getTemplateData( templates )
|
increaseProgress();
|
||||||
.done( ve.bind( function ( specs ) {
|
|
||||||
this.specs = specs;
|
|
||||||
}, this ) )
|
|
||||||
.always( ve.bind( this.setupPages, this ) );
|
|
||||||
} else {
|
|
||||||
this.setupPages();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle window close events.
|
* Handle window close events.
|
||||||
*
|
*
|
||||||
* @method
|
|
||||||
* @param {string} action Action that caused the window to be closed
|
* @param {string} action Action that caused the window to be closed
|
||||||
*/
|
*/
|
||||||
ve.ui.MWTemplateDialog.prototype.onClose = function ( action ) {
|
ve.ui.MWTemplateDialog.prototype.onClose = function ( action ) {
|
||||||
var i, len, wt,
|
var i, len, parts,
|
||||||
surfaceModel = this.surface.getModel();
|
surfaceModel = this.surface.getModel();
|
||||||
|
|
||||||
// Save changes
|
// Save changes
|
||||||
if ( action === 'apply' ) {
|
if ( action === 'apply' ) {
|
||||||
// Expand wikitext content
|
|
||||||
for ( i = 0, len = this.content.parts.length; i < len; i++ ) {
|
// Undo non-standard changes we made to the content model in #onOpen
|
||||||
wt = this.content.parts[i].wt;
|
parts = this.content.parts;
|
||||||
if ( typeof wt === 'string' ) {
|
|
||||||
// Replace object wrapper with plain text
|
for ( i = 0, len = parts.length; i < len; i++ ) {
|
||||||
this.content.parts[i] = wt;
|
|
||||||
|
// 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
|
// Restore single template format
|
||||||
if ( this.content.parts.length === 1 ) {
|
if ( this.content.parts.length === 1 ) {
|
||||||
this.content = this.content.parts[0].template;
|
this.content = this.content.parts[0].template;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Wrap attribute changes in ve.dm.SurfaceFragment
|
// TODO: Wrap attribute changes in ve.dm.SurfaceFragment
|
||||||
surfaceModel.change(
|
surfaceModel.change(
|
||||||
ve.dm.Transaction.newFromAttributeChange(
|
ve.dm.Transaction.newFromAttributeChange(
|
||||||
surfaceModel.getDocument(), this.node.getOffset(), 'mw', this.content
|
surfaceModel.getDocument(),
|
||||||
|
this.node.getOffset(),
|
||||||
|
'mw',
|
||||||
|
this.content
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -128,7 +164,6 @@ ve.ui.MWTemplateDialog.prototype.onClose = function ( action ) {
|
||||||
this.clearPages();
|
this.clearPages();
|
||||||
this.node = null;
|
this.node = null;
|
||||||
this.content = null;
|
this.content = null;
|
||||||
this.specs = {};
|
|
||||||
|
|
||||||
// Parent method
|
// Parent method
|
||||||
ve.ui.PagedDialog.prototype.onClose.call( this );
|
ve.ui.PagedDialog.prototype.onClose.call( this );
|
||||||
|
@ -142,8 +177,7 @@ ve.ui.MWTemplateDialog.prototype.onClose = function ( action ) {
|
||||||
ve.ui.MWTemplateDialog.prototype.setupPages = function () {
|
ve.ui.MWTemplateDialog.prototype.setupPages = function () {
|
||||||
// Build pages from parts
|
// Build pages from parts
|
||||||
var i, len, template, spec, param,
|
var i, len, template, spec, param,
|
||||||
parts = this.content.parts,
|
parts = this.content.parts;
|
||||||
specs = this.specs;
|
|
||||||
|
|
||||||
// Parent method
|
// Parent method
|
||||||
ve.ui.PagedDialog.prototype.onOpen.call( this );
|
ve.ui.PagedDialog.prototype.onOpen.call( this );
|
||||||
|
@ -152,7 +186,7 @@ ve.ui.MWTemplateDialog.prototype.setupPages = function () {
|
||||||
for ( i = 0, len = parts.length; i < len; i++ ) {
|
for ( i = 0, len = parts.length; i < len; i++ ) {
|
||||||
if ( parts[i].template ) {
|
if ( parts[i].template ) {
|
||||||
template = parts[i].template;
|
template = parts[i].template;
|
||||||
spec = specs[template.target.url];
|
spec = template.spec;
|
||||||
// Add template page
|
// Add template page
|
||||||
this.addTemplatePage( 'part_' + i, template );
|
this.addTemplatePage( 'part_' + i, template );
|
||||||
// Add parameter pages
|
// Add parameter pages
|
||||||
|
@ -172,23 +206,111 @@ ve.ui.MWTemplateDialog.prototype.setupPages = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a promise for template data.
|
* Backfill missing template data based on template invocation.
|
||||||
*
|
* @param {Object} template Template invocation description
|
||||||
* TODO: Backfill template info from params objects
|
* @return {Object} Template data blob
|
||||||
*
|
|
||||||
* @method
|
|
||||||
* @param {Object[]} templates Template information containing `title` and `params` properties
|
|
||||||
* @return {jQuery.Promise} Template data blob on success, or an error code on failure
|
|
||||||
*/
|
*/
|
||||||
ve.ui.MWTemplateDialog.prototype.getTemplateData = function ( templates ) {
|
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,
|
var i, len,
|
||||||
|
d = $.Deferred(),
|
||||||
titles = [],
|
titles = [],
|
||||||
specs = {},
|
specs = {};
|
||||||
deferred = $.Deferred();
|
|
||||||
|
|
||||||
// Collect all titles
|
// Collect all titles
|
||||||
for ( i = 0, len = templates.length; i < len; i++ ) {
|
for ( i = 0, len = templates.length; i < len; i++ ) {
|
||||||
titles.push( templates[i].title );
|
if ( templates[i].target.url ) {
|
||||||
|
titles.push( templates[i].target.url );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimise for empty lists
|
||||||
|
if ( !templates.length ) {
|
||||||
|
setTimeout( d.reject );
|
||||||
|
return d.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request template data from server
|
// Request template data from server
|
||||||
|
@ -202,27 +324,31 @@ ve.ui.MWTemplateDialog.prototype.getTemplateData = function ( templates ) {
|
||||||
}
|
}
|
||||||
} )
|
} )
|
||||||
.done( function ( data ) {
|
.done( function ( data ) {
|
||||||
var id;
|
var i, len, id;
|
||||||
if ( data && data.pages ) {
|
if ( data && data.pages ) {
|
||||||
// Add template data to spec
|
|
||||||
for ( id in data.pages ) {
|
for ( id in data.pages ) {
|
||||||
specs[data.pages[id].title] = data.pages[id];
|
specs[data.pages[id].title] = data.pages[id];
|
||||||
}
|
}
|
||||||
deferred.resolve( specs );
|
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 );
|
||||||
}
|
}
|
||||||
deferred.reject( 'unavailable', arguments );
|
|
||||||
} )
|
} )
|
||||||
.fail( function () {
|
.fail( function () {
|
||||||
deferred.reject( 'http', arguments );
|
d.reject( 'http', arguments );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return deferred.promise();
|
return d.promise();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add page for wikitext.
|
* Add page for wikitext.
|
||||||
*
|
*
|
||||||
* @method
|
|
||||||
* @param {string} page Unique page name
|
* @param {string} page Unique page name
|
||||||
* @param {Object} value Parameter value
|
* @param {Object} value Parameter value
|
||||||
*/
|
*/
|
||||||
|
@ -250,7 +376,6 @@ ve.ui.MWTemplateDialog.prototype.addWikitextPage = function ( page, value ) {
|
||||||
/**
|
/**
|
||||||
* Add page for a template.
|
* Add page for a template.
|
||||||
*
|
*
|
||||||
* @method
|
|
||||||
* @param {string} page Unique page name
|
* @param {string} page Unique page name
|
||||||
* @param {Object} template Template info
|
* @param {Object} template Template info
|
||||||
*/
|
*/
|
||||||
|
@ -271,7 +396,6 @@ ve.ui.MWTemplateDialog.prototype.addTemplatePage = function ( page, template ) {
|
||||||
/**
|
/**
|
||||||
* Add page for a parameter.
|
* Add page for a parameter.
|
||||||
*
|
*
|
||||||
* @method
|
|
||||||
* @param {string} page Unique page name
|
* @param {string} page Unique page name
|
||||||
* @param {string} name Parameter name
|
* @param {string} name Parameter name
|
||||||
* @param {Object} value Parameter value
|
* @param {Object} value Parameter value
|
||||||
|
|
Loading…
Reference in a new issue