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.



git-svn-id: https://develop.svn.wordpress.org/trunk@39150 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Ella van Dorpe 2016-11-06 23:37:09 +00:00
parent 7f069a69dc
commit c78f002ae6
2 changed files with 117 additions and 29 deletions

View File

@ -3,12 +3,46 @@
*
* @since 4.3.0
*
* This plugin can automatically format text patterns as you type. It includes two patterns:
* This plugin can automatically format text patterns as you type. It includes several groups of patterns.
*
* Start of line patterns:
* As-you-type:
* - Unordered list (`* ` and `- `).
* - Ordered list (`1. ` and `1) `).
*
* On enter:
* - h2 (## ).
* - h3 (### ).
* - h4 (#### ).
* - h5 (##### ).
* - h6 (###### ).
* - blockquote (> ).
* - hr (---).
*
* Inline patterns:
* - <code> (`) (backtick).
*
* If the transformation in unwanted, the user can undo the change by pressing backspace,
* using the undo shortcut, or the undo button in the toolbar.
*
* Setting for the patterns can be overridden by plugins by using the `tiny_mce_before_init` PHP filter.
* The setting name is `wptextpattern` and the value is an object containing override arrays for each
* patterns group. There are three groups: "space", "enter", and "inline". Example (PHP):
*
* add_filter( 'tiny_mce_before_init', 'my_mce_init_wptextpattern' );
* function my_mce_init_wptextpattern( $init ) {
* $init['wptextpattern'] = wp_json_encode( array(
* 'inline' => array(
* array( 'delimiter' => '**', 'format' => 'bold' ),
* array( 'delimiter' => '__', 'format' => 'italic' ),
* ),
* ) );
*
* return $init;
* }
*
* Note that setting this will override the default text patterns. You will need to include them
* in your settings array if you want to keep them working.
*/
( function( tinymce, setTimeout ) {
if ( tinymce.Env.ie && tinymce.Env.ie < 9 ) {
@ -46,19 +80,10 @@
];
var inlinePatterns = settings.inline || [
{ start: '`', end: '`', format: 'code' }
{ delimiter: '`', format: 'code' }
];
var canUndo;
var chars = [];
tinymce.each( inlinePatterns, function( pattern ) {
tinymce.each( ( pattern.start + pattern.end ).split( '' ), function( c ) {
if ( tinymce.inArray( chars, c ) === -1 ) {
chars.push( c );
}
} );
} );
editor.on( 'selectionchange', function() {
canUndo = null;
@ -100,28 +125,44 @@
return;
}
// The ending character should exist in the patterns registered.
if ( tinymce.inArray( chars, node.data.charAt( offset - 1 ) ) === -1 ) {
return;
}
var string = node.data.slice( 0, offset );
var lastChar = node.data.charAt( offset - 1 );
tinymce.each( inlinePatterns, function( p ) {
var regExp = new RegExp( escapeRegExp( p.start ) + '\\S+' + escapeRegExp( p.end ) + '$' );
// Character before selection should be delimiter.
if ( lastChar !== p.delimiter.slice( -1 ) ) {
return;
}
var escDelimiter = escapeRegExp( p.delimiter );
var delimiterFirstChar = p.delimiter.charAt( 0 );
var regExp = new RegExp( '(.*)' + escDelimiter + '.+' + escDelimiter + '$' );
var match = string.match( regExp );
if ( ! match ) {
return;
}
// Don't allow pattern characters in the text.
if ( node.data.slice( match.index + p.start.length, offset - p.end.length ).indexOf( p.start.slice( 0, 1 ) ) !== -1 ) {
startOffset = match[1].length;
endOffset = offset - p.delimiter.length;
var before = string.charAt( startOffset - 1 );
var after = string.charAt( startOffset + p.delimiter.length );
// test*test* => format applied
// test *test* => applied
// test* test* => not applied
if ( startOffset && /\S/.test( before ) ) {
if ( /\s/.test( after ) || before === delimiterFirstChar ) {
return;
}
}
// Do not replace when only whitespace and delimiter characters.
if ( ( new RegExp( '^[\\s' + escapeRegExp( delimiterFirstChar ) + ']+$' ) ).test( string.slice( startOffset, endOffset ) ) ) {
return;
}
startOffset = match.index;
endOffset = offset - p.end.length;
pattern = p;
return false;
@ -142,8 +183,8 @@
node = node.splitText( startOffset );
zero = node.splitText( offset - startOffset );
node.deleteData( 0, pattern.start.length );
node.deleteData( node.data.length - pattern.end.length, pattern.end.length );
node.deleteData( 0, pattern.delimiter.length );
node.deleteData( node.data.length - pattern.delimiter.length, pattern.delimiter.length );
editor.formatter.apply( pattern.format, {}, node );

View File

@ -157,8 +157,9 @@
plugins: 'wptextpattern',
wptextpattern: {
inline: [
{ start: '`', end: '`', format: 'code' },
{ start: '``', end: '``', format: 'bold' }
{ delimiter: '`', format: 'code' },
{ delimiter: '``', format: 'bold' },
{ delimiter: '```', format: 'italic' }
]
},
init_instance_callback: function() {
@ -319,6 +320,34 @@
}, assert.async() );
} );
QUnit.test( 'Inline: allow spaces within text.', function( assert ) {
type( '`a a`', function() {
assert.equal( editor.getContent(), '<p><code>a a</code></p>' );
assert.equal( editor.selection.getRng().startOffset, 1 );
}, assert.async() );
} );
QUnit.test( 'Inline: disallow \\S-delimiter-\\s.', function( assert ) {
type( 'a` a`', function() {
assert.equal( editor.getContent(), '<p>a` a`</p>' );
assert.equal( editor.selection.getRng().startOffset, 5 );
}, assert.async() );
} );
QUnit.test( 'Inline: allow \\s-delimiter-\\s.', function( assert ) {
type( 'a ` a`', function() {
assert.equal( editor.getContent(), '<p>a <code> a</code></p>' );
assert.equal( editor.selection.getRng().startOffset, 1 );
}, assert.async() );
} );
QUnit.test( 'Inline: allow \\S-delimiter-\\S.', function( assert ) {
type( 'a`a`', function() {
assert.equal( editor.getContent(), '<p>a<code>a</code></p>' );
assert.equal( editor.selection.getRng().startOffset, 1 );
}, assert.async() );
} );
QUnit.test( 'Inline: after typing.', function( assert ) {
editor.setContent( '<p>test test test</p>' );
editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 5 );
@ -331,13 +360,13 @@
}, assert.async() );
} );
QUnit.test( 'Inline: no change.', function( assert ) {
type( 'test `````', function() {
assert.equal( editor.getContent(), '<p>test `````</p>' );
QUnit.test( 'Inline: no change without content.', function( assert ) {
type( 'test `` ``` ````', function() {
assert.equal( editor.getContent(), '<p>test `` ``` ````</p>' );
}, assert.async() );
} );
QUnit.test( 'Convert with previously unconverted pattern', function( assert ) {
QUnit.test( 'Inline: convert with previously unconverted pattern.', function( assert ) {
editor.setContent( '<p>`test` test&nbsp;</p>' );
editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 12 );
@ -345,4 +374,22 @@
assert.equal( editor.getContent(), '<p>`test` test&nbsp;<code>test</code></p>' );
}, assert.async() );
} );
QUnit.test( 'Inline: convert with previous pattern characters.', function( assert ) {
editor.setContent( '<p>test``` 123</p>' );
editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 11 );
type( '``456``', function() {
assert.equal( editor.getContent(), '<p>test``` 123<strong>456</strong></p>' );
}, assert.async() );
} );
QUnit.test( 'Inline: disallow after previous pattern characters and leading space.', function( assert ) {
editor.setContent( '<p>test``` 123</p>' );
editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 11 );
type( '``` 456```', function() {
assert.equal( editor.getContent(), '<p>test``` 123``` 456```</p>' );
}, assert.async() );
} );
} )( window.jQuery, window.QUnit, window.tinymce, window.setTimeout );