]> scripts.mit.edu Git - autoinstalls/wordpress.git/blobdiff - wp-includes/js/tinymce/plugins/wptextpattern/plugin.js
WordPress 4.7
[autoinstalls/wordpress.git] / wp-includes / js / tinymce / plugins / wptextpattern / plugin.js
index 5f3ff1a827f88b18198653cb1e30f29b47e1b651..236775034cce381f30df6f412d795d60424971f0 100644 (file)
@@ -3,27 +3,73 @@
  *
  * @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 ) {
                return;
        }
 
+       /**
+        * Escapes characters for use in a Regular Expression.
+        *
+        * @param  {String} string Characters to escape
+        *
+        * @return {String}        Escaped characters
+        */
+       function escapeRegExp( string ) {
+               return string.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' );
+       }
+
        tinymce.PluginManager.add( 'wptextpattern', function( editor ) {
                var VK = tinymce.util.VK;
+               var settings = editor.settings.wptextpattern || {};
 
-               var spacePatterns = [
+               var spacePatterns = settings.space || [
                        { regExp: /^[*-]\s/, cmd: 'InsertUnorderedList' },
                        { regExp: /^1[.)]\s/, cmd: 'InsertOrderedList' }
                ];
 
-               var enterPatterns = [
+               var enterPatterns = settings.enter || [
                        { start: '##', format: 'h2' },
                        { start: '###', format: 'h3' },
                        { start: '####', format: 'h4' },
                        { regExp: /^(-){3,}$/, element: 'hr' }
                ];
 
-               var inlinePatterns = [
-                       { start: '`', end: '`', format: 'code' }
+               var inlinePatterns = settings.inline || [
+                       { 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;
                                event.stopImmediatePropagation();
                        }
 
-                       if ( event.keyCode === VK.ENTER && ! VK.modifierPressed( event ) ) {
-                               enter();
+                       if ( VK.metaKeyPressed( event ) ) {
+                               return;
                        }
 
+                       if ( event.keyCode === VK.ENTER ) {
+                               enter();
                        // Wait for the browser to insert the character.
-                       if ( event.keyCode === VK.SPACEBAR && ! event.ctrlKey && ! event.metaKey && ! event.altKey ) {
+                       } else if ( event.keyCode === VK.SPACEBAR ) {
                                setTimeout( space );
                        } else if ( event.keyCode > 47 && ! ( event.keyCode >= 91 && event.keyCode <= 93 ) ) {
                                setTimeout( inline );
                        var format;
                        var zero;
 
+                       // We need a non empty text node with an offset greater than zero.
                        if ( ! node || node.nodeType !== 3 || ! node.data.length || ! offset ) {
                                return;
                        }
 
-                       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 );
 
-                       function findStart( node ) {
-                               var i = inlinePatterns.length;
-                               var offset;
+                       tinymce.each( inlinePatterns, function( p ) {
+                               // 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 );
 
-                               while ( i-- ) {
-                                       pattern = inlinePatterns[ i ];
-                                       offset = node.data.indexOf( pattern.end );
+                               if ( ! match ) {
+                                       return;
+                               }
 
-                                       if ( offset !== -1 ) {
-                                               return offset;
+                               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;
                                        }
                                }
-                       }
 
-                       startOffset = findStart( node );
-                       endOffset = node.data.lastIndexOf( pattern.end );
+                               // Do not replace when only whitespace and delimiter characters.
+                               if ( ( new RegExp( '^[\\s' + escapeRegExp( delimiterFirstChar ) + ']+$' ) ).test( string.slice( startOffset, endOffset ) ) ) {
+                                       return;
+                               }
 
-                       if ( startOffset === endOffset || endOffset === -1 ) {
-                               return;
-                       }
+                               pattern = p;
 
-                       if ( endOffset - startOffset <= pattern.start.length ) {
-                               return;
-                       }
+                               return false;
+                       } );
 
-                       if ( node.data.slice( startOffset + pattern.start.length, endOffset ).indexOf( pattern.start.slice( 0, 1 ) ) !== -1 ) {
+                       if ( ! pattern ) {
                                return;
                        }
 
                                editor.undoManager.add();
 
                                editor.undoManager.transact( function() {
-                                       node.insertData( offset, '\u200b' );
+                                       node.insertData( offset, '\uFEFF' );
 
                                        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 );
 
                                                var offset;
 
                                                if ( zero ) {
-                                                       offset = zero.data.indexOf( '\u200b' );
+                                                       offset = zero.data.indexOf( '\uFEFF' );
 
                                                        if ( offset !== -1 ) {
                                                                zero.deleteData( offset, offset + 1 );