4 * A lightweight publish/subscribe implementation.
7 var PubSub, fullscreen, wptitlehint;
13 PubSub.prototype.subscribe = function( topic, callback ) {
14 if ( ! this.topics[ topic ] )
15 this.topics[ topic ] = [];
17 this.topics[ topic ].push( callback );
21 PubSub.prototype.unsubscribe = function( topic, callback ) {
23 topics = this.topics[ topic ];
26 return callback || [];
28 // Clear matching callbacks
30 for ( i = 0, l = topics.length; i < l; i++ ) {
31 if ( callback == topics[i] )
32 topics.splice( i, 1 );
36 // Clear all callbacks
38 this.topics[ topic ] = [];
43 PubSub.prototype.publish = function( topic, args ) {
45 topics = this.topics[ topic ];
52 for ( i = 0, l = topics.length; i < l; i++ ) {
53 broken = ( topics[i].apply( null, args ) === false || broken );
59 * Distraction Free Writing
62 * Access the API globally using the fullscreen variable.
66 var api, ps, bounder, s;
68 // Initialize the fullscreen/api object
69 fullscreen = api = {};
71 // Create the PubSub (publish/subscribe) interface.
72 ps = api.pubsub = new PubSub();
76 s = api.settings = { // Settings
79 editor_id : 'content',
88 * Creates a function that publishes start/stop topics.
89 * Used to throttle events.
91 bounder = api.bounder = function( start, stop, delay, e ) {
94 delay = delay || 1250;
97 y = e.pageY || e.clientY || e.offsetY;
98 top = $(document).scrollTop();
100 if ( !e.isDefaultPrevented ) // test if e ic jQuery normalized
112 setTimeout( function() {
117 clearTimeout( s.timer );
126 s.timer = setTimeout( timed, delay );
132 * Turns fullscreen on.
134 * @param string mode Optional. Switch to the given mode before opening.
136 api.on = function() {
140 // Settings can be added or changed by defining "wp_fullscreen_settings" JS object.
141 // This can be done by defining it as PHP associative array, json encoding it and passing it to JS with:
142 // wp_add_script_before( 'wp-fullscreen', 'wp_fullscreen_settings = ' . $json_encoded_array . ';' );
143 if ( typeof(wp_fullscreen_settings) == 'object' )
144 $.extend( s, wp_fullscreen_settings );
146 s.editor_id = wpActiveEditor || 'content';
149 if ( $('input#title').length && s.editor_id == 'content' )
150 s.title_id = 'title';
151 else if ( $('input#' + s.editor_id + '-title').length ) // the title input field should have [editor_id]-title HTML ID to be auto detected
152 s.title_id = s.editor_id + '-title';
154 $('#wp-fullscreen-title, #wp-fullscreen-title-prompt-text').hide();
157 s.mode = $('#' + s.editor_id).is(':hidden') ? 'tinymce' : 'html';
158 s.qt_canvas = $('#' + s.editor_id).get(0);
163 s.is_mce_on = s.has_tinymce && typeof( tinyMCE.get(s.editor_id) ) != 'undefined';
165 api.ui.fade( 'show', 'showing', 'shown' );
171 * Turns fullscreen off.
173 api.off = function() {
177 api.ui.fade( 'hide', 'hiding', 'hidden' );
183 * @return string - The current mode.
185 * @param string to - The fullscreen mode to switch to.
187 * @eventparam string to - The new mode.
188 * @eventparam string from - The old mode.
190 api.switchmode = function( to ) {
193 if ( ! to || ! s.visible || ! s.has_tinymce )
196 // Don't switch if the mode is the same.
200 ps.publish( 'switchMode', [ from, to ] );
202 ps.publish( 'switchedMode', [ from, to ] );
211 api.save = function() {
212 var hidden = $('#hiddenaction'), old = hidden.val(), spinner = $('#wp-fullscreen-save img'),
213 message = $('#wp-fullscreen-save span');
218 hidden.val('wp-fullscreen-save-post');
220 $.post( ajaxurl, $('form#post').serialize(), function(r){
224 setTimeout( function(){
225 message.fadeOut(1000);
229 $('#wp-fullscreen-save input').attr( 'title', r.last_edited );
236 api.savecontent = function() {
240 $('#' + s.title_id).val( $('#wp-fullscreen-title').val() );
242 if ( s.mode === 'tinymce' && (ed = tinyMCE.get('wp_mce_fullscreen')) ) {
245 content = $('#wp_mce_fullscreen').val();
248 $('#' + s.editor_id).val( content );
249 $(document).triggerHandler('wpcountwords', [ content ]);
252 set_title_hint = function( title ) {
253 if ( ! title.val().length )
254 title.siblings('label').css( 'visibility', '' );
256 title.siblings('label').css( 'visibility', 'hidden' );
259 api.dfw_width = function(n) {
260 var el = $('#wp-fullscreen-wrap'), w = el.width();
262 if ( !n ) { // reset to theme width
263 el.width( $('#wp-fullscreen-central-toolbar').width() );
264 deleteUserSetting('dfw_width');
270 if ( w < 200 || w > 1200 ) // sanity check
274 setUserSetting('dfw_width', w);
277 ps.subscribe( 'showToolbar', function() {
278 s.toolbars.removeClass('fade-1000').addClass('fade-300');
279 api.fade.In( s.toolbars, 300, function(){ ps.publish('toolbarShown'); }, true );
280 $('#wp-fullscreen-body').addClass('wp-fullscreen-focus');
281 s.toolbar_shown = true;
284 ps.subscribe( 'hideToolbar', function() {
285 s.toolbars.removeClass('fade-300').addClass('fade-1000');
286 api.fade.Out( s.toolbars, 1000, function(){ ps.publish('toolbarHidden'); }, true );
287 $('#wp-fullscreen-body').removeClass('wp-fullscreen-focus');
290 ps.subscribe( 'toolbarShown', function() {
291 s.toolbars.removeClass('fade-300');
294 ps.subscribe( 'toolbarHidden', function() {
295 s.toolbars.removeClass('fade-1000');
296 s.toolbar_shown = false;
299 ps.subscribe( 'show', function() { // This event occurs before the overlay blocks the UI.
303 title = $('#wp-fullscreen-title').val( $('#' + s.title_id).val() );
304 set_title_hint( title );
307 $('#wp-fullscreen-save input').attr( 'title', $('#last-edit').text() );
309 s.textarea_obj.value = s.qt_canvas.value;
311 if ( s.has_tinymce && s.mode === 'tinymce' )
312 tinyMCE.execCommand('wpFullScreenInit');
314 s.orig_y = $(window).scrollTop();
317 ps.subscribe( 'showing', function() { // This event occurs while the DFW overlay blocks the UI.
318 $( document.body ).addClass( 'fullscreen-active' );
319 api.refresh_buttons();
321 $( document ).bind( 'mousemove.fullscreen', function(e) { bounder( 'showToolbar', 'hideToolbar', 2000, e ); } );
322 bounder( 'showToolbar', 'hideToolbar', 2000 );
325 setTimeout( api.resize_textarea, 200 );
327 // scroll to top so the user is not disoriented
330 // needed it for IE7 and compat mode
331 $('#wpadminbar').hide();
334 ps.subscribe( 'shown', function() { // This event occurs after the DFW overlay is shown
339 // init the standard TinyMCE instance if missing
340 if ( s.has_tinymce && ! s.is_mce_on ) {
342 interim_init = function(mce, ed) {
343 var el = ed.getElement(), old_val = el.value, settings = tinyMCEPreInit.mceInit[s.editor_id];
345 if ( settings && settings.wpautop && typeof(switchEditors) != 'undefined' )
346 el.value = switchEditors.wpautop( el.value );
348 ed.onInit.add(function(ed) {
350 ed.getElement().value = old_val;
351 tinymce.onAddEditor.remove(interim_init);
355 tinymce.onAddEditor.add(interim_init);
356 tinyMCE.init(tinyMCEPreInit.mceInit[s.editor_id]);
361 wpActiveEditor = 'wp_mce_fullscreen';
364 ps.subscribe( 'hide', function() { // This event occurs before the overlay blocks DFW.
365 var htmled_is_hidden = $('#' + s.editor_id).is(':hidden');
366 // Make sure the correct editor is displaying.
367 if ( s.has_tinymce && s.mode === 'tinymce' && !htmled_is_hidden ) {
368 switchEditors.go(s.editor_id, 'tmce');
369 } else if ( s.mode === 'html' && htmled_is_hidden ) {
370 switchEditors.go(s.editor_id, 'html');
373 // Save content must be after switchEditors or content will be overwritten. See #17229.
376 $( document ).unbind( '.fullscreen' );
377 $(s.textarea_obj).unbind('.grow');
379 if ( s.has_tinymce && s.mode === 'tinymce' )
380 tinyMCE.execCommand('wpFullScreenSave');
383 set_title_hint( $('#' + s.title_id) );
385 s.qt_canvas.value = s.textarea_obj.value;
388 ps.subscribe( 'hiding', function() { // This event occurs while the overlay blocks the DFW UI.
390 $( document.body ).removeClass( 'fullscreen-active' );
391 scrollTo(0, s.orig_y);
392 $('#wpadminbar').show();
395 ps.subscribe( 'hidden', function() { // This event occurs after DFW is removed.
397 $('#wp_mce_fullscreen, #wp-fullscreen-title').removeAttr('style');
399 if ( s.has_tinymce && s.is_mce_on )
400 tinyMCE.execCommand('wpFullScreenClose');
402 s.textarea_obj.value = '';
404 wpActiveEditor = s.editor_id;
407 ps.subscribe( 'switchMode', function( from, to ) {
410 if ( !s.has_tinymce || !s.is_mce_on )
413 ed = tinyMCE.get('wp_mce_fullscreen');
415 if ( from === 'html' && to === 'tinymce' ) {
417 if ( tinyMCE.get(s.editor_id).getParam('wpautop') && typeof(switchEditors) != 'undefined' )
418 s.textarea_obj.value = switchEditors.wpautop( s.textarea_obj.value );
420 if ( 'undefined' == typeof(ed) )
421 tinyMCE.execCommand('wpFullScreenInit');
425 } else if ( from === 'tinymce' && to === 'html' ) {
431 ps.subscribe( 'switchedMode', function( from, to ) {
432 api.refresh_buttons(true);
435 setTimeout( api.resize_textarea, 200 );
442 if ( s.has_tinymce && 'tinymce' === s.mode )
443 tinyMCE.execCommand('Bold');
447 if ( s.has_tinymce && 'tinymce' === s.mode )
448 tinyMCE.execCommand('Italic');
451 api.ul = function() {
452 if ( s.has_tinymce && 'tinymce' === s.mode )
453 tinyMCE.execCommand('InsertUnorderedList');
456 api.ol = function() {
457 if ( s.has_tinymce && 'tinymce' === s.mode )
458 tinyMCE.execCommand('InsertOrderedList');
461 api.link = function() {
462 if ( s.has_tinymce && 'tinymce' === s.mode )
463 tinyMCE.execCommand('WP_Link');
468 api.unlink = function() {
469 if ( s.has_tinymce && 'tinymce' === s.mode )
470 tinyMCE.execCommand('unlink');
473 api.atd = function() {
474 if ( s.has_tinymce && 'tinymce' === s.mode )
475 tinyMCE.execCommand('mceWritingImprovementTool');
478 api.help = function() {
479 if ( s.has_tinymce && 'tinymce' === s.mode )
480 tinyMCE.execCommand('WP_Help');
483 api.blockquote = function() {
484 if ( s.has_tinymce && 'tinymce' === s.mode )
485 tinyMCE.execCommand('mceBlockQuote');
488 api.medialib = function() {
489 if ( s.has_tinymce && 'tinymce' === s.mode ) {
490 tinyMCE.execCommand('WP_Medialib');
492 var href = $('#wp-' + s.editor_id + '-media-buttons a.thickbox').attr('href') || '';
499 api.refresh_buttons = function( fade ) {
500 fade = fade || false;
502 if ( s.mode === 'html' ) {
503 $('#wp-fullscreen-mode-bar').removeClass('wp-tmce-mode').addClass('wp-html-mode');
506 $('#wp-fullscreen-button-bar').fadeOut( 150, function(){
507 $(this).addClass('wp-html-mode').fadeIn( 150 );
510 $('#wp-fullscreen-button-bar').addClass('wp-html-mode');
512 } else if ( s.mode === 'tinymce' ) {
513 $('#wp-fullscreen-mode-bar').removeClass('wp-html-mode').addClass('wp-tmce-mode');
516 $('#wp-fullscreen-button-bar').fadeOut( 150, function(){
517 $(this).removeClass('wp-html-mode').fadeIn( 150 );
520 $('#wp-fullscreen-button-bar').removeClass('wp-html-mode');
527 * Used for transitioning between states.
531 var topbar = $('#fullscreen-topbar'), txtarea = $('#wp_mce_fullscreen'), last = 0;
533 s.toolbars = topbar.add( $('#wp-fullscreen-status') );
534 s.element = $('#fullscreen-fader');
535 s.textarea_obj = txtarea[0];
536 s.has_tinymce = typeof(tinymce) != 'undefined';
538 if ( !s.has_tinymce )
539 $('#wp-fullscreen-mode-bar').hide();
541 if ( wptitlehint && $('#wp-fullscreen-title').length )
542 wptitlehint('wp-fullscreen-title');
544 $(document).keyup(function(e){
545 var c = e.keyCode || e.charCode, a, data;
547 if ( !fullscreen.settings.visible )
550 if ( navigator.platform && navigator.platform.indexOf('Mac') != -1 )
551 a = e.ctrlKey; // Ctrl key for Mac
553 a = e.altKey; // Alt key for Win & Linux
555 if ( 27 == c ) { // Esc
560 condition: function(){
561 if ( $('#TB_window').is(':visible') || $('.wp-dialog').is(':visible') )
567 if ( ! jQuery(document).triggerHandler( 'wp_CloseOnEscape', [data] ) )
571 if ( a && (61 == c || 107 == c || 187 == c) ) // +
574 if ( a && (45 == c || 109 == c || 189 == c) ) // -
577 if ( a && 48 == c ) // 0
583 // word count in HTML mode
584 if ( typeof(wpWordCount) != 'undefined' ) {
586 txtarea.keyup( function(e) {
587 var k = e.keyCode || e.charCode;
592 if ( 13 == k || 8 == last || 46 == last )
593 $(document).triggerHandler('wpcountwords', [ txtarea.val() ]);
600 topbar.mouseenter(function(e){
601 s.toolbars.addClass('fullscreen-make-sticky');
602 $( document ).unbind( '.fullscreen' );
603 clearTimeout( s.timer );
605 }).mouseleave(function(e){
606 s.toolbars.removeClass('fullscreen-make-sticky');
609 $( document ).bind( 'mousemove.fullscreen', function(e) { bounder( 'showToolbar', 'hideToolbar', 2000, e ); } );
613 fade: function( before, during, after ) {
617 // If any callback bound to before returns false, bail.
618 if ( before && ! ps.publish( before ) )
621 api.fade.In( s.element, 600, function() {
623 ps.publish( during );
625 api.fade.Out( s.element, 600, function() {
634 transitionend: 'transitionend webkitTransitionEnd oTransitionEnd',
636 // Sensitivity to allow browsers to render the blank element before animating.
639 In: function( element, speed, callback, stop ) {
641 callback = callback || $.noop;
642 speed = speed || 400;
643 stop = stop || false;
645 if ( api.fade.transitions ) {
646 if ( element.is(':visible') ) {
647 element.addClass( 'fade-trigger' );
652 element.first().one( this.transitionend, function() {
655 setTimeout( function() { element.addClass( 'fade-trigger' ); }, this.sensitivity );
660 element.css( 'opacity', 1 );
661 element.first().fadeIn( speed, callback );
663 if ( element.length > 1 )
664 element.not(':first').fadeIn( speed );
670 Out: function( element, speed, callback, stop ) {
672 callback = callback || $.noop;
673 speed = speed || 400;
674 stop = stop || false;
676 if ( ! element.is(':visible') )
679 if ( api.fade.transitions ) {
680 element.first().one( api.fade.transitionend, function() {
681 if ( element.hasClass('fade-trigger') )
687 setTimeout( function() { element.removeClass( 'fade-trigger' ); }, this.sensitivity );
692 element.first().fadeOut( speed, callback );
694 if ( element.length > 1 )
695 element.not(':first').fadeOut( speed );
701 transitions: (function() { // Check if the browser supports CSS 3.0 transitions
702 var s = document.documentElement.style;
704 return ( typeof ( s.WebkitTransition ) == 'string' ||
705 typeof ( s.MozTransition ) == 'string' ||
706 typeof ( s.OTransition ) == 'string' ||
707 typeof ( s.transition ) == 'string' );
715 * Automatically updates textarea height.
718 api.bind_resize = function() {
719 $(s.textarea_obj).bind('keypress.grow click.grow paste.grow', function(){
720 setTimeout( api.resize_textarea, 200 );
725 api.resize_textarea = function() {
726 var txt = s.textarea_obj, newheight;
728 newheight = txt.scrollHeight > 300 ? txt.scrollHeight : 300;
730 if ( newheight != api.oldheight ) {
731 txt.style.height = newheight + 'px';
732 api.oldheight = newheight;