2011-12-12 20:53:14 +00:00
|
|
|
/*
|
|
|
|
* Italic/Bold handling.
|
|
|
|
*
|
|
|
|
* - list of tokens
|
|
|
|
* - NEWLINE
|
|
|
|
* - ticks (2+) -> list with link in line token list?
|
|
|
|
* - process on newline
|
|
|
|
* - need access to text nodes before for conversion back to text
|
|
|
|
*/
|
|
|
|
|
|
|
|
function QuoteTransformer ( ) {
|
2011-12-13 11:45:12 +00:00
|
|
|
// Bold and italic tokens are collected in these lists, and then processed
|
|
|
|
// in onNewLine.
|
2011-12-12 20:53:14 +00:00
|
|
|
this.italics = [];
|
|
|
|
this.bolds = [];
|
|
|
|
}
|
|
|
|
|
2011-12-13 10:25:18 +00:00
|
|
|
// Register this transformer with the TokenTransformer
|
2011-12-13 11:45:12 +00:00
|
|
|
QuoteTransformer.prototype.register = function ( dispatcher ) {
|
2011-12-12 20:53:14 +00:00
|
|
|
// Register for NEWLINE and QUOTE tag tokens
|
|
|
|
var self = this;
|
2011-12-13 11:45:12 +00:00
|
|
|
dispatcher.appendListener( function (ctx) {
|
2011-12-12 20:53:14 +00:00
|
|
|
return self.onNewLine(ctx);
|
|
|
|
}, 'newline' );
|
2011-12-13 11:45:12 +00:00
|
|
|
dispatcher.appendListener( function (ctx) {
|
2011-12-12 20:53:14 +00:00
|
|
|
return self.onQuote(ctx);
|
2011-12-13 14:48:47 +00:00
|
|
|
}, 'tag', 'mw-quote' );
|
2011-12-12 20:53:14 +00:00
|
|
|
};
|
|
|
|
|
2011-12-13 11:18:15 +00:00
|
|
|
// Make a copy of the token context
|
2011-12-12 20:53:14 +00:00
|
|
|
QuoteTransformer.prototype.ctx = function ( tokenCTX ) {
|
2011-12-13 11:18:15 +00:00
|
|
|
return $.extend({}, tokenCTX);
|
2011-12-12 20:53:14 +00:00
|
|
|
};
|
|
|
|
|
2011-12-13 10:25:18 +00:00
|
|
|
// Handle QUOTE tags. These are collected in italic/bold lists depending on
|
|
|
|
// the length of quote string. Actual analysis and conversion to the
|
|
|
|
// appropriate tag tokens is deferred until the next NEWLINE token triggers
|
|
|
|
// onNewLine.
|
2011-12-12 20:53:14 +00:00
|
|
|
QuoteTransformer.prototype.onQuote = function ( tokenCTX ) {
|
|
|
|
var token = tokenCTX.token,
|
|
|
|
qlen = token.value.length,
|
|
|
|
out = null,
|
|
|
|
lastToken = tokenCTX.lastToken,
|
|
|
|
ctx = this.ctx(tokenCTX),
|
|
|
|
ctx2,
|
|
|
|
accum = tokenCTX.accum;
|
2011-12-13 10:25:18 +00:00
|
|
|
|
2011-12-12 20:53:14 +00:00
|
|
|
switch (qlen) {
|
|
|
|
case 2:
|
2011-12-13 10:25:18 +00:00
|
|
|
// Start a new accumulator, so we can later go back using the
|
|
|
|
// reference to this accumulator and append our tags at the end of
|
|
|
|
// it.
|
2011-12-13 11:45:12 +00:00
|
|
|
accum = tokenCTX.dispatcher.newAccumulator(accum);
|
2011-12-12 20:53:14 +00:00
|
|
|
this.italics.push(ctx);
|
|
|
|
break;
|
|
|
|
case 3:
|
2011-12-13 11:45:12 +00:00
|
|
|
accum = tokenCTX.dispatcher.newAccumulator(accum);
|
2011-12-12 20:53:14 +00:00
|
|
|
this.bolds.push(ctx);
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
if (lastToken && lastToken.type === 'TEXT') {
|
|
|
|
lastToken.value += "'";
|
|
|
|
} else {
|
|
|
|
out = {type: 'TEXT', value: "'"};
|
|
|
|
}
|
2011-12-13 11:45:12 +00:00
|
|
|
accum = tokenCTX.dispatcher.newAccumulator(accum);
|
2011-12-12 20:53:14 +00:00
|
|
|
this.bolds.push(ctx);
|
|
|
|
break;
|
|
|
|
case 5:
|
2011-12-13 10:25:18 +00:00
|
|
|
// The order of italic vs. bold does not matter. Those are
|
|
|
|
// processed in a fixed order, and any nesting issues are fixed up
|
|
|
|
// by the HTML 5 tree builder. This does not always result in the
|
|
|
|
// prettiest result, but at least it is always correct and very
|
|
|
|
// convenient.
|
2011-12-13 11:45:12 +00:00
|
|
|
accum = tokenCTX.dispatcher.newAccumulator(accum, 2);
|
2011-12-12 20:53:14 +00:00
|
|
|
this.italics.push(ctx);
|
|
|
|
ctx2 = this.ctx(tokenCTX);
|
|
|
|
ctx2.token = {attribs: ctx.token.attribs};
|
|
|
|
this.bolds.push(ctx2);
|
|
|
|
break;
|
|
|
|
default: // longer than 5, only use the last 5 ticks
|
|
|
|
var newvalue = token.value.substr(0, qlen - 5 );
|
|
|
|
if (lastToken && lastToken.type === 'TEXT') {
|
|
|
|
lastToken.value += newvalue;
|
|
|
|
} else {
|
|
|
|
out = {type: 'TEXT', value: newvalue};
|
|
|
|
}
|
2011-12-13 11:45:12 +00:00
|
|
|
accum = tokenCTX.dispatcher.newAccumulator(accum, 2);
|
2011-12-12 20:53:14 +00:00
|
|
|
this.italics.push(ctx);
|
|
|
|
ctx2 = this.ctx(tokenCTX);
|
|
|
|
ctx2.token = {attribs: ctx.token.attribs};
|
|
|
|
this.bolds.push(ctx2);
|
|
|
|
break;
|
|
|
|
}
|
2011-12-13 10:25:18 +00:00
|
|
|
|
2011-12-12 20:53:14 +00:00
|
|
|
tokenCTX.token = out;
|
|
|
|
tokenCTX.accum = accum;
|
|
|
|
return tokenCTX;
|
|
|
|
};
|
|
|
|
|
2011-12-13 10:25:18 +00:00
|
|
|
// Handle NEWLINE tokens, which trigger the actual quote analysis on the
|
|
|
|
// collected quote tokens so far.
|
2011-12-12 20:53:14 +00:00
|
|
|
QuoteTransformer.prototype.onNewLine = function ( tokenCTX ) {
|
|
|
|
if(!this.bolds && !this.italics) {
|
|
|
|
// Nothing to do, quick abort.
|
|
|
|
return tokenCTX;
|
|
|
|
}
|
|
|
|
//console.log("onNewLine: " + this.italics + this.bolds);
|
|
|
|
// balance out tokens, convert placeholders into tags
|
|
|
|
if (this.italics.length % 2 && this.bolds.length % 2) {
|
|
|
|
var firstsingleletterword = -1,
|
|
|
|
firstmultiletterword = -1,
|
|
|
|
firstspace = -1;
|
|
|
|
for (var j = 0; j < this.bolds.length; j++) {
|
|
|
|
var ctx = this.bolds[j];
|
|
|
|
//console.log("balancing!" + JSON.stringify(ctx.lastToken, null, 2));
|
|
|
|
if (ctx.lastToken) {
|
|
|
|
if (ctx.lastToken.type === 'TEXT') {
|
|
|
|
var lastchar = ctx.lastToken.value[ctx.lastToken.value.length - 1],
|
|
|
|
secondtolastchar = ctx.lastToken.value[ctx.lastToken.value.length - 2];
|
|
|
|
if (lastchar === ' ' && firstspace === -1) {
|
|
|
|
firstspace = j;
|
|
|
|
} else if (lastchar !== ' ') {
|
|
|
|
if ( secondtolastchar === ' ' &&
|
|
|
|
firstsingleletterword === -1)
|
|
|
|
{
|
|
|
|
firstsingleletterword = j;
|
|
|
|
} else if ( firstmultiletterword == -1) {
|
|
|
|
firstmultiletterword = j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( ( ctx.lastToken.type === 'NEWLINE' ||
|
|
|
|
ctx.lastToken.type === 'TAG' ) &&
|
2011-12-13 10:25:18 +00:00
|
|
|
firstmultiletterword == -1 ) {
|
|
|
|
// This is an approximation, as the original doQuotes
|
|
|
|
// operates on the source and just looks at space vs.
|
|
|
|
// non-space. At least some tags are thus recognized as
|
|
|
|
// words in the original implementation.
|
2011-12-12 20:53:14 +00:00
|
|
|
firstmultiletterword = j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// now see if we can convert a bold to an italic and
|
|
|
|
// an apostrophe
|
|
|
|
if (firstsingleletterword > -1) {
|
|
|
|
this.convertBold(firstsingleletterword);
|
|
|
|
} else if (firstmultiletterword > -1) {
|
|
|
|
this.convertBold(firstmultiletterword);
|
|
|
|
} else if (firstspace > -1) {
|
|
|
|
this.convertBold(firstspace);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-13 11:45:12 +00:00
|
|
|
this.quotesToTags(this.italics, 'i', tokenCTX.dispatcher);
|
|
|
|
this.quotesToTags(this.bolds, 'b', tokenCTX.dispatcher);
|
2011-12-12 20:53:14 +00:00
|
|
|
|
|
|
|
this.bolds = [];
|
|
|
|
this.italics = [];
|
|
|
|
|
|
|
|
// Pass through the NEWLINE token unchanged
|
|
|
|
return tokenCTX;
|
|
|
|
};
|
|
|
|
|
2011-12-13 10:25:18 +00:00
|
|
|
// Convert a bold token to italic to balance an uneven number of both bold and
|
|
|
|
// italic tags. In the process, one quote needs to be converted back to text.
|
2011-12-12 20:53:14 +00:00
|
|
|
QuoteTransformer.prototype.convertBold = function ( i ) {
|
|
|
|
var ctx = this.bolds[i];
|
|
|
|
//console.log('convertbold!');
|
|
|
|
if ( ctx.lastToken && ctx.lastToken.type === 'TEXT' ) {
|
|
|
|
ctx.lastToken.value += "'";
|
|
|
|
} else {
|
|
|
|
// Add a text token!
|
|
|
|
ctx.token = [{type: 'TEXT', value: "'"}, ctx.token];
|
|
|
|
}
|
|
|
|
|
|
|
|
this.bolds.splice(i, 1);
|
|
|
|
|
|
|
|
this.italics.push(ctx);
|
|
|
|
this.italics.sort(function(a,b) { return a.pos - b.pos; } );
|
|
|
|
//console.log(this.italics.map(function(a) { return a.pos }));
|
|
|
|
//console.log(this.bolds.map(function(a) { return a.pos }));
|
|
|
|
};
|
|
|
|
|
2011-12-13 10:25:18 +00:00
|
|
|
// Convert italics/bolds into tags
|
2011-12-13 11:45:12 +00:00
|
|
|
QuoteTransformer.prototype.quotesToTags = function ( contexts, name, dispatcher ) {
|
2011-12-12 20:53:14 +00:00
|
|
|
var toggle = true,
|
|
|
|
t,
|
|
|
|
out = [];
|
|
|
|
for (var j = 0; j < contexts.length; j++) {
|
|
|
|
t = contexts[j].token;
|
|
|
|
|
|
|
|
if ( $.isArray(t) ) {
|
2011-12-13 10:25:18 +00:00
|
|
|
// Slip in a text token from bold to italic rebalancing. Don't
|
|
|
|
// count this callback towards completion.
|
2011-12-12 20:53:14 +00:00
|
|
|
var realToken = t.pop();
|
2011-12-13 11:45:12 +00:00
|
|
|
dispatcher.transformTokens( t, contexts[j].accum, 0 );
|
2011-12-12 20:53:14 +00:00
|
|
|
t = realToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(toggle) {
|
|
|
|
t.type = 'TAG';
|
|
|
|
} else {
|
|
|
|
t.type = 'ENDTAG';
|
|
|
|
}
|
|
|
|
t.name = name;
|
|
|
|
delete t.value;
|
|
|
|
toggle = !toggle;
|
2011-12-13 10:25:18 +00:00
|
|
|
// Re-add and process the new token with the original accumulator, but
|
|
|
|
// don't yet count this callback towards callback completion.
|
2011-12-13 11:45:12 +00:00
|
|
|
dispatcher.transformTokens( [t], contexts[j].accum, 0 );
|
2011-12-12 20:53:14 +00:00
|
|
|
}
|
|
|
|
var l = contexts.length;
|
|
|
|
if (!toggle) {
|
2011-12-13 10:25:18 +00:00
|
|
|
// Add end tag, but don't count it towards completion.
|
2011-12-13 11:45:12 +00:00
|
|
|
dispatcher.transformTokens( [{type: 'ENDTAG', name: name}],
|
2011-12-12 20:53:14 +00:00
|
|
|
contexts[contexts.length - 1].accum, 0 );
|
|
|
|
}
|
2011-12-13 10:25:18 +00:00
|
|
|
// Now finally count the number of contexts towards completion, which
|
2011-12-13 11:45:12 +00:00
|
|
|
// causes the dispatcher to call its own callback if no more asynch
|
2011-12-13 10:25:18 +00:00
|
|
|
// callbacks are outstanding.
|
2011-12-13 11:45:12 +00:00
|
|
|
dispatcher.finish( contexts.length );
|
2011-12-12 20:53:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (typeof module == "object") {
|
|
|
|
module.exports.QuoteTransformer = QuoteTransformer;
|
|
|
|
}
|