1 /* global tinymce, getUserSetting, setUserSetting */
3 // Set the minimum value for the modals z-index higher than #wpadminbar (100000)
4 tinymce.ui.FloatPanel.zIndex = 100100;
6 tinymce.PluginManager.add( 'wordpress', function( editor ) {
9 __ = editor.editorManager.i18n.translate,
13 if ( typeof window.jQuery !== 'undefined' ) {
14 window.jQuery( document ).triggerHandler( 'tinymce-editor-setup', [ editor ] );
17 function toggleToolbars( state ) {
18 var iframe, initial, toolbars,
21 initial = ( state === 'hide' );
23 if ( editor.theme.panel ) {
24 toolbars = editor.theme.panel.find('.toolbar:not(.menubar)');
27 if ( ! toolbars || toolbars.length < 2 || ( state === 'hide' && ! toolbars[1].visible() ) ) {
31 if ( ! state && toolbars[1].visible() ) {
35 each( toolbars, function( toolbar, i ) {
37 if ( state === 'hide' ) {
47 if ( pixels && ! initial ) {
48 // Resize iframe, not needed in iOS
49 if ( ! tinymce.Env.iOS ) {
50 iframe = editor.getContentAreaContainer().firstChild;
51 DOM.setStyle( iframe, 'height', iframe.clientHeight + pixels );
54 if ( state === 'hide' ) {
55 setUserSetting('hidetb', '0');
56 wpAdvButton && wpAdvButton.active( false );
58 setUserSetting('hidetb', '1');
59 wpAdvButton && wpAdvButton.active( true );
63 editor.fire( 'wp-toolbar-toggle' );
66 // Add the kitchen sink button :)
67 editor.addButton( 'wp_adv', {
68 tooltip: 'Toolbar Toggle',
70 onPostRender: function() {
72 wpAdvButton.active( getUserSetting( 'hidetb' ) === '1' ? true : false );
76 // Hide the toolbars after loading
77 editor.on( 'PostRender', function() {
78 if ( editor.getParam( 'wordpress_adv_hidden', true ) && getUserSetting( 'hidetb', '0' ) === '0' ) {
79 toggleToolbars( 'hide' );
83 editor.addCommand( 'WP_Adv', function() {
87 editor.on( 'focus', function() {
88 window.wpActiveEditor = editor.id;
91 // Replace Read More/Next Page tags with images
92 editor.on( 'BeforeSetContent', function( e ) {
96 if ( e.content.indexOf( '<!--more' ) !== -1 ) {
97 title = __( 'Read more...' );
99 e.content = e.content.replace( /<!--more(.*?)-->/g, function( match, moretext ) {
100 return '<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="more" data-wp-more-text="' + moretext + '" ' +
101 'class="wp-more-tag mce-wp-more" title="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />';
105 if ( e.content.indexOf( '<!--nextpage-->' ) !== -1 ) {
106 title = __( 'Page break' );
108 e.content = e.content.replace( /<!--nextpage-->/g,
109 '<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="nextpage" class="wp-more-tag mce-wp-nextpage" ' +
110 'title="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />' );
115 // Replace images with tags
116 editor.on( 'PostProcess', function( e ) {
118 e.content = e.content.replace(/<img[^>]+>/g, function( image ) {
119 var match, moretext = '';
121 if ( image.indexOf( 'data-wp-more="more"' ) !== -1 ) {
122 if ( match = image.match( /data-wp-more-text="([^"]+)"/ ) ) {
126 image = '<!--more' + moretext + '-->';
127 } else if ( image.indexOf( 'data-wp-more="nextpage"' ) !== -1 ) {
128 image = '<!--nextpage-->';
136 // Display the tag name instead of img in element path
137 editor.on( 'ResolveName', function( event ) {
140 if ( event.target.nodeName === 'IMG' && ( attr = editor.dom.getAttrib( event.target, 'data-wp-more' ) ) ) {
146 editor.addCommand( 'WP_More', function( tag ) {
147 var parent, html, title,
148 classname = 'wp-more-tag',
150 node = editor.selection.getNode();
153 classname += ' mce-wp-' + tag;
154 title = tag === 'more' ? 'Read more...' : 'Next page';
156 html = '<img src="' + tinymce.Env.transparentSrc + '" title="' + title + '" class="' + classname + '" ' +
157 'data-wp-more="' + tag + '" data-mce-resize="false" data-mce-placeholder="1" />';
160 if ( node.nodeName === 'BODY' || ( node.nodeName === 'P' && node.parentNode.nodeName === 'BODY' ) ) {
161 editor.insertContent( html );
165 // Get the top level parent node
166 parent = dom.getParent( node, function( found ) {
167 if ( found.parentNode && found.parentNode.nodeName === 'BODY' ) {
172 }, editor.getBody() );
175 if ( parent.nodeName === 'P' ) {
176 parent.appendChild( dom.create( 'p', null, html ).firstChild );
178 dom.insertAfter( dom.create( 'p', null, html ), parent );
181 editor.nodeChanged();
185 editor.addCommand( 'WP_Code', function() {
186 editor.formatter.toggle('code');
189 editor.addCommand( 'WP_Page', function() {
190 editor.execCommand( 'WP_More', 'nextpage' );
193 editor.addCommand( 'WP_Help', function() {
194 editor.windowManager.open({
195 url: tinymce.baseURL + '/wp-mce-help.php',
196 title: 'Keyboard Shortcuts',
200 buttons: { text: 'Close', onclick: 'close' }
204 editor.addCommand( 'WP_Medialib', function() {
205 if ( typeof wp !== 'undefined' && wp.media && wp.media.editor ) {
206 wp.media.editor.open( editor.id );
211 editor.addButton( 'wp_more', {
212 tooltip: 'Insert Read More tag',
213 onclick: function() {
214 editor.execCommand( 'WP_More', 'more' );
218 editor.addButton( 'wp_page', {
219 tooltip: 'Page break',
220 onclick: function() {
221 editor.execCommand( 'WP_More', 'nextpage' );
225 editor.addButton( 'wp_help', {
226 tooltip: 'Keyboard Shortcuts',
230 editor.addButton( 'wp_code', {
233 stateSelector: 'code'
238 if ( typeof wp !== 'undefined' && wp.media && wp.media.editor ) {
239 editor.addMenuItem( 'add_media', {
241 icon: 'wp-media-library',
247 // Insert "Read More..."
248 editor.addMenuItem( 'wp_more', {
249 text: 'Insert Read More tag',
252 onclick: function() {
253 editor.execCommand( 'WP_More', 'more' );
257 // Insert "Next Page"
258 editor.addMenuItem( 'wp_page', {
262 onclick: function() {
263 editor.execCommand( 'WP_More', 'nextpage' );
267 editor.on( 'BeforeExecCommand', function(e) {
268 if ( tinymce.Env.webkit && ( e.command === 'InsertUnorderedList' || e.command === 'InsertOrderedList' ) ) {
270 style = editor.dom.create( 'style', {'type': 'text/css'},
271 '#tinymce,#tinymce span,#tinymce li,#tinymce li>span,#tinymce p,#tinymce p>span{font:medium sans-serif;color:#000;line-height:normal;}');
274 editor.getDoc().head.appendChild( style );
278 editor.on( 'ExecCommand', function( e ) {
279 if ( tinymce.Env.webkit && style &&
280 ( 'InsertUnorderedList' === e.command || 'InsertOrderedList' === e.command ) ) {
282 editor.dom.remove( style );
286 editor.on( 'init', function() {
287 var env = tinymce.Env,
288 bodyClass = ['mceContentBody'], // back-compat for themes that use this in editor-style.css...
289 doc = editor.getDoc(),
292 if ( tinymce.Env.iOS ) {
293 dom.addClass( doc.documentElement, 'ios' );
296 if ( editor.getParam( 'directionality' ) === 'rtl' ) {
297 bodyClass.push('rtl');
298 dom.setAttrib( doc.documentElement, 'dir', 'rtl' );
302 if ( parseInt( env.ie, 10 ) === 9 ) {
303 bodyClass.push('ie9');
304 } else if ( parseInt( env.ie, 10 ) === 8 ) {
305 bodyClass.push('ie8');
306 } else if ( env.ie < 8 ) {
307 bodyClass.push('ie7');
309 } else if ( env.webkit ) {
310 bodyClass.push('webkit');
313 bodyClass.push('wp-editor');
315 each( bodyClass, function( cls ) {
317 dom.addClass( doc.body, cls );
321 // Remove invalid parent paragraphs when inserting HTML
322 // TODO: still needed?
323 editor.on( 'BeforeSetContent', function( e ) {
325 e.content = e.content.replace(/<p>\s*<(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre|address)( [^>]*)?>/gi, '<$1$2>');
326 e.content = e.content.replace(/<\/(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre|address)>\s*<\/p>/gi, '</$1>');
330 if ( typeof window.jQuery !== 'undefined' ) {
331 window.jQuery( document ).triggerHandler( 'tinymce-editor-init', [editor] );
334 if ( window.tinyMCEPreInit && window.tinyMCEPreInit.dragDropUpload ) {
335 dom.bind( doc, 'dragstart dragend dragover drop', function( event ) {
336 if ( typeof window.jQuery !== 'undefined' ) {
337 // Trigger the jQuery handlers.
338 window.jQuery( document ).trigger( new window.jQuery.Event( event ) );
343 if ( editor.getParam( 'wp_paste_filters', true ) ) {
344 if ( ! tinymce.Env.webkit ) {
345 // In WebKit handled by removeWebKitStyles()
346 editor.on( 'PastePreProcess', function( event ) {
347 // Remove all inline styles
348 event.content = event.content.replace( /(<[^>]+) style="[^"]*"([^>]*>)/gi, '$1$2' );
350 // Put back the internal styles
351 event.content = event.content.replace(/(<[^>]+) data-mce-style=([^>]+>)/gi, '$1 style=$2' );
355 editor.on( 'PastePostProcess', function( event ) {
356 // Remove empty paragraphs
357 each( dom.select( 'p', event.node ), function( node ) {
358 if ( dom.isEmpty( node ) ) {
367 if ( typeof window.jQuery !== 'undefined' ) {
368 editor.on( 'keyup', function( e ) {
369 var key = e.keyCode || e.charCode;
371 if ( key === last ) {
375 if ( 13 === key || 8 === last || 46 === last ) {
376 window.jQuery( document ).triggerHandler( 'wpcountwords', [ editor.getContent({ format : 'raw' }) ] );
383 editor.on( 'SaveContent', function( e ) {
384 // If editor is hidden, we just want the textarea's value to be saved
385 if ( ! editor.inline && editor.isHidden() ) {
386 e.content = e.element.value;
390 // Keep empty paragraphs :(
391 e.content = e.content.replace( /<p>(?:<br ?\/?>|\u00a0|\uFEFF| )*<\/p>/g, '<p> </p>' );
393 if ( editor.getParam( 'wpautop', true ) && typeof window.switchEditors !== 'undefined' ) {
394 e.content = window.switchEditors.pre_wpautop( e.content );
398 // Remove spaces from empty paragraphs.
399 editor.on( 'BeforeSetContent', function( event ) {
400 var paragraph = tinymce.Env.webkit ? '<p><br /></p>' : '<p></p>';
402 if ( event.content ) {
403 event.content = event.content.replace( /<p>(?: |\u00a0|\uFEFF|\s)+<\/p>/gi, paragraph );
407 editor.on( 'preInit', function() {
408 // Don't replace <i> with <em> and <b> with <strong> and don't remove them when empty
409 editor.schema.addValidElements( '@[id|accesskey|class|dir|lang|style|tabindex|title|contenteditable|draggable|dropzone|hidden|spellcheck|translate],i,b' );
411 if ( tinymce.Env.iOS ) {
412 editor.settings.height = 300;
421 u: 'InsertUnorderedList',
422 o: 'InsertOrderedList',
431 }, function( command, key ) {
432 editor.shortcuts.add( 'access+' + key, '', command );
435 editor.addShortcut( 'ctrl+s', '', function() {
436 if ( typeof wp !== 'undefined' && wp.autosave ) {
437 wp.autosave.server.triggerSave();
443 * Experimental: create a floating toolbar.
444 * This functionality will change in the next releases. Not recommended for use by plugins.
447 var Factory = tinymce.ui.Factory,
448 settings = editor.settings,
452 function create( buttons ) {
457 each( buttons, function( item ) {
460 function bindSelectorChanged() {
461 var selection = editor.selection;
463 if ( itemName === 'bullist' ) {
464 selection.selectorChanged( 'ul > li', function( state, args ) {
465 var i = args.parents.length,
469 nodeName = args.parents[ i ].nodeName;
471 if ( nodeName === 'OL' || nodeName == 'UL' ) {
476 item.active( state && nodeName === 'UL' );
480 if ( itemName === 'numlist' ) {
481 selection.selectorChanged( 'ol > li', function( state, args ) {
482 var i = args.parents.length,
486 nodeName = args.parents[ i ].nodeName;
488 if ( nodeName === 'OL' || nodeName === 'UL' ) {
493 item.active( state && nodeName === 'OL' );
497 if ( item.settings.stateSelector ) {
498 selection.selectorChanged( item.settings.stateSelector, function( state ) {
499 item.active( state );
503 if ( item.settings.disabledStateSelector ) {
504 selection.selectorChanged( item.settings.disabledStateSelector, function( state ) {
505 item.disabled( state );
510 if ( item === '|' ) {
513 if ( Factory.has( item ) ) {
518 if ( settings.toolbar_items_size ) {
519 item.size = settings.toolbar_items_size;
522 toolbarItems.push( item );
526 if ( ! buttonGroup ) {
532 toolbarItems.push( buttonGroup );
535 if ( editor.buttons[ item ] ) {
537 item = editor.buttons[ itemName ];
539 if ( typeof item === 'function' ) {
543 item.type = item.type || 'button';
545 if ( settings.toolbar_items_size ) {
546 item.size = settings.toolbar_items_size;
549 item = Factory.create( item );
551 buttonGroup.items.push( item );
553 if ( editor.initialized ) {
554 bindSelectorChanged();
556 editor.on( 'init', bindSelectorChanged );
563 toolbar = Factory.create( {
566 classes: 'toolbar-grp inline-toolbar-grp',
580 function reposition() {
581 var top, left, minTop, className,
582 windowPos, adminbar, mceToolbar, boundary,
583 boundaryMiddle, boundaryVerticalMiddle, spaceTop,
584 spaceBottom, windowWidth, toolbarWidth, toolbarHalf,
585 iframe, iframePos, iframeWidth, iframeHeigth,
586 toolbarNodeHeight, verticalSpaceNeeded,
587 toolbarNode = this.getEl(),
592 if ( ! currentSelection ) {
596 windowPos = window.pageYOffset || document.documentElement.scrollTop;
597 adminbar = tinymce.$( '#wpadminbar' )[0];
598 mceToolbar = tinymce.$( '.mce-toolbar-grp', editor.getContainer() )[0];
599 boundary = currentSelection.getBoundingClientRect();
600 boundaryMiddle = ( boundary.left + boundary.right ) / 2;
601 boundaryVerticalMiddle = ( boundary.top + boundary.bottom ) / 2;
602 spaceTop = boundary.top;
603 spaceBottom = iframeHeigth - boundary.bottom;
604 windowWidth = window.innerWidth;
605 toolbarWidth = toolbarNode.offsetWidth;
606 toolbarHalf = toolbarWidth / 2;
607 iframe = document.getElementById( editor.id + '_ifr' );
608 iframePos = DOM.getPos( iframe );
609 iframeWidth = iframe.offsetWidth;
610 iframeHeigth = iframe.offsetHeight;
611 toolbarNodeHeight = toolbarNode.offsetHeight;
612 verticalSpaceNeeded = toolbarNodeHeight + margin + buffer;
614 if ( spaceTop >= verticalSpaceNeeded ) {
615 className = ' mce-arrow-down';
616 top = boundary.top + iframePos.y - toolbarNodeHeight - margin;
617 } else if ( spaceBottom >= verticalSpaceNeeded ) {
618 className = ' mce-arrow-up';
619 top = boundary.bottom + iframePos.y;
623 if ( boundaryVerticalMiddle >= verticalSpaceNeeded ) {
624 className = ' mce-arrow-down';
626 className = ' mce-arrow-up';
630 // Make sure the image toolbar is below the main toolbar.
632 minTop = DOM.getPos( mceToolbar ).y + mceToolbar.clientHeight;
634 minTop = iframePos.y;
637 // Make sure the image toolbar is below the adminbar (if visible) or below the top of the window.
639 if ( adminbar && adminbar.getBoundingClientRect().top === 0 ) {
640 adminbarHeight = adminbar.clientHeight;
643 if ( windowPos + adminbarHeight > minTop ) {
644 minTop = windowPos + adminbarHeight;
648 if ( top && minTop && ( minTop + buffer > top ) ) {
649 top = minTop + buffer;
653 left = boundaryMiddle - toolbarHalf;
656 if ( boundary.left < 0 || boundary.right > iframeWidth ) {
657 left = iframePos.x + ( iframeWidth - toolbarWidth ) / 2;
658 } else if ( toolbarWidth >= windowWidth ) {
659 className += ' mce-arrow-full';
661 } else if ( ( left < 0 && boundary.left + toolbarWidth > windowWidth ) ||
662 ( left + toolbarWidth > windowWidth && boundary.right - toolbarWidth < 0 ) ) {
664 left = ( windowWidth - toolbarWidth ) / 2;
665 } else if ( left < iframePos.x ) {
666 className += ' mce-arrow-left';
667 left = boundary.left + iframePos.x;
668 } else if ( left + toolbarWidth > iframeWidth + iframePos.x ) {
669 className += ' mce-arrow-right';
670 left = boundary.right - toolbarWidth + iframePos.x;
673 toolbarNode.className = toolbarNode.className.replace( / ?mce-arrow-[\w]+/g, '' );
674 toolbarNode.className += className;
676 DOM.setStyles( toolbarNode, { 'left': left, 'top': top } );
681 toolbar.on( 'show', function() {
682 currentToolbar = this;
686 toolbar.on( 'hide', function() {
687 currentToolbar = false;
690 toolbar.on( 'keydown', function( event ) {
691 if ( event.keyCode === 27 ) {
697 toolbar.on( 'remove', function() {
698 DOM.unbind( window, 'resize scroll', hide );
699 editor.dom.unbind( editor.getWin(), 'resize scroll', hide );
700 editor.off( 'blur hide', hide );
703 editor.once( 'init', function() {
704 DOM.bind( window, 'resize scroll', hide );
705 editor.dom.bind( editor.getWin(), 'resize scroll', hide );
706 editor.on( 'blur hide', hide );
709 toolbar.reposition = reposition;
710 toolbar.hide().renderTo( document.body );
715 editor.shortcuts.add( 'alt+119', '', function() {
718 if ( currentToolbar ) {
719 node = currentToolbar.find( 'toolbar' )[0];
720 node && node.focus( true );
724 editor.on( 'nodechange', function( event ) {
725 var collapsed = editor.selection.isCollapsed();
728 element: event.element,
729 parents: event.parents,
733 editor.fire( 'wptoolbar', args );
735 currentSelection = args.selection || args.element;
737 currentToolbar && currentToolbar.hide();
738 args.toolbar && args.toolbar.show();
741 editor.wp = editor.wp || {};
742 editor.wp._createToolbar = create;
747 // Expose some functions (back-compat)