mediawiki-extensions-Visual.../modules/ve-mw/init/ve.init.mw.LinkCache.js
Alex Monk 8f7713e796 Apply link styling logic to transclusion nodes
Only template nodes for now. Not sure what we can do about generated content nodes in general...

Bug: 65353
Change-Id: I848f36764b446ed30c74c0e641d0973008f6880b
2014-10-08 15:03:40 +01:00

182 lines
5.1 KiB
JavaScript

/*!
* VisualEditor MediaWiki Initialization LinkCache class.
*
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
( function () {
var hasOwn = Object.prototype.hasOwnProperty;
// TODO should reuse ve.dm.MWInternalLinkAnnotation#getLookupTitle once factored out
function normalizeTitle( name ) {
var title = mw.Title.newFromText( name );
if ( !title ) {
return name;
}
return title.getPrefixedText();
}
/**
* MediaWiki link status cache.
*
* Used to track how to style links to a given page based on their existence status etc.
*
* @class
*
* @constructor
*/
ve.init.mw.LinkCache = function VeInitMwLinkCache() {
// Mixin constructor
OO.EventEmitter.call( this );
// Keys are page names, values are deferreds
this.cache = {};
// Keys are page names, values are link data objects
// This is kept for synchronous retrieval of cached values via #getCached
this.cacheValues = {};
// Array of page names queued to be looked up
this.queue = [];
this.schedule = ve.debounce( this.processQueue.bind( this ), 0 );
};
OO.mixinClass( ve.init.mw.LinkCache, OO.EventEmitter );
// TODO maybe factor out generic parts into ve.StatusCache or whatever
// TODO unit tests
/**
* Fired when a new entry is added to the cache.
* @event add
* @param {Object} entries Cache entries that were added. Object mapping names to data objects.
*/
/**
* Look up data about a page in the cache. If the data about this page is already in the cache,
* this returns an already-resolved promise. Otherwise, it returns a pending promise and schedules
* an API call to retrieve the data.
*
* @param {string} name Normalized page title
* @returns {jQuery.Promise} Promise that will be resolved with the data once it's available
*/
ve.init.mw.LinkCache.prototype.get = function ( name ) {
if ( !hasOwn.call( this.cache, name ) ) {
this.cache[name] = $.Deferred();
this.queue.push( name );
this.schedule();
}
return this.cache[name].promise();
};
/**
* Look up data about a page in the cache. If the data about this page is already in the cache,
* this returns that data. Otherwise, it returns undefined.
*
* @param {string} name Normalized page title
* @returns {Object|undefined} Cache data for this name.
*/
ve.init.mw.LinkCache.prototype.getCached = function ( name ) {
if ( hasOwn.call( this.cacheValues, name ) ) {
return this.cacheValues[name];
}
};
/**
* Requests information about the title, then adds classes to the provided element as appropriate.
*
* @param {string} title Defaults to 'href' attribute of $element
* @param {jQuery} $element Element to style
*/
ve.init.mw.LinkCache.prototype.styleElement = function ( title, $element ) {
this.get( title ).done( function ( data ) {
if ( data.missing ) {
$element.addClass( 'new' );
} else {
// Provided by core MediaWiki, no styles by default.
if ( data.redirect ) {
$element.addClass( 'mw-redirect' );
}
// Should be provided by the Disambiguator extension, but no one has yet written a suitably
// performant patch to do it. It is instead implemented in JavaScript in on-wiki gadgets.
if ( data.disambiguation ) {
$element.addClass( 'mw-disambig' );
}
}
} );
};
/**
* Add entries to the cache.
* @param {Object} entries Object keyed by page title, with the values being data objects
* @fires add
*/
ve.init.mw.LinkCache.prototype.set = function ( entries ) {
var name;
for ( name in entries ) {
if ( !hasOwn.call( this.cache, name ) ) {
this.cache[name] = $.Deferred();
}
this.cache[name].resolve( entries[name] );
this.cacheValues[name] = entries[name];
}
this.emit( 'add', ve.getObjectKeys( entries ) );
};
/**
* Perform any scheduled API requests.
* @private
* @fires add
*/
ve.init.mw.LinkCache.prototype.processQueue = function () {
var subqueue, queue, linkCache = this;
function rejectSubqueue() {
var i, len;
for ( i = 0, len = subqueue.length; i < len; i++ ) {
linkCache.cache[subqueue[i]].reject();
}
}
function processData( data ) {
var pageid, page, info,
pages = data.query && data.query.pages,
processed = {};
if ( pages ) {
for ( pageid in pages ) {
page = pages[pageid];
info = {
missing: page.missing !== undefined,
redirect: page.redirect !== undefined,
// Disambiguator extension
disambiguation: page.pageprops && page.pageprops.disambiguation !== undefined
};
processed[page.title] = info;
}
linkCache.set( processed );
}
// Reject everything in subqueue; this will only reject the ones
// that weren't already resolved above, because .reject() on an
// already resolved Deferred is a no-op.
rejectSubqueue();
}
queue = this.queue;
this.queue = [];
while ( queue.length ) {
subqueue = queue.splice( 0, 50 ).map( normalizeTitle );
ve.init.target.constructor.static.apiRequest( {
action: 'query',
prop: 'info|pageprops',
ppprop: 'disambiguation',
titles: subqueue.join( '|' )
} )
.done( processData )
.fail( rejectSubqueue );
}
};
} () );