mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-28 00:00:49 +00:00
Add MediaWiki-compatible quote handling including quirks and overlapped
structures like ''[[Link|Link text'']]. This is another transform on the token stream.
This commit is contained in:
parent
e91d7ddaaf
commit
dee262658f
|
@ -165,6 +165,157 @@
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/after for conversion back to text
|
||||||
|
*/
|
||||||
|
var doQuotes = function ( tokens ) {
|
||||||
|
|
||||||
|
var italics = [],
|
||||||
|
bolds = [],
|
||||||
|
out = [],
|
||||||
|
inserted = 0;
|
||||||
|
|
||||||
|
var convertBold = function ( i ) {
|
||||||
|
var index = bolds[i];
|
||||||
|
var txt = out[index - 1];
|
||||||
|
txt.value += "'";
|
||||||
|
bolds = bolds.slice(0, i)
|
||||||
|
.concat(bolds.slice(i + 1, bolds.length - i - 1));
|
||||||
|
italics.push(index);
|
||||||
|
italics.sort();
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert italics/bolds into tags
|
||||||
|
var quotesToTags = function ( offsets, name ) {
|
||||||
|
var toggle = true;
|
||||||
|
for (var j = 0; j < offsets.length; j++) {
|
||||||
|
var t = out[offsets[j]];
|
||||||
|
if(toggle) {
|
||||||
|
t.type = 'TAG';
|
||||||
|
} else {
|
||||||
|
t.type = 'ENDTAG';
|
||||||
|
}
|
||||||
|
t.name = name;
|
||||||
|
delete t.value;
|
||||||
|
toggle = !toggle;
|
||||||
|
}
|
||||||
|
if (!toggle) {
|
||||||
|
// add end tag
|
||||||
|
out.push({type: 'ENDTAG', name: name});
|
||||||
|
inserted++;
|
||||||
|
}
|
||||||
|
toggle = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0, length = tokens.length; i < length; i++) {
|
||||||
|
var token = tokens[i];
|
||||||
|
switch (token.type) {
|
||||||
|
case 'QUOTE':
|
||||||
|
// depending on length, add starting 's to preceding text node
|
||||||
|
// (if any)
|
||||||
|
// add token index to italic/bold lists
|
||||||
|
// add placeholder for token
|
||||||
|
var qlen = token.value.length;
|
||||||
|
switch (qlen) {
|
||||||
|
case 2: italics.push(i + inserted); out.push(token); break;
|
||||||
|
case 3: bolds.push(i + inserted); out.push(token); break;
|
||||||
|
case 4:
|
||||||
|
token.value = "'''";
|
||||||
|
if (i > 0 && tokens[i-1].type === 'TEXT') {
|
||||||
|
tokens[i-1].value += "'";
|
||||||
|
} else {
|
||||||
|
out.push({type: 'TEXT', value: "'"});
|
||||||
|
inserted++;
|
||||||
|
}
|
||||||
|
bolds.push(i + inserted);
|
||||||
|
out.push(token);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
// order does not matter here, will be fixed
|
||||||
|
// by HTML parser backend
|
||||||
|
bolds.push(i + inserted);
|
||||||
|
out.push({type: 'QUOTE', value: "'''"});
|
||||||
|
inserted++;
|
||||||
|
italics.push(i + inserted);
|
||||||
|
out.push({type: 'QUOTE', value: "''"});
|
||||||
|
break;
|
||||||
|
default: // longer than 5, only use the last 5 ticks
|
||||||
|
token.value = "'''''";
|
||||||
|
var newvalue = token.value.substr(0, qlen - 5 );
|
||||||
|
if (i > 0 && tokens[i-1].type === 'TEXT') {
|
||||||
|
tokens[i-1].value += newvalue;
|
||||||
|
} else {
|
||||||
|
out.push({type: 'TEXT', value: newvalue});
|
||||||
|
inserted++;
|
||||||
|
}
|
||||||
|
bolds.push(i + inserted);
|
||||||
|
out.push({type: 'QUOTE', value: "'''"});
|
||||||
|
inserted++;
|
||||||
|
italics.push(i + inserted);
|
||||||
|
out.push({type: 'QUOTE', value: "''"});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NEWLINE':
|
||||||
|
// balance out tokens, convert placeholders into tags
|
||||||
|
if (italics.length % 2 && bolds.length % 2) {
|
||||||
|
dp("balancing!");
|
||||||
|
var firstsingleletterword = -1,
|
||||||
|
firstmultiletterword = -1,
|
||||||
|
firstspace = -1;
|
||||||
|
for (var j = 0; j < bolds.length; j++) {
|
||||||
|
var ticki = bolds[j];
|
||||||
|
if (ticki > 0 && out[ticki - 1].type === 'TEXT') {
|
||||||
|
var txt = out[ticki - 1],
|
||||||
|
lastchar = txt.value[txt.value.length - 1],
|
||||||
|
secondtolastchar = txt.value[txt.value.length - 2];
|
||||||
|
dp('txt: ' + pp(txt));
|
||||||
|
if (lastchar === ' ' && firstspace === -1) {
|
||||||
|
firstspace = j;
|
||||||
|
} else if (lastchar !== ' ') {
|
||||||
|
if ( secondtolastchar === ' ' &&
|
||||||
|
firstsingleletterword === -1) {
|
||||||
|
firstsingleletterword = j;
|
||||||
|
} else if ( secondtolastchar &&
|
||||||
|
secondtolastchar !== ' ') {
|
||||||
|
firstmultiletterword = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// now see if we can convert a bold to an italic and
|
||||||
|
// an apostrophe
|
||||||
|
if (firstsingleletterword > -1) {
|
||||||
|
convertBold(firstsingleletterword);
|
||||||
|
} else if (firstmultiletterword > -1) {
|
||||||
|
convertBold(firstmultiletterword);
|
||||||
|
} else if (firstspace > -1) {
|
||||||
|
convertBold(firstspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quotesToTags(bolds, 'b');
|
||||||
|
quotesToTags(italics, 'i');
|
||||||
|
bolds = [];
|
||||||
|
italics = [];
|
||||||
|
out.push(token);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
out.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* End static utilities */
|
/* End static utilities */
|
||||||
|
|
||||||
|
@ -213,7 +364,7 @@ space
|
||||||
// Start of line
|
// Start of line
|
||||||
sol = (newline / & { return pos === 0; } { return true; })
|
sol = (newline / & { return pos === 0; } { return true; })
|
||||||
cn:(c:comment n:newline? { return [c, n] })? {
|
cn:(c:comment n:newline? { return [c, n] })? {
|
||||||
return cn;
|
return [{type: 'NEWLINE'}].concat(cn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -229,11 +380,14 @@ toplevelblock
|
||||||
bs.attribs = [];
|
bs.attribs = [];
|
||||||
}
|
}
|
||||||
bs.attribs.push(['data-sourcePos', blockStart + ':' + pos]);
|
bs.attribs.push(['data-sourcePos', blockStart + ':' + pos]);
|
||||||
|
// XXX: only run this for lines that actually need it!
|
||||||
|
b.push({type: 'NEWLINE'});
|
||||||
|
b = doQuotes(b);
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
block
|
block
|
||||||
= (sol space* &newline)? bl:block_lines { return bl; }
|
= (sol space* &newline)? bl:block_lines { return [{type: 'NEWLINE'}].concat(bl); }
|
||||||
/ para
|
/ para
|
||||||
/ comment
|
/ comment
|
||||||
/ (s:sol {
|
/ (s:sol {
|
||||||
|
@ -431,8 +585,9 @@ inline_element
|
||||||
/ extlink
|
/ extlink
|
||||||
/ template
|
/ template
|
||||||
/ link
|
/ link
|
||||||
/ bold
|
/ quote
|
||||||
/ italic
|
// / bold
|
||||||
|
// / italic
|
||||||
|
|
||||||
comment
|
comment
|
||||||
= '<!--' c:comment_chars* '-->'
|
= '<!--' c:comment_chars* '-->'
|
||||||
|
@ -558,6 +713,12 @@ bold
|
||||||
|
|
||||||
bold_marker = "'''"
|
bold_marker = "'''"
|
||||||
|
|
||||||
|
quote = "''" x:"'"* {
|
||||||
|
return {
|
||||||
|
type : 'QUOTE',
|
||||||
|
value: "''" + x.join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
italic
|
italic
|
||||||
= italic_marker
|
= italic_marker
|
||||||
|
@ -691,18 +852,18 @@ lists = es:(dtdd / li)+
|
||||||
,[{ type: 'ENDTAG', name: 'list' }]));
|
,[{ type: 'ENDTAG', name: 'list' }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
li = sol
|
li = s:sol
|
||||||
bullets:list_char+
|
bullets:list_char+
|
||||||
c:inlineline
|
c:inlineline
|
||||||
&newline
|
&newline
|
||||||
{
|
{
|
||||||
return [ { type: 'TAG',
|
return s.concat([ { type: 'TAG',
|
||||||
name: 'listItem',
|
name: 'listItem',
|
||||||
bullets: bullets }
|
bullets: bullets }
|
||||||
, c ];
|
, c ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
dtdd = sol
|
dtdd = s:sol
|
||||||
bullets:list_char+
|
bullets:list_char+
|
||||||
c:(inline_element / (n:[^:\n] { return {type: 'TEXT', value: n}; }))+
|
c:(inline_element / (n:[^:\n] { return {type: 'TEXT', value: n}; }))+
|
||||||
":"
|
":"
|
||||||
|
@ -715,7 +876,7 @@ dtdd = sol
|
||||||
} else {
|
} else {
|
||||||
var dtbullets = bullets.slice(0, bullets.length - 1);
|
var dtbullets = bullets.slice(0, bullets.length - 1);
|
||||||
dtbullets.push(':');
|
dtbullets.push(':');
|
||||||
return [ { type: 'TAG', name: 'listItem', bullets: bullets } ]
|
return s.concat([ { type: 'TAG', name: 'listItem', bullets: bullets } ])
|
||||||
.concat( c
|
.concat( c
|
||||||
,[{ type: 'TAG', name: 'listItem', bullets: dtbullets } ]
|
,[{ type: 'TAG', name: 'listItem', bullets: dtbullets } ]
|
||||||
, d );
|
, d );
|
||||||
|
|
|
@ -158,9 +158,11 @@ function processTest(item) {
|
||||||
|
|
||||||
console.log('RENDERED:');
|
console.log('RENDERED:');
|
||||||
//console.log(JSON.stringify(tree, null, 2));
|
//console.log(JSON.stringify(tree, null, 2));
|
||||||
console.log(tokenizer.parser.document
|
var out = tokenizer.parser.document
|
||||||
.getElementsByTagName('body')[0]
|
.getElementsByTagName('body')[0]
|
||||||
.innerHTML);
|
.innerHTML
|
||||||
|
.replace(/<li>/g, '\n<li>');
|
||||||
|
console.log(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue