at path:ROOT / wp-admin / js / editor.js
run:R W Run
DIR
2026-03-11 16:18:51
R W Run
2.86 KB
2026-03-11 16:18:51
R W Run
758 By
2026-03-11 16:18:51
R W Run
6.24 KB
2026-03-11 16:18:51
R W Run
2.95 KB
2026-03-11 16:18:51
R W Run
5.66 KB
2026-03-11 16:18:51
R W Run
2.04 KB
2026-03-11 16:18:51
R W Run
11.32 KB
2026-03-11 16:18:51
R W Run
3.01 KB
2026-03-11 16:18:51
R W Run
9.54 KB
2026-03-11 16:18:51
R W Run
3.4 KB
2026-03-11 16:18:51
R W Run
2.85 KB
2026-03-11 16:18:51
R W Run
1.28 KB
2026-03-11 16:18:51
R W Run
61.15 KB
2026-03-11 16:18:51
R W Run
23.12 KB
2026-03-11 16:18:51
R W Run
3.35 KB
2026-03-11 16:18:51
R W Run
1.18 KB
2026-03-11 16:18:51
R W Run
1.98 KB
2026-03-11 16:18:51
R W Run
288.41 KB
2026-03-11 16:18:51
R W Run
109.69 KB
2026-03-11 16:18:51
R W Run
111.46 KB
2026-03-11 16:18:51
R W Run
47.14 KB
2026-03-11 16:18:51
R W Run
70.05 KB
2026-03-11 16:18:51
R W Run
27.41 KB
2026-03-11 16:18:51
R W Run
27.02 KB
2026-03-11 16:18:51
R W Run
8.65 KB
2026-03-11 16:18:51
R W Run
37.12 KB
2026-03-11 16:18:51
R W Run
15.13 KB
2026-03-11 16:18:51
R W Run
41.61 KB
2026-03-11 16:18:51
R W Run
13.14 KB
2026-03-11 16:18:51
R W Run
44 KB
2026-03-11 16:18:51
R W Run
12.78 KB
2026-03-11 16:18:51
R W Run
7.67 KB
2026-03-11 16:18:51
R W Run
5.41 KB
2026-03-11 16:18:51
R W Run
3.65 KB
2026-03-11 16:18:51
R W Run
39.98 KB
2026-03-11 16:18:51
R W Run
15.15 KB
2026-03-11 16:18:51
R W Run
20.17 KB
2026-03-11 16:18:51
R W Run
9.41 KB
2026-03-11 16:18:51
R W Run
7.61 KB
2026-03-11 16:18:51
R W Run
2.93 KB
2026-03-11 16:18:51
R W Run
23.09 KB
2026-03-11 16:18:51
R W Run
890 By
2026-03-11 16:18:51
R W Run
423 By
2026-03-11 16:18:51
R W Run
3.89 KB
2026-03-11 16:18:51
R W Run
1.7 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
611 By
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
1.13 KB
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
2.38 KB
2026-03-11 16:18:51
R W Run
61.15 KB
2026-03-11 16:18:51
R W Run
30.06 KB
2026-03-11 16:18:51
R W Run
4.14 KB
2026-03-11 16:18:51
R W Run
1.1 KB
2026-03-11 16:18:51
R W Run
1.31 KB
2026-03-11 16:18:51
R W Run
847 By
2026-03-11 16:18:51
R W Run
6.92 KB
2026-03-11 16:18:51
R W Run
2.35 KB
2026-03-11 16:18:51
R W Run
38.68 KB
2026-03-11 16:18:51
R W Run
18.4 KB
2026-03-11 16:18:51
R W Run
18.49 KB
2026-03-11 16:18:51
R W Run
6.6 KB
2026-03-11 16:18:51
R W Run
10.67 KB
2026-03-11 16:18:51
R W Run
5.03 KB
2026-03-11 16:18:51
R W Run
33.92 KB
2026-03-11 16:18:51
R W Run
17.97 KB
2026-03-11 16:18:51
R W Run
876 By
2026-03-11 16:18:51
R W Run
620 By
2026-03-11 16:18:51
R W Run
13.15 KB
2026-03-11 16:18:51
R W Run
6.13 KB
2026-03-11 16:18:51
R W Run
6.1 KB
2026-03-11 16:18:51
R W Run
2.2 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
1.53 KB
2026-03-11 16:18:51
R W Run
10.88 KB
2026-03-11 16:18:51
R W Run
3 KB
2026-03-11 16:18:51
R W Run
5.64 KB
2026-03-11 16:18:51
R W Run
2.22 KB
2026-03-11 16:18:51
R W Run
5.96 KB
2026-03-11 16:18:51
R W Run
2.41 KB
2026-03-11 16:18:51
R W Run
24.77 KB
2026-03-11 16:18:51
R W Run
11.43 KB
2026-03-11 16:18:51
R W Run
54.94 KB
2026-03-11 16:18:51
R W Run
26.51 KB
2026-03-11 16:18:51
R W Run
109.37 KB
2026-03-11 16:18:51
R W Run
47.31 KB
2026-03-11 16:18:51
R W Run
17.91 KB
2026-03-11 16:18:51
R W Run
7.81 KB
2026-03-11 16:18:51
R W Run
2.25 KB
2026-03-11 16:18:51
R W Run
676 By
2026-03-11 16:18:51
R W Run
22.56 KB
2026-03-11 16:18:51
R W Run
12.31 KB
2026-03-11 16:18:51
R W Run
7.52 KB
2026-03-11 16:18:51
R W Run
1.49 KB
2026-03-11 16:18:51
R W Run
740 By
2026-03-11 16:18:51
R W Run
458 By
2026-03-11 16:18:51
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 ));
1405
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1