WordPress.org

Make WordPress Core

Changeset 39150


Ignore:
Timestamp:
11/06/2016 11:37:09 PM (3 years ago)
Author:
iseulde
Message:

TinyMCE: wptextpattern: Fix inline patterns.

  • Allow spaces inside inline pattern text, unless the delimiter stands alone.
  • Add more unit tests.
  • Add more inline docs.

Part props azaozz.
Fixes #37693.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/js/tinymce/plugins/wptextpattern/plugin.js

    r39077 r39150  
    44 * @since 4.3.0
    55 *
    6  * This plugin can automatically format text patterns as you type. It includes two patterns:
     6 * This plugin can automatically format text patterns as you type. It includes several groups of patterns.
     7 *
     8 * Start of line patterns:
     9 *  As-you-type:
    710 *  - Unordered list (`* ` and `- `).
    811 *  - Ordered list (`1. ` and `1) `).
    912 *
     13 *  On enter:
     14 *  - h2 (## ).
     15 *  - h3 (### ).
     16 *  - h4 (#### ).
     17 *  - h5 (##### ).
     18 *  - h6 (###### ).
     19 *  - blockquote (> ).
     20 *  - hr (---).
     21 *
     22 * Inline patterns:
     23 *  - <code> (`) (backtick).
     24 *
    1025 * If the transformation in unwanted, the user can undo the change by pressing backspace,
    1126 * using the undo shortcut, or the undo button in the toolbar.
     27 *
     28 * Setting for the patterns can be overridden by plugins by using the `tiny_mce_before_init` PHP filter.
     29 * The setting name is `wptextpattern` and the value is an object containing override arrays for each
     30 * patterns group. There are three groups: "space", "enter", and "inline". Example (PHP):
     31 *
     32 * add_filter( 'tiny_mce_before_init', 'my_mce_init_wptextpattern' );
     33 * function my_mce_init_wptextpattern( $init ) {
     34 *   $init['wptextpattern'] = wp_json_encode( array(
     35 *      'inline' => array(
     36 *        array( 'delimiter' => '**', 'format' => 'bold' ),
     37 *        array( 'delimiter' => '__', 'format' => 'italic' ),
     38 *      ),
     39 *   ) );
     40 *
     41 *   return $init;
     42 * }
     43 *
     44 * Note that setting this will override the default text patterns. You will need to include them
     45 * in your settings array if you want to keep them working.
    1246 */
    1347( function( tinymce, setTimeout ) {
     
    4781
    4882        var inlinePatterns = settings.inline || [
    49             { start: '`', end: '`', format: 'code' }
     83            { delimiter: '`', format: 'code' }
    5084        ];
    5185
    5286        var canUndo;
    53         var chars = [];
    54 
    55         tinymce.each( inlinePatterns, function( pattern ) {
    56             tinymce.each( ( pattern.start + pattern.end ).split( '' ), function( c ) {
    57                 if ( tinymce.inArray( chars, c ) === -1 ) {
    58                     chars.push( c );
    59                 }
    60             } );
    61         } );
    6287
    6388        editor.on( 'selectionchange', function() {
     
    101126            }
    102127
    103             // The ending character should exist in the patterns registered.
    104             if ( tinymce.inArray( chars, node.data.charAt( offset - 1 ) ) === -1 ) {
    105                 return;
    106             }
    107 
    108128            var string = node.data.slice( 0, offset );
     129            var lastChar = node.data.charAt( offset - 1 );
    109130
    110131            tinymce.each( inlinePatterns, function( p ) {
    111                 var regExp = new RegExp( escapeRegExp( p.start ) + '\\S+' + escapeRegExp( p.end ) + '$' );
     132                // Character before selection should be delimiter.
     133                if ( lastChar !== p.delimiter.slice( -1 ) ) {
     134                    return;
     135                }
     136
     137                var escDelimiter = escapeRegExp( p.delimiter );
     138                var delimiterFirstChar = p.delimiter.charAt( 0 );
     139                var regExp = new RegExp( '(.*)' + escDelimiter + '.+' + escDelimiter + '$' );
    112140                var match = string.match( regExp );
    113141
     
    116144                }
    117145
    118                 // Don't allow pattern characters in the text.
    119                 if ( node.data.slice( match.index + p.start.length, offset - p.end.length ).indexOf( p.start.slice( 0, 1 ) ) !== -1 ) {
     146                startOffset = match[1].length;
     147                endOffset = offset - p.delimiter.length;
     148
     149                var before = string.charAt( startOffset - 1 );
     150                var after = string.charAt( startOffset + p.delimiter.length );
     151
     152                // test*test* => format applied
     153                // test *test* => applied
     154                // test* test* => not applied
     155                if ( startOffset && /\S/.test( before ) ) {
     156                    if ( /\s/.test( after ) || before === delimiterFirstChar ) {
     157                        return;
     158                    }
     159                }
     160
     161                // Do not replace when only whitespace and delimiter characters.
     162                if ( ( new RegExp( '^[\\s' + escapeRegExp( delimiterFirstChar ) + ']+$' ) ).test( string.slice( startOffset, endOffset ) ) ) {
    120163                    return;
    121164                }
    122165
    123                 startOffset = match.index;
    124                 endOffset = offset - p.end.length;
    125166                pattern = p;
    126167
     
    143184                    zero = node.splitText( offset - startOffset );
    144185
    145                     node.deleteData( 0, pattern.start.length );
    146                     node.deleteData( node.data.length - pattern.end.length, pattern.end.length );
     186                    node.deleteData( 0, pattern.delimiter.length );
     187                    node.deleteData( node.data.length - pattern.delimiter.length, pattern.delimiter.length );
    147188
    148189                    editor.formatter.apply( pattern.format, {}, node );
  • trunk/tests/qunit/wp-includes/js/tinymce/plugins/wptextpattern/plugin.js

    r39075 r39150  
    158158                    wptextpattern: {
    159159                        inline: [
    160                             { start: '`', end: '`', format: 'code' },
    161                             { start: '``', end: '``', format: 'bold' }
     160                            { delimiter: '`', format: 'code' },
     161                            { delimiter: '``', format: 'bold' },
     162                            { delimiter: '```', format: 'italic' }
    162163                        ]
    163164                    },
     
    320321    } );
    321322
     323    QUnit.test( 'Inline: allow spaces within text.', function( assert ) {
     324        type( '`a a`', function() {
     325            assert.equal( editor.getContent(), '<p><code>a a</code></p>' );
     326            assert.equal( editor.selection.getRng().startOffset, 1 );
     327        }, assert.async() );
     328    } );
     329
     330    QUnit.test( 'Inline: disallow \\S-delimiter-\\s.', function( assert ) {
     331        type( 'a` a`', function() {
     332            assert.equal( editor.getContent(), '<p>a` a`</p>' );
     333            assert.equal( editor.selection.getRng().startOffset, 5 );
     334        }, assert.async() );
     335    } );
     336
     337    QUnit.test( 'Inline: allow \\s-delimiter-\\s.', function( assert ) {
     338        type( 'a ` a`', function() {
     339            assert.equal( editor.getContent(), '<p>a <code> a</code></p>' );
     340            assert.equal( editor.selection.getRng().startOffset, 1 );
     341        }, assert.async() );
     342    } );
     343
     344    QUnit.test( 'Inline: allow \\S-delimiter-\\S.', function( assert ) {
     345        type( 'a`a`', function() {
     346            assert.equal( editor.getContent(), '<p>a<code>a</code></p>' );
     347            assert.equal( editor.selection.getRng().startOffset, 1 );
     348        }, assert.async() );
     349    } );
     350
    322351    QUnit.test( 'Inline: after typing.', function( assert ) {
    323352        editor.setContent( '<p>test test test</p>' );
     
    332361    } );
    333362
    334     QUnit.test( 'Inline: no change.', function( assert ) {
    335         type( 'test `````', function() {
    336             assert.equal( editor.getContent(), '<p>test `````</p>' );
    337         }, assert.async() );
    338     } );
    339 
    340     QUnit.test( 'Convert with previously unconverted pattern', function( assert ) {
     363    QUnit.test( 'Inline: no change without content.', function( assert ) {
     364        type( 'test `` ``` ````', function() {
     365            assert.equal( editor.getContent(), '<p>test `` ``` ````</p>' );
     366        }, assert.async() );
     367    } );
     368
     369    QUnit.test( 'Inline: convert with previously unconverted pattern.', function( assert ) {
    341370        editor.setContent( '<p>`test` test&nbsp;</p>' );
    342371        editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 12 );
     
    346375        }, assert.async() );
    347376    } );
     377
     378    QUnit.test( 'Inline: convert with previous pattern characters.', function( assert ) {
     379        editor.setContent( '<p>test``` 123</p>' );
     380        editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 11 );
     381
     382        type( '``456``', function() {
     383            assert.equal( editor.getContent(), '<p>test``` 123<strong>456</strong></p>' );
     384        }, assert.async() );
     385    } );
     386
     387    QUnit.test( 'Inline: disallow after previous pattern characters and leading space.', function( assert ) {
     388        editor.setContent( '<p>test``` 123</p>' );
     389        editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 11 );
     390
     391        type( '``` 456```', function() {
     392            assert.equal( editor.getContent(), '<p>test``` 123``` 456```</p>' );
     393        }, assert.async() );
     394    } );
    348395} )( window.jQuery, window.QUnit, window.tinymce, window.setTimeout );
Note: See TracChangeset for help on using the changeset viewer.