[ Index ]

PHP Cross Reference of WordPress

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


Generated: Sun Sep 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1