[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/tinymce/plugins/wptextpattern/ -> plugin.js (source)

   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 );


Generated : Mon Mar 18 08:20:01 2024 Cross-referenced by PHPXref