mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-09-27 04:06:51 +00:00
Improve async template data loading
Objective: * Fix issue where async behavior of addTemplate caused templates to be added in the wrong order Bonus: * Get rid of special adders for transclusion parts, just construct objects outside and use addPart Change-Id: Ibe579f033873446376d72d3bd1b9f92d9f361de5
This commit is contained in:
parent
e486307976
commit
30b56e7597
|
@ -135,32 +135,28 @@ ve.dm.MWTemplateModel.prototype.getParameterNames = function () {
|
|||
* Add a parameter to template.
|
||||
*
|
||||
* @method
|
||||
* @param {string} name Parameter name
|
||||
* @param {string} value Parameter value
|
||||
* @returns {ve.dm.MWTemplateParameterModel} Added param
|
||||
* @param {ve.dm.MWTemplateParameterModel} param Parameter to add
|
||||
* @emits add
|
||||
*/
|
||||
ve.dm.MWTemplateModel.prototype.addParameter = function ( name, value ) {
|
||||
var param = new ve.dm.MWTemplateParameterModel( this, name, value );
|
||||
ve.dm.MWTemplateModel.prototype.addParameter = function ( param ) {
|
||||
var name = param.getName();
|
||||
this.sequence = null;
|
||||
this.params[name] = param;
|
||||
this.spec.fill();
|
||||
this.emit( 'add', param );
|
||||
return param;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove parameter from template.
|
||||
*
|
||||
* @method
|
||||
* @param {string} name Parameter name
|
||||
* @param {ve.dm.MWTemplateParameterModel} param Parameter to remove
|
||||
* @emits remove
|
||||
*/
|
||||
ve.dm.MWTemplateModel.prototype.removeParameter = function ( name ) {
|
||||
var param = this.params[name];
|
||||
ve.dm.MWTemplateModel.prototype.removeParameter = function ( param ) {
|
||||
if ( param ) {
|
||||
this.sequence = null;
|
||||
delete this.params[name];
|
||||
delete this.params[param.getName()];
|
||||
this.emit( 'remove', param );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -80,5 +80,5 @@ ve.dm.MWTemplateParameterModel.prototype.setValue = function ( value ) {
|
|||
* @method
|
||||
*/
|
||||
ve.dm.MWTemplateParameterModel.prototype.remove = function () {
|
||||
this.template.removeParameter( this.name );
|
||||
this.template.removeParameter( this );
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ ve.dm.MWTransclusionModel = function VeDmMWTransclusionModel() {
|
|||
this.parts = [];
|
||||
this.uid = 0;
|
||||
this.requests = [];
|
||||
this.queue = [];
|
||||
};
|
||||
|
||||
/* Inheritance */
|
||||
|
@ -55,8 +56,7 @@ ve.mixinClass( ve.dm.MWTransclusionModel, ve.EventEmitter );
|
|||
* @returns {jQuery.Promise} Promise, resolved when spec is loaded
|
||||
*/
|
||||
ve.dm.MWTransclusionModel.prototype.load = function ( data ) {
|
||||
var i, len, key, part, template,
|
||||
templates = [];
|
||||
var i, len, key, part, template;
|
||||
|
||||
// Convert single part format to multi-part format
|
||||
if ( data.params && data.target ) {
|
||||
|
@ -67,50 +67,85 @@ ve.dm.MWTransclusionModel.prototype.load = function ( data ) {
|
|||
for ( i = 0, len = data.parts.length; i < len; i++ ) {
|
||||
part = data.parts[i];
|
||||
if ( part.template ) {
|
||||
template = this.addTemplate( part.template.target );
|
||||
template = new ve.dm.MWTemplateModel( this, part.template.target );
|
||||
for ( key in part.template.params ) {
|
||||
template.addParameter( key, part.template.params[key].wt );
|
||||
}
|
||||
// Don't load specs for templates that don't have a resolvable target
|
||||
if ( part.template.target.href ) {
|
||||
templates.push( template );
|
||||
template.addParameter(
|
||||
new ve.dm.MWTemplateParameterModel(
|
||||
template, key, part.template.params[key].wt
|
||||
)
|
||||
);
|
||||
}
|
||||
this.queue.push( { 'part': template } );
|
||||
} else if ( typeof part === 'string' ) {
|
||||
this.addContent( part );
|
||||
this.queue.push( { 'part': new ve.dm.MWTransclusionContentModel( this, part ) } );
|
||||
}
|
||||
}
|
||||
setTimeout( ve.bind( this.fetch, this ) );
|
||||
}
|
||||
// Add fetched specs to #specs store when the promise is resolved
|
||||
return this.fetchSpecs( templates ).done( function ( specs ) {
|
||||
ve.extendObject( specCache, specs );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch template specifications from server.
|
||||
* Process one or more queue items.
|
||||
*
|
||||
* @param {ve.dm.MWTransclusionModel[]} templates List of templates to load data for
|
||||
* @returns {jQuery.Promise} Promise, resolved when spec is loaded
|
||||
* @method
|
||||
* @param {Object[]} queue List of objects containing parts to add and optionally indexes to add
|
||||
* them at, if no index is given parts will be added at the end
|
||||
* @emits add For each item added
|
||||
*/
|
||||
ve.dm.MWTransclusionModel.prototype.fetchSpecs = function ( templates ) {
|
||||
var i, len, title, request,
|
||||
requests = this.requests,
|
||||
deferred = $.Deferred(),
|
||||
specs = {},
|
||||
titles = [];
|
||||
ve.dm.MWTransclusionModel.prototype.process = function ( queue ) {
|
||||
var i, len, item, title, index;
|
||||
|
||||
// Get unique list of titles that aren't already loaded
|
||||
for ( i = 0, len = templates.length; i < len; i++ ) {
|
||||
title = templates[i].getTitle();
|
||||
if ( !specCache[title] && ve.indexOf( title, titles ) === -1 ) {
|
||||
titles.push( title );
|
||||
for ( i = 0, len = queue.length; i < len; i++ ) {
|
||||
item = queue[i];
|
||||
if ( item.part instanceof ve.dm.MWTemplateModel ) {
|
||||
title = item.part.getTitle();
|
||||
if ( hasOwn.call( specCache, title ) && specCache[title] ) {
|
||||
item.part.getSpec().extend( specCache[title] );
|
||||
}
|
||||
}
|
||||
index = item.index === undefined ? this.parts.length : item.index;
|
||||
this.parts.splice( index, 0, item.part );
|
||||
this.emit( 'add', item.part );
|
||||
}
|
||||
};
|
||||
|
||||
ve.dm.MWTransclusionModel.prototype.fetch = function () {
|
||||
if ( !this.queue.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var i, len, item, title, request,
|
||||
titles = [],
|
||||
specs = {},
|
||||
queue = this.queue.slice();
|
||||
|
||||
// Clear shared queue for future calls
|
||||
this.queue.length = 0;
|
||||
|
||||
// Get unique list of template titles that aren't already loaded
|
||||
for ( i = 0, len = queue.length; i < len; i++ ) {
|
||||
item = queue[i];
|
||||
if ( item.part instanceof ve.dm.MWTemplateModel ) {
|
||||
title = item.part.getTitle();
|
||||
if (
|
||||
// Skip titles that don't have a resolvable href
|
||||
title &&
|
||||
// Skip titles outside the template namespace
|
||||
title.charAt( 0 ) !== ':' &&
|
||||
// Skip already cached data
|
||||
!hasOwn.call( specCache, title ) &&
|
||||
// Skip duplicate titles in the same batch
|
||||
ve.indexOf( title, titles ) === -1
|
||||
) {
|
||||
titles.push( title );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bypass server for empty lists
|
||||
if ( !titles.length ) {
|
||||
setTimeout( deferred.reject );
|
||||
return deferred.promise();
|
||||
setTimeout( ve.bind( this.process, this, queue ) );
|
||||
return;
|
||||
}
|
||||
|
||||
// Request template specs from server
|
||||
|
@ -124,7 +159,7 @@ ve.dm.MWTransclusionModel.prototype.fetchSpecs = function ( templates ) {
|
|||
}
|
||||
} )
|
||||
.done( function ( data ) {
|
||||
var i, len, id, title;
|
||||
var i, len, id;
|
||||
|
||||
if ( data && data.pages ) {
|
||||
// Keep spec data on hand for future use
|
||||
|
@ -141,31 +176,28 @@ ve.dm.MWTransclusionModel.prototype.fetchSpecs = function ( templates ) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Load into existing templates
|
||||
for ( i = 0, len = templates.length; i < len; i++ ) {
|
||||
title = templates[i].getTitle();
|
||||
if ( hasOwn.call( specs, title ) ) {
|
||||
templates[i].getSpec().extend( specs[title] );
|
||||
// Prevent asking again for templates that have no specs
|
||||
for ( i = 0, len = titles.length; i < len; i++ ) {
|
||||
title = titles[i];
|
||||
if ( !specs[title] ) {
|
||||
specs[title] = null;
|
||||
}
|
||||
}
|
||||
deferred.resolve( specs );
|
||||
} else {
|
||||
deferred.reject( 'unavailable', arguments );
|
||||
}
|
||||
} )
|
||||
.fail( function () {
|
||||
deferred.reject( 'http', arguments );
|
||||
} )
|
||||
.always( function () {
|
||||
// Prune requests when complete
|
||||
var index = requests.indexOf( request );
|
||||
if ( index !== -1 ) {
|
||||
requests.splice( index, 1 );
|
||||
}
|
||||
} );
|
||||
requests.push( request );
|
||||
|
||||
return deferred.promise();
|
||||
ve.extendObject( specCache, specs );
|
||||
}
|
||||
} )
|
||||
.always( ve.bind( function () {
|
||||
// Prune completed request
|
||||
var index = ve.indexOf( request, this.requests );
|
||||
if ( index !== -1 ) {
|
||||
this.requests.splice( index, 1 );
|
||||
}
|
||||
// Actually add queued items
|
||||
this.process( queue );
|
||||
}, this ) );
|
||||
|
||||
this.requests.push( request );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -230,79 +262,24 @@ ve.dm.MWTransclusionModel.prototype.getUniquePartId = 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.MWTransclusionContentModel} Added content part
|
||||
* @emits add
|
||||
*/
|
||||
ve.dm.MWTransclusionModel.prototype.addContent = function ( value, index ) {
|
||||
var part = new ve.dm.MWTransclusionContentModel( this, value );
|
||||
this.addPart( part, index );
|
||||
return part;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add template part.
|
||||
*
|
||||
* Templates are added asynchronously.
|
||||
*
|
||||
* @method
|
||||
* @param {Object} target Template target
|
||||
* @param {string} target.wt Original wikitext of target
|
||||
* @param {string} [target.href] Hypertext reference to target
|
||||
* @param {number} [index] Specific index to add template at
|
||||
* @returns {ve.dm.MWTemplateModel} Added template part
|
||||
* @emits add
|
||||
*/
|
||||
ve.dm.MWTransclusionModel.prototype.addTemplate = function ( target, index ) {
|
||||
var part = new ve.dm.MWTemplateModel( this, target ),
|
||||
title = part.getTitle(),
|
||||
finish = ve.bind( this.addPart, this, part, index );
|
||||
|
||||
if ( hasOwn.call( specCache, title ) ) {
|
||||
part.getSpec().extend( specCache[title] );
|
||||
setTimeout( finish );
|
||||
} else {
|
||||
// Add fetched specs to #specs store when the promise is resolved
|
||||
this.fetchSpecs( [ part ] )
|
||||
.done( function ( specs ) {
|
||||
ve.extendObject( specCache, specs );
|
||||
} )
|
||||
.always( finish );
|
||||
}
|
||||
return part;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add template placeholder part.
|
||||
*
|
||||
* @method
|
||||
* @param {number} [index] Specific index to add placeholder at
|
||||
* @returns {ve.dm.MWTransclusionModel} Added template part
|
||||
* @emits add
|
||||
*/
|
||||
ve.dm.MWTransclusionModel.prototype.addPlaceholder = function ( index ) {
|
||||
var part = new ve.dm.MWTemplatePlaceholderModel( this );
|
||||
|
||||
this.addPart( part, index );
|
||||
return part;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add part.
|
||||
*
|
||||
* Added asynchronously, but order is preserved.
|
||||
*
|
||||
* @method
|
||||
* @param {ve.dm.MWTransclusionPartModel} part Part to add
|
||||
* @param {number} [index] Specific index to add content at
|
||||
* @emits add
|
||||
* @param {number} [index] Specific index to add content at, defaults to the end
|
||||
* @throws {Error} If part is not valid
|
||||
*/
|
||||
ve.dm.MWTransclusionModel.prototype.addPart = function ( part, index ) {
|
||||
this.parts.splice( index === undefined ? this.parts.length : index, 0, part );
|
||||
this.emit( 'add', part );
|
||||
if ( !( part instanceof ve.dm.MWTransclusionPartModel ) ) {
|
||||
throw new Error( 'Invalid transclusion part' );
|
||||
}
|
||||
this.queue.push( { 'part': part, 'index': index } );
|
||||
// Fetch on next yield to process items in the queue together, subsequent calls to fetch will
|
||||
// have no effect because the queue will be clear
|
||||
setTimeout( ve.bind( this.fetch, this ) );
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -80,13 +80,14 @@ ve.ui.MWTransclusionDialog.prototype.onOpen = function () {
|
|||
// Properties
|
||||
this.transclusion = new ve.dm.MWTransclusionModel();
|
||||
|
||||
// Events
|
||||
this.transclusion.connect( this, { 'add': 'onAddPart', 'remove': 'onRemovePart' } );
|
||||
|
||||
// Initialization
|
||||
if ( this.node instanceof ve.ce.MWTransclusionNode ) {
|
||||
this.transclusion.load( ve.copyObject( this.node.getModel().getAttribute( 'mw' ) ) )
|
||||
.always( ve.bind( this.setupPages, this ) );
|
||||
this.transclusion.load( ve.copyObject( this.node.getModel().getAttribute( 'mw' ) ) );
|
||||
} else {
|
||||
this.transclusion.addPlaceholder();
|
||||
this.setupPages();
|
||||
this.transclusion.addPart( new ve.dm.MWTemplatePlaceholderModel( this.transclusion ) );
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -249,11 +250,13 @@ ve.ui.MWTransclusionDialog.prototype.onOutlineControlsAdd = function ( type ) {
|
|||
|
||||
switch ( type ) {
|
||||
case 'content':
|
||||
part = this.transclusion.addContent( '', this.getPartInsertionIndex() );
|
||||
part = new ve.dm.MWTransclusionContentModel( this.transclusion, '' );
|
||||
this.transclusion.addPart( part, this.getPartInsertionIndex() );
|
||||
this.setPageByName( part.getId() );
|
||||
break;
|
||||
case 'template':
|
||||
part = this.transclusion.addPlaceholder( this.getPartInsertionIndex() );
|
||||
part = new ve.dm.MWTemplatePlaceholderModel( this.transclusion );
|
||||
this.transclusion.addPart( part, this.getPartInsertionIndex() );
|
||||
this.setPageByName( part.getId() );
|
||||
break;
|
||||
}
|
||||
|
@ -320,43 +323,6 @@ ve.ui.MWTransclusionDialog.prototype.getPageIndex = function ( item ) {
|
|||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronize pages with transclusion.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
ve.ui.MWTransclusionDialog.prototype.setupPages = function () {
|
||||
// Build pages from parts
|
||||
var i, iLen, j, jLen, part, param, names,
|
||||
parts = this.transclusion.getParts();
|
||||
|
||||
// Populate pages
|
||||
for ( i = 0, iLen = parts.length; i < iLen; i++ ) {
|
||||
part = parts[i];
|
||||
if ( part instanceof ve.dm.MWTemplateModel ) {
|
||||
// Add template page
|
||||
this.addPage( part.getId(), this.getTemplatePage( part ) );
|
||||
// Listen for changes to parameters
|
||||
part.connect( this, { 'add': 'onAddParameter', 'remove': 'onRemoveParameter' } );
|
||||
// Add parameter pages
|
||||
names = part.getParameterNames();
|
||||
for ( j = 0, jLen = names.length; j < jLen; j++ ) {
|
||||
param = part.getParameter( names[j] );
|
||||
this.addPage( param.getId(), this.getParameterPage( param ) );
|
||||
}
|
||||
} else if ( part instanceof ve.dm.MWTransclusionContentModel ) {
|
||||
// Add wikitext page
|
||||
this.addPage( part.getId(), this.getContentPage( part ) );
|
||||
} else if ( part instanceof ve.dm.MWTemplatePlaceholderModel ) {
|
||||
// Add template placeholder page
|
||||
this.addPage( part.getId(), this.getPlaceholderPage( part ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for changes to parts
|
||||
this.transclusion.connect( this, { 'add': 'onAddPart', 'remove': 'onRemovePart' } );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get page for transclusion content.
|
||||
*
|
||||
|
@ -418,7 +384,8 @@ ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) {
|
|||
description = spec.getDescription();
|
||||
|
||||
function addParameter() {
|
||||
var param = template.addParameter( addParameterInput.getValue() );
|
||||
var param = new ve.dm.MWTemplateParameterModel( template, addParameterInput.getValue() );
|
||||
template.addParameter( param );
|
||||
addParameterInput.setValue();
|
||||
this.setPageByName( param.getId() );
|
||||
}
|
||||
|
@ -568,7 +535,8 @@ ve.ui.MWTransclusionDialog.prototype.getPlaceholderPage = function ( placeholder
|
|||
}
|
||||
|
||||
target = { 'href': new mw.Title( href ).getPrefixedText(), 'wt': value };
|
||||
part = this.transclusion.addTemplate( target, ve.indexOf( placeholder, parts ) );
|
||||
part = new ve.dm.MWTemplateModel( this.transclusion, target );
|
||||
this.transclusion.addPart( part, ve.indexOf( placeholder, parts ) );
|
||||
this.pending.push( { 'part': part, 'placeholder': placeholder } );
|
||||
addTemplateInput.pushPending();
|
||||
addTemplateButton.setDisabled( true );
|
||||
|
|
Loading…
Reference in a new issue