home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-admin / js / editor.js < prev    next >
Encoding:
JavaScript  |  2018-01-23  |  44.3 KB  |  1,410 lines

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