mediawiki-extensions-Visual.../modules/parser/ext.Cite.js
Gabriel Wicke a5cc10a06b Change token format to plain strings for text tokens, and specific objects for
other tokens. This is only the first half of the conversion. The next step is
to drop the type attribute on most tokens and match on the constructor in the
token transform machinery.
2012-02-01 16:30:43 +00:00

307 lines
6.5 KiB
JavaScript

/**
* Simple token transform version of the Cite extension.
*
* @class
* @constructor
*/
function Cite ( dispatcher ) {
this.refGroups = {};
this.refTokens = [];
// Within ref block
this.isActive = false;
this.register( dispatcher );
}
/**
* Register with dispatcher.
*
* @method
* @param {Object} TokenTransformDispatcher to register to
*/
Cite.prototype.register = function ( dispatcher ) {
// Register for ref and references tag tokens
var self = this;
this.onRefCB = function (ctx) {
return self.onRef(ctx);
};
dispatcher.appendListener( this.onRefCB, 'tag', 'ref' );
dispatcher.appendListener( function (ctx) {
return self.onReferences(ctx);
}, 'tag', 'references' );
dispatcher.appendListener( function (ctx) {
return self.onEnd(ctx);
}, 'end' );
};
/**
* Convert list of key-value pairs to object, with first entry for a
* key winning.
*
* XXX: Move to general utils
*
* @static
* @method
* @param {Array} List of [key, value] pairs
* @returns {Object} Object with key/values set, first entry wins.
*/
Cite.prototype.attribsToObject = function ( attribs ) {
if ( attribs === undefined ) {
return {};
}
var obj = {};
for ( var i = 0, l = attribs.length; i < l; i++ ) {
var kv = attribs[i];
if (! kv[0] in obj) {
obj[kv[0]] = kv[1];
}
}
return obj;
};
/**
* Handle ref tag tokens.
*
* @method
* @param {Object} TokenContext
* @returns {Object} TokenContext
*/
Cite.prototype.onRef = function ( tokenCTX ) {
var refGroups = this.refGroups;
var getRefGroup = function(group) {
if (!(group in refGroups)) {
var refs = [],
byName = {};
refGroups[group] = {
refs: refs,
byName: byName,
add: function(tokens, options) {
var ref;
if (options.name && options.name in byName) {
ref = byName[options.name];
} else {
var n = refs.length;
var key = n + '';
if (options.name) {
key = options.name + '-' + key;
}
ref = {
tokens: tokens,
index: n,
groupIndex: n, // @fixme
name: options.name,
group: options.group,
key: key,
target: 'cite_note-' + key,
linkbacks: []
};
refs[n] = ref;
if (options.name) {
byName[options.name] = ref;
}
}
ref.linkbacks.push(
'cite_ref-' + ref.key + '-' + ref.linkbacks.length
);
return ref;
}
};
}
return refGroups[group];
};
var token = tokenCTX.token;
// Collect all tokens between ref start and endtag
if ( ! this.isActive &&
token.type === 'TAG' &&
token.name.toLowerCase() === 'ref' ) {
this.curRef = tokenCTX.token;
// Prepend self for 'any' token type
tokenCTX.dispatcher.prependListener(this.onRefCB, 'any' );
tokenCTX.token = null;
this.isActive = true;
return tokenCTX;
} else if ( this.isActive &&
// Also accept really broken ref close tags..
['TAG', 'ENDTAG', 'SELFCLOSINGTAG'].indexOf(token.type) >= 0 &&
token.name.toLowerCase() === 'ref'
)
{
this.isActive = false;
tokenCTX.dispatcher.removeListener(this.onRefCB, 'any' );
// fall through for further processing!
} else {
// Inside ref block: Collect all other tokens in refTokens and abort
//console.log(JSON.stringify(tokenCTX.token, null, 2));
this.refTokens.push(tokenCTX.token);
tokenCTX.token = null;
return tokenCTX;
}
var options = $.extend({
name: null,
group: null
}, this.attribsToObject(this.curRef.attribs));
var group = getRefGroup(options.group);
var ref = group.add(this.refTokens, options);
this.refTokens = [];
var linkback = ref.linkbacks[ref.linkbacks.length - 1];
var bits = [];
if (options.group) {
bits.push(options.group);
}
//bits.push(env.formatNum( ref.groupIndex + 1 ));
bits.push(ref.groupIndex + 1);
tokenCTX.token = [
{
type: 'TAG',
name: 'span',
attribs: [
['id', linkback],
['class', 'reference'],
// ignore element when serializing back to wikitext
['data-nosource', '']
]
},
{
type: 'TAG',
name: 'a',
attribs: [
['data-type', 'hashlink'],
['href', '#' + ref.target]
// XXX: Add round-trip info here?
]
},
'[' + bits.join(' ') + ']',
{
type: 'ENDTAG',
name: 'a'
},
{
type: 'ENDTAG',
name: 'span'
}
];
return tokenCTX;
};
/**
* Handle references tag tokens.
*
* @method
* @param {Object} TokenContext
* @returns {Object} TokenContext
*/
Cite.prototype.onReferences = function ( tokenCTX ) {
var refGroups = this.refGroups;
var arrow = '↑';
var renderLine = function( ref ) {
//console.log('reftokens: ' + JSON.stringify(ref.tokens, null, 2));
var out = [{
type: 'TAG',
name: 'li',
attribs: [['id', ref.target]]
}];
if (ref.linkbacks.length == 1) {
out = out.concat([
{
type: 'TAG',
name: 'a',
attribs: [
['data-type', 'hashlink'],
['href', '#' + ref.linkbacks[0]]
]
},
{type: 'TEXT', value: arrow},
{type: 'ENDTAG', name: 'a'}
],
ref.tokens // The original content tokens
);
} else {
out.content.push({type: 'TEXT', value: arrow});
$.each(ref.linkbacks, function(i, linkback) {
out = out.concat([
{
type: 'TAG',
name: 'a',
attribs: [
['data-type', 'hashlink'],
['href', '#' + ref.linkbacks[0]]
]
},
// XXX: make formatNum available!
//{
// type: 'TEXT',
// value: env.formatNum( ref.groupIndex + '.' + i)
//},
{type: 'TEXT', value: ref.groupIndex + '.' + i},
{type: 'ENDTAG', name: 'a'}
],
ref.tokens // The original content tokens
);
});
}
return out;
};
var token = tokenCTX.token;
var options = $.extend({
name: null,
group: null
}, this.attribsToObject(token.attribs));
if (options.group in refGroups) {
var group = refGroups[options.group];
var listItems = $.map(group.refs, renderLine);
tokenCTX.token = [
{
type: 'TAG',
name: 'ol',
attribs: [
['class', 'references'],
['data-object', 'references'] // Object type
]
}
].concat( listItems, { type: 'ENDTAG', name: 'ol' } );
} else {
tokenCTX.token = {
type: 'SELFCLOSINGTAG',
name: 'placeholder',
attribs: [
['data-origNode', JSON.stringify(token)]
]
};
}
return tokenCTX;
};
/**
* Handle end token.
*
* @method
* @param {Object} TokenContext
* @returns {Object} TokenContext
*/
Cite.prototype.onEnd = function ( tokenCTX ) {
// XXX: Emit error messages if references tag was missing!
// Clean up
this.refGroups = {};
this.refTokens = [];
this.isActive = false;
return tokenCTX;
};
if (typeof module == "object") {
module.exports.Cite = Cite;
}