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


Generated : Tue Sep 8 08:20:28 2026 Cross-referenced by PHPXref