fix bold and italic apostrophes (v 3.2)

Bug: T108455
Change-Id: Ie9e0d734004d062e4c347f7940eb34bdc231d026
This commit is contained in:
Pavel Astakhov 2015-08-19 21:10:31 +06:00
parent 66b518d8dd
commit 9acce89672
3 changed files with 141 additions and 46 deletions

View file

@ -1,6 +1,6 @@
{
"name": "CodeMirror",
"version": "3.1.14",
"version": "3.2",
"author": [
"[https://www.mediawiki.org/wiki/User:Pastakhov Pavel Astakhov]",
"[https://www.mediawiki.org/wiki/User:Florianschmidtwelzow Florian Schmidt]"

View file

@ -19,7 +19,7 @@
.cm-mw-indenting {color: #08f; font-weight: bold; background-color: #ddd;}
.cm-mw-mnemonic {color: #090;}
.cm-mw-comment {color: #aaa; font-weight: normal;}
.cm-mw-apostrophes {color: #08f;}
.cm-mw-apostrophes-bold, .cm-mw-apostrophes-italic {color: #08f;}
pre.cm-mw-section-1 {font-size: 1.8em;}
pre.cm-mw-section-2 {font-size: 1.5em;}

View file

@ -22,23 +22,24 @@ function eatMnemonic( stream, style, mnemonicStyle ) {
CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
var urlProtocols = new RegExp( config.mwextUrlProtocols, 'i' );
var permittedHtmlTags = {'b': true, 'bdi': true, 'del': true, 'i': true, 'ins': true,
'u': true, 'font': true, 'big': true, 'small': true, 'sub': true, 'sup': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 'cite': true,
'code': true, 'em': true, 's': true, 'strike': true, 'strong': true, 'tt': true,
'var': true, 'div': true, 'center': true, 'blockquote': true, 'ol': true, 'ul': true,
'dl': true, 'table': true, 'caption': true, 'pre': true, 'ruby': true, 'rb': true,
'rp': true, 'rt': true, 'rtc': true, 'p': true, 'span': true, 'abbr': true, 'dfn': true,
'kbd': true, 'samp': true, 'data': true, 'time': true, 'mark': true, 'br': true,
'wbr': true, 'hr': true, 'li': true, 'dt': true, 'dd': true, 'td': true, 'th': true,
'tr': true, 'noinclude': true, 'includeonly': true, 'onlyinclude': true};
var urlProtocols = new RegExp( config.mwextUrlProtocols, 'i' ),
permittedHtmlTags = {'b': true, 'bdi': true, 'del': true, 'i': true, 'ins': true,
'u': true, 'font': true, 'big': true, 'small': true, 'sub': true, 'sup': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 'cite': true,
'code': true, 'em': true, 's': true, 'strike': true, 'strong': true, 'tt': true,
'var': true, 'div': true, 'center': true, 'blockquote': true, 'ol': true, 'ul': true,
'dl': true, 'table': true, 'caption': true, 'pre': true, 'ruby': true, 'rb': true,
'rp': true, 'rt': true, 'rtc': true, 'p': true, 'span': true, 'abbr': true, 'dfn': true,
'kbd': true, 'samp': true, 'data': true, 'time': true, 'mark': true, 'br': true,
'wbr': true, 'hr': true, 'li': true, 'dt': true, 'dd': true, 'td': true, 'th': true,
'tr': true, 'noinclude': true, 'includeonly': true, 'onlyinclude': true},
isBold, isItalic, firstsingleletterword, firstmultiletterword, firstspace, mBold, mItalic, mTokens = [], mStyle;
function makeStyle( style, state, endGround ) {
if ( state.isBold ) {
if ( isBold ) {
style += ' strong';
}
if ( state.isItalic ) {
if ( isItalic ) {
style += ' em';
}
return makeLocalStyle( style, state, endGround );
@ -351,25 +352,25 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
}
function eatLinkText() {
var isBold, isItalic;
var linkIsBold, linkIsItalic;
return function ( stream, state ) {
if ( stream.match( ']]' ) ) {
state.tokenize = state.stack.pop();
return makeLocalStyle( 'mw-link-bracket', state, 'nLink' );
}
if ( stream.match( '\'\'\'' ) ) {
isBold = (isBold ? false : true);
linkIsBold = (linkIsBold ? false : true);
return makeLocalStyle( 'mw-link-text mw-apostrophes', state );
}
if ( stream.match( '\'\'' ) ) {
isItalic = (isItalic ? false : true);
linkIsItalic = (linkIsItalic ? false : true);
return makeLocalStyle( 'mw-link-text mw-apostrophes', state );
}
var tmpstyle = 'mw-link-text';
if ( isBold ) {
if ( linkIsBold ) {
tmpstyle += ' strong';
}
if ( isItalic ) {
if ( linkIsItalic ) {
tmpstyle += ' em';
}
if ( stream.match( /[^'\]\{\&~]+/ ) ) {
@ -457,9 +458,10 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
function eatExtTagArea( name ) {
return function( stream, state ) {
var origString = false, from = stream.pos, to;
var pattern = new RegExp( '</' + name + '\\s*>' );
var m = pattern.exec( from ? stream.string.slice( from ) : stream.string );
var origString = false, from = stream.pos, to,
pattern = new RegExp( '</' + name + '\\s*>' ),
m = pattern.exec( from ? stream.string.slice( from ) : stream.string );
if ( m ) {
if ( m.index === 0 ) {
state.tokenize = eatExtCloseTag( name );
@ -474,6 +476,7 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
origString = stream.string;
stream.string = origString.slice( 0, to );
}
state.stack.push( state.tokenize );
state.tokenize = eatExtTokens( origString );
return state.tokenize( stream, state );
@ -525,13 +528,9 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
}
function inTableCaption( stream, state ) {
if ( stream.sol() ) {
state.isBold = false;
state.isItalic = false;
if ( stream.match( /[\s\u00a0]*[\|!]/, false ) ) {
state.tokenize = inTable;
return inTable( stream, state );
}
if ( stream.sol() && stream.match( /[\s\u00a0]*[\|!]/, false ) ) {
state.tokenize = inTable;
return inTable( stream, state );
}
return eatWikiText( 'mw-table-caption', '' )( stream, state );
}
@ -570,8 +569,6 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
function eatTableRow( isStart, isHead ) {
return function ( stream, state ) {
if ( stream.sol() ) {
state.isBold = false;
state.isItalic = false;
if ( stream.match( /[\s\u00a0]*[\|!]/, false ) ) {
state.tokenize = inTable;
return inTable( stream, state );
@ -581,8 +578,8 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
return makeStyle( (isHead ? 'strong' : ''), state );
}
if ( stream.match( '||' ) || isHead && stream.match( '!!' ) || (isStart && stream.eat( '|' )) ) {
state.isBold = false;
state.isItalic = false;
isBold = false;
isItalic = false;
if ( isStart ) {
state.tokenize = eatTableRow( false, isHead );
}
@ -636,8 +633,6 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
var ch, sol = stream.sol();
if ( sol ) {
state.isBold = false;
state.isItalic = false;
if ( stream.match( urlProtocols ) ) { // highlight free external links, bug T108448
state.stack.push( state.tokenize );
state.tokenize = eatFreeExternalLink;
@ -704,12 +699,18 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
case '&':
return makeStyle( eatMnemonic( stream, style, mnemonicStyle ), state );
case '\'':
if ( stream.match( '\'\'' ) ) {
state.isBold = state.isBold ? false : true;
return makeLocalStyle( 'mw-apostrophes', state );
} else if ( stream.eat( '\'' ) ) {
state.isItalic = state.isItalic ? false : true;
return makeLocalStyle( 'mw-apostrophes', state );
if ( stream.match( /'*(?=''''')/ ) || stream.match( /'''(?!')/, false ) ) { // skip the irrelevant apostrophes ( >5 or =4 )
break;
}
if ( stream.match( '\'\'' ) ) { // bold\
if ( !(firstsingleletterword || stream.match( '\'\'', false )) ) {
prepareItalicForCorrection( stream );
}
isBold = isBold ? false : true;
return makeLocalStyle( 'mw-apostrophes-bold', state );
} else if ( stream.eat( '\'' ) ) { // italic
isItalic = isItalic ? false : true;
return makeLocalStyle( 'mw-apostrophes-italic', state );
}
break;
case '[':
@ -815,17 +816,50 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
};
}
/**
* Remembers position and status for rollbacking.
* It needed for change bold to italic with apostrophe before it if required
* @see https://phabricator.wikimedia.org/T108455
* @param CodeMirror.StringStream stream
* @returns null
*/
function prepareItalicForCorrection( stream ) {
// see Parser::doQuotes() in MediaWiki core, it works similar
// firstsingleletterword has maximum priority
// firstmultiletterword has medium priority
// firstspace has low priority
var end = stream.pos,
str = stream.string.substr( 0, end - 3 ),
x1 = str.substr( -1, 1 ),
x2 = str.substr( -2, 1 );
// firstsingleletterword olways is undefined here
if ( x1 === ' ' ) {
if ( firstmultiletterword || firstspace ) {
return;
}
firstspace = end;
} else if ( x2 === ' ' ) {
firstsingleletterword = end;
} else if ( firstmultiletterword ) {
return;
} else {
firstmultiletterword = end;
}
// remember bold and italic state for restore
mBold = isBold;
mItalic = isItalic;
}
return {
startState: function() {
return { tokenize: eatWikiText('', ''), stack: [], InHtmlTag:[], isBold: false, isItalic: false, extName: false, extMode: false, extState: false, nTemplate: 0, nLink: 0, nExt: 0 };
return { tokenize: eatWikiText('', ''), stack: [], InHtmlTag:[], extName: false, extMode: false, extState: false, nTemplate: 0, nLink: 0, nExt: 0 };
},
copyState: function( state ) {
return {
tokenize: state.tokenize,
stack: state.stack.concat( [] ),
InHtmlTag: state.InHtmlTag.concat( [] ),
isBold: state.isBold,
isItalic: state.isItalic,
extName: state.extName,
extMode: state.extMode,
extState: state.extMode !== false && CodeMirror.copyState( state.extMode, state.extState ),
@ -835,7 +869,68 @@ CodeMirror.defineMode( 'mediawiki', function( config/*, parserConfig */ ) {
};
},
token: function( stream, state ) {
return state.tokenize( stream, state );
var style, p, t, f,
readyTokens = [],
tmpTokens = [];
if ( mTokens.length > 0 ) { // just send saved tokens till they exists
t = mTokens.shift();
stream.pos = t.pos;
state = t.state;
return t.style;
}
if ( stream.sol() ) { // reset bold and italic status in every new line
isBold = false;
isItalic = false;
firstsingleletterword = undefined;
firstmultiletterword = undefined;
firstspace = undefined;
}
do {
style = state.tokenize( stream, state ); // get token style
f = firstsingleletterword || firstmultiletterword || firstspace;
if ( f ) { // rollback point exists
if ( f !== p ) { // new rollbak point
p = f;
if ( tmpTokens.length > 0 ) { // it's not first rollbak point
readyTokens = readyTokens.concat( tmpTokens ); // save tokens
tmpTokens = [];
}
}
tmpTokens.push( { // save token
pos: stream.pos,
style: style,
state: CodeMirror.copyState( state.extMode ? state.extMode : 'mediawiki', state )
} );
} else { // rollback point not exists
mStyle = style; // remember style before possible rollback point
return style; // just return token style
}
} while ( !stream.eol() );
if ( isBold && isItalic ) { // needs to rollback
isItalic = mItalic; // restore status
isBold = mBold;
firstsingleletterword = undefined;
firstmultiletterword = undefined;
firstspace = undefined;
if ( readyTokens.length > 0 ) { // it contains tickets before the point of rollback
readyTokens[readyTokens.length-1].pos++; // add one apostrophe, next token will be italic (two apostrophes)
mTokens = readyTokens; // for sending tokens till the point of rollback
} else { // there are no tikets before the point of rollback
stream.pos = tmpTokens[0].pos - 2; // eat( '\'')
return mStyle; // send saved Style
}
} else { // not needs to rollback
mTokens = readyTokens.concat( tmpTokens ); // send all saved tokens
}
// return first saved token
t = mTokens.shift();
stream.pos = t.pos;
state = t.state;
return t.style;
},
blankLine: function( state ) {
if ( state.extName ) {