home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / js / tinymce / plugins / wptextpattern / plugin.js next >
Encoding:
Text File  |  2016-11-06  |  8.6 KB  |  349 lines

  1. /**
  2.  * Text pattern plugin for TinyMCE
  3.  *
  4.  * @since 4.3.0
  5.  *
  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:
  10.  *  - Unordered list (`* ` and `- `).
  11.  *  - Ordered list (`1. ` and `1) `).
  12.  *
  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.  *
  25.  * If the transformation in unwanted, the user can undo the change by pressing backspace,
  26.  * 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.
  46.  */
  47. ( function( tinymce, setTimeout ) {
  48.     if ( tinymce.Env.ie && tinymce.Env.ie < 9 ) {
  49.         return;
  50.     }
  51.  
  52.     /**
  53.      * Escapes characters for use in a Regular Expression.
  54.      *
  55.      * @param  {String} string Characters to escape
  56.      *
  57.      * @return {String}        Escaped characters
  58.      */
  59.     function escapeRegExp( string ) {
  60.         return string.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' );
  61.     }
  62.  
  63.     tinymce.PluginManager.add( 'wptextpattern', function( editor ) {
  64.         var VK = tinymce.util.VK;
  65.         var settings = editor.settings.wptextpattern || {};
  66.  
  67.         var spacePatterns = settings.space || [
  68.             { regExp: /^[*-]\s/, cmd: 'InsertUnorderedList' },
  69.             { regExp: /^1[.)]\s/, cmd: 'InsertOrderedList' }
  70.         ];
  71.  
  72.         var enterPatterns = settings.enter || [
  73.             { start: '##', format: 'h2' },
  74.             { start: '###', format: 'h3' },
  75.             { start: '####', format: 'h4' },
  76.             { start: '#####', format: 'h5' },
  77.             { start: '######', format: 'h6' },
  78.             { start: '>', format: 'blockquote' },
  79.             { regExp: /^(-){3,}$/, element: 'hr' }
  80.         ];
  81.  
  82.         var inlinePatterns = settings.inline || [
  83.             { delimiter: '`', format: 'code' }
  84.         ];
  85.  
  86.         var canUndo;
  87.  
  88.         editor.on( 'selectionchange', function() {
  89.             canUndo = null;
  90.         } );
  91.  
  92.         editor.on( 'keydown', function( event ) {
  93.             if ( ( canUndo && event.keyCode === 27 /* ESCAPE */ ) || ( canUndo === 'space' && event.keyCode === VK.BACKSPACE ) ) {
  94.                 editor.undoManager.undo();
  95.                 event.preventDefault();
  96.                 event.stopImmediatePropagation();
  97.             }
  98.  
  99.             if ( VK.metaKeyPressed( event ) ) {
  100.                 return;
  101.             }
  102.  
  103.             if ( event.keyCode === VK.ENTER ) {
  104.                 enter();
  105.             // Wait for the browser to insert the character.
  106.             } else if ( event.keyCode === VK.SPACEBAR ) {
  107.                 setTimeout( space );
  108.             } else if ( event.keyCode > 47 && ! ( event.keyCode >= 91 && event.keyCode <= 93 ) ) {
  109.                 setTimeout( inline );
  110.             }
  111.         }, true );
  112.  
  113.         function inline() {
  114.             var rng = editor.selection.getRng();
  115.             var node = rng.startContainer;
  116.             var offset = rng.startOffset;
  117.             var startOffset;
  118.             var endOffset;
  119.             var pattern;
  120.             var format;
  121.             var zero;
  122.  
  123.             // We need a non empty text node with an offset greater than zero.
  124.             if ( ! node || node.nodeType !== 3 || ! node.data.length || ! offset ) {
  125.                 return;
  126.             }
  127.  
  128.             var string = node.data.slice( 0, offset );
  129.             var lastChar = node.data.charAt( offset - 1 );
  130.  
  131.             tinymce.each( inlinePatterns, function( p ) {
  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 + '$' );
  140.                 var match = string.match( regExp );
  141.  
  142.                 if ( ! match ) {
  143.                     return;
  144.                 }
  145.  
  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 ) ) ) {
  163.                     return;
  164.                 }
  165.  
  166.                 pattern = p;
  167.  
  168.                 return false;
  169.             } );
  170.  
  171.             if ( ! pattern ) {
  172.                 return;
  173.             }
  174.  
  175.             format = editor.formatter.get( pattern.format );
  176.  
  177.             if ( format && format[0].inline ) {
  178.                 editor.undoManager.add();
  179.  
  180.                 editor.undoManager.transact( function() {
  181.                     node.insertData( offset, '\uFEFF' );
  182.  
  183.                     node = node.splitText( startOffset );
  184.                     zero = node.splitText( offset - startOffset );
  185.  
  186.                     node.deleteData( 0, pattern.delimiter.length );
  187.                     node.deleteData( node.data.length - pattern.delimiter.length, pattern.delimiter.length );
  188.  
  189.                     editor.formatter.apply( pattern.format, {}, node );
  190.  
  191.                     editor.selection.setCursorLocation( zero, 1 );
  192.                 } );
  193.  
  194.                 // We need to wait for native events to be triggered.
  195.                 setTimeout( function() {
  196.                     canUndo = 'space';
  197.  
  198.                     editor.once( 'selectionchange', function() {
  199.                         var offset;
  200.  
  201.                         if ( zero ) {
  202.                             offset = zero.data.indexOf( '\uFEFF' );
  203.  
  204.                             if ( offset !== -1 ) {
  205.                                 zero.deleteData( offset, offset + 1 );
  206.                             }
  207.                         }
  208.                     } );
  209.                 } );
  210.             }
  211.         }
  212.  
  213.         function firstTextNode( node ) {
  214.             var parent = editor.dom.getParent( node, 'p' ),
  215.                 child;
  216.  
  217.             if ( ! parent ) {
  218.                 return;
  219.             }
  220.  
  221.             while ( child = parent.firstChild ) {
  222.                 if ( child.nodeType !== 3 ) {
  223.                     parent = child;
  224.                 } else {
  225.                     break;
  226.                 }
  227.             }
  228.  
  229.             if ( ! child ) {
  230.                 return;
  231.             }
  232.  
  233.             if ( ! child.data ) {
  234.                 if ( child.nextSibling && child.nextSibling.nodeType === 3 ) {
  235.                     child = child.nextSibling;
  236.                 } else {
  237.                     child = null;
  238.                 }
  239.             }
  240.  
  241.             return child;
  242.         }
  243.  
  244.         function space() {
  245.             var rng = editor.selection.getRng(),
  246.                 node = rng.startContainer,
  247.                 parent,
  248.                 text;
  249.  
  250.             if ( ! node || firstTextNode( node ) !== node ) {
  251.                 return;
  252.             }
  253.  
  254.             parent = node.parentNode;
  255.             text = node.data;
  256.  
  257.             tinymce.each( spacePatterns, function( pattern ) {
  258.                 var match = text.match( pattern.regExp );
  259.  
  260.                 if ( ! match || rng.startOffset !== match[0].length ) {
  261.                     return;
  262.                 }
  263.  
  264.                 editor.undoManager.add();
  265.  
  266.                 editor.undoManager.transact( function() {
  267.                     node.deleteData( 0, match[0].length );
  268.  
  269.                     if ( ! parent.innerHTML ) {
  270.                         parent.appendChild( document.createElement( 'br' ) );
  271.                     }
  272.  
  273.                     editor.selection.setCursorLocation( parent );
  274.                     editor.execCommand( pattern.cmd );
  275.                 } );
  276.  
  277.                 // We need to wait for native events to be triggered.
  278.                 setTimeout( function() {
  279.                     canUndo = 'space';
  280.                 } );
  281.  
  282.                 return false;
  283.             } );
  284.         }
  285.  
  286.         function enter() {
  287.             var rng = editor.selection.getRng(),
  288.                 start = rng.startContainer,
  289.                 node = firstTextNode( start ),
  290.                 i = enterPatterns.length,
  291.                 text, pattern, parent;
  292.  
  293.             if ( ! node ) {
  294.                 return;
  295.             }
  296.  
  297.             text = node.data;
  298.  
  299.             while ( i-- ) {
  300.                 if ( enterPatterns[ i ].start ) {
  301.                     if ( text.indexOf( enterPatterns[ i ].start ) === 0 ) {
  302.                         pattern = enterPatterns[ i ];
  303.                         break;
  304.                     }
  305.                 } else if ( enterPatterns[ i ].regExp ) {
  306.                     if ( enterPatterns[ i ].regExp.test( text ) ) {
  307.                         pattern = enterPatterns[ i ];
  308.                         break;
  309.                     }
  310.                 }
  311.             }
  312.  
  313.             if ( ! pattern ) {
  314.                 return;
  315.             }
  316.  
  317.             if ( node === start && tinymce.trim( text ) === pattern.start ) {
  318.                 return;
  319.             }
  320.  
  321.             editor.once( 'keyup', function() {
  322.                 editor.undoManager.add();
  323.  
  324.                 editor.undoManager.transact( function() {
  325.                     if ( pattern.format ) {
  326.                         editor.formatter.apply( pattern.format, {}, node );
  327.                         node.replaceData( 0, node.data.length, ltrim( node.data.slice( pattern.start.length ) ) );
  328.                     } else if ( pattern.element ) {
  329.                         parent = node.parentNode && node.parentNode.parentNode;
  330.  
  331.                         if ( parent ) {
  332.                             parent.replaceChild( document.createElement( pattern.element ), node.parentNode );
  333.                         }
  334.                     }
  335.                 } );
  336.  
  337.                 // We need to wait for native events to be triggered.
  338.                 setTimeout( function() {
  339.                     canUndo = 'enter';
  340.                 } );
  341.             } );
  342.         }
  343.  
  344.         function ltrim( text ) {
  345.             return text ? text.replace( /^\s+/, '' ) : '';
  346.         }
  347.     } );
  348. } )( window.tinymce, window.setTimeout );
  349.