[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> editor.js (source)

   1  /**
   2   * @output wp-admin/js/editor.js
   3   */
   4  
   5  window.wp = window.wp || {};
   6  
   7  ( function( $, wp ) {
   8      wp.editor = wp.editor || {};
   9  
  10      /**
  11       * Utility functions for the editor.
  12       *
  13       * @since 2.5.0
  14       */
  15  	function SwitchEditors() {
  16          var tinymce, $$,
  17              exports = {};
  18  
  19  		function init() {
  20              if ( ! tinymce && window.tinymce ) {
  21                  tinymce = window.tinymce;
  22                  $$ = tinymce.$;
  23  
  24                  /**
  25                   * Handles onclick events for the Visual/Text tabs.
  26                   *
  27                   * @since 4.3.0
  28                   *
  29                   * @return {void}
  30                   */
  31                  $$( document ).on( 'click', function( event ) {
  32                      var id, mode,
  33                          target = $$( event.target );
  34  
  35                      if ( target.hasClass( 'wp-switch-editor' ) ) {
  36                          id = target.attr( 'data-wp-editor-id' );
  37                          mode = target.hasClass( 'switch-tmce' ) ? 'tmce' : 'html';
  38                          switchEditor( id, mode );
  39                      }
  40                  });
  41              }
  42          }
  43  
  44          /**
  45           * Returns the height of the editor toolbar(s) in px.
  46           *
  47           * @since 3.9.0
  48           *
  49           * @param {Object} editor The TinyMCE editor.
  50           * @return {number} If the height is between 10 and 200 return the height,
  51           * else return 30.
  52           */
  53  		function getToolbarHeight( editor ) {
  54              var node = $$( '.mce-toolbar-grp', editor.getContainer() )[0],
  55                  height = node && node.clientHeight;
  56  
  57              if ( height && height > 10 && height < 200 ) {
  58                  return parseInt( height, 10 );
  59              }
  60  
  61              return 30;
  62          }
  63  
  64          /**
  65           * Switches the editor between Visual and Text mode.
  66           *
  67           * @since 2.5.0
  68           *
  69           * @memberof switchEditors
  70           *
  71           * @param {string} id The id of the editor you want to change the editor mode for. Default: `content`.
  72           * @param {string} mode The mode you want to switch to. Default: `toggle`.
  73           * @return {void}
  74           */
  75  		function switchEditor( id, mode ) {
  76              id = id || 'content';
  77              mode = mode || 'toggle';
  78  
  79              var editorHeight, toolbarHeight, iframe,
  80                  editor = tinymce.get( id ),
  81                  wrap = $$( '#wp-' + id + '-wrap' ),
  82                  htmlSwitch = wrap.find( '.switch-tmce' ),
  83                  tmceSwitch = wrap.find( '.switch-html' ),
  84                  $textarea = $$( '#' + id ),
  85                  textarea = $textarea[0];
  86  
  87              if ( 'toggle' === mode ) {
  88                  if ( editor && ! editor.isHidden() ) {
  89                      mode = 'html';
  90                  } else {
  91                      mode = 'tmce';
  92                  }
  93              }
  94  
  95              if ( 'tmce' === mode || 'tinymce' === mode ) {
  96                  // If the editor is visible we are already in `tinymce` mode.
  97                  if ( editor && ! editor.isHidden() ) {
  98                      return false;
  99                  }
 100  
 101                  // Insert closing tags for any open tags in QuickTags.
 102                  if ( typeof( window.QTags ) !== 'undefined' ) {
 103                      window.QTags.closeAllTags( id );
 104                  }
 105  
 106                  editorHeight = parseInt( textarea.style.height, 10 ) || 0;
 107  
 108                  addHTMLBookmarkInTextAreaContent( $textarea );
 109  
 110                  if ( editor ) {
 111                      editor.show();
 112  
 113                      // No point to resize the iframe in iOS.
 114                      if ( ! tinymce.Env.iOS && editorHeight ) {
 115                          toolbarHeight = getToolbarHeight( editor );
 116                          editorHeight = editorHeight - toolbarHeight + 14;
 117  
 118                          // Sane limit for the editor height.
 119                          if ( editorHeight > 50 && editorHeight < 5000 ) {
 120                              editor.theme.resizeTo( null, editorHeight );
 121                          }
 122                      }
 123  
 124                      focusHTMLBookmarkInVisualEditor( editor );
 125                  } else {
 126                      tinymce.init( window.tinyMCEPreInit.mceInit[ id ] );
 127                  }
 128  
 129                  wrap.removeClass( 'html-active' ).addClass( 'tmce-active' );
 130                  tmceSwitch.attr( 'aria-pressed', false );
 131                  htmlSwitch.attr( 'aria-pressed', true );
 132                  $textarea.attr( 'aria-hidden', true );
 133                  window.setUserSetting( 'editor', 'tinymce' );
 134  
 135              } else if ( 'html' === mode ) {
 136                  // If the editor is hidden (Quicktags is shown) we don't need to switch.
 137                  if ( editor && editor.isHidden() ) {
 138                      return false;
 139                  }
 140  
 141                  if ( editor ) {
 142                      // Don't resize the textarea in iOS.
 143                      // The iframe is forced to 100% height there, we shouldn't match it.
 144                      if ( ! tinymce.Env.iOS ) {
 145                          iframe = editor.iframeElement;
 146                          editorHeight = iframe ? parseInt( iframe.style.height, 10 ) : 0;
 147  
 148                          if ( editorHeight ) {
 149                              toolbarHeight = getToolbarHeight( editor );
 150                              editorHeight = editorHeight + toolbarHeight - 14;
 151  
 152                              // Sane limit for the textarea height.
 153                              if ( editorHeight > 50 && editorHeight < 5000 ) {
 154                                  textarea.style.height = editorHeight + 'px';
 155                              }
 156                          }
 157                      }
 158  
 159                      var selectionRange = null;
 160  
 161                      selectionRange = findBookmarkedPosition( editor );
 162  
 163                      editor.hide();
 164  
 165                      if ( selectionRange ) {
 166                          selectTextInTextArea( editor, selectionRange );
 167                      }
 168                  } else {
 169                      // There is probably a JS error on the page.
 170                      // The TinyMCE editor instance doesn't exist. Show the textarea.
 171                      $textarea.css({ 'display': '', 'visibility': '' });
 172                  }
 173  
 174                  wrap.removeClass( 'tmce-active' ).addClass( 'html-active' );
 175                  tmceSwitch.attr( 'aria-pressed', true );
 176                  htmlSwitch.attr( 'aria-pressed', false );
 177                  $textarea.attr( 'aria-hidden', false );
 178                  window.setUserSetting( 'editor', 'html' );
 179              }
 180          }
 181  
 182          /**
 183           * Checks if a cursor is inside an HTML tag or comment.
 184           *
 185           * In order to prevent breaking HTML tags when selecting text, the cursor
 186           * must be moved to either the start or end of the tag.
 187           *
 188           * This will prevent the selection marker to be inserted in the middle of an HTML tag.
 189           *
 190           * This function gives information whether the cursor is inside a tag or not, as well as
 191           * the tag type, if it is a closing tag and check if the HTML tag is inside a shortcode tag,
 192           * e.g. `[caption]<img.../>..`.
 193           *
 194           * @param {string} content The test content where the cursor is.
 195           * @param {number} cursorPosition The cursor position inside the content.
 196           *
 197           * @return {(null|Object)} Null if cursor is not in a tag, Object if the cursor is inside a tag.
 198           */
 199  		function getContainingTagInfo( content, cursorPosition ) {
 200              var lastLtPos = content.lastIndexOf( '<', cursorPosition - 1 ),
 201                  lastGtPos = content.lastIndexOf( '>', cursorPosition );
 202  
 203              if ( lastLtPos > lastGtPos || content.substr( cursorPosition, 1 ) === '>' ) {
 204                  // Find what the tag is.
 205                  var tagContent = content.substr( lastLtPos ),
 206                      tagMatch = tagContent.match( /<\s*(\/)?(\w+|\!-{2}.*-{2})/ );
 207  
 208                  if ( ! tagMatch ) {
 209                      return null;
 210                  }
 211  
 212                  var tagType = tagMatch[2],
 213                      closingGt = tagContent.indexOf( '>' );
 214  
 215                  return {
 216                      ltPos: lastLtPos,
 217                      gtPos: lastLtPos + closingGt + 1, // Offset by one to get the position _after_ the character.
 218                      tagType: tagType,
 219                      isClosingTag: !! tagMatch[1]
 220                  };
 221              }
 222              return null;
 223          }
 224  
 225          /**
 226           * Checks if the cursor is inside a shortcode
 227           *
 228           * If the cursor is inside a shortcode wrapping tag, e.g. `[caption]` it's better to
 229           * move the selection marker to before or after the shortcode.
 230           *
 231           * For example `[caption]` rewrites/removes anything that's between the `[caption]` tag and the
 232           * `<img/>` tag inside.
 233           *
 234           * `[caption]<span>ThisIsGone</span><img .../>[caption]`
 235           *
 236           * Moving the selection to before or after the short code is better, since it allows to select
 237           * something, instead of just losing focus and going to the start of the content.
 238           *
 239           * @param {string} content The text content to check against.
 240           * @param {number} cursorPosition    The cursor position to check.
 241           *
 242           * @return {(undefined|Object)} Undefined if the cursor is not wrapped in a shortcode tag.
 243           *                              Information about the wrapping shortcode tag if it's wrapped in one.
 244           */
 245  		function getShortcodeWrapperInfo( content, cursorPosition ) {
 246              var contentShortcodes = getShortCodePositionsInText( content );
 247  
 248              for ( var i = 0; i < contentShortcodes.length; i++ ) {
 249                  var element = contentShortcodes[ i ];
 250  
 251                  if ( cursorPosition >= element.startIndex && cursorPosition <= element.endIndex ) {
 252                      return element;
 253                  }
 254              }
 255          }
 256  
 257          /**
 258           * Gets a list of unique shortcodes or shortcode-lookalikes in the content.
 259           *
 260           * @param {string} content The content we want to scan for shortcodes.
 261           */
 262  		function getShortcodesInText( content ) {
 263              var shortcodes = content.match( /\[+([\w_-])+/g ),
 264                  result = [];
 265  
 266              if ( shortcodes ) {
 267                  for ( var i = 0; i < shortcodes.length; i++ ) {
 268                      var shortcode = shortcodes[ i ].replace( /^\[+/g, '' );
 269  
 270                      if ( result.indexOf( shortcode ) === -1 ) {
 271                          result.push( shortcode );
 272                      }
 273                  }
 274              }
 275  
 276              return result;
 277          }
 278  
 279          /**
 280           * Gets all shortcodes and their positions in the content
 281           *
 282           * This function returns all the shortcodes that could be found in the textarea content
 283           * along with their character positions and boundaries.
 284           *
 285           * This is used to check if the selection cursor is inside the boundaries of a shortcode
 286           * and move it accordingly, to avoid breakage.
 287           *
 288           * @link adjustTextAreaSelectionCursors
 289           *
 290           * The information can also be used in other cases when we need to lookup shortcode data,
 291           * as it's already structured!
 292           *
 293           * @param {string} content The content we want to scan for shortcodes
 294           */
 295  		function getShortCodePositionsInText( content ) {
 296              var allShortcodes = getShortcodesInText( content ), shortcodeInfo;
 297  
 298              if ( allShortcodes.length === 0 ) {
 299                  return [];
 300              }
 301  
 302              var shortcodeDetailsRegexp = wp.shortcode.regexp( allShortcodes.join( '|' ) ),
 303                  shortcodeMatch, // Define local scope for the variable to be used in the loop below.
 304                  shortcodesDetails = [];
 305  
 306              while ( shortcodeMatch = shortcodeDetailsRegexp.exec( content ) ) {
 307                  /**
 308                   * Check if the shortcode should be shown as plain text.
 309                   *
 310                   * This corresponds to the [[shortcode]] syntax, which doesn't parse the shortcode
 311                   * and just shows it as text.
 312                   */
 313                  var showAsPlainText = shortcodeMatch[1] === '[';
 314  
 315                  shortcodeInfo = {
 316                      shortcodeName: shortcodeMatch[2],
 317                      showAsPlainText: showAsPlainText,
 318                      startIndex: shortcodeMatch.index,
 319                      endIndex: shortcodeMatch.index + shortcodeMatch[0].length,
 320                      length: shortcodeMatch[0].length
 321                  };
 322  
 323                  shortcodesDetails.push( shortcodeInfo );
 324              }
 325  
 326              /**
 327               * Get all URL matches, and treat them as embeds.
 328               *
 329               * Since there isn't a good way to detect if a URL by itself on a line is a previewable
 330               * object, it's best to treat all of them as such.
 331               *
 332               * This means that the selection will capture the whole URL, in a similar way shrotcodes
 333               * are treated.
 334               */
 335              var urlRegexp = new RegExp(
 336                  '(^|[\\n\\r][\\n\\r]|<p>)(https?:\\/\\/[^\s"]+?)(<\\/p>\s*|[\\n\\r][\\n\\r]|$)', 'gi'
 337              );
 338  
 339              while ( shortcodeMatch = urlRegexp.exec( content ) ) {
 340                  shortcodeInfo = {
 341                      shortcodeName: 'url',
 342                      showAsPlainText: false,
 343                      startIndex: shortcodeMatch.index,
 344                      endIndex: shortcodeMatch.index + shortcodeMatch[ 0 ].length,
 345                      length: shortcodeMatch[ 0 ].length,
 346                      urlAtStartOfContent: shortcodeMatch[ 1 ] === '',
 347                      urlAtEndOfContent: shortcodeMatch[ 3 ] === ''
 348                  };
 349  
 350                  shortcodesDetails.push( shortcodeInfo );
 351              }
 352  
 353              return shortcodesDetails;
 354          }
 355  
 356          /**
 357           * Generate a cursor marker element to be inserted in the content.
 358           *
 359           * `span` seems to be the least destructive element that can be used.
 360           *
 361           * Using DomQuery syntax to create it, since it's used as both text and as a DOM element.
 362           *
 363           * @param {Object} domLib DOM library instance.
 364           * @param {string} content The content to insert into the cursor marker element.
 365           */
 366  		function getCursorMarkerSpan( domLib, content ) {
 367              return domLib( '<span>' ).css( {
 368                          display: 'inline-block',
 369                          width: 0,
 370                          overflow: 'hidden',
 371                          'line-height': 0
 372                      } )
 373                      .html( content ? content : '' );
 374          }
 375  
 376          /**
 377           * Gets adjusted selection cursor positions according to HTML tags, comments, and shortcodes.
 378           *
 379           * Shortcodes and HTML codes are a bit of a special case when selecting, since they may render
 380           * content in Visual mode. If we insert selection markers somewhere inside them, it's really possible
 381           * to break the syntax and render the HTML tag or shortcode broken.
 382           *
 383           * @link getShortcodeWrapperInfo
 384           *
 385           * @param {string} content Textarea content that the cursors are in
 386           * @param {{cursorStart: number, cursorEnd: number}} cursorPositions Cursor start and end positions
 387           *
 388           * @return {{cursorStart: number, cursorEnd: number}}
 389           */
 390  		function adjustTextAreaSelectionCursors( content, cursorPositions ) {
 391              var voidElements = [
 392                  'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
 393                  'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
 394              ];
 395  
 396              var cursorStart = cursorPositions.cursorStart,
 397                  cursorEnd = cursorPositions.cursorEnd,
 398                  // Check if the cursor is in a tag and if so, adjust it.
 399                  isCursorStartInTag = getContainingTagInfo( content, cursorStart );
 400  
 401              if ( isCursorStartInTag ) {
 402                  /**
 403                   * Only move to the start of the HTML tag (to select the whole element) if the tag
 404                   * is part of the voidElements list above.
 405                   *
 406                   * This list includes tags that are self-contained and don't need a closing tag, according to the
 407                   * HTML5 specification.
 408                   *
 409                   * This is done in order to make selection of text a bit more consistent when selecting text in
 410                   * `<p>` tags or such.
 411                   *
 412                   * In cases where the tag is not a void element, the cursor is put to the end of the tag,
 413                   * so it's either between the opening and closing tag elements or after the closing tag.
 414                   */
 415                  if ( voidElements.indexOf( isCursorStartInTag.tagType ) !== -1 ) {
 416                      cursorStart = isCursorStartInTag.ltPos;
 417                  } else {
 418                      cursorStart = isCursorStartInTag.gtPos;
 419                  }
 420              }
 421  
 422              var isCursorEndInTag = getContainingTagInfo( content, cursorEnd );
 423              if ( isCursorEndInTag ) {
 424                  cursorEnd = isCursorEndInTag.gtPos;
 425              }
 426  
 427              var isCursorStartInShortcode = getShortcodeWrapperInfo( content, cursorStart );
 428              if ( isCursorStartInShortcode && ! isCursorStartInShortcode.showAsPlainText ) {
 429                  /**
 430                   * If a URL is at the start or the end of the content,
 431                   * the selection doesn't work, because it inserts a marker in the text,
 432                   * which breaks the embedURL detection.
 433                   *
 434                   * The best way to avoid that and not modify the user content is to
 435                   * adjust the cursor to either after or before URL.
 436                   */
 437                  if ( isCursorStartInShortcode.urlAtStartOfContent ) {
 438                      cursorStart = isCursorStartInShortcode.endIndex;
 439                  } else {
 440                      cursorStart = isCursorStartInShortcode.startIndex;
 441                  }
 442              }
 443  
 444              var isCursorEndInShortcode = getShortcodeWrapperInfo( content, cursorEnd );
 445              if ( isCursorEndInShortcode && ! isCursorEndInShortcode.showAsPlainText ) {
 446                  if ( isCursorEndInShortcode.urlAtEndOfContent ) {
 447                      cursorEnd = isCursorEndInShortcode.startIndex;
 448                  } else {
 449                      cursorEnd = isCursorEndInShortcode.endIndex;
 450                  }
 451              }
 452  
 453              return {
 454                  cursorStart: cursorStart,
 455                  cursorEnd: cursorEnd
 456              };
 457          }
 458  
 459          /**
 460           * Adds text selection markers in the editor textarea.
 461           *
 462           * Adds selection markers in the content of the editor `textarea`.
 463           * The method directly manipulates the `textarea` content, to allow TinyMCE plugins
 464           * to run after the markers are added.
 465           *
 466           * @param {Object} $textarea TinyMCE's textarea wrapped as a DomQuery object
 467           */
 468  		function addHTMLBookmarkInTextAreaContent( $textarea ) {
 469              if ( ! $textarea || ! $textarea.length ) {
 470                  // If no valid $textarea object is provided, there's nothing we can do.
 471                  return;
 472              }
 473  
 474              var textArea = $textarea[0],
 475                  textAreaContent = textArea.value,
 476  
 477                  adjustedCursorPositions = adjustTextAreaSelectionCursors( textAreaContent, {
 478                      cursorStart: textArea.selectionStart,
 479                      cursorEnd: textArea.selectionEnd
 480                  } ),
 481  
 482                  htmlModeCursorStartPosition = adjustedCursorPositions.cursorStart,
 483                  htmlModeCursorEndPosition = adjustedCursorPositions.cursorEnd,
 484  
 485                  mode = htmlModeCursorStartPosition !== htmlModeCursorEndPosition ? 'range' : 'single',
 486  
 487                  selectedText = null,
 488                  cursorMarkerSkeleton = getCursorMarkerSpan( $$, '&#65279;' ).attr( 'data-mce-type','bookmark' );
 489  
 490              if ( mode === 'range' ) {
 491                  var markedText = textArea.value.slice( htmlModeCursorStartPosition, htmlModeCursorEndPosition ),
 492                      bookMarkEnd = cursorMarkerSkeleton.clone().addClass( 'mce_SELRES_end' );
 493  
 494                  selectedText = [
 495                      markedText,
 496                      bookMarkEnd[0].outerHTML
 497                  ].join( '' );
 498              }
 499  
 500              textArea.value = [
 501                  textArea.value.slice( 0, htmlModeCursorStartPosition ), // Text until the cursor/selection position.
 502                  cursorMarkerSkeleton.clone()                            // Cursor/selection start marker.
 503                      .addClass( 'mce_SELRES_start' )[0].outerHTML,
 504                  selectedText,                                             // Selected text with end cursor/position marker.
 505                  textArea.value.slice( htmlModeCursorEndPosition )        // Text from last cursor/selection position to end.
 506              ].join( '' );
 507          }
 508  
 509          /**
 510           * Focuses the selection markers in Visual mode.
 511           *
 512           * The method checks for existing selection markers inside the editor DOM (Visual mode)
 513           * and create a selection between the two nodes using the DOM `createRange` selection API.
 514           *
 515           * If there is only a single node, select only the single node through TinyMCE's selection API
 516           *
 517           * @param {Object} editor TinyMCE editor instance.
 518           */
 519  		function focusHTMLBookmarkInVisualEditor( editor ) {
 520              var startNode = editor.$( '.mce_SELRES_start' ).attr( 'data-mce-bogus', 1 ),
 521                  endNode = editor.$( '.mce_SELRES_end' ).attr( 'data-mce-bogus', 1 );
 522  
 523              if ( startNode.length ) {
 524                  editor.focus();
 525  
 526                  if ( ! endNode.length ) {
 527                      editor.selection.select( startNode[0] );
 528                  } else {
 529                      var selection = editor.getDoc().createRange();
 530  
 531                      selection.setStartAfter( startNode[0] );
 532                      selection.setEndBefore( endNode[0] );
 533  
 534                      editor.selection.setRng( selection );
 535                  }
 536              }
 537  
 538              scrollVisualModeToStartElement( editor, startNode );
 539  
 540              removeSelectionMarker( startNode );
 541              removeSelectionMarker( endNode );
 542  
 543              editor.save();
 544          }
 545  
 546          /**
 547           * Removes selection marker and the parent node if it is an empty paragraph.
 548           *
 549           * By default TinyMCE wraps loose inline tags in a `<p>`.
 550           * When removing selection markers an empty `<p>` may be left behind, remove it.
 551           *
 552           * @param {Object} $marker The marker to be removed from the editor DOM, wrapped in an instance of `editor.$`
 553           */
 554  		function removeSelectionMarker( $marker ) {
 555              var $markerParent = $marker.parent();
 556  
 557              $marker.remove();
 558  
 559              //Remove empty paragraph left over after removing the marker.
 560              if ( $markerParent.is( 'p' ) && ! $markerParent.children().length && ! $markerParent.text() ) {
 561                  $markerParent.remove();
 562              }
 563          }
 564  
 565          /**
 566           * Scrolls the content to place the selected element in the center of the screen.
 567           *
 568           * Takes an element, that is usually the selection start element, selected in
 569           * `focusHTMLBookmarkInVisualEditor()` and scrolls the screen so the element appears roughly
 570           * in the middle of the screen.
 571           *
 572           * I order to achieve the proper positioning, the editor media bar and toolbar are subtracted
 573           * from the window height, to get the proper viewport window, that the user sees.
 574           *
 575           * @param {Object} editor TinyMCE editor instance.
 576           * @param {Object} element HTMLElement that should be scrolled into view.
 577           */
 578  		function scrollVisualModeToStartElement( editor, element ) {
 579              var elementTop = editor.$( element ).offset().top,
 580                  TinyMCEContentAreaTop = editor.$( editor.getContentAreaContainer() ).offset().top,
 581  
 582                  toolbarHeight = getToolbarHeight( editor ),
 583  
 584                  edTools = $( '#wp-content-editor-tools' ),
 585                  edToolsHeight = 0,
 586                  edToolsOffsetTop = 0,
 587  
 588                  $scrollArea;
 589  
 590              if ( edTools.length ) {
 591                  edToolsHeight = edTools.height();
 592                  edToolsOffsetTop = edTools.offset().top;
 593              }
 594  
 595              var windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
 596  
 597                  selectionPosition = TinyMCEContentAreaTop + elementTop,
 598                  visibleAreaHeight = windowHeight - ( edToolsHeight + toolbarHeight );
 599  
 600              // There's no need to scroll if the selection is inside the visible area.
 601              if ( selectionPosition < visibleAreaHeight ) {
 602                  return;
 603              }
 604  
 605              /**
 606               * The minimum scroll height should be to the top of the editor, to offer a consistent
 607               * experience.
 608               *
 609               * In order to find the top of the editor, we calculate the offset of `#wp-content-editor-tools` and
 610               * subtracting the height. This gives the scroll position where the top of the editor tools aligns with
 611               * the top of the viewport (under the Master Bar)
 612               */
 613              var adjustedScroll;
 614              if ( editor.settings.wp_autoresize_on ) {
 615                  $scrollArea = $( 'html,body' );
 616                  adjustedScroll = Math.max( selectionPosition - visibleAreaHeight / 2, edToolsOffsetTop - edToolsHeight );
 617              } else {
 618                  $scrollArea = $( editor.contentDocument ).find( 'html,body' );
 619                  adjustedScroll = elementTop;
 620              }
 621  
 622              $scrollArea.animate( {
 623                  scrollTop: parseInt( adjustedScroll, 10 )
 624              }, 100 );
 625          }
 626  
 627          /**
 628           * This method was extracted from the `SaveContent` hook in
 629           * `wp-includes/js/tinymce/plugins/wordpress/plugin.js`.
 630           *
 631           * It's needed here, since the method changes the content a bit, which confuses the cursor position.
 632           *
 633           * @param {Object} event TinyMCE event object.
 634           */
 635  		function fixTextAreaContent( event ) {
 636              // Keep empty paragraphs :(
 637              event.content = event.content.replace( /<p>(?:<br ?\/?>|\u00a0|\uFEFF| )*<\/p>/g, '<p>&nbsp;</p>' );
 638          }
 639  
 640          /**
 641           * Finds the current selection position in the Visual editor.
 642           *
 643           * Find the current selection in the Visual editor by inserting marker elements at the start
 644           * and end of the selection.
 645           *
 646           * Uses the standard DOM selection API to achieve that goal.
 647           *
 648           * Check the notes in the comments in the code below for more information on some gotchas
 649           * and why this solution was chosen.
 650           *
 651           * @param {Object} editor The editor where we must find the selection.
 652           * @return {(null|Object)} The selection range position in the editor.
 653           */
 654  		function findBookmarkedPosition( editor ) {
 655              // Get the TinyMCE `window` reference, since we need to access the raw selection.
 656              var TinyMCEWindow = editor.getWin(),
 657                  selection = TinyMCEWindow.getSelection();
 658  
 659              if ( ! selection || selection.rangeCount < 1 ) {
 660                  // no selection, no need to continue.
 661                  return;
 662              }
 663  
 664              /**
 665               * The ID is used to avoid replacing user generated content, that may coincide with the
 666               * format specified below.
 667               * @type {string}
 668               */
 669              var selectionID = 'SELRES_' + Math.random();
 670  
 671              /**
 672               * Create two marker elements that will be used to mark the start and the end of the range.
 673               *
 674               * The elements have hardcoded style that makes them invisible. This is done to avoid seeing
 675               * random content flickering in the editor when switching between modes.
 676               */
 677              var spanSkeleton = getCursorMarkerSpan( editor.$, selectionID ),
 678                  startElement = spanSkeleton.clone().addClass( 'mce_SELRES_start' ),
 679                  endElement = spanSkeleton.clone().addClass( 'mce_SELRES_end' );
 680  
 681              /**
 682               * Inspired by:
 683               * @link https://stackoverflow.com/a/17497803/153310
 684               *
 685               * Why do it this way and not with TinyMCE's bookmarks?
 686               *
 687               * TinyMCE's bookmarks are very nice when working with selections and positions, BUT
 688               * there is no way to determine the precise position of the bookmark when switching modes, since
 689               * TinyMCE does some serialization of the content, to fix things like shortcodes, run plugins, prettify
 690               * HTML code and so on. In this process, the bookmark markup gets lost.
 691               *
 692               * If we decide to hook right after the bookmark is added, we can see where the bookmark is in the raw HTML
 693               * in TinyMCE. Unfortunately this state is before the serialization, so any visual markup in the content will
 694               * throw off the positioning.
 695               *
 696               * To avoid this, we insert two custom `span`s that will serve as the markers at the beginning and end of the
 697               * selection.
 698               *
 699               * Why not use TinyMCE's selection API or the DOM API to wrap the contents? Because if we do that, this creates
 700               * a new node, which is inserted in the dom. Now this will be fine, if we worked with fixed selections to
 701               * full nodes. Unfortunately in our case, the user can select whatever they like, which means that the
 702               * selection may start in the middle of one node and end in the middle of a completely different one. If we
 703               * wrap the selection in another node, this will create artifacts in the content.
 704               *
 705               * Using the method below, we insert the custom `span` nodes at the start and at the end of the selection.
 706               * This helps us not break the content and also gives us the option to work with multi-node selections without
 707               * breaking the markup.
 708               */
 709              var range = selection.getRangeAt( 0 ),
 710                  startNode = range.startContainer,
 711                  startOffset = range.startOffset,
 712                  boundaryRange = range.cloneRange();
 713  
 714              /**
 715               * If the selection is on a shortcode with Live View, TinyMCE creates a bogus markup,
 716               * which we have to account for.
 717               */
 718              if ( editor.$( startNode ).parents( '.mce-offscreen-selection' ).length > 0 ) {
 719                  startNode = editor.$( '[data-mce-selected]' )[0];
 720  
 721                  /**
 722                   * Marking the start and end element with `data-mce-object-selection` helps
 723                   * discern when the selected object is a Live Preview selection.
 724                   *
 725                   * This way we can adjust the selection to properly select only the content, ignoring
 726                   * whitespace inserted around the selected object by the Editor.
 727                   */
 728                  startElement.attr( 'data-mce-object-selection', 'true' );
 729                  endElement.attr( 'data-mce-object-selection', 'true' );
 730  
 731                  editor.$( startNode ).before( startElement[0] );
 732                  editor.$( startNode ).after( endElement[0] );
 733              } else {
 734                  boundaryRange.collapse( false );
 735                  boundaryRange.insertNode( endElement[0] );
 736  
 737                  boundaryRange.setStart( startNode, startOffset );
 738                  boundaryRange.collapse( true );
 739                  boundaryRange.insertNode( startElement[0] );
 740  
 741                  range.setStartAfter( startElement[0] );
 742                  range.setEndBefore( endElement[0] );
 743                  selection.removeAllRanges();
 744                  selection.addRange( range );
 745              }
 746  
 747              /**
 748               * Now the editor's content has the start/end nodes.
 749               *
 750               * Unfortunately the content goes through some more changes after this step, before it gets inserted
 751               * in the `textarea`. This means that we have to do some minor cleanup on our own here.
 752               */
 753              editor.on( 'GetContent', fixTextAreaContent );
 754  
 755              var content = removep( editor.getContent() );
 756  
 757              editor.off( 'GetContent', fixTextAreaContent );
 758  
 759              startElement.remove();
 760              endElement.remove();
 761  
 762              var startRegex = new RegExp(
 763                  '<span[^>]*\\s*class="mce_SELRES_start"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>(\\s*)'
 764              );
 765  
 766              var endRegex = new RegExp(
 767                  '(\\s*)<span[^>]*\\s*class="mce_SELRES_end"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>'
 768              );
 769  
 770              var startMatch = content.match( startRegex ),
 771                  endMatch = content.match( endRegex );
 772  
 773              if ( ! startMatch ) {
 774                  return null;
 775              }
 776  
 777              var startIndex = startMatch.index,
 778                  startMatchLength = startMatch[0].length,
 779                  endIndex = null;
 780  
 781              if (endMatch) {
 782                  /**
 783                   * Adjust the selection index, if the selection contains a Live Preview object or not.
 784                   *
 785                   * Check where the `data-mce-object-selection` attribute is set above for more context.
 786                   */
 787                  if ( startMatch[0].indexOf( 'data-mce-object-selection' ) !== -1 ) {
 788                      startMatchLength -= startMatch[1].length;
 789                  }
 790  
 791                  var endMatchIndex = endMatch.index;
 792  
 793                  if ( endMatch[0].indexOf( 'data-mce-object-selection' ) !== -1 ) {
 794                      endMatchIndex -= endMatch[1].length;
 795                  }
 796  
 797                  // We need to adjust the end position to discard the length of the range start marker.
 798                  endIndex = endMatchIndex - startMatchLength;
 799              }
 800  
 801              return {
 802                  start: startIndex,
 803                  end: endIndex
 804              };
 805          }
 806  
 807          /**
 808           * Selects text in the TinyMCE `textarea`.
 809           *
 810           * Selects the text in TinyMCE's textarea that's between `selection.start` and `selection.end`.
 811           *
 812           * For `selection` parameter:
 813           * @link findBookmarkedPosition
 814           *
 815           * @param {Object} editor TinyMCE's editor instance.
 816           * @param {Object} selection Selection data.
 817           */
 818  		function selectTextInTextArea( editor, selection ) {
 819              // Only valid in the text area mode and if we have selection.
 820              if ( ! selection ) {
 821                  return;
 822              }
 823  
 824              var textArea = editor.getElement(),
 825                  start = selection.start,
 826                  end = selection.end || selection.start;
 827  
 828              if ( textArea.focus ) {
 829                  // Wait for the Visual editor to be hidden, then focus and scroll to the position.
 830                  setTimeout( function() {
 831                      textArea.setSelectionRange( start, end );
 832                      if ( textArea.blur ) {
 833                          // Defocus before focusing.
 834                          textArea.blur();
 835                      }
 836                      textArea.focus();
 837                  }, 100 );
 838              }
 839          }
 840  
 841          // Restore the selection when the editor is initialized. Needed when the Text editor is the default.
 842          $( document ).on( 'tinymce-editor-init.keep-scroll-position', function( event, editor ) {
 843              if ( editor.$( '.mce_SELRES_start' ).length ) {
 844                  focusHTMLBookmarkInVisualEditor( editor );
 845              }
 846          } );
 847  
 848          /**
 849           * Replaces <p> tags with two line breaks. "Opposite" of wpautop().
 850           *
 851           * Replaces <p> tags with two line breaks except where the <p> has attributes.
 852           * Unifies whitespace.
 853           * Indents <li>, <dt> and <dd> for better readability.
 854           *
 855           * @since 2.5.0
 856           *
 857           * @memberof switchEditors
 858           *
 859           * @param {string} html The content from the editor.
 860           * @return {string} The content with stripped paragraph tags.
 861           */
 862  		function removep( html ) {
 863              var blocklist = 'blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure',
 864                  blocklist1 = blocklist + '|div|p',
 865                  blocklist2 = blocklist + '|pre',
 866                  preserve_linebreaks = false,
 867                  preserve_br = false,
 868                  preserve = [];
 869  
 870              if ( ! html ) {
 871                  return '';
 872              }
 873  
 874              // Protect script and style tags.
 875              if ( html.indexOf( '<script' ) !== -1 || html.indexOf( '<style' ) !== -1 ) {
 876                  html = html.replace( /<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function( match ) {
 877                      preserve.push( match );
 878                      return '<wp-preserve>';
 879                  } );
 880              }
 881  
 882              // Protect pre tags.
 883              if ( html.indexOf( '<pre' ) !== -1 ) {
 884                  preserve_linebreaks = true;
 885                  html = html.replace( /<pre[^>]*>[\s\S]+?<\/pre>/g, function( a ) {
 886                      a = a.replace( /<br ?\/?>(\r\n|\n)?/g, '<wp-line-break>' );
 887                      a = a.replace( /<\/?p( [^>]*)?>(\r\n|\n)?/g, '<wp-line-break>' );
 888                      return a.replace( /\r?\n/g, '<wp-line-break>' );
 889                  });
 890              }
 891  
 892              // Remove line breaks but keep <br> tags inside image captions.
 893              if ( html.indexOf( '[caption' ) !== -1 ) {
 894                  preserve_br = true;
 895                  html = html.replace( /\[caption[\s\S]+?\[\/caption\]/g, function( a ) {
 896                      return a.replace( /<br([^>]*)>/g, '<wp-temp-br$1>' ).replace( /[\r\n\t]+/, '' );
 897                  });
 898              }
 899  
 900              // Normalize white space characters before and after block tags.
 901              html = html.replace( new RegExp( '\\s*</(' + blocklist1 + ')>\\s*', 'g' ), '</$1>\n' );
 902              html = html.replace( new RegExp( '\\s*<((?:' + blocklist1 + ')(?: [^>]*)?)>', 'g' ), '\n<$1>' );
 903  
 904              // Mark </p> if it has any attributes.
 905              html = html.replace( /(<p [^>]+>.*?)<\/p>/g, '$1</p#>' );
 906  
 907              // Preserve the first <p> inside a <div>.
 908              html = html.replace( /<div( [^>]*)?>\s*<p>/gi, '<div$1>\n\n' );
 909  
 910              // Remove paragraph tags.
 911              html = html.replace( /\s*<p>/gi, '' );
 912              html = html.replace( /\s*<\/p>\s*/gi, '\n\n' );
 913  
 914              // Normalize white space chars and remove multiple line breaks.
 915              html = html.replace( /\n[\s\u00a0]+\n/g, '\n\n' );
 916  
 917              // Replace <br> tags with line breaks.
 918              html = html.replace( /(\s*)<br ?\/?>\s*/gi, function( match, space ) {
 919                  if ( space && space.indexOf( '\n' ) !== -1 ) {
 920                      return '\n\n';
 921                  }
 922  
 923                  return '\n';
 924              });
 925  
 926              // Fix line breaks around <div>.
 927              html = html.replace( /\s*<div/g, '\n<div' );
 928              html = html.replace( /<\/div>\s*/g, '</div>\n' );
 929  
 930              // Fix line breaks around caption shortcodes.
 931              html = html.replace( /\s*\[caption([^\[]+)\[\/caption\]\s*/gi, '\n\n[caption$1[/caption]\n\n' );
 932              html = html.replace( /caption\]\n\n+\[caption/g, 'caption]\n\n[caption' );
 933  
 934              // Pad block elements tags with a line break.
 935              html = html.replace( new RegExp('\\s*<((?:' + blocklist2 + ')(?: [^>]*)?)\\s*>', 'g' ), '\n<$1>' );
 936              html = html.replace( new RegExp('\\s*</(' + blocklist2 + ')>\\s*', 'g' ), '</$1>\n' );
 937  
 938              // Indent <li>, <dt> and <dd> tags.
 939              html = html.replace( /<((li|dt|dd)[^>]*)>/g, ' \t<$1>' );
 940  
 941              // Fix line breaks around <select> and <option>.
 942              if ( html.indexOf( '<option' ) !== -1 ) {
 943                  html = html.replace( /\s*<option/g, '\n<option' );
 944                  html = html.replace( /\s*<\/select>/g, '\n</select>' );
 945              }
 946  
 947              // Pad <hr> with two line breaks.
 948              if ( html.indexOf( '<hr' ) !== -1 ) {
 949                  html = html.replace( /\s*<hr( [^>]*)?>\s*/g, '\n\n<hr$1>\n\n' );
 950              }
 951  
 952              // Remove line breaks in <object> tags.
 953              if ( html.indexOf( '<object' ) !== -1 ) {
 954                  html = html.replace( /<object[\s\S]+?<\/object>/g, function( a ) {
 955                      return a.replace( /[\r\n]+/g, '' );
 956                  });
 957              }
 958  
 959              // Unmark special paragraph closing tags.
 960              html = html.replace( /<\/p#>/g, '</p>\n' );
 961  
 962              // Pad remaining <p> tags whit a line break.
 963              html = html.replace( /\s*(<p [^>]+>[\s\S]*?<\/p>)/g, '\n$1' );
 964  
 965              // Trim.
 966              html = html.replace( /^\s+/, '' );
 967              html = html.replace( /[\s\u00a0]+$/, '' );
 968  
 969              if ( preserve_linebreaks ) {
 970                  html = html.replace( /<wp-line-break>/g, '\n' );
 971              }
 972  
 973              if ( preserve_br ) {
 974                  html = html.replace( /<wp-temp-br([^>]*)>/g, '<br$1>' );
 975              }
 976  
 977              // Restore preserved tags.
 978              if ( preserve.length ) {
 979                  html = html.replace( /<wp-preserve>/g, function() {
 980                      return preserve.shift();
 981                  } );
 982              }
 983  
 984              return html;
 985          }
 986  
 987          /**
 988           * Replaces two line breaks with a paragraph tag and one line break with a <br>.
 989           *
 990           * Similar to `wpautop()` in formatting.php.
 991           *
 992           * @since 2.5.0
 993           *
 994           * @memberof switchEditors
 995           *
 996           * @param {string} text The text input.
 997           * @return {string} The formatted text.
 998           */
 999  		function autop( text ) {
1000              var preserve_linebreaks = false,
1001                  preserve_br = false,
1002                  blocklist = 'table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre' +
1003                      '|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section' +
1004                      '|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary';
1005  
1006              // Normalize line breaks.
1007              text = text.replace( /\r\n|\r/g, '\n' );
1008  
1009              // Remove line breaks from <object>.
1010              if ( text.indexOf( '<object' ) !== -1 ) {
1011                  text = text.replace( /<object[\s\S]+?<\/object>/g, function( a ) {
1012                      return a.replace( /\n+/g, '' );
1013                  });
1014              }
1015  
1016              // Remove line breaks from tags.
1017              text = text.replace( /<[^<>]+>/g, function( a ) {
1018                  return a.replace( /[\n\t ]+/g, ' ' );
1019              });
1020  
1021              // Preserve line breaks in <pre> and <script> tags.
1022              if ( text.indexOf( '<pre' ) !== -1 || text.indexOf( '<script' ) !== -1 ) {
1023                  preserve_linebreaks = true;
1024                  text = text.replace( /<(pre|script)[^>]*>[\s\S]*?<\/\1>/g, function( a ) {
1025                      return a.replace( /\n/g, '<wp-line-break>' );
1026                  });
1027              }
1028  
1029              if ( text.indexOf( '<figcaption' ) !== -1 ) {
1030                  text = text.replace( /\s*(<figcaption[^>]*>)/g, '$1' );
1031                  text = text.replace( /<\/figcaption>\s*/g, '</figcaption>' );
1032              }
1033  
1034              // Keep <br> tags inside captions.
1035              if ( text.indexOf( '[caption' ) !== -1 ) {
1036                  preserve_br = true;
1037  
1038                  text = text.replace( /\[caption[\s\S]+?\[\/caption\]/g, function( a ) {
1039                      a = a.replace( /<br([^>]*)>/g, '<wp-temp-br$1>' );
1040  
1041                      a = a.replace( /<[^<>]+>/g, function( b ) {
1042                          return b.replace( /[\n\t ]+/, ' ' );
1043                      });
1044  
1045                      return a.replace( /\s*\n\s*/g, '<wp-temp-br />' );
1046                  });
1047              }
1048  
1049              text = text + '\n\n';
1050              text = text.replace( /<br \/>\s*<br \/>/gi, '\n\n' );
1051  
1052              // Pad block tags with two line breaks.
1053              text = text.replace( new RegExp( '(<(?:' + blocklist + ')(?: [^>]*)?>)', 'gi' ), '\n\n$1' );
1054              text = text.replace( new RegExp( '(</(?:' + blocklist + ')>)', 'gi' ), '$1\n\n' );
1055              text = text.replace( /<hr( [^>]*)?>/gi, '<hr$1>\n\n' );
1056  
1057              // Remove white space chars around <option>.
1058              text = text.replace( /\s*<option/gi, '<option' );
1059              text = text.replace( /<\/option>\s*/gi, '</option>' );
1060  
1061              // Normalize multiple line breaks and white space chars.
1062              text = text.replace( /\n\s*\n+/g, '\n\n' );
1063  
1064              // Convert two line breaks to a paragraph.
1065              text = text.replace( /([\s\S]+?)\n\n/g, '<p>$1</p>\n' );
1066  
1067              // Remove empty paragraphs.
1068              text = text.replace( /<p>\s*?<\/p>/gi, '');
1069  
1070              // Remove <p> tags that are around block tags.
1071              text = text.replace( new RegExp( '<p>\\s*(</?(?:' + blocklist + ')(?: [^>]*)?>)\\s*</p>', 'gi' ), '$1' );
1072              text = text.replace( /<p>(<li.+?)<\/p>/gi, '$1');
1073  
1074              // Fix <p> in blockquotes.
1075              text = text.replace( /<p>\s*<blockquote([^>]*)>/gi, '<blockquote$1><p>');
1076              text = text.replace( /<\/blockquote>\s*<\/p>/gi, '</p></blockquote>');
1077  
1078              // Remove <p> tags that are wrapped around block tags.
1079              text = text.replace( new RegExp( '<p>\\s*(</?(?:' + blocklist + ')(?: [^>]*)?>)', 'gi' ), '$1' );
1080              text = text.replace( new RegExp( '(</?(?:' + blocklist + ')(?: [^>]*)?>)\\s*</p>', 'gi' ), '$1' );
1081  
1082              text = text.replace( /(<br[^>]*>)\s*\n/gi, '$1' );
1083  
1084              // Add <br> tags.
1085              text = text.replace( /\s*\n/g, '<br />\n');
1086  
1087              // Remove <br> tags that are around block tags.
1088              text = text.replace( new RegExp( '(</?(?:' + blocklist + ')[^>]*>)\\s*<br />', 'gi' ), '$1' );
1089              text = text.replace( /<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)>)/gi, '$1' );
1090  
1091              // Remove <p> and <br> around captions.
1092              text = text.replace( /(?:<p>|<br ?\/?>)*\s*\[caption([^\[]+)\[\/caption\]\s*(?:<\/p>|<br ?\/?>)*/gi, '[caption$1[/caption]' );
1093  
1094              // Make sure there is <p> when there is </p> inside block tags that can contain other blocks.
1095              text = text.replace( /(<(?:div|th|td|form|fieldset|dd)[^>]*>)(.*?)<\/p>/g, function( a, b, c ) {
1096                  if ( c.match( /<p( [^>]*)?>/ ) ) {
1097                      return a;
1098                  }
1099  
1100                  return b + '<p>' + c + '</p>';
1101              });
1102  
1103              // Restore the line breaks in <pre> and <script> tags.
1104              if ( preserve_linebreaks ) {
1105                  text = text.replace( /<wp-line-break>/g, '\n' );
1106              }
1107  
1108              // Restore the <br> tags in captions.
1109              if ( preserve_br ) {
1110                  text = text.replace( /<wp-temp-br([^>]*)>/g, '<br$1>' );
1111              }
1112  
1113              return text;
1114          }
1115  
1116          /**
1117           * Fires custom jQuery events `beforePreWpautop` and `afterPreWpautop` when jQuery is available.
1118           *
1119           * @since 2.9.0
1120           *
1121           * @memberof switchEditors
1122           *
1123           * @param {string} html The content from the visual editor.
1124           * @return {string} the filtered content.
1125           */
1126  		function pre_wpautop( html ) {
1127              var obj = { o: exports, data: html, unfiltered: html };
1128  
1129              if ( $ ) {
1130                  $( 'body' ).trigger( 'beforePreWpautop', [ obj ] );
1131              }
1132  
1133              obj.data = removep( obj.data );
1134  
1135              if ( $ ) {
1136                  $( 'body' ).trigger( 'afterPreWpautop', [ obj ] );
1137              }
1138  
1139              return obj.data;
1140          }
1141  
1142          /**
1143           * Fires custom jQuery events `beforeWpautop` and `afterWpautop` when jQuery is available.
1144           *
1145           * @since 2.9.0
1146           *
1147           * @memberof switchEditors
1148           *
1149           * @param {string} text The content from the text editor.
1150           * @return {string} filtered content.
1151           */
1152  		function wpautop( text ) {
1153              var obj = { o: exports, data: text, unfiltered: text };
1154  
1155              if ( $ ) {
1156                  $( 'body' ).trigger( 'beforeWpautop', [ obj ] );
1157              }
1158  
1159              obj.data = autop( obj.data );
1160  
1161              if ( $ ) {
1162                  $( 'body' ).trigger( 'afterWpautop', [ obj ] );
1163              }
1164  
1165              return obj.data;
1166          }
1167  
1168          if ( $ ) {
1169              $( init );
1170          } else if ( document.addEventListener ) {
1171              document.addEventListener( 'DOMContentLoaded', init, false );
1172              window.addEventListener( 'load', init, false );
1173          } else if ( window.attachEvent ) {
1174              window.attachEvent( 'onload', init );
1175              document.attachEvent( 'onreadystatechange', function() {
1176                  if ( 'complete' === document.readyState ) {
1177                      init();
1178                  }
1179              } );
1180          }
1181  
1182          wp.editor.autop = wpautop;
1183          wp.editor.removep = pre_wpautop;
1184  
1185          exports = {
1186              go: switchEditor,
1187              wpautop: wpautop,
1188              pre_wpautop: pre_wpautop,
1189              _wp_Autop: autop,
1190              _wp_Nop: removep
1191          };
1192  
1193          return exports;
1194      }
1195  
1196      /**
1197       * Expose the switch editors to be used globally.
1198       *
1199       * @namespace switchEditors
1200       */
1201      window.switchEditors = new SwitchEditors();
1202  
1203      /**
1204       * Initialize TinyMCE and/or Quicktags. For use with wp_enqueue_editor() (PHP).
1205       *
1206       * Intended for use with an existing textarea that will become the Text editor tab.
1207       * The editor width will be the width of the textarea container, height will be adjustable.
1208       *
1209       * Settings for both TinyMCE and Quicktags can be passed on initialization, and are "filtered"
1210       * with custom jQuery events on the document element, wp-before-tinymce-init and wp-before-quicktags-init.
1211       *
1212       * @since 4.8.0
1213       *
1214       * @param {string} id The HTML id of the textarea that is used for the editor.
1215       *                    Has to be jQuery compliant. No brackets, special chars, etc.
1216       * @param {Object} settings Example:
1217       * settings = {
1218       *    // See https://www.tinymce.com/docs/configure/integration-and-setup/.
1219       *    // Alternatively set to `true` to use the defaults.
1220       *    tinymce: {
1221       *        setup: function( editor ) {
1222       *            console.log( 'Editor initialized', editor );
1223       *        }
1224       *    }
1225       *
1226       *    // Alternatively set to `true` to use the defaults.
1227       *      quicktags: {
1228       *        buttons: 'strong,em,link'
1229       *    }
1230       * }
1231       */
1232      wp.editor.initialize = function( id, settings ) {
1233          var init;
1234          var defaults;
1235  
1236          if ( ! $ || ! id || ! wp.editor.getDefaultSettings ) {
1237              return;
1238          }
1239  
1240          defaults = wp.editor.getDefaultSettings();
1241  
1242          // Initialize TinyMCE by default.
1243          if ( ! settings ) {
1244              settings = {
1245                  tinymce: true
1246              };
1247          }
1248  
1249          // Add wrap and the Visual|Text tabs.
1250          if ( settings.tinymce && settings.quicktags ) {
1251              var $textarea = $( '#' + id );
1252  
1253              var $wrap = $( '<div>' ).attr( {
1254                      'class': 'wp-core-ui wp-editor-wrap tmce-active',
1255                      id: 'wp-' + id + '-wrap'
1256                  } );
1257  
1258              var $editorContainer = $( '<div class="wp-editor-container">' );
1259  
1260              var $button = $( '<button>' ).attr( {
1261                      type: 'button',
1262                      'data-wp-editor-id': id
1263                  } );
1264  
1265              var $editorTools = $( '<div class="wp-editor-tools">' );
1266  
1267              if ( settings.mediaButtons ) {
1268                  var buttonText = 'Add Media';
1269  
1270                  if ( window._wpMediaViewsL10n && window._wpMediaViewsL10n.addMedia ) {
1271                      buttonText = window._wpMediaViewsL10n.addMedia;
1272                  }
1273  
1274                  var $addMediaButton = $( '<button type="button" class="button insert-media add_media">' );
1275  
1276                  $addMediaButton.append( '<span class="wp-media-buttons-icon"></span>' );
1277                  $addMediaButton.append( document.createTextNode( ' ' + buttonText ) );
1278                  $addMediaButton.data( 'editor', id );
1279  
1280                  $editorTools.append(
1281                      $( '<div class="wp-media-buttons">' )
1282                          .append( $addMediaButton )
1283                  );
1284              }
1285  
1286              $wrap.append(
1287                  $editorTools
1288                      .append( $( '<div class="wp-editor-tabs">' )
1289                          .append( $button.clone().attr({
1290                              id: id + '-tmce',
1291                              'class': 'wp-switch-editor switch-tmce'
1292                          }).text( window.tinymce.translate( 'Visual' ) ) )
1293                          .append( $button.attr({
1294                              id: id + '-html',
1295                              'class': 'wp-switch-editor switch-html'
1296                          }).text( window.tinymce.translate( 'Text' ) ) )
1297                      ).append( $editorContainer )
1298              );
1299  
1300              $textarea.after( $wrap );
1301              $editorContainer.append( $textarea );
1302          }
1303  
1304          if ( window.tinymce && settings.tinymce ) {
1305              if ( typeof settings.tinymce !== 'object' ) {
1306                  settings.tinymce = {};
1307              }
1308  
1309              init = $.extend( {}, defaults.tinymce, settings.tinymce );
1310              init.selector = '#' + id;
1311  
1312              $( document ).trigger( 'wp-before-tinymce-init', init );
1313              window.tinymce.init( init );
1314  
1315              if ( ! window.wpActiveEditor ) {
1316                  window.wpActiveEditor = id;
1317              }
1318          }
1319  
1320          if ( window.quicktags && settings.quicktags ) {
1321              if ( typeof settings.quicktags !== 'object' ) {
1322                  settings.quicktags = {};
1323              }
1324  
1325              init = $.extend( {}, defaults.quicktags, settings.quicktags );
1326              init.id = id;
1327  
1328              $( document ).trigger( 'wp-before-quicktags-init', init );
1329              window.quicktags( init );
1330  
1331              if ( ! window.wpActiveEditor ) {
1332                  window.wpActiveEditor = init.id;
1333              }
1334          }
1335      };
1336  
1337      /**
1338       * Remove one editor instance.
1339       *
1340       * Intended for use with editors that were initialized with wp.editor.initialize().
1341       *
1342       * @since 4.8.0
1343       *
1344       * @param {string} id The HTML id of the editor textarea.
1345       */
1346      wp.editor.remove = function( id ) {
1347          var mceInstance, qtInstance,
1348              $wrap = $( '#wp-' + id + '-wrap' );
1349  
1350          if ( window.tinymce ) {
1351              mceInstance = window.tinymce.get( id );
1352  
1353              if ( mceInstance ) {
1354                  if ( ! mceInstance.isHidden() ) {
1355                      mceInstance.save();
1356                  }
1357  
1358                  mceInstance.remove();
1359              }
1360          }
1361  
1362          if ( window.quicktags ) {
1363              qtInstance = window.QTags.getInstance( id );
1364  
1365              if ( qtInstance ) {
1366                  qtInstance.remove();
1367              }
1368          }
1369  
1370          if ( $wrap.length ) {
1371              $wrap.after( $( '#' + id ) );
1372              $wrap.remove();
1373          }
1374      };
1375  
1376      /**
1377       * Get the editor content.
1378       *
1379       * Intended for use with editors that were initialized with wp.editor.initialize().
1380       *
1381       * @since 4.8.0
1382       *
1383       * @param {string} id The HTML id of the editor textarea.
1384       * @return The editor content.
1385       */
1386      wp.editor.getContent = function( id ) {
1387          var editor;
1388  
1389          if ( ! $ || ! id ) {
1390              return;
1391          }
1392  
1393          if ( window.tinymce ) {
1394              editor = window.tinymce.get( id );
1395  
1396              if ( editor && ! editor.isHidden() ) {
1397                  editor.save();
1398              }
1399          }
1400  
1401          return $( '#' + id ).val();
1402      };
1403  
1404  }( window.jQuery, window.wp ));


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref