mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-14 18:15:19 +00:00
ee2ddbd3cb
Lists interrupted by non-empty lines would not close the list properly. Register for any token instead of just for newlines and close the list if no listItem follows the newline. Change-Id: I1743901e3db541bbeda78d17707db943e6ceb9b9
165 lines
4.2 KiB
JavaScript
165 lines
4.2 KiB
JavaScript
/*
|
|
* Create list tag around list items and map wiki bullet levels to html
|
|
*/
|
|
|
|
function ListHandler ( manager ) {
|
|
this.manager = manager
|
|
this.reset();
|
|
this.manager.addTransform( this.onListItem.bind(this),
|
|
this.listRank, 'tag', 'listItem' );
|
|
this.manager.addTransform( this.onEnd.bind(this),
|
|
this.listRank, 'end' );
|
|
}
|
|
|
|
ListHandler.prototype.listRank = 2.49; // before PostExpandParagraphHandler
|
|
ListHandler.prototype.anyRank = 2.49 + 0.001; // before PostExpandParagraphHandler
|
|
|
|
ListHandler.prototype.bulletCharsMap = {
|
|
'*': { list: 'ul', item: 'li' },
|
|
'#': { list: 'ol', item: 'li' },
|
|
';': { list: 'dl', item: 'dt' },
|
|
':': { list: 'dl', item: 'dd' },
|
|
};
|
|
|
|
ListHandler.prototype.reset = function() {
|
|
this.newline = false; // flag to identify a list-less line that terminates
|
|
// a list block
|
|
this.bstack = []; // Bullet stack, previous element's listStyle
|
|
this.endtags = []; // Stack of end tags
|
|
};
|
|
|
|
ListHandler.prototype.onAny = function ( token, frame, prevToken ) {
|
|
var tokens;
|
|
if ( token.constructor === NlTk ) {
|
|
if (this.newline) {
|
|
// second newline without a list item in between, close the list
|
|
tokens = this.end().concat( [token] );
|
|
this.newline = false;
|
|
} else {
|
|
tokens = [token];
|
|
this.newline = true;
|
|
}
|
|
return { tokens: tokens };
|
|
} else if ( this.newline ) {
|
|
tokens = this.end().concat( [token] );
|
|
this.newline = false;
|
|
return { tokens: tokens };
|
|
} else {
|
|
return { token: token };
|
|
}
|
|
};
|
|
|
|
|
|
ListHandler.prototype.onEnd = function( token, frame, prevToken ) {
|
|
return { tokens: this.end().concat([token]) };
|
|
};
|
|
|
|
ListHandler.prototype.end = function( ) {
|
|
// pop all open list item tokens
|
|
var tokens = this.popTags(this.bstack.length);
|
|
this.reset();
|
|
this.manager.removeTransform( this.anyRank, 'any' );
|
|
return tokens;
|
|
};
|
|
|
|
ListHandler.prototype.onListItem = function ( token, frame, prevToken ) {
|
|
this.newline = false;
|
|
if (token.constructor === TagTk){
|
|
// convert listItem to list and list item tokens
|
|
return { tokens: this.doListItem( this.bstack, token.bullets ) };
|
|
}
|
|
return { token: token };
|
|
};
|
|
|
|
ListHandler.prototype.commonPrefixLength = function (x, y) {
|
|
var minLength = Math.min(x.length, y.length);
|
|
for(var i = 0; i < minLength; i++) {
|
|
if (x[i] != y[i])
|
|
break;
|
|
}
|
|
return i;
|
|
};
|
|
|
|
ListHandler.prototype.pushList = function ( container ) {
|
|
this.endtags.push( new EndTagTk( container.list ));
|
|
this.endtags.push( new EndTagTk( container.item ));
|
|
return [
|
|
new TagTk( container.list ),
|
|
new TagTk( container.item )
|
|
];
|
|
};
|
|
|
|
ListHandler.prototype.popTags = function ( n ) {
|
|
var tokens = [];
|
|
for(;n > 0; n--) {
|
|
// push list item..
|
|
tokens.push(this.endtags.pop());
|
|
// and the list end tag
|
|
tokens.push(this.endtags.pop());
|
|
}
|
|
return tokens;
|
|
};
|
|
|
|
ListHandler.prototype.isDlDd = function (a, b) {
|
|
var ab = [a,b].sort();
|
|
return (ab[0] === ':' && ab[1] === ';');
|
|
};
|
|
|
|
ListHandler.prototype.doListItem = function ( bs, bn ) {
|
|
var prefixLen = this.commonPrefixLength (bs, bn),
|
|
changeLen = Math.max(bs.length, bn.length) - prefixLen,
|
|
prefix = bn.slice(0, prefixLen);
|
|
this.newline = false;
|
|
this.bstack = bn;
|
|
if (!bs.length)
|
|
{
|
|
this.manager.addTransform( this.onAny.bind(this),
|
|
this.anyRank, 'any' );
|
|
}
|
|
// emit close tag tokens for closed lists
|
|
if (changeLen === 0)
|
|
{
|
|
var itemToken = this.endtags.pop();
|
|
this.endtags.push(new EndTagTk( itemToken.name ));
|
|
return [
|
|
itemToken,
|
|
new TagTk( itemToken.name )
|
|
];
|
|
}
|
|
else if ( bs.length == bn.length
|
|
&& changeLen == 1
|
|
&& this.isDlDd( bs[prefixLen], bn[prefixLen] ) )
|
|
{
|
|
// handle dd/dt transitions
|
|
var newName = this.bulletCharsMap[bn[prefixLen]].item;
|
|
this.endtags.push(new EndTagTk( newName ));
|
|
return [
|
|
this.endtags.pop(),
|
|
new TagTk( newName )
|
|
];
|
|
}
|
|
else
|
|
{
|
|
var tokens = this.popTags(bs.length - prefixLen);
|
|
|
|
if (prefixLen > 0 && bn.length == prefixLen ) {
|
|
var itemToken = this.endtags.pop();
|
|
tokens.push(itemToken);
|
|
tokens.push(new TagTk( itemToken.name ));
|
|
this.endtags.push(new EndTagTk( itemToken.name ));
|
|
}
|
|
|
|
for(var i = prefixLen; i < bn.length; i++) {
|
|
if (!this.bulletCharsMap[bn[i]])
|
|
throw("Unknown node prefix " + prefix[i]);
|
|
|
|
tokens = tokens.concat(this.pushList(this.bulletCharsMap[bn[i]]));
|
|
}
|
|
return tokens;
|
|
}
|
|
};
|
|
|
|
if (typeof module == "object") {
|
|
module.exports.ListHandler = ListHandler;
|
|
}
|