1 /* global deleteUserSetting, setUserSetting, switchEditors, tinymce, tinyMCEPreInit */
3 * Distraction-Free Writing
6 * Access the API globally using the window.wp.editor.fullscreen variable.
8 ( function( $, window ) {
9 var api, ps, s, toggleUI, uiTimer, PubSub,
11 transitionend = 'transitionend webkitTransitionEnd',
12 $body = $( document.body ),
13 $document = $( document );
18 * A lightweight publish/subscribe implementation.
25 this.subscribe = function( topic, callback ) {
26 if ( ! this.topics[ topic ] )
27 this.topics[ topic ] = [];
29 this.topics[ topic ].push( callback );
33 this.unsubscribe = function( topic, callback ) {
35 topics = this.topics[ topic ];
38 return callback || [];
40 // Clear matching callbacks
42 for ( i = 0, l = topics.length; i < l; i++ ) {
43 if ( callback == topics[i] )
44 topics.splice( i, 1 );
48 // Clear all callbacks
50 this.topics[ topic ] = [];
55 this.publish = function( topic, args ) {
57 topics = this.topics[ topic ];
64 for ( i = 0, l = topics.length; i < l; i++ ) {
65 broken = ( topics[i].apply( null, args ) === false || broken );
71 // Initialize the fullscreen/api object
74 // Create the PubSub (publish/subscribe) interface.
75 ps = api.pubsub = new PubSub();
77 s = api.settings = { // Settings
87 $body.removeClass('wp-dfw-show-ui');
93 * Toggle the CSS class to show/hide the toolbar, borders and statusbar.
95 toggleUI = api.toggleUI = function( show ) {
96 clearTimeout( uiTimer );
98 if ( ! $body.hasClass('wp-dfw-show-ui') || show === 'show' ) {
99 $body.addClass('wp-dfw-show-ui');
100 } else if ( show !== 'autohide' ) {
101 $body.removeClass('wp-dfw-show-ui');
104 if ( show === 'autohide' ) {
105 uiTimer = setTimeout( _hideUI, 2000 );
109 function resetCssPosition( add ) {
110 s.$dfwWrap.parents().each( function( i, parent ) {
111 var cssPosition, $parent = $(parent);
114 if ( parent.style.position ) {
115 $parent.data( 'wp-dfw-css-position', parent.style.position );
118 $parent.css( 'position', 'static' );
120 cssPosition = $parent.data( 'wp-dfw-css-position' );
121 cssPosition = cssPosition || '';
122 $parent.css( 'position', cssPosition );
125 if ( parent.nodeName === 'BODY' ) {
134 * Turns fullscreen on.
136 * @param string mode Optional. Switch to the given mode before opening.
138 api.on = function() {
139 var id, $dfwWrap, titleId;
145 if ( ! s.$fullscreenFader ) {
149 // Settings can be added or changed by defining "wp_fullscreen_settings" JS object.
150 if ( typeof window.wp_fullscreen_settings === 'object' )
151 $.extend( s, window.wp_fullscreen_settings );
153 id = s.id || window.wpActiveEditor;
156 if ( s.hasTinymce ) {
157 id = tinymce.activeEditor.id;
164 $dfwWrap = s.$dfwWrap = $( '#wp-' + id + '-wrap' );
166 if ( ! $dfwWrap.length ) {
170 s.$dfwTextarea = $( '#' + id );
171 s.$editorContainer = $dfwWrap.find( '.wp-editor-container' );
172 uiScrollTop = $document.scrollTop();
174 if ( s.hasTinymce ) {
175 s.editor = tinymce.get( id );
178 if ( s.editor && ! s.editor.isHidden() ) {
179 s.origHeight = $( '#' + id + '_ifr' ).height();
182 s.origHeight = s.$dfwTextarea.height();
186 // Try to find title field
187 if ( typeof window.adminpage !== 'undefined' &&
188 ( window.adminpage === 'post-php' || window.adminpage === 'post-new-php' ) ) {
192 titleId = id + '-title';
195 s.$dfwTitle = $( '#' + titleId );
197 if ( ! s.$dfwTitle.length ) {
201 api.ui.fade( 'show', 'showing', 'shown' );
207 * Turns fullscreen off.
209 api.off = function() {
213 api.ui.fade( 'hide', 'hiding', 'hidden' );
219 * @return string - The current mode.
221 * @param string to - The fullscreen mode to switch to.
223 * @eventparam string to - The new mode.
224 * @eventparam string from - The old mode.
226 api.switchmode = function( to ) {
229 if ( ! to || ! s.visible || ! s.hasTinymce || typeof switchEditors === 'undefined' ) {
233 // Don't switch if the mode is the same.
237 if ( to === 'tinymce' && ! s.editor ) {
238 s.editor = tinymce.get( s.id );
240 if ( ! s.editor && typeof tinyMCEPreInit !== 'undefined' &&
241 tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[ s.id ] ) {
243 // If the TinyMCE instance hasn't been created, set the "wp_fulscreen" flag on creating it
244 tinyMCEPreInit.mceInit[ s.id ].wp_fullscreen = true;
249 switchEditors.go( s.id, to );
250 api.refreshButtons( true );
252 if ( to === 'html' ) {
253 setTimeout( api.resizeTextarea, 200 );
263 api.save = function() {
264 var $hidden = $('#hiddenaction'),
265 oldVal = $hidden.val(),
266 $spinner = $('#wp-fullscreen-save .spinner'),
267 $saveMessage = $('#wp-fullscreen-save .wp-fullscreen-saved-message'),
268 $errorMessage = $('#wp-fullscreen-save .wp-fullscreen-error-message');
270 $spinner.addClass( 'is-active' );
271 $errorMessage.hide();
273 $hidden.val('wp-fullscreen-save-post');
275 if ( s.editor && ! s.editor.isHidden() ) {
282 data: $('form#post').serialize(),
284 }).done( function( response ) {
285 $spinner.removeClass( 'is-active' );
287 if ( response && response.success ) {
290 setTimeout( function() {
291 $saveMessage.fadeOut(300);
294 if ( response.data && response.data.last_edited ) {
295 $('#wp-fullscreen-save input').attr( 'title', response.data.last_edited );
298 $errorMessage.show();
300 }).fail( function() {
301 $spinner.removeClass( 'is-active' );
302 $errorMessage.show();
305 $hidden.val( oldVal );
308 api.dfwWidth = function( pixels, total ) {
311 if ( pixels && pixels.toString().indexOf('%') !== -1 ) {
312 s.$editorContainer.css( 'width', pixels );
313 s.$statusbar.css( 'width', pixels );
316 s.$dfwTitle.css( 'width', pixels );
322 // Reset to theme width
323 width = $('#wp-fullscreen-body').data('theme-width') || 800;
324 s.$editorContainer.width( width );
325 s.$statusbar.width( width );
328 s.$dfwTitle.width( width - 16 );
331 deleteUserSetting('dfw_width');
338 width = s.$editorContainer.width();
342 if ( width < 200 || width > 1200 ) {
347 s.$editorContainer.width( width );
348 s.$statusbar.width( width );
351 s.$dfwTitle.width( width - 16 );
354 setUserSetting( 'dfw_width', width );
357 // This event occurs before the overlay blocks the UI.
358 ps.subscribe( 'show', function() {
359 var title = $('#last-edit').text();
362 $('#wp-fullscreen-save input').attr( 'title', title );
366 // This event occurs while the overlay blocks the UI.
367 ps.subscribe( 'showing', function() {
368 $body.addClass( 'wp-fullscreen-active' );
369 s.$dfwWrap.addClass( 'wp-fullscreen-wrap' );
372 s.$dfwTitle.after( '<span id="wp-fullscreen-title-placeholder">' );
373 s.$dfwWrap.prepend( s.$dfwTitle.addClass('wp-fullscreen-title') );
376 api.refreshButtons();
377 resetCssPosition( true );
378 $('#wpadminbar').hide();
380 // Show the UI for 2 sec. when opening
381 toggleUI('autohide');
386 s.editor.execCommand( 'wpFullScreenOn' );
389 if ( 'ontouchstart' in window ) {
390 api.dfwWidth( '90%' );
392 api.dfwWidth( $( '#wp-fullscreen-body' ).data('dfw-width') || 800, true );
395 // scroll to top so the user is not disoriented
399 // This event occurs after the overlay unblocks the UI
400 ps.subscribe( 'shown', function() {
403 if ( s.editor && ! s.editor.isHidden() ) {
404 s.editor.execCommand( 'wpAutoResize' );
406 api.resizeTextarea( 'force' );
410 ps.subscribe( 'hide', function() { // This event occurs before the overlay blocks DFW.
411 $document.unbind( '.fullscreen' );
412 s.$dfwTextarea.unbind('.wp-dfw-resize');
415 ps.subscribe( 'hiding', function() { // This event occurs while the overlay blocks the DFW UI.
416 $body.removeClass( 'wp-fullscreen-active' );
419 $( '#wp-fullscreen-title-placeholder' ).before( s.$dfwTitle.removeClass('wp-fullscreen-title').css( 'width', '' ) ).remove();
422 s.$dfwWrap.removeClass( 'wp-fullscreen-wrap' );
423 s.$editorContainer.css( 'width', '' );
424 s.$dfwTextarea.add( '#' + s.id + '_ifr' ).height( s.origHeight );
427 s.editor.execCommand( 'wpFullScreenOff' );
430 resetCssPosition( false );
432 window.scrollTo( 0, uiScrollTop );
433 $('#wpadminbar').show();
436 // This event occurs after DFW is removed.
437 ps.subscribe( 'hidden', function() {
441 api.refreshButtons = function( fade ) {
442 if ( s.mode === 'html' ) {
443 $('#wp-fullscreen-mode-bar').removeClass('wp-tmce-mode').addClass('wp-html-mode')
444 .find('a').removeClass( 'active' ).filter('.wp-fullscreen-mode-html').addClass( 'active' );
447 $('#wp-fullscreen-button-bar').fadeOut( 150, function(){
448 $(this).addClass('wp-html-mode').fadeIn( 150 );
451 $('#wp-fullscreen-button-bar').addClass('wp-html-mode');
453 } else if ( s.mode === 'tinymce' ) {
454 $('#wp-fullscreen-mode-bar').removeClass('wp-html-mode').addClass('wp-tmce-mode')
455 .find('a').removeClass( 'active' ).filter('.wp-fullscreen-mode-tinymce').addClass( 'active' );
458 $('#wp-fullscreen-button-bar').fadeOut( 150, function(){
459 $(this).removeClass('wp-html-mode').fadeIn( 150 );
462 $('#wp-fullscreen-button-bar').removeClass('wp-html-mode');
470 * Used for transitioning between states.
476 s.toolbar = toolbar = $('#fullscreen-topbar');
477 s.$fullscreenFader = $('#fullscreen-fader');
478 s.$statusbar = $('#wp-fullscreen-status');
479 s.hasTinymce = typeof tinymce !== 'undefined';
481 if ( ! s.hasTinymce )
482 $('#wp-fullscreen-mode-bar').hide();
484 $document.keyup( function(e) {
485 var c = e.keyCode || e.charCode, modKey;
491 if ( navigator.platform && navigator.platform.indexOf('Mac') !== -1 ) {
492 modKey = e.ctrlKey; // Ctrl key for Mac
494 modKey = e.altKey; // Alt key for Win & Linux
497 if ( modKey && ( 61 === c || 107 === c || 187 === c ) ) { // +
502 if ( modKey && ( 45 === c || 109 === c || 189 === c ) ) { // -
507 if ( modKey && 48 === c ) { // 0
513 $( window ).on( 'keydown.wp-fullscreen', function( event ) {
514 // Turn fullscreen off when Esc is pressed.
515 if ( 27 === event.keyCode && s.visible ) {
517 event.stopImmediatePropagation();
521 if ( 'ontouchstart' in window ) {
522 $body.addClass('wp-dfw-touch');
525 toolbar.on( 'mouseenter', function() {
527 }).on( 'mouseleave', function() {
528 toggleUI('autohide');
532 $('#wp-fullscreen-buttons').on( 'click.wp-fullscreen', 'button', function( event ) {
533 var command = event.currentTarget.id ? event.currentTarget.id.substr(6) : null;
535 if ( s.editor && 'tinymce' === s.mode ) {
538 s.editor.execCommand('Bold');
541 s.editor.execCommand('Italic');
544 s.editor.execCommand('InsertUnorderedList');
547 s.editor.execCommand('InsertOrderedList');
550 s.editor.execCommand('WP_Link');
553 s.editor.execCommand('unlink');
556 s.editor.execCommand('WP_Help');
559 s.editor.execCommand('mceBlockQuote');
562 } else if ( command === 'link' && window.wpLink ) {
563 window.wpLink.open();
566 if ( command === 'wp-media-library' && typeof wp !== 'undefined' && wp.media && wp.media.editor ) {
567 wp.media.editor.open( s.id );
572 fade: function( before, during, after ) {
573 if ( ! s.$fullscreenFader ) {
577 // If any callback bound to before returns false, bail.
578 if ( before && ! ps.publish( before ) ) {
582 api.fade.In( s.$fullscreenFader, 200, function() {
584 ps.publish( during );
587 api.fade.Out( s.$fullscreenFader, 200, function() {
597 // Sensitivity to allow browsers to render the blank element before animating.
600 In: function( element, speed, callback, stop ) {
602 callback = callback || $.noop;
603 speed = speed || 400;
604 stop = stop || false;
606 if ( api.fade.transitions ) {
607 if ( element.is(':visible') ) {
608 element.addClass( 'fade-trigger' );
613 element.first().one( transitionend, function() {
617 setTimeout( function() { element.addClass( 'fade-trigger' ); }, this.sensitivity );
623 element.css( 'opacity', 1 );
624 element.first().fadeIn( speed, callback );
626 if ( element.length > 1 ) {
627 element.not(':first').fadeIn( speed );
634 Out: function( element, speed, callback, stop ) {
636 callback = callback || $.noop;
637 speed = speed || 400;
638 stop = stop || false;
640 if ( ! element.is(':visible') ) {
644 if ( api.fade.transitions ) {
645 element.first().one( transitionend, function() {
646 if ( element.hasClass('fade-trigger') ) {
653 setTimeout( function() { element.removeClass( 'fade-trigger' ); }, this.sensitivity );
659 element.first().fadeOut( speed, callback );
661 if ( element.length > 1 ) {
662 element.not(':first').fadeOut( speed );
669 // Check if the browser supports CSS 3.0 transitions
670 transitions: ( function() {
671 var style = document.documentElement.style;
673 return ( typeof style.WebkitTransition === 'string' ||
674 typeof style.MozTransition === 'string' ||
675 typeof style.OTransition === 'string' ||
676 typeof style.transition === 'string' );
683 * Automatically updates textarea height.
685 api.bind_resize = function() {
686 s.$dfwTextarea.on( 'keydown.wp-dfw-resize click.wp-dfw-resize paste.wp-dfw-resize', function() {
687 api.resizeTextarea();
691 api.resizeTextarea = function() {
692 var node = s.$dfwTextarea[0];
694 if ( node.scrollHeight > node.clientHeight ) {
695 node.style.height = node.scrollHeight + 50 + 'px';
700 window.wp = window.wp || {};
701 window.wp.editor = window.wp.editor || {};
702 window.wp.editor.fullscreen = api;
704 })( jQuery, window );