diff --git a/wp-admin/js/editor.js b/wp-admin/js/editor.js index e34d208396..6d94c03112 100644 --- a/wp-admin/js/editor.js +++ b/wp-admin/js/editor.js @@ -133,10 +133,10 @@ window.wp = window.wp || {}; */ var tinyMCEConfig = $.extend( {}, - window.tinyMCEPreInit.mceInit[id], + window.tinyMCEPreInit.mceInit[ id ], { - setup: function(editor) { - editor.on('init', function(event) { + setup: function( editor ) { + editor.on( 'init', function( event ) { focusHTMLBookmarkInVisualEditor( event.target ); }); } @@ -210,72 +210,156 @@ window.wp = window.wp || {}; * @returns {(null|Object)} Null if cursor is not in a tag, Object if the cursor is inside a tag. */ function getContainingTagInfo( content, cursorPosition ) { - var lastLtPos = content.lastIndexOf( '<', cursorPosition ), + var lastLtPos = content.lastIndexOf( '<', cursorPosition - 1 ), lastGtPos = content.lastIndexOf( '>', cursorPosition ); if ( lastLtPos > lastGtPos || content.substr( cursorPosition, 1 ) === '>' ) { // find what the tag is - var tagContent = content.substr( lastLtPos ); - var tagMatch = tagContent.match( /<\s*(\/)?(\w+)/ ); + var tagContent = content.substr( lastLtPos ), + tagMatch = tagContent.match( /<\s*(\/)?(\w+)/ ); + if ( ! tagMatch ) { return null; } - var tagType = tagMatch[ 2 ]; - var closingGt = tagContent.indexOf( '>' ); - var isClosingTag = ! ! tagMatch[ 1 ]; - var shortcodeWrapperInfo = getShortcodeWrapperInfo( content, lastLtPos ); + var tagType = tagMatch[2], + closingGt = tagContent.indexOf( '>' ); return { ltPos: lastLtPos, gtPos: lastLtPos + closingGt + 1, // offset by one to get the position _after_ the character, tagType: tagType, - isClosingTag: isClosingTag, - shortcodeTagInfo: shortcodeWrapperInfo + isClosingTag: !! tagMatch[1] }; } return null; } /** - * @summary Check if a given HTML tag is enclosed in a shortcode tag + * @summary Check if the cursor is inside a shortcode * * If the cursor is inside a shortcode wrapping tag, e.g. `[caption]` it's better to - * move the selection marker to before the short tag. + * move the selection marker to before or after the shortcode. * * For example `[caption]` rewrites/removes anything that's between the `[caption]` tag and the * `` tag inside. * - * `[caption]ThisIsGone[caption]` + * `[caption]ThisIsGone[caption]` * - * Moving the selection to before the short code is better, since it allows to select - * something, instead of just losing focus and going to the start of the content. + * Moving the selection to before or after the short code is better, since it allows to select + * something, instead of just losing focus and going to the start of the content. * - * @param {string} content The text content to check against - * @param {number} cursorPosition The cursor position to check from. Usually this is the opening symbol of - * an HTML tag. + * @param {string} content The text content to check against. + * @param {number} cursorPosition The cursor position to check. * - * @return {(null|Object)} Null if the oject is not wrapped in a shortcode tag. - * Information about the wrapping shortcode tag if it's wrapped in one. + * @return {(undefined|Object)} Undefined if the cursor is not wrapped in a shortcode tag. + * Information about the wrapping shortcode tag if it's wrapped in one. */ function getShortcodeWrapperInfo( content, cursorPosition ) { - if ( content.substr( cursorPosition - 1, 1 ) === ']' ) { - var shortTagStart = content.lastIndexOf( '[', cursorPosition ); - var shortTagContent = content.substr(shortTagStart, cursorPosition - shortTagStart); - var shortTag = content.match( /\[\s*(\/)?(\w+)/ ); - var tagType = shortTag[ 2 ]; - var closingGt = shortTagContent.indexOf( '>' ); - var isClosingTag = ! ! shortTag[ 1 ]; + var contentShortcodes = getShortCodePositionsInText( content ); - return { - openingBracket: shortTagStart, - shortcode: tagType, - closingBracket: closingGt, - isClosingTag: isClosingTag - }; + return _.find( contentShortcodes, function( element ) { + return cursorPosition >= element.startIndex && cursorPosition <= element.endIndex; + } ); + } + + /** + * Gets a list of unique shortcodes or shortcode-look-alikes in the content. + * + * @param {string} content The content we want to scan for shortcodes. + */ + function getShortcodesInText( content ) { + var shortcodes = content.match( /\[+([\w_-])+/g ); + + return _.uniq( + _.map( shortcodes, function( element ) { + return element.replace( /^\[+/g, '' ); + } ) + ); + } + + /** + * @summary Check if a shortcode has Live Preview enabled for it. + * + * Previewable shortcodes here refers to shortcodes that have Live Preview enabled. + * + * These shortcodes get rewritten when the editor is in Visual mode, which means that + * we don't want to change anything inside them, i.e. inserting a selection marker + * inside the shortcode will break it :( + * + * @link wp-includes/js/mce-view.js + * + * @param {string} shortcode The shortcode to check. + * @return {boolean} If a shortcode has Live Preview or not + */ + function isShortcodePreviewable( shortcode ) { + var defaultPreviewableShortcodes = [ 'caption' ]; + + return ( + defaultPreviewableShortcodes.indexOf( shortcode ) !== -1 || + wp.mce.views.get( shortcode ) !== undefined + ); + + } + + /** + * @summary Get all shortcodes and their positions in the content + * + * This function returns all the shortcodes that could be found in the textarea content + * along with their character positions and boundaries. + * + * This is used to check if the selection cursor is inside the boundaries of a shortcode + * and move it accordingly, to avoid breakage. + * + * @link adjustTextAreaSelectionCursors + * + * The information can also be used in other cases when we need to lookup shortcode data, + * as it's already structured! + * + * @param {string} content The content we want to scan for shortcodes + */ + function getShortCodePositionsInText( content ) { + var allShortcodes = getShortcodesInText( content ); + + if ( allShortcodes.length === 0 ) { + return []; } - return null; + var shortcodeDetailsRegexp = wp.shortcode.regexp( allShortcodes.join( '|' ) ), + shortcodeMatch, // Define local scope for the variable to be used in the loop below. + shortcodesDetails = []; + + while ( shortcodeMatch = shortcodeDetailsRegexp.exec( content ) ) { + /** + * Check if the shortcode should be shown as plain text. + * + * This corresponds to the [[shortcode]] syntax, which doesn't parse the shortcode + * and just shows it as text. + */ + var showAsPlainText = shortcodeMatch[1] === '['; + + /** + * For more context check the docs for: + * + * @link isShortcodePreviewable + * + * In addition, if the shortcode will get rendered as plain text ( see above ), + * we can treat it as text and use the selection markers in it. + */ + var isPreviewable = ! showAsPlainText && isShortcodePreviewable( shortcodeMatch[2] ), + shortcodeInfo = { + shortcodeName: shortcodeMatch[2], + showAsPlainText: showAsPlainText, + startIndex: shortcodeMatch.index, + endIndex: shortcodeMatch.index + shortcodeMatch[0].length, + length: shortcodeMatch[0].length, + isPreviewable: isPreviewable + }; + + shortcodesDetails.push( shortcodeInfo ); + } + + return shortcodesDetails; } /** @@ -299,27 +383,30 @@ window.wp = window.wp || {}; } /** - * @summary Adds text selection markers in the editor textarea. + * @summary Get adjusted selection cursor positions according to HTML tags/shortcodes * - * Adds selection markers in the content of the editor `textarea`. - * The method directly manipulates the `textarea` content, to allow TinyMCE plugins - * to run after the markers are added. + * Shortcodes and HTML codes are a bit of a special case when selecting, since they may render + * content in Visual mode. If we insert selection markers somewhere inside them, it's really possible + * to break the syntax and render the HTML tag or shortcode broken. * - * @param {object} $textarea TinyMCE's textarea wrapped as a DomQuery object - * @param {object} jQuery A jQuery instance + * @link getShortcodeWrapperInfo + * + * @param {string} content Textarea content that the cursors are in + * @param {{cursorStart: number, cursorEnd: number}} cursorPositions Cursor start and end positions + * + * @return {{cursorStart: number, cursorEnd: number}} */ - function addHTMLBookmarkInTextAreaContent( $textarea, jQuery ) { - var textArea = $textarea[ 0 ], // TODO add error checking - htmlModeCursorStartPosition = textArea.selectionStart, - htmlModeCursorEndPosition = textArea.selectionEnd; - + function adjustTextAreaSelectionCursors( content, cursorPositions ) { var voidElements = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ]; - // check if the cursor is in a tag and if so, adjust it - var isCursorStartInTag = getContainingTagInfo( textArea.value, htmlModeCursorStartPosition ); + var cursorStart = cursorPositions.cursorStart, + cursorEnd = cursorPositions.cursorEnd, + // check if the cursor is in a tag and if so, adjust it + isCursorStartInTag = getContainingTagInfo( content, cursorStart ); + if ( isCursorStartInTag ) { /** * Only move to the start of the HTML tag (to select the whole element) if the tag @@ -334,78 +421,74 @@ window.wp = window.wp || {}; * In cases where the tag is not a void element, the cursor is put to the end of the tag, * so it's either between the opening and closing tag elements or after the closing tag. */ - if ( voidElements.indexOf( isCursorStartInTag.tagType ) !== - 1 ) { - htmlModeCursorStartPosition = isCursorStartInTag.ltPos; + if ( voidElements.indexOf( isCursorStartInTag.tagType ) !== -1 ) { + cursorStart = isCursorStartInTag.ltPos; } else { - htmlModeCursorStartPosition = isCursorStartInTag.gtPos; + cursorStart = isCursorStartInTag.gtPos; } } - var isCursorEndInTag = getContainingTagInfo( textArea.value, htmlModeCursorEndPosition ); + var isCursorEndInTag = getContainingTagInfo( content, cursorEnd ); if ( isCursorEndInTag ) { - htmlModeCursorEndPosition = isCursorEndInTag.gtPos; + cursorEnd = isCursorEndInTag.gtPos; } - var mode = htmlModeCursorStartPosition !== htmlModeCursorEndPosition ? 'range' : 'single'; + var isCursorStartInShortcode = getShortcodeWrapperInfo( content, cursorStart ); + if ( isCursorStartInShortcode && isCursorStartInShortcode.isPreviewable ) { + cursorStart = isCursorStartInShortcode.startIndex; + } - var selectedText = null; - var cursorMarkerSkeleton = getCursorMarkerSpan( { $: jQuery }, '' ); + var isCursorEndInShortcode = getShortcodeWrapperInfo( content, cursorEnd ); + if ( isCursorEndInShortcode && isCursorEndInShortcode.isPreviewable ) { + cursorEnd = isCursorEndInShortcode.endIndex; + } + + return { + cursorStart: cursorStart, + cursorEnd: cursorEnd + }; + } + + /** + * @summary Adds text selection markers in the editor textarea. + * + * Adds selection markers in the content of the editor `textarea`. + * The method directly manipulates the `textarea` content, to allow TinyMCE plugins + * to run after the markers are added. + * + * @param {object} $textarea TinyMCE's textarea wrapped as a DomQuery object + * @param {object} jQuery A jQuery instance + */ + function addHTMLBookmarkInTextAreaContent( $textarea, jQuery ) { + if ( ! $textarea || ! $textarea.length ) { + // If no valid $textarea object is provided, there's nothing we can do. + return; + } + + var textArea = $textarea[0], + textAreaContent = textArea.value, + + adjustedCursorPositions = adjustTextAreaSelectionCursors( textAreaContent, { + cursorStart: textArea.selectionStart, + cursorEnd: textArea.selectionEnd + } ), + + htmlModeCursorStartPosition = adjustedCursorPositions.cursorStart, + htmlModeCursorEndPosition = adjustedCursorPositions.cursorEnd, + + mode = htmlModeCursorStartPosition !== htmlModeCursorEndPosition ? 'range' : 'single', + + selectedText = null, + cursorMarkerSkeleton = getCursorMarkerSpan( { $: jQuery }, '' ); if ( mode === 'range' ) { - var markedText = textArea.value.slice( htmlModeCursorStartPosition, htmlModeCursorEndPosition ); - - /** - * Since the shortcodes convert the tags in them a bit, we need to mark the tag itself, - * and not rely on the cursor marker. - * - * @see getShortcodeWrapperInfo - */ - if ( isCursorStartInTag && isCursorStartInTag.shortcodeTagInfo ) { - // Get the tag on the cursor start - var tagEndPosition = isCursorStartInTag.gtPos - isCursorStartInTag.ltPos; - var tagContent = markedText.slice( 0, tagEndPosition ); - - // Check if the tag already has a `class` attribute. - var classMatch = /class=(['"])([^$1]*?)\1/; - - /** - * Add a marker class to the selected tag, to be used later. - * - * @see focusHTMLBookmarkInVisualEditor - */ - if ( tagContent.match( classMatch ) ) { - tagContent = tagContent.replace( classMatch, 'class=$1$2 mce_SELRES_start_target$1' ); - } - else { - tagContent = tagContent.replace( /(<\w+)/, '$1 class="mce_SELRES_start_target" ' ); - } - - // Update the selected text content with the marked tag above - markedText = [ - tagContent, - markedText.substr( tagEndPosition ) - ].join( '' ); - } - - var bookMarkEnd = cursorMarkerSkeleton.clone() - .addClass( 'mce_SELRES_end' )[ 0 ].outerHTML; - - /** - * A small workaround when selecting just a single HTML tag inside a shortcode. - * - * This removes the end selection marker, to make sure the HTML tag is the only selected - * thing. This prevents the selection to appear like it contains multiple items in it (i.e. - * all highlighted blue) - */ - if ( isCursorStartInTag && isCursorStartInTag.shortcodeTagInfo && isCursorEndInTag && - isCursorStartInTag.ltPos === isCursorEndInTag.ltPos ) { - bookMarkEnd = ''; - } + var markedText = textArea.value.slice( htmlModeCursorStartPosition, htmlModeCursorEndPosition ), + bookMarkEnd = cursorMarkerSkeleton.clone().addClass( 'mce_SELRES_end' ); selectedText = [ markedText, - bookMarkEnd + bookMarkEnd[0].outerHTML ].join( '' ); } @@ -432,34 +515,66 @@ window.wp = window.wp || {}; var startNode = editor.$( '.mce_SELRES_start' ), endNode = editor.$( '.mce_SELRES_end' ); - if ( ! startNode.length ) { - startNode = editor.$( '.mce_SELRES_start_target' ); - } - if ( startNode.length ) { editor.focus(); if ( ! endNode.length ) { - editor.selection.select( startNode[ 0 ] ); + editor.selection.select( startNode[0] ); } else { var selection = editor.getDoc().createRange(); - selection.setStartAfter( startNode[ 0 ] ); - selection.setEndBefore( endNode[ 0 ] ); + selection.setStartAfter( startNode[0] ); + selection.setEndBefore( endNode[0] ); editor.selection.setRng( selection ); } - - scrollVisualModeToStartElement( editor, startNode ); } - if ( startNode.hasClass( 'mce_SELRES_start_target' ) ) { - startNode.removeClass( 'mce_SELRES_start_target' ); + scrollVisualModeToStartElement( editor, startNode ); + + + removeSelectionMarker( editor, startNode ); + removeSelectionMarker( editor, endNode ); + } + + /** + * @summary Remove selection marker with optional `

` parent. + * + * By default TinyMCE puts every inline node at the main level in a `

` wrapping tag. + * + * In the case with selection markers, when removed they leave an empty `

` behind, + * which adds an empty paragraph line with ` ` when switched to Text mode. + * + * In order to prevent that the wrapping `

` needs to be removed when removing the + * selection marker. + * + * @param {object} editor The TinyMCE Editor instance + * @param {object} marker The marker to be removed from the editor DOM + */ + function removeSelectionMarker( editor, marker ) { + var markerParent = editor.$( marker ).parent(); + + if ( + ! markerParent.length || + markerParent.prop('tagName').toLowerCase() !== 'p' || + markerParent[0].childNodes.length > 1 || + ! markerParent.prop('outerHTML').match(/^

/) + ) { + /** + * The selection marker is not self-contained in a

. + * In this case only the selection marker is removed, since + * it will affect the content. + */ + marker.remove(); } else { - startNode.remove(); + /** + * The marker is self-contained in an blank `

` tag. + * + * This is usually inserted by TinyMCE + */ + markerParent.remove(); } - endNode.remove(); } /** @@ -476,23 +591,19 @@ window.wp = window.wp || {}; * @param {Object} element HTMLElement that should be scrolled into view. */ function scrollVisualModeToStartElement( editor, element ) { - /** - * TODO: - * * Decide if we should animate the transition or not ( motion sickness/accessibility ) - */ - var elementTop = editor.$( element ).offset().top; - var TinyMCEContentAreaTop = editor.$( editor.getContentAreaContainer() ).offset().top; + var elementTop = editor.$( element ).offset().top, + TinyMCEContentAreaTop = editor.$( editor.getContentAreaContainer() ).offset().top, - var edTools = $('#wp-content-editor-tools'); - var edToolsHeight = edTools.height(); - var edToolsOffsetTop = edTools.offset().top; + edTools = $( '#wp-content-editor-tools' ), + edToolsHeight = edTools.height(), + edToolsOffsetTop = edTools.offset().top, - var toolbarHeight = getToolbarHeight( editor ); + toolbarHeight = getToolbarHeight( editor ), - var windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; + windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, - var selectionPosition = TinyMCEContentAreaTop + elementTop; - var visibleAreaHeight = windowHeight - ( edToolsHeight + toolbarHeight ); + selectionPosition = TinyMCEContentAreaTop + elementTop, + visibleAreaHeight = windowHeight - ( edToolsHeight + toolbarHeight ); /** * The minimum scroll height should be to the top of the editor, to offer a consistent @@ -502,10 +613,9 @@ window.wp = window.wp || {}; * subtracting the height. This gives the scroll position where the top of the editor tools aligns with * the top of the viewport (under the Master Bar) */ - var adjustedScroll = Math.max(selectionPosition - visibleAreaHeight / 2, edToolsOffsetTop - edToolsHeight); + var adjustedScroll = Math.max( selectionPosition - visibleAreaHeight / 2, edToolsOffsetTop - edToolsHeight ); - - $( 'body' ).animate( { + $( 'html,body' ).animate( { scrollTop: parseInt( adjustedScroll, 10 ) }, 100 ); } @@ -560,10 +670,9 @@ window.wp = window.wp || {}; * The elements have hardcoded style that makes them invisible. This is done to avoid seeing * random content flickering in the editor when switching between modes. */ - var spanSkeleton = getCursorMarkerSpan(editor, selectionID); - - var startElement = spanSkeleton.clone().addClass('mce_SELRES_start'); - var endElement = spanSkeleton.clone().addClass('mce_SELRES_end'); + var spanSkeleton = getCursorMarkerSpan( editor, selectionID ), + startElement = spanSkeleton.clone().addClass( 'mce_SELRES_start' ), + endElement = spanSkeleton.clone().addClass( 'mce_SELRES_end' ); /** * Inspired by: @@ -598,37 +707,40 @@ window.wp = window.wp || {}; startOffset = range.startOffset, boundaryRange = range.cloneRange(); - boundaryRange.collapse( false ); - boundaryRange.insertNode( endElement[0] ); - /** - * Sometimes the selection starts at the `` tag, which makes the - * boundary range `insertNode` insert `startElement` inside the `` tag itself, i.e.: - * - * `...` - * - * As this is an invalid syntax, it breaks the selection. - * - * The conditional below checks if `startNode` is a tag that suffer from that and - * manually inserts the selection start maker before it. - * - * In the future this will probably include a list of tags, not just ``, depending on the needs. + * If the selection is on a shortcode with Live View, TinyMCE creates a bogus markup, + * which we have to account for. */ - if ( startNode && startNode.tagName && startNode.tagName.toLowerCase() === 'img' ) { - editor.$( startNode ).before( startElement[ 0 ] ); + if ( editor.$( startNode ).parents( '.mce-offscreen-selection' ).length > 0 ) { + startNode = editor.$( '[data-mce-selected]' )[0]; + + /** + * Marking the start and end element with `data-mce-object-selection` helps + * discern when the selected object is a Live Preview selection. + * + * This way we can adjust the selection to properly select only the content, ignoring + * whitespace inserted around the selected object by the Editor. + */ + startElement.attr('data-mce-object-selection', 'true'); + endElement.attr('data-mce-object-selection', 'true'); + + editor.$( startNode ).before( startElement[0] ); + editor.$( startNode ).after( endElement[0] ); } else { + boundaryRange.collapse( false ); + boundaryRange.insertNode( endElement[0] ); + boundaryRange.setStart( startNode, startOffset ); boundaryRange.collapse( true ); - boundaryRange.insertNode( startElement[ 0 ] ); + boundaryRange.insertNode( startElement[0] ); + + range.setStartAfter( startElement[0] ); + range.setEndBefore( endElement[0] ); + selection.removeAllRanges(); + selection.addRange( range ); } - - range.setStartAfter( startElement[0] ); - range.setEndBefore( endElement[0] ); - selection.removeAllRanges(); - selection.addRange( range ); - /** * Now the editor's content has the start/end nodes. * @@ -645,24 +757,47 @@ window.wp = window.wp || {}; endElement.remove(); var startRegex = new RegExp( - ']*\\s*class="mce_SELRES_start"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>' + ']*\\s*class="mce_SELRES_start"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>(\\s*)' ); var endRegex = new RegExp( - ']*\\s*class="mce_SELRES_end"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>' + '(\\s*)]*\\s*class="mce_SELRES_end"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>' ); - var startMatch = content.match( startRegex ); - var endMatch = content.match( endRegex ); + var startMatch = content.match( startRegex ), + endMatch = content.match( endRegex ); + if ( ! startMatch ) { return null; } - return { - start: startMatch.index, + var startIndex = startMatch.index, + startMatchLength = startMatch[0].length, + endIndex = null; + + if (endMatch) { + /** + * Adjust the selection index, if the selection contains a Live Preview object or not. + * + * Check where the `data-mce-object-selection` attribute is set above for more context. + */ + if ( startMatch[0].indexOf( 'data-mce-object-selection' ) !== -1 ) { + startMatchLength -= startMatch[1].length; + } + + var endMatchIndex = endMatch.index; + + if ( endMatch[0].indexOf( 'data-mce-object-selection' ) !== -1 ) { + endMatchIndex -= endMatch[1].length; + } // We need to adjust the end position to discard the length of the range start marker - end: endMatch ? endMatch.index - startMatch[ 0 ].length : null + endIndex = endMatchIndex - startMatchLength; + } + + return { + start: startIndex, + end: endIndex }; } @@ -672,7 +807,7 @@ window.wp = window.wp || {}; * Selects the text in TinyMCE's textarea that's between `selection.start` and `selection.end`. * * For `selection` parameter: - * @see findBookmarkedPosition + * @link findBookmarkedPosition * * @param {Object} editor TinyMCE's editor instance. * @param {Object} selection Selection data. diff --git a/wp-admin/js/editor.min.js b/wp-admin/js/editor.min.js index 4bfc7b4fea..30108fbc62 100644 --- a/wp-admin/js/editor.min.js +++ b/wp-admin/js/editor.min.js @@ -1 +1 @@ -window.wp=window.wp||{},function(a,b){function c(){function c(){!s&&window.tinymce&&(s=window.tinymce,t=s.$,t(document).on("click",function(a){var b,c,d=t(a.target);d.hasClass("wp-switch-editor")&&(b=d.attr("data-wp-editor-id"),c=d.hasClass("switch-tmce")?"tmce":"html",e(b,c))}))}function d(a){var b=t(".mce-toolbar-grp",a.getContainer())[0],c=b&&b.clientHeight;return c&&c>10&&c<200?parseInt(c,10):30}function e(b,c){b=b||"content",c=c||"toggle";var e,f,g,h=s.get(b),k=t("#wp-"+b+"-wrap"),l=t("#"+b),o=l[0];if("toggle"===c&&(c=h&&!h.isHidden()?"html":"tmce"),"tmce"===c||"tinymce"===c){if(h&&!h.isHidden())return!1;if("undefined"!=typeof window.QTags&&window.QTags.closeAllTags(b),e=parseInt(o.style.height,10)||0,i(l,a),h)h.show(),!s.Env.iOS&&e&&(f=d(h),e=e-f+14,e>50&&e<5e3&&h.theme.resizeTo(null,e)),j(h);else{var p=a.extend({},window.tinyMCEPreInit.mceInit[b],{setup:function(a){a.on("init",function(a){j(a.target)})}});s.init(p)}k.removeClass("html-active").addClass("tmce-active"),l.attr("aria-hidden",!0),window.setUserSetting("editor","tinymce")}else if("html"===c){if(h&&h.isHidden())return!1;var q=null;h?(s.Env.iOS||(g=h.iframeElement,e=g?parseInt(g.style.height,10):0,e&&(f=d(h),e=e+f-14,e>50&&e<5e3&&(o.style.height=e+"px"))),q=m(h),h.hide(),q&&n(h,q)):l.css({display:"",visibility:""}),k.removeClass("tmce-active").addClass("html-active"),l.attr("aria-hidden",!1),window.setUserSetting("editor","html")}}function f(a,b){var c=a.lastIndexOf("<",b),d=a.lastIndexOf(">",b);if(c>d||">"===a.substr(b,1)){var e=a.substr(c),f=e.match(/<\s*(\/)?(\w+)/);if(!f)return null;var h=f[2],i=e.indexOf(">"),j=!!f[1],k=g(a,c);return{ltPos:c,gtPos:c+i+1,tagType:h,isClosingTag:j,shortcodeTagInfo:k}}return null}function g(a,b){if("]"===a.substr(b-1,1)){var c=a.lastIndexOf("[",b),d=a.substr(c,b-c),e=a.match(/\[\s*(\/)?(\w+)/),f=e[2],g=d.indexOf(">"),h=!!e[1];return{openingBracket:c,shortcode:f,closingBracket:g,isClosingTag:h}}return null}function h(a,b){return a.$("").css({display:"inline-block",width:0,overflow:"hidden","line-height":0}).html(b?b:"")}function i(a,b){var c=a[0],d=c.selectionStart,e=c.selectionEnd,g=["area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],i=f(c.value,d);i&&(d=g.indexOf(i.tagType)!==-1?i.ltPos:i.gtPos);var j=f(c.value,e);j&&(e=j.gtPos);var k=d!==e?"range":"single",l=null,m=h({$:b},"");if("range"===k){var n=c.value.slice(d,e);if(i&&i.shortcodeTagInfo){var o=i.gtPos-i.ltPos,p=n.slice(0,o),q=/class=(['"])([^$1]*?)\1/;p=p.match(q)?p.replace(q,"class=$1$2 mce_SELRES_start_target$1"):p.replace(/(<\w+)/,'$1 class="mce_SELRES_start_target" '),n=[p,n.substr(o)].join("")}var r=m.clone().addClass("mce_SELRES_end")[0].outerHTML;i&&i.shortcodeTagInfo&&j&&i.ltPos===j.ltPos&&(r=""),l=[n,r].join("")}c.value=[c.value.slice(0,d),m.clone().addClass("mce_SELRES_start")[0].outerHTML,l,c.value.slice(e)].join("")}function j(a){var b=a.$(".mce_SELRES_start"),c=a.$(".mce_SELRES_end");if(b.length||(b=a.$(".mce_SELRES_start_target")),b.length){if(a.focus(),c.length){var d=a.getDoc().createRange();d.setStartAfter(b[0]),d.setEndBefore(c[0]),a.selection.setRng(d)}else a.selection.select(b[0]);k(a,b)}b.hasClass("mce_SELRES_start_target")?b.removeClass("mce_SELRES_start_target"):b.remove(),c.remove()}function k(b,c){var e=b.$(c).offset().top,f=b.$(b.getContentAreaContainer()).offset().top,g=a("#wp-content-editor-tools"),h=g.height(),i=g.offset().top,j=d(b),k=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,l=f+e,m=k-(h+j),n=Math.max(l-m/2,i-h);a("body").animate({scrollTop:parseInt(n,10)},100)}function l(a){a.content=a.content.replace(/

(?:
|\u00a0|\uFEFF| )*<\/p>/g,"

 

")}function m(a){var b=a.getWin(),c=b.getSelection();if(!(c.rangeCount<=0)){var d="SELRES_"+Math.random(),e=h(a,d),f=e.clone().addClass("mce_SELRES_start"),g=e.clone().addClass("mce_SELRES_end"),i=c.getRangeAt(0),j=i.startContainer,k=i.startOffset,m=i.cloneRange();m.collapse(!1),m.insertNode(g[0]),j&&j.tagName&&"img"===j.tagName.toLowerCase()?a.$(j).before(f[0]):(m.setStart(j,k),m.collapse(!0),m.insertNode(f[0])),i.setStartAfter(f[0]),i.setEndBefore(g[0]),c.removeAllRanges(),c.addRange(i),a.on("GetContent",l);var n=o(a.getContent());a.off("GetContent",l),f.remove(),g.remove();var p=new RegExp(']*\\s*class="mce_SELRES_start"[^>]+>\\s*'+d+"[^<]*<\\/span>"),q=new RegExp(']*\\s*class="mce_SELRES_end"[^>]+>\\s*'+d+"[^<]*<\\/span>"),r=n.match(p),s=n.match(q);return r?{start:r.index,end:s?s.index-r[0].length:null}:null}}function n(a,b){if(b){var c=a.getElement(),d=b.start,e=b.end||b.start;c.focus&&(setTimeout(function(){c.blur&&c.blur(),c.focus()},100),c.focus()),c.setSelectionRange(d,e)}}function o(a){var b="blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure",c=b+"|div|p",d=b+"|pre",e=!1,f=!1,g=[];return a?(a.indexOf("]*>[\s\S]*?<\/\1>/g,function(a){return g.push(a),""})),a.indexOf("]*>[\s\S]+?<\/pre>/g,function(a){return a=a.replace(/
(\r\n|\n)?/g,""),a=a.replace(/<\/?p( [^>]*)?>(\r\n|\n)?/g,""),a.replace(/\r?\n/g,"")})),a.indexOf("[caption")!==-1&&(f=!0,a=a.replace(/\[caption[\s\S]+?\[\/caption\]/g,function(a){return a.replace(/]*)>/g,"").replace(/[\r\n\t]+/,"")})),a=a.replace(new RegExp("\\s*\\s*","g"),"\n"),a=a.replace(new RegExp("\\s*<((?:"+c+")(?: [^>]*)?)>","g"),"\n<$1>"),a=a.replace(/(

]+>.*?)<\/p>/g,"$1"),a=a.replace(/]*)?>\s*

/gi,"\n\n"),a=a.replace(/\s*

/gi,""),a=a.replace(/\s*<\/p>\s*/gi,"\n\n"),a=a.replace(/\n[\s\u00a0]+\n/g,"\n\n"),a=a.replace(/(\s*)
\s*/gi,function(a,b){return b&&b.indexOf("\n")!==-1?"\n\n":"\n"}),a=a.replace(/\s*

\s*/g,"
\n"),a=a.replace(/\s*\[caption([^\[]+)\[\/caption\]\s*/gi,"\n\n[caption$1[/caption]\n\n"),a=a.replace(/caption\]\n\n+\[caption/g,"caption]\n\n[caption"),a=a.replace(new RegExp("\\s*<((?:"+d+")(?: [^>]*)?)\\s*>","g"),"\n<$1>"),a=a.replace(new RegExp("\\s*\\s*","g"),"\n"),a=a.replace(/<((li|dt|dd)[^>]*)>/g," \t<$1>"),a.indexOf("/g,"\n")),a.indexOf("]*)?>\s*/g,"\n\n\n\n")),a.indexOf("/g,function(a){return a.replace(/[\r\n]+/g,"")})),a=a.replace(/<\/p#>/g,"

\n"),a=a.replace(/\s*(

]+>[\s\S]*?<\/p>)/g,"\n$1"),a=a.replace(/^\s+/,""),a=a.replace(/[\s\u00a0]+$/,""),e&&(a=a.replace(//g,"\n")),f&&(a=a.replace(/]*)>/g,"")),g.length&&(a=a.replace(//g,function(){return g.shift()})),a):""}function p(a){var b=!1,c=!1,d="table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary";return a=a.replace(/\r\n|\r/g,"\n"),a.indexOf("\n")===-1?a:(a.indexOf("/g,function(a){return a.replace(/\n+/g,"")})),a=a.replace(/<[^<>]+>/g,function(a){return a.replace(/[\n\t ]+/g," ")}),a.indexOf("]*>[\s\S]*?<\/\1>/g,function(a){return a.replace(/\n/g,"")})),a.indexOf("]*>)/g,"$1"),a=a.replace(/<\/figcaption>\s*/g,"")),a.indexOf("[caption")!==-1&&(c=!0,a=a.replace(/\[caption[\s\S]+?\[\/caption\]/g,function(a){return a=a.replace(/]*)>/g,""),a=a.replace(/<[^<>]+>/g,function(a){return a.replace(/[\n\t ]+/," ")}),a.replace(/\s*\n\s*/g,"")})),a+="\n\n",a=a.replace(/
\s*
/gi,"\n\n"),a=a.replace(new RegExp("(<(?:"+d+")(?: [^>]*)?>)","gi"),"\n\n$1"),a=a.replace(new RegExp("()","gi"),"$1\n\n"),a=a.replace(/]*)?>/gi,"\n\n"),a=a.replace(/\s*"),a=a.replace(/\n\s*\n+/g,"\n\n"),a=a.replace(/([\s\S]+?)\n\n/g,"

$1

\n"),a=a.replace(/

\s*?<\/p>/gi,""),a=a.replace(new RegExp("

\\s*(]*)?>)\\s*

","gi"),"$1"),a=a.replace(/

(/gi,"$1"),a=a.replace(/

\s*]*)>/gi,"

"),a=a.replace(/<\/blockquote>\s*<\/p>/gi,"

"),a=a.replace(new RegExp("

\\s*(]*)?>)","gi"),"$1"),a=a.replace(new RegExp("(]*)?>)\\s*

","gi"),"$1"),a=a.replace(/(]*>)\s*\n/gi,"$1"),a=a.replace(/\s*\n/g,"
\n"),a=a.replace(new RegExp("(]*>)\\s*
","gi"),"$1"),a=a.replace(/
(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)>)/gi,"$1"),a=a.replace(/(?:

|
)*\s*\[caption([^\[]+)\[\/caption\]\s*(?:<\/p>|
)*/gi,"[caption$1[/caption]"),a=a.replace(/(<(?:div|th|td|form|fieldset|dd)[^>]*>)(.*?)<\/p>/g,function(a,b,c){return c.match(/]*)?>/)?a:b+"

"+c+"

"}),b&&(a=a.replace(//g,"\n")),c&&(a=a.replace(/]*)>/g,"")),a)}function q(b){var c={o:u,data:b,unfiltered:b};return a&&a("body").trigger("beforePreWpautop",[c]),c.data=o(c.data),a&&a("body").trigger("afterPreWpautop",[c]),c.data}function r(b){var c={o:u,data:b,unfiltered:b};return a&&a("body").trigger("beforeWpautop",[c]),c.data=p(c.data),a&&a("body").trigger("afterWpautop",[c]),c.data}var s,t,u={};return a?a(document).ready(c):document.addEventListener?(document.addEventListener("DOMContentLoaded",c,!1),window.addEventListener("load",c,!1)):window.attachEvent&&(window.attachEvent("onload",c),document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&c()})),b.editor.autop=r,b.editor.removep=q,u={go:e,wpautop:r,pre_wpautop:q,_wp_Autop:p,_wp_Nop:o}}b.editor=b.editor||{},window.switchEditors=new c,b.editor.initialize=function(c,d){var e,f;if(a&&c&&b.editor.getDefaultSettings){if(f=b.editor.getDefaultSettings(),d||(d={tinymce:!0}),d.tinymce&&d.quicktags){var g=a("#"+c),h=a("
").attr({"class":"wp-core-ui wp-editor-wrap tmce-active",id:"wp-"+c+"-wrap"}),i=a('
'),j=a("