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');
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 ) {
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() {
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 );