jenkins-bot has submitted this change and it was merged.

Change subject: fix bold and italic apostrophes (v 3.2)
......................................................................


fix bold and italic apostrophes (v 3.2)

Bug: T108455
Change-Id: Ie9e0d734004d062e4c347f7940eb34bdc231d026
---
M extension.json
M resources/mode/mediawiki/mediawiki.css
M resources/mode/mediawiki/mediawiki.js
3 files changed, 141 insertions(+), 46 deletions(-)

Approvals:
  Pastakhov: Looks good to me, approved
  Florianschmidtwelzow: Looks good to me, but someone else must approve
  jenkins-bot: Verified



diff --git a/extension.json b/extension.json
index 5c73a5f..907c317 100644
--- a/extension.json
+++ b/extension.json
@@ -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]"
diff --git a/resources/mode/mediawiki/mediawiki.css 
b/resources/mode/mediawiki/mediawiki.css
index 197d764..aae2300 100644
--- a/resources/mode/mediawiki/mediawiki.css
+++ b/resources/mode/mediawiki/mediawiki.css
@@ -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;}
diff --git a/resources/mode/mediawiki/mediawiki.js 
b/resources/mode/mediawiki/mediawiki.js
index 0eca7f4..60b65fb 100644
--- a/resources/mode/mediawiki/mediawiki.js
+++ b/resources/mode/mediawiki/mediawiki.js
@@ -22,23 +22,24 @@
 
 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 @@
        }
 
        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 @@
 
        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 @@
                                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 @@
        }
 
        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 @@
        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 @@
                                        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 @@
                        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 @@
                                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 @@
                };
        }
 
+       /**
+        * 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 @@
                        };
                },
                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 ) {

-- 
To view, visit https://gerrit.wikimedia.org/r/232447
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ie9e0d734004d062e4c347f7940eb34bdc231d026
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/CodeMirror
Gerrit-Branch: master
Gerrit-Owner: Pastakhov <[email protected]>
Gerrit-Reviewer: Florianschmidtwelzow <[email protected]>
Gerrit-Reviewer: Pastakhov <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to