at path๏ผšROOT / wp-includes / js / tinymce / plugins / wordpress / plugin.js
run๏ผšR W Run
33.35 KB
2026-03-11 16:18:51
R W Run
16.02 KB
2026-03-11 16:18:51
R W Run
error_log
๐Ÿ“„plugin.js
1/* global getUserSetting, setUserSetting */
2( function( tinymce ) {
3// Set the minimum value for the modals z-index higher than #wpadminbar (100000).
4if ( ! tinymce.ui.FloatPanel.zIndex || tinymce.ui.FloatPanel.zIndex < 100100 ) {
5 tinymce.ui.FloatPanel.zIndex = 100100;
6}
7
8tinymce.PluginManager.add( 'wordpress', function( editor ) {
9 var wpAdvButton, style,
10 DOM = tinymce.DOM,
11 each = tinymce.each,
12 __ = editor.editorManager.i18n.translate,
13 $ = window.jQuery,
14 wp = window.wp,
15 hasWpautop = ( wp && wp.editor && wp.editor.autop && editor.getParam( 'wpautop', true ) ),
16 wpTooltips = false;
17
18 if ( $ ) {
19 // Runs as soon as TinyMCE has started initializing, while plugins are loading.
20 // Handlers attached after the `tinymce.init()` call may not get triggered for this instance.
21 $( document ).triggerHandler( 'tinymce-editor-setup', [ editor ] );
22 }
23
24 function toggleToolbars( state ) {
25 var initial, toolbars, iframeHeight,
26 pixels = 0,
27 classicBlockToolbar = tinymce.$( '.block-library-classic__toolbar' );
28
29 if ( state === 'hide' ) {
30 initial = true;
31 } else if ( classicBlockToolbar.length && ! classicBlockToolbar.hasClass( 'has-advanced-toolbar' ) ) {
32 // Show the second, third, etc. toolbar rows in the Classic block instance.
33 classicBlockToolbar.addClass( 'has-advanced-toolbar' );
34 state = 'show';
35 }
36
37 if ( editor.theme.panel ) {
38 toolbars = editor.theme.panel.find('.toolbar:not(.menubar)');
39 }
40
41 if ( toolbars && toolbars.length > 1 ) {
42 if ( ! state && toolbars[1].visible() ) {
43 state = 'hide';
44 }
45
46 each( toolbars, function( toolbar, i ) {
47 if ( i > 0 ) {
48 if ( state === 'hide' ) {
49 toolbar.hide();
50 pixels += 34;
51 } else {
52 toolbar.show();
53 pixels -= 34;
54 }
55 }
56 });
57 }
58
59 // Resize editor iframe, not needed for iOS and inline instances.
60 // Don't resize if the editor is in a hidden container.
61 if ( pixels && ! tinymce.Env.iOS && editor.iframeElement && editor.iframeElement.clientHeight ) {
62 iframeHeight = editor.iframeElement.clientHeight + pixels;
63
64 // Keep min-height.
65 if ( iframeHeight > 50 ) {
66 DOM.setStyle( editor.iframeElement, 'height', iframeHeight );
67 }
68 }
69
70 if ( ! initial ) {
71 if ( state === 'hide' ) {
72 setUserSetting( 'hidetb', '0' );
73 wpAdvButton && wpAdvButton.active( false );
74 } else {
75 setUserSetting( 'hidetb', '1' );
76 wpAdvButton && wpAdvButton.active( true );
77 }
78 }
79
80 editor.fire( 'wp-toolbar-toggle' );
81 }
82
83 // Add the kitchen sink button :)
84 editor.addButton( 'wp_adv', {
85 tooltip: 'Toolbar Toggle',
86 cmd: 'WP_Adv',
87 onPostRender: function() {
88 wpAdvButton = this;
89 wpAdvButton.active( getUserSetting( 'hidetb' ) === '1' );
90 }
91 });
92
93 // Hide the toolbars after loading.
94 editor.on( 'PostRender', function() {
95 if ( editor.getParam( 'wordpress_adv_hidden', true ) && getUserSetting( 'hidetb', '0' ) === '0' ) {
96 toggleToolbars( 'hide' );
97 } else {
98 tinymce.$( '.block-library-classic__toolbar' ).addClass( 'has-advanced-toolbar' );
99 }
100 });
101
102 editor.addCommand( 'WP_Adv', function() {
103 toggleToolbars();
104 });
105
106 editor.on( 'focus', function() {
107 window.wpActiveEditor = editor.id;
108 });
109
110 editor.on( 'BeforeSetContent', function( event ) {
111 var title;
112
113 if ( event.content ) {
114 if ( event.content.indexOf( '<!--more' ) !== -1 ) {
115 title = __( 'Read more...' );
116
117 event.content = event.content.replace( /<!--more(.*?)-->/g, function( match, moretext ) {
118 return '<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="more" data-wp-more-text="' + moretext + '" ' +
119 'class="wp-more-tag mce-wp-more" alt="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />';
120 });
121 }
122
123 if ( event.content.indexOf( '<!--nextpage-->' ) !== -1 ) {
124 title = __( 'Page break' );
125
126 event.content = event.content.replace( /<!--nextpage-->/g,
127 '<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="nextpage" class="wp-more-tag mce-wp-nextpage" ' +
128 'alt="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />' );
129 }
130
131 if ( event.load && event.format !== 'raw' ) {
132 if ( hasWpautop ) {
133 event.content = wp.editor.autop( event.content );
134 } else {
135 // Prevent creation of paragraphs out of multiple HTML comments.
136 event.content = event.content.replace( /-->\s+<!--/g, '--><!--' );
137 }
138 }
139
140 if ( event.content.indexOf( '<script' ) !== -1 || event.content.indexOf( '<style' ) !== -1 ) {
141 event.content = event.content.replace( /<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function( match, tag ) {
142 return '<img ' +
143 'src="' + tinymce.Env.transparentSrc + '" ' +
144 'data-wp-preserve="' + encodeURIComponent( match ) + '" ' +
145 'data-mce-resize="false" ' +
146 'data-mce-placeholder="1" '+
147 'class="mce-object mce-object-' + tag + '" ' +
148 'width="20" height="20" '+
149 'alt="&lt;' + tag + '&gt;" ' +
150 '/>';
151 } );
152 }
153 }
154 });
155
156 editor.on( 'setcontent', function() {
157 // Remove spaces from empty paragraphs.
158 editor.$( 'p' ).each( function( i, node ) {
159 if ( node.innerHTML && node.innerHTML.length < 10 ) {
160 var html = tinymce.trim( node.innerHTML );
161
162 if ( ! html || html === '&nbsp;' ) {
163 node.innerHTML = ( tinymce.Env.ie && tinymce.Env.ie < 11 ) ? '' : '<br data-mce-bogus="1">';
164 }
165 }
166 } );
167 });
168
169 editor.on( 'PostProcess', function( event ) {
170 if ( event.get ) {
171 event.content = event.content.replace(/<img[^>]+>/g, function( image ) {
172 var match,
173 string,
174 moretext = '';
175
176 if ( image.indexOf( 'data-wp-more="more"' ) !== -1 ) {
177 if ( match = image.match( /data-wp-more-text="([^"]+)"/ ) ) {
178 moretext = match[1];
179 }
180
181 string = '<!--more' + moretext + '-->';
182 } else if ( image.indexOf( 'data-wp-more="nextpage"' ) !== -1 ) {
183 string = '<!--nextpage-->';
184 } else if ( image.indexOf( 'data-wp-preserve' ) !== -1 ) {
185 if ( match = image.match( / data-wp-preserve="([^"]+)"/ ) ) {
186 string = decodeURIComponent( match[1] );
187 }
188 }
189
190 return string || image;
191 });
192 }
193 });
194
195 // Display the tag name instead of img in element path.
196 editor.on( 'ResolveName', function( event ) {
197 var attr;
198
199 if ( event.target.nodeName === 'IMG' && ( attr = editor.dom.getAttrib( event.target, 'data-wp-more' ) ) ) {
200 event.name = attr;
201 }
202 });
203
204 // Register commands.
205 editor.addCommand( 'WP_More', function( tag ) {
206 var parent, html, title,
207 classname = 'wp-more-tag',
208 dom = editor.dom,
209 node = editor.selection.getNode(),
210 rootNode = editor.getBody();
211
212 tag = tag || 'more';
213 classname += ' mce-wp-' + tag;
214 title = tag === 'more' ? 'Read more...' : 'Next page';
215 title = __( title );
216 html = '<img src="' + tinymce.Env.transparentSrc + '" alt="' + title + '" class="' + classname + '" ' +
217 'data-wp-more="' + tag + '" data-mce-resize="false" data-mce-placeholder="1" />';
218
219 // Most common case.
220 if ( node === rootNode || ( node.nodeName === 'P' && node.parentNode === rootNode ) ) {
221 editor.insertContent( html );
222 return;
223 }
224
225 // Get the top level parent node.
226 parent = dom.getParent( node, function( found ) {
227 if ( found.parentNode && found.parentNode === rootNode ) {
228 return true;
229 }
230
231 return false;
232 }, editor.getBody() );
233
234 if ( parent ) {
235 if ( parent.nodeName === 'P' ) {
236 parent.appendChild( dom.create( 'p', null, html ).firstChild );
237 } else {
238 dom.insertAfter( dom.create( 'p', null, html ), parent );
239 }
240
241 editor.nodeChanged();
242 }
243 });
244
245 editor.addCommand( 'WP_Code', function() {
246 editor.formatter.toggle('code');
247 });
248
249 editor.addCommand( 'WP_Page', function() {
250 editor.execCommand( 'WP_More', 'nextpage' );
251 });
252
253 editor.addCommand( 'WP_Help', function() {
254 var access = tinymce.Env.mac ? __( 'Ctrl + Alt + letter:' ) : __( 'Shift + Alt + letter:' ),
255 meta = tinymce.Env.mac ? __( 'โŒ˜ + letter:' ) : __( 'Ctrl + letter:' ),
256 table1 = [],
257 table2 = [],
258 row1 = {},
259 row2 = {},
260 i1 = 0,
261 i2 = 0,
262 labels = editor.settings.wp_shortcut_labels,
263 header, html, dialog, $wrap;
264
265 if ( ! labels ) {
266 return;
267 }
268
269 function tr( row, columns ) {
270 var out = '<tr>';
271 var i = 0;
272
273 columns = columns || 1;
274
275 each( row, function( text, key ) {
276 out += '<td><kbd>' + key + '</kbd></td><td>' + __( text ) + '</td>';
277 i++;
278 });
279
280 while ( i < columns ) {
281 out += '<td></td><td></td>';
282 i++;
283 }
284
285 return out + '</tr>';
286 }
287
288 each ( labels, function( label, name ) {
289 var letter;
290
291 if ( label.indexOf( 'meta' ) !== -1 ) {
292 i1++;
293 letter = label.replace( 'meta', '' ).toLowerCase();
294
295 if ( letter ) {
296 row1[ letter ] = name;
297
298 if ( i1 % 2 === 0 ) {
299 table1.push( tr( row1, 2 ) );
300 row1 = {};
301 }
302 }
303 } else if ( label.indexOf( 'access' ) !== -1 ) {
304 i2++;
305 letter = label.replace( 'access', '' ).toLowerCase();
306
307 if ( letter ) {
308 row2[ letter ] = name;
309
310 if ( i2 % 2 === 0 ) {
311 table2.push( tr( row2, 2 ) );
312 row2 = {};
313 }
314 }
315 }
316 } );
317
318 // Add remaining single entries.
319 if ( i1 % 2 > 0 ) {
320 table1.push( tr( row1, 2 ) );
321 }
322
323 if ( i2 % 2 > 0 ) {
324 table2.push( tr( row2, 2 ) );
325 }
326
327 header = [ __( 'Letter' ), __( 'Action' ), __( 'Letter' ), __( 'Action' ) ];
328 header = '<tr><th>' + header.join( '</th><th>' ) + '</th></tr>';
329
330 html = '<div class="wp-editor-help">';
331
332 // Main section, default and additional shortcuts.
333 html = html +
334 '<h2>' + __( 'Default shortcuts,' ) + ' ' + meta + '</h2>' +
335 '<table class="wp-help-th-center fixed">' +
336 header +
337 table1.join('') +
338 '</table>' +
339 '<h2>' + __( 'Additional shortcuts,' ) + ' ' + access + '</h2>' +
340 '<table class="wp-help-th-center fixed">' +
341 header +
342 table2.join('') +
343 '</table>';
344
345 if ( editor.plugins.wptextpattern && ( ! tinymce.Env.ie || tinymce.Env.ie > 8 ) ) {
346 // Text pattern section.
347 html = html +
348 '<h2>' + __( 'When starting a new paragraph with one of these formatting shortcuts followed by a space, the formatting will be applied automatically. Press Backspace or Escape to undo.' ) + '</h2>' +
349 '<table class="wp-help-th-center fixed">' +
350 tr({ '*': 'Bullet list', '1.': 'Numbered list' }) +
351 tr({ '-': 'Bullet list', '1)': 'Numbered list' }) +
352 '</table>';
353
354 html = html +
355 '<h2>' + __( 'The following formatting shortcuts are replaced when pressing Enter. Press Escape or the Undo button to undo.' ) + '</h2>' +
356 '<table class="wp-help-single">' +
357 tr({ '>': 'Blockquote' }) +
358 tr({ '##': 'Heading 2' }) +
359 tr({ '###': 'Heading 3' }) +
360 tr({ '####': 'Heading 4' }) +
361 tr({ '#####': 'Heading 5' }) +
362 tr({ '######': 'Heading 6' }) +
363 tr({ '---': 'Horizontal line' }) +
364 '</table>';
365 }
366
367 // Focus management section.
368 html = html +
369 '<h2>' + __( 'Focus shortcuts:' ) + '</h2>' +
370 '<table class="wp-help-single">' +
371 tr({ 'Alt + F8': 'Inline toolbar (when an image, link or preview is selected)' }) +
372 tr({ 'Alt + F9': 'Editor menu (when enabled)' }) +
373 tr({ 'Alt + F10': 'Editor toolbar' }) +
374 tr({ 'Alt + F11': 'Elements path' }) +
375 '</table>' +
376 '<p>' + __( 'To move focus to other buttons use Tab or the arrow keys. To return focus to the editor press Escape or use one of the buttons.' ) + '</p>';
377
378 html += '</div>';
379
380 dialog = editor.windowManager.open( {
381 title: editor.settings.classic_block_editor ? 'Classic Block Keyboard Shortcuts' : 'Keyboard Shortcuts',
382 items: {
383 type: 'container',
384 classes: 'wp-help',
385 html: html
386 },
387 buttons: {
388 text: 'Close',
389 onclick: 'close'
390 }
391 } );
392
393 if ( dialog.$el ) {
394 dialog.$el.find( 'div[role="application"]' ).attr( 'role', 'document' );
395 $wrap = dialog.$el.find( '.mce-wp-help' );
396
397 if ( $wrap[0] ) {
398 $wrap.attr( 'tabindex', '0' );
399 $wrap[0].focus();
400 $wrap.on( 'keydown', function( event ) {
401 // Prevent use of: page up, page down, end, home, left arrow, up arrow, right arrow, down arrow
402 // in the dialog keydown handler.
403 if ( event.keyCode >= 33 && event.keyCode <= 40 ) {
404 event.stopPropagation();
405 }
406 });
407 }
408 }
409 } );
410
411 editor.addCommand( 'WP_Medialib', function() {
412 if ( wp && wp.media && wp.media.editor ) {
413 wp.media.editor.open( editor.id );
414 }
415 });
416
417 // Register buttons.
418 editor.addButton( 'wp_more', {
419 tooltip: 'Insert Read More tag',
420 onclick: function() {
421 editor.execCommand( 'WP_More', 'more' );
422 }
423 });
424
425 editor.addButton( 'wp_page', {
426 tooltip: 'Page break',
427 onclick: function() {
428 editor.execCommand( 'WP_More', 'nextpage' );
429 }
430 });
431
432 editor.addButton( 'wp_help', {
433 tooltip: 'Keyboard Shortcuts',
434 cmd: 'WP_Help'
435 });
436
437 editor.addButton( 'wp_code', {
438 tooltip: 'Code',
439 cmd: 'WP_Code',
440 stateSelector: 'code'
441 });
442
443 // Insert->Add Media.
444 if ( wp && wp.media && wp.media.editor ) {
445 editor.addButton( 'wp_add_media', {
446 tooltip: 'Add Media',
447 icon: 'dashicon dashicons-admin-media',
448 cmd: 'WP_Medialib'
449 } );
450
451 editor.addMenuItem( 'add_media', {
452 text: 'Add Media',
453 icon: 'wp-media-library',
454 context: 'insert',
455 cmd: 'WP_Medialib'
456 });
457 }
458
459 // Insert "Read More...".
460 editor.addMenuItem( 'wp_more', {
461 text: 'Insert Read More tag',
462 icon: 'wp_more',
463 context: 'insert',
464 onclick: function() {
465 editor.execCommand( 'WP_More', 'more' );
466 }
467 });
468
469 // Insert "Next Page".
470 editor.addMenuItem( 'wp_page', {
471 text: 'Page break',
472 icon: 'wp_page',
473 context: 'insert',
474 onclick: function() {
475 editor.execCommand( 'WP_More', 'nextpage' );
476 }
477 });
478
479 editor.on( 'BeforeExecCommand', function(e) {
480 if ( tinymce.Env.webkit && ( e.command === 'InsertUnorderedList' || e.command === 'InsertOrderedList' ) ) {
481 if ( ! style ) {
482 style = editor.dom.create( 'style', {'type': 'text/css'},
483 '#tinymce,#tinymce span,#tinymce li,#tinymce li>span,#tinymce p,#tinymce p>span{font:medium sans-serif;color:#000;line-height:normal;}');
484 }
485
486 editor.getDoc().head.appendChild( style );
487 }
488 });
489
490 editor.on( 'ExecCommand', function( e ) {
491 if ( tinymce.Env.webkit && style &&
492 ( 'InsertUnorderedList' === e.command || 'InsertOrderedList' === e.command ) ) {
493
494 editor.dom.remove( style );
495 }
496 });
497
498 editor.on( 'init', function() {
499 var env = tinymce.Env,
500 bodyClass = ['mceContentBody'], // Back-compat for themes that use this in editor-style.css...
501 doc = editor.getDoc(),
502 dom = editor.dom;
503
504 if ( env.iOS ) {
505 dom.addClass( doc.documentElement, 'ios' );
506 }
507
508 if ( editor.getParam( 'directionality' ) === 'rtl' ) {
509 bodyClass.push('rtl');
510 dom.setAttrib( doc.documentElement, 'dir', 'rtl' );
511 }
512
513 dom.setAttrib( doc.documentElement, 'lang', editor.getParam( 'wp_lang_attr' ) );
514
515 if ( env.ie ) {
516 if ( parseInt( env.ie, 10 ) === 9 ) {
517 bodyClass.push('ie9');
518 } else if ( parseInt( env.ie, 10 ) === 8 ) {
519 bodyClass.push('ie8');
520 } else if ( env.ie < 8 ) {
521 bodyClass.push('ie7');
522 }
523 } else if ( env.webkit ) {
524 bodyClass.push('webkit');
525 }
526
527 bodyClass.push('wp-editor');
528
529 each( bodyClass, function( cls ) {
530 if ( cls ) {
531 dom.addClass( doc.body, cls );
532 }
533 });
534
535 // Remove invalid parent paragraphs when inserting HTML.
536 editor.on( 'BeforeSetContent', function( event ) {
537 if ( event.content ) {
538 event.content = event.content.replace( /<p>\s*<(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre)( [^>]*)?>/gi, '<$1$2>' )
539 .replace( /<\/(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre)>\s*<\/p>/gi, '</$1>' );
540 }
541 });
542
543 if ( $ ) {
544 // Run on DOM ready. Otherwise TinyMCE may initialize earlier and handlers attached
545 // on DOM ready of after the `tinymce.init()` call may not get triggered.
546 $( function() {
547 $( document ).triggerHandler( 'tinymce-editor-init', [editor] );
548 });
549 }
550
551 if ( window.tinyMCEPreInit && window.tinyMCEPreInit.dragDropUpload ) {
552 dom.bind( doc, 'dragstart dragend dragover drop', function( event ) {
553 if ( $ ) {
554 // Trigger the jQuery handlers.
555 $( document ).trigger( new $.Event( event ) );
556 }
557 });
558 }
559
560 if ( editor.getParam( 'wp_paste_filters', true ) ) {
561 editor.on( 'PastePreProcess', function( event ) {
562 // Remove trailing <br> added by WebKit browsers to the clipboard.
563 event.content = event.content.replace( /<br class="?Apple-interchange-newline"?>/gi, '' );
564
565 // In WebKit this is handled by removeWebKitStyles().
566 if ( ! tinymce.Env.webkit ) {
567 // Remove all inline styles.
568 event.content = event.content.replace( /(<[^>]+) style="[^"]*"([^>]*>)/gi, '$1$2' );
569
570 // Put back the internal styles.
571 event.content = event.content.replace(/(<[^>]+) data-mce-style=([^>]+>)/gi, '$1 style=$2' );
572 }
573 });
574
575 editor.on( 'PastePostProcess', function( event ) {
576 // Remove empty paragraphs.
577 editor.$( 'p', event.node ).each( function( i, node ) {
578 if ( dom.isEmpty( node ) ) {
579 dom.remove( node );
580 }
581 });
582
583 if ( tinymce.isIE ) {
584 editor.$( 'a', event.node ).find( 'font, u' ).each( function( i, node ) {
585 dom.remove( node, true );
586 });
587 }
588 });
589 }
590 });
591
592 editor.on( 'SaveContent', function( event ) {
593 // If editor is hidden, we just want the textarea's value to be saved.
594 if ( ! editor.inline && editor.isHidden() ) {
595 event.content = event.element.value;
596 return;
597 }
598
599 // Keep empty paragraphs :(
600 event.content = event.content.replace( /<p>(?:<br ?\/?>|\u00a0|\uFEFF| )*<\/p>/g, '<p>&nbsp;</p>' );
601
602 if ( hasWpautop ) {
603 event.content = wp.editor.removep( event.content );
604 } else {
605 // Restore formatting of block boundaries.
606 event.content = event.content.replace( /-->\s*<!-- wp:/g, '-->\n\n<!-- wp:' );
607 }
608 });
609
610 editor.on( 'preInit', function() {
611 var validElementsSetting = '@[id|accesskey|class|dir|lang|style|tabindex|' +
612 'title|contenteditable|draggable|dropzone|hidden|spellcheck|translate],' + // Global attributes.
613 'i,' + // Don't replace <i> with <em> and <b> with <strong> and don't remove them when empty.
614 'b,' +
615 'script[src|async|defer|type|charset|crossorigin|integrity]'; // Add support for <script>.
616
617 editor.schema.addValidElements( validElementsSetting );
618
619 if ( tinymce.Env.iOS ) {
620 editor.settings.height = 300;
621 }
622
623 each( {
624 c: 'JustifyCenter',
625 r: 'JustifyRight',
626 l: 'JustifyLeft',
627 j: 'JustifyFull',
628 q: 'mceBlockQuote',
629 u: 'InsertUnorderedList',
630 o: 'InsertOrderedList',
631 m: 'WP_Medialib',
632 t: 'WP_More',
633 d: 'Strikethrough',
634 p: 'WP_Page',
635 x: 'WP_Code'
636 }, function( command, key ) {
637 editor.shortcuts.add( 'access+' + key, '', command );
638 } );
639
640 editor.addShortcut( 'meta+s', '', function() {
641 if ( wp && wp.autosave ) {
642 wp.autosave.server.triggerSave();
643 }
644 } );
645
646 // Alt+Shift+Z removes a block in the block editor, don't add it to the Classic block.
647 if ( ! editor.settings.classic_block_editor ) {
648 editor.addShortcut( 'access+z', '', 'WP_Adv' );
649 }
650
651 // Workaround for not triggering the global help modal in the block editor by the Classic block shortcut.
652 editor.on( 'keydown', function( event ) {
653 var match;
654
655 if ( tinymce.Env.mac ) {
656 match = event.ctrlKey && event.altKey && event.code === 'KeyH';
657 } else {
658 match = event.shiftKey && event.altKey && event.code === 'KeyH';
659 }
660
661 if ( match ) {
662 editor.execCommand( 'WP_Help' );
663 event.stopPropagation();
664 event.stopImmediatePropagation();
665 return false;
666 }
667
668 return true;
669 });
670
671 if ( window.getUserSetting( 'editor_plain_text_paste_warning' ) > 1 ) {
672 editor.settings.paste_plaintext_inform = false;
673 }
674
675 // Change the editor iframe title on MacOS, add the correct help shortcut.
676 if ( tinymce.Env.mac ) {
677 tinymce.$( editor.iframeElement ).attr( 'title', __( 'Rich Text Area. Press Control-Option-H for help.' ) );
678 }
679 } );
680
681 editor.on( 'PastePlainTextToggle', function( event ) {
682 // Warn twice, then stop.
683 if ( event.state === true ) {
684 var times = parseInt( window.getUserSetting( 'editor_plain_text_paste_warning' ), 10 ) || 0;
685
686 if ( times < 2 ) {
687 window.setUserSetting( 'editor_plain_text_paste_warning', ++times );
688 }
689 }
690 });
691
692 editor.on( 'beforerenderui', function() {
693 if ( editor.theme.panel ) {
694 each( [ 'button', 'colorbutton', 'splitbutton' ], function( buttonType ) {
695 replaceButtonsTooltips( editor.theme.panel.find( buttonType ) );
696 } );
697
698 addShortcutsToListbox();
699 }
700 } );
701
702 function prepareTooltips() {
703 var access = 'Shift+Alt+';
704 var meta = 'Ctrl+';
705
706 wpTooltips = {};
707
708 // For MacOS: ctrl = \u2303, cmd = \u2318, alt = \u2325.
709 if ( tinymce.Env.mac ) {
710 access = '\u2303\u2325';
711 meta = '\u2318';
712 }
713
714 // Some tooltips are translated, others are not...
715 if ( editor.settings.wp_shortcut_labels ) {
716 each( editor.settings.wp_shortcut_labels, function( value, tooltip ) {
717 var translated = editor.translate( tooltip );
718
719 value = value.replace( 'access', access ).replace( 'meta', meta );
720 wpTooltips[ tooltip ] = value;
721
722 // Add the translated so we can match all of them.
723 if ( tooltip !== translated ) {
724 wpTooltips[ translated ] = value;
725 }
726 } );
727 }
728 }
729
730 function getTooltip( tooltip ) {
731 var translated = editor.translate( tooltip );
732 var label;
733
734 if ( ! wpTooltips ) {
735 prepareTooltips();
736 }
737
738 if ( wpTooltips.hasOwnProperty( translated ) ) {
739 label = wpTooltips[ translated ];
740 } else if ( wpTooltips.hasOwnProperty( tooltip ) ) {
741 label = wpTooltips[ tooltip ];
742 }
743
744 return label ? translated + ' (' + label + ')' : translated;
745 }
746
747 function replaceButtonsTooltips( buttons ) {
748
749 if ( ! buttons ) {
750 return;
751 }
752
753 each( buttons, function( button ) {
754 var tooltip;
755
756 if ( button && button.settings.tooltip ) {
757 tooltip = getTooltip( button.settings.tooltip );
758 button.settings.tooltip = tooltip;
759
760 // Override the aria label with the translated tooltip + shortcut.
761 if ( button._aria && button._aria.label ) {
762 button._aria.label = tooltip;
763 }
764 }
765 } );
766 }
767
768 function addShortcutsToListbox() {
769 // listbox for the "blocks" drop-down.
770 each( editor.theme.panel.find( 'listbox' ), function( listbox ) {
771 if ( listbox && listbox.settings.text === 'Paragraph' ) {
772 each( listbox.settings.values, function( item ) {
773 if ( item.text && wpTooltips.hasOwnProperty( item.text ) ) {
774 item.shortcut = '(' + wpTooltips[ item.text ] + ')';
775 }
776 } );
777 }
778 } );
779 }
780
781 /**
782 * Experimental: create a floating toolbar.
783 * This functionality will change in the next releases. Not recommended for use by plugins.
784 */
785 editor.on( 'preinit', function() {
786 var Factory = tinymce.ui.Factory,
787 settings = editor.settings,
788 activeToolbar,
789 currentSelection,
790 timeout,
791 container = editor.getContainer(),
792 wpAdminbar = document.getElementById( 'wpadminbar' ),
793 mceIframe = document.getElementById( editor.id + '_ifr' ),
794 mceToolbar,
795 mceStatusbar,
796 wpStatusbar,
797 cachedWinSize;
798
799 if ( container ) {
800 mceToolbar = tinymce.$( '.mce-toolbar-grp', container )[0];
801 mceStatusbar = tinymce.$( '.mce-statusbar', container )[0];
802 }
803
804 if ( editor.id === 'content' ) {
805 wpStatusbar = document.getElementById( 'post-status-info' );
806 }
807
808 function create( buttons, bottom ) {
809 var toolbar,
810 toolbarItems = [],
811 buttonGroup;
812
813 each( buttons, function( item ) {
814 var itemName;
815 var tooltip;
816
817 function bindSelectorChanged() {
818 var selection = editor.selection;
819
820 if ( itemName === 'bullist' ) {
821 selection.selectorChanged( 'ul > li', function( state, args ) {
822 var i = args.parents.length,
823 nodeName;
824
825 while ( i-- ) {
826 nodeName = args.parents[ i ].nodeName;
827
828 if ( nodeName === 'OL' || nodeName == 'UL' ) {
829 break;
830 }
831 }
832
833 item.active( state && nodeName === 'UL' );
834 } );
835 }
836
837 if ( itemName === 'numlist' ) {
838 selection.selectorChanged( 'ol > li', function( state, args ) {
839 var i = args.parents.length,
840 nodeName;
841
842 while ( i-- ) {
843 nodeName = args.parents[ i ].nodeName;
844
845 if ( nodeName === 'OL' || nodeName === 'UL' ) {
846 break;
847 }
848 }
849
850 item.active( state && nodeName === 'OL' );
851 } );
852 }
853
854 if ( item.settings.stateSelector ) {
855 selection.selectorChanged( item.settings.stateSelector, function( state ) {
856 item.active( state );
857 }, true );
858 }
859
860 if ( item.settings.disabledStateSelector ) {
861 selection.selectorChanged( item.settings.disabledStateSelector, function( state ) {
862 item.disabled( state );
863 } );
864 }
865 }
866
867 if ( item === '|' ) {
868 buttonGroup = null;
869 } else {
870 if ( Factory.has( item ) ) {
871 item = {
872 type: item
873 };
874
875 if ( settings.toolbar_items_size ) {
876 item.size = settings.toolbar_items_size;
877 }
878
879 toolbarItems.push( item );
880
881 buttonGroup = null;
882 } else {
883 if ( ! buttonGroup ) {
884 buttonGroup = {
885 type: 'buttongroup',
886 items: []
887 };
888
889 toolbarItems.push( buttonGroup );
890 }
891
892 if ( editor.buttons[ item ] ) {
893 itemName = item;
894 item = editor.buttons[ itemName ];
895
896 if ( typeof item === 'function' ) {
897 item = item();
898 }
899
900 item.type = item.type || 'button';
901
902 if ( settings.toolbar_items_size ) {
903 item.size = settings.toolbar_items_size;
904 }
905
906 tooltip = item.tooltip || item.title;
907
908 if ( tooltip ) {
909 item.tooltip = getTooltip( tooltip );
910 }
911
912 item = Factory.create( item );
913
914 buttonGroup.items.push( item );
915
916 if ( editor.initialized ) {
917 bindSelectorChanged();
918 } else {
919 editor.on( 'init', bindSelectorChanged );
920 }
921 }
922 }
923 }
924 } );
925
926 toolbar = Factory.create( {
927 type: 'panel',
928 layout: 'stack',
929 classes: 'toolbar-grp inline-toolbar-grp',
930 ariaRoot: true,
931 ariaRemember: true,
932 items: [ {
933 type: 'toolbar',
934 layout: 'flow',
935 items: toolbarItems
936 } ]
937 } );
938
939 toolbar.bottom = bottom;
940
941 function reposition() {
942 if ( ! currentSelection ) {
943 return this;
944 }
945
946 var scrollX = window.pageXOffset || document.documentElement.scrollLeft,
947 scrollY = window.pageYOffset || document.documentElement.scrollTop,
948 windowWidth = window.innerWidth,
949 windowHeight = window.innerHeight,
950 iframeRect = mceIframe ? mceIframe.getBoundingClientRect() : {
951 top: 0,
952 right: windowWidth,
953 bottom: windowHeight,
954 left: 0,
955 width: windowWidth,
956 height: windowHeight
957 },
958 toolbar = this.getEl(),
959 toolbarWidth = toolbar.offsetWidth,
960 toolbarHeight = toolbar.clientHeight,
961 selection = currentSelection.getBoundingClientRect(),
962 selectionMiddle = ( selection.left + selection.right ) / 2,
963 buffer = 5,
964 spaceNeeded = toolbarHeight + buffer,
965 wpAdminbarBottom = wpAdminbar ? wpAdminbar.getBoundingClientRect().bottom : 0,
966 mceToolbarBottom = mceToolbar ? mceToolbar.getBoundingClientRect().bottom : 0,
967 mceStatusbarTop = mceStatusbar ? windowHeight - mceStatusbar.getBoundingClientRect().top : 0,
968 wpStatusbarTop = wpStatusbar ? windowHeight - wpStatusbar.getBoundingClientRect().top : 0,
969 blockedTop = Math.max( 0, wpAdminbarBottom, mceToolbarBottom, iframeRect.top ),
970 blockedBottom = Math.max( 0, mceStatusbarTop, wpStatusbarTop, windowHeight - iframeRect.bottom ),
971 spaceTop = selection.top + iframeRect.top - blockedTop,
972 spaceBottom = windowHeight - iframeRect.top - selection.bottom - blockedBottom,
973 editorHeight = windowHeight - blockedTop - blockedBottom,
974 className = '',
975 iosOffsetTop = 0,
976 iosOffsetBottom = 0,
977 top, left;
978
979 if ( spaceTop >= editorHeight || spaceBottom >= editorHeight ) {
980 this.scrolling = true;
981 this.hide();
982 this.scrolling = false;
983 return this;
984 }
985
986 // Add offset in iOS to move the menu over the image, out of the way of the default iOS menu.
987 if ( tinymce.Env.iOS && currentSelection.nodeName === 'IMG' ) {
988 iosOffsetTop = 54;
989 iosOffsetBottom = 46;
990 }
991
992 if ( this.bottom ) {
993 if ( spaceBottom >= spaceNeeded ) {
994 className = ' mce-arrow-up';
995 top = selection.bottom + iframeRect.top + scrollY - iosOffsetBottom;
996 } else if ( spaceTop >= spaceNeeded ) {
997 className = ' mce-arrow-down';
998 top = selection.top + iframeRect.top + scrollY - toolbarHeight + iosOffsetTop;
999 }
1000 } else {
1001 if ( spaceTop >= spaceNeeded ) {
1002 className = ' mce-arrow-down';
1003 top = selection.top + iframeRect.top + scrollY - toolbarHeight + iosOffsetTop;
1004 } else if ( spaceBottom >= spaceNeeded && editorHeight / 2 > selection.bottom + iframeRect.top - blockedTop ) {
1005 className = ' mce-arrow-up';
1006 top = selection.bottom + iframeRect.top + scrollY - iosOffsetBottom;
1007 }
1008 }
1009
1010 if ( typeof top === 'undefined' ) {
1011 top = scrollY + blockedTop + buffer + iosOffsetBottom;
1012 }
1013
1014 left = selectionMiddle - toolbarWidth / 2 + iframeRect.left + scrollX;
1015
1016 if ( selection.left < 0 || selection.right > iframeRect.width ) {
1017 left = iframeRect.left + scrollX + ( iframeRect.width - toolbarWidth ) / 2;
1018 } else if ( toolbarWidth >= windowWidth ) {
1019 className += ' mce-arrow-full';
1020 left = 0;
1021 } else if ( ( left < 0 && selection.left + toolbarWidth > windowWidth ) || ( left + toolbarWidth > windowWidth && selection.right - toolbarWidth < 0 ) ) {
1022 left = ( windowWidth - toolbarWidth ) / 2;
1023 } else if ( left < iframeRect.left + scrollX ) {
1024 className += ' mce-arrow-left';
1025 left = selection.left + iframeRect.left + scrollX;
1026 } else if ( left + toolbarWidth > iframeRect.width + iframeRect.left + scrollX ) {
1027 className += ' mce-arrow-right';
1028 left = selection.right - toolbarWidth + iframeRect.left + scrollX;
1029 }
1030
1031 // No up/down arrows on the menu over images in iOS.
1032 if ( tinymce.Env.iOS && currentSelection.nodeName === 'IMG' ) {
1033 className = className.replace( / ?mce-arrow-(up|down)/g, '' );
1034 }
1035
1036 toolbar.className = toolbar.className.replace( / ?mce-arrow-[\w]+/g, '' ) + className;
1037
1038 DOM.setStyles( toolbar, {
1039 'left': left,
1040 'top': top
1041 } );
1042
1043 return this;
1044 }
1045
1046 toolbar.on( 'show', function() {
1047 this.reposition();
1048 } );
1049
1050 toolbar.on( 'keydown', function( event ) {
1051 if ( event.keyCode === 27 ) {
1052 this.hide();
1053 editor.focus();
1054 }
1055 } );
1056
1057 editor.on( 'remove', function() {
1058 toolbar.remove();
1059 } );
1060
1061 toolbar.reposition = reposition;
1062 toolbar.hide().renderTo( document.body );
1063
1064 return toolbar;
1065 }
1066
1067 editor.shortcuts.add( 'alt+119', '', function() {
1068 var node;
1069
1070 if ( activeToolbar ) {
1071 node = activeToolbar.find( 'toolbar' )[0];
1072 node && node.focus( true );
1073 }
1074 } );
1075
1076 editor.on( 'nodechange', function( event ) {
1077 var collapsed = editor.selection.isCollapsed();
1078
1079 var args = {
1080 element: event.element,
1081 parents: event.parents,
1082 collapsed: collapsed
1083 };
1084
1085 editor.fire( 'wptoolbar', args );
1086
1087 currentSelection = args.selection || args.element;
1088
1089 if ( activeToolbar && activeToolbar !== args.toolbar ) {
1090 activeToolbar.hide();
1091 }
1092
1093 if ( args.toolbar ) {
1094 activeToolbar = args.toolbar;
1095
1096 if ( activeToolbar.visible() ) {
1097 activeToolbar.reposition();
1098 } else {
1099 activeToolbar.show();
1100 }
1101 } else {
1102 activeToolbar = false;
1103 }
1104 } );
1105
1106 editor.on( 'focus', function() {
1107 if ( activeToolbar ) {
1108 activeToolbar.show();
1109 }
1110 } );
1111
1112 function hide( event ) {
1113 var win;
1114 var size;
1115
1116 if ( activeToolbar ) {
1117 if ( activeToolbar.tempHide || event.type === 'hide' || event.type === 'blur' ) {
1118 activeToolbar.hide();
1119 activeToolbar = false;
1120 } else if ( (
1121 event.type === 'resizewindow' ||
1122 event.type === 'scrollwindow' ||
1123 event.type === 'resize' ||
1124 event.type === 'scroll'
1125 ) && ! activeToolbar.blockHide ) {
1126 /*
1127 * Showing a tooltip may trigger a `resize` event in Chromium browsers.
1128 * That results in a flicketing inline menu; tooltips are shown on hovering over a button,
1129 * which then hides the toolbar on `resize`, then it repeats as soon as the toolbar is shown again.
1130 */
1131 if ( event.type === 'resize' || event.type === 'resizewindow' ) {
1132 win = editor.getWin();
1133 size = win.innerHeight + win.innerWidth;
1134
1135 // Reset old cached size.
1136 if ( cachedWinSize && ( new Date() ).getTime() - cachedWinSize.timestamp > 2000 ) {
1137 cachedWinSize = null;
1138 }
1139
1140 if ( cachedWinSize ) {
1141 if ( size && Math.abs( size - cachedWinSize.size ) < 2 ) {
1142 // `resize` fired but the window hasn't been resized. Bail.
1143 return;
1144 }
1145 } else {
1146 // First of a new series of `resize` events. Store the cached size and bail.
1147 cachedWinSize = {
1148 timestamp: ( new Date() ).getTime(),
1149 size: size,
1150 };
1151
1152 return;
1153 }
1154 }
1155
1156 clearTimeout( timeout );
1157
1158 timeout = setTimeout( function() {
1159 if ( activeToolbar && typeof activeToolbar.show === 'function' ) {
1160 activeToolbar.scrolling = false;
1161 activeToolbar.show();
1162 }
1163 }, 250 );
1164
1165 activeToolbar.scrolling = true;
1166 activeToolbar.hide();
1167 }
1168 }
1169 }
1170
1171 if ( editor.inline ) {
1172 editor.on( 'resizewindow', hide );
1173
1174 // Enable `capture` for the event.
1175 // This will hide/reposition the toolbar on any scrolling in the document.
1176 document.addEventListener( 'scroll', hide, true );
1177 } else {
1178 // Bind to the editor iframe and to the parent window.
1179 editor.dom.bind( editor.getWin(), 'resize scroll', hide );
1180 editor.on( 'resizewindow scrollwindow', hide );
1181 }
1182
1183 editor.on( 'remove', function() {
1184 document.removeEventListener( 'scroll', hide, true );
1185 editor.off( 'resizewindow scrollwindow', hide );
1186 editor.dom.unbind( editor.getWin(), 'resize scroll', hide );
1187 } );
1188
1189 editor.on( 'blur hide', hide );
1190
1191 editor.wp = editor.wp || {};
1192 editor.wp._createToolbar = create;
1193 }, true );
1194
1195 function noop() {}
1196
1197 // Expose some functions (back-compat).
1198 return {
1199 _showButtons: noop,
1200 _hideButtons: noop,
1201 _setEmbed: noop,
1202 _getEmbed: noop
1203 };
1204});
1205
1206}( window.tinymce ));
1207