1 /* global ajaxurl, deleteUserSetting, setUserSetting, switchEditors, tinymce, tinyMCEPreInit, wp_fullscreen_settings, wpActiveEditor:true, wpLink */
5 * A lightweight publish/subscribe implementation.
8 var PubSub, fullscreen, wptitlehint;
14 PubSub.prototype.subscribe = function( topic, callback ) {
15 if ( ! this.topics[ topic ] )
16 this.topics[ topic ] = [];
18 this.topics[ topic ].push( callback );
22 PubSub.prototype.unsubscribe = function( topic, callback ) {
24 topics = this.topics[ topic ];
27 return callback || [];
29 // Clear matching callbacks
31 for ( i = 0, l = topics.length; i < l; i++ ) {
32 if ( callback == topics[i] )
33 topics.splice( i, 1 );
37 // Clear all callbacks
39 this.topics[ topic ] = [];
44 PubSub.prototype.publish = function( topic, args ) {
46 topics = this.topics[ topic ];
53 for ( i = 0, l = topics.length; i < l; i++ ) {
54 broken = ( topics[i].apply( null, args ) === false || broken );
60 * Distraction Free Writing
63 * Access the API globally using the fullscreen variable.
67 var api, ps, bounder, s, timer, block, set_title_hint;
69 // Initialize the fullscreen/api object
70 fullscreen = api = {};
72 // Create the PubSub (publish/subscribe) interface.
73 ps = api.pubsub = new PubSub();
77 s = api.settings = { // Settings
80 editor_id : 'content',
89 * Creates a function that publishes start/stop topics.
90 * Used to throttle events.
92 bounder = api.bounder = function( start, stop, delay, e ) {
95 delay = delay || 1250;
98 y = e.pageY || e.clientY || e.offsetY;
99 top = $(document).scrollTop();
101 if ( !e.isDefaultPrevented ) // test if e ic jQuery normalized
113 setTimeout( function() {
118 clearTimeout( s.timer );
127 s.timer = setTimeout( timed, delay );
133 * Turns fullscreen on.
135 * @param string mode Optional. Switch to the given mode before opening.
137 api.on = function() {
141 // Settings can be added or changed by defining "wp_fullscreen_settings" JS object.
142 if ( typeof(wp_fullscreen_settings) == 'object' )
143 $.extend( s, wp_fullscreen_settings );
145 s.editor_id = wpActiveEditor || 'content';
147 if ( $('input#title').length && s.editor_id == 'content' )
148 s.title_id = 'title';
149 else if ( $('input#' + s.editor_id + '-title').length ) // the title input field should have [editor_id]-title HTML ID to be auto detected
150 s.title_id = s.editor_id + '-title';
152 $('#wp-fullscreen-title, #wp-fullscreen-title-prompt-text').hide();
154 s.mode = $('#' + s.editor_id).is(':hidden') ? 'tinymce' : 'html';
155 s.qt_canvas = $('#' + s.editor_id).get(0);
160 s.is_mce_on = s.has_tinymce && typeof( tinymce.get(s.editor_id) ) != 'undefined';
162 api.ui.fade( 'show', 'showing', 'shown' );
168 * Turns fullscreen off.
170 api.off = function() {
174 api.ui.fade( 'hide', 'hiding', 'hidden' );
180 * @return string - The current mode.
182 * @param string to - The fullscreen mode to switch to.
184 * @eventparam string to - The new mode.
185 * @eventparam string from - The old mode.
187 api.switchmode = function( to ) {
190 if ( ! to || ! s.visible || ! s.has_tinymce )
193 // Don't switch if the mode is the same.
197 ps.publish( 'switchMode', [ from, to ] );
199 ps.publish( 'switchedMode', [ from, to ] );
208 api.save = function() {
209 var hidden = $('#hiddenaction'), old = hidden.val(), spinner = $('#wp-fullscreen-save .spinner'),
210 message = $('#wp-fullscreen-save span');
215 hidden.val('wp-fullscreen-save-post');
217 $.post( ajaxurl, $('form#post').serialize(), function(r){
221 setTimeout( function(){
222 message.fadeOut(1000);
226 $('#wp-fullscreen-save input').attr( 'title', r.last_edited );
233 api.savecontent = function() {
237 $('#' + s.title_id).val( $('#wp-fullscreen-title').val() );
239 if ( s.mode === 'tinymce' && (ed = tinymce.get('wp_mce_fullscreen')) ) {
242 content = $('#wp_mce_fullscreen').val();
245 $('#' + s.editor_id).val( content );
246 $(document).triggerHandler('wpcountwords', [ content ]);
249 set_title_hint = function( title ) {
250 if ( ! title.val().length )
251 title.siblings('label').css( 'visibility', '' );
253 title.siblings('label').css( 'visibility', 'hidden' );
256 api.dfw_width = function(n) {
257 var el = $('#wp-fullscreen-wrap'), w = el.width();
259 if ( !n ) { // reset to theme width
260 el.width( $('#wp-fullscreen-central-toolbar').width() );
261 deleteUserSetting('dfw_width');
267 if ( w < 200 || w > 1200 ) // sanity check
271 setUserSetting('dfw_width', w);
274 ps.subscribe( 'showToolbar', function() {
275 s.toolbars.removeClass('fade-1000').addClass('fade-300');
276 api.fade.In( s.toolbars, 300, function(){ ps.publish('toolbarShown'); }, true );
277 $('#wp-fullscreen-body').addClass('wp-fullscreen-focus');
278 s.toolbar_shown = true;
281 ps.subscribe( 'hideToolbar', function() {
282 s.toolbars.removeClass('fade-300').addClass('fade-1000');
283 api.fade.Out( s.toolbars, 1000, function(){ ps.publish('toolbarHidden'); }, true );
284 $('#wp-fullscreen-body').removeClass('wp-fullscreen-focus');
287 ps.subscribe( 'toolbarShown', function() {
288 s.toolbars.removeClass('fade-300');
291 ps.subscribe( 'toolbarHidden', function() {
292 s.toolbars.removeClass('fade-1000');
293 s.toolbar_shown = false;
296 ps.subscribe( 'show', function() { // This event occurs before the overlay blocks the UI.
300 title = $('#wp-fullscreen-title').val( $('#' + s.title_id).val() );
301 set_title_hint( title );
304 $('#wp-fullscreen-save input').attr( 'title', $('#last-edit').text() );
306 s.textarea_obj.value = s.qt_canvas.value;
308 if ( s.has_tinymce && s.mode === 'tinymce' )
309 tinymce.execCommand('wpFullScreenInit');
311 s.orig_y = $(window).scrollTop();
314 ps.subscribe( 'showing', function() { // This event occurs while the DFW overlay blocks the UI.
315 $( document.body ).addClass( 'fullscreen-active' );
316 api.refresh_buttons();
318 $( document ).bind( 'mousemove.fullscreen', function(e) { bounder( 'showToolbar', 'hideToolbar', 2000, e ); } );
319 bounder( 'showToolbar', 'hideToolbar', 2000 );
322 setTimeout( api.resize_textarea, 200 );
324 // scroll to top so the user is not disoriented
327 // needed it for IE7 and compat mode
328 $('#wpadminbar').hide();
331 ps.subscribe( 'shown', function() { // This event occurs after the DFW overlay is shown
336 // init the standard TinyMCE instance if missing
337 if ( s.has_tinymce && ! s.is_mce_on ) {
339 interim_init = function(mce, ed) {
340 var el = ed.getElement(), old_val = el.value, settings = tinyMCEPreInit.mceInit[s.editor_id];
342 if ( settings && settings.wpautop && typeof(switchEditors) != 'undefined' )
343 el.value = switchEditors.wpautop( el.value );
345 ed.onInit.add(function(ed) {
347 ed.getElement().value = old_val;
348 tinymce.onAddEditor.remove(interim_init);
352 tinymce.onAddEditor.add(interim_init);
353 tinymce.init(tinyMCEPreInit.mceInit[s.editor_id]);
358 wpActiveEditor = 'wp_mce_fullscreen';
361 ps.subscribe( 'hide', function() { // This event occurs before the overlay blocks DFW.
362 var htmled_is_hidden = $('#' + s.editor_id).is(':hidden');
363 // Make sure the correct editor is displaying.
364 if ( s.has_tinymce && s.mode === 'tinymce' && !htmled_is_hidden ) {
365 switchEditors.go(s.editor_id, 'tmce');
366 } else if ( s.mode === 'html' && htmled_is_hidden ) {
367 switchEditors.go(s.editor_id, 'html');
370 // Save content must be after switchEditors or content will be overwritten. See #17229.
373 $( document ).unbind( '.fullscreen' );
374 $(s.textarea_obj).unbind('.grow');
376 if ( s.has_tinymce && s.mode === 'tinymce' )
377 tinymce.execCommand('wpFullScreenSave');
380 set_title_hint( $('#' + s.title_id) );
382 s.qt_canvas.value = s.textarea_obj.value;
385 ps.subscribe( 'hiding', function() { // This event occurs while the overlay blocks the DFW UI.
387 $( document.body ).removeClass( 'fullscreen-active' );
388 scrollTo(0, s.orig_y);
389 $('#wpadminbar').show();
392 ps.subscribe( 'hidden', function() { // This event occurs after DFW is removed.
394 $('#wp_mce_fullscreen, #wp-fullscreen-title').removeAttr('style');
396 if ( s.has_tinymce && s.is_mce_on )
397 tinymce.execCommand('wpFullScreenClose');
399 s.textarea_obj.value = '';
401 wpActiveEditor = s.editor_id;
404 ps.subscribe( 'switchMode', function( from, to ) {
407 if ( !s.has_tinymce || !s.is_mce_on )
410 ed = tinymce.get('wp_mce_fullscreen');
412 if ( from === 'html' && to === 'tinymce' ) {
414 if ( tinymce.get(s.editor_id).getParam('wpautop') && typeof(switchEditors) != 'undefined' )
415 s.textarea_obj.value = switchEditors.wpautop( s.textarea_obj.value );
417 if ( 'undefined' == typeof(ed) )
418 tinymce.execCommand('wpFullScreenInit');
422 } else if ( from === 'tinymce' && to === 'html' ) {
428 ps.subscribe( 'switchedMode', function( from, to ) {
429 api.refresh_buttons(true);
432 setTimeout( api.resize_textarea, 200 );
439 if ( s.has_tinymce && 'tinymce' === s.mode )
440 tinymce.execCommand('Bold');
444 if ( s.has_tinymce && 'tinymce' === s.mode )
445 tinymce.execCommand('Italic');
448 api.ul = function() {
449 if ( s.has_tinymce && 'tinymce' === s.mode )
450 tinymce.execCommand('InsertUnorderedList');
453 api.ol = function() {
454 if ( s.has_tinymce && 'tinymce' === s.mode )
455 tinymce.execCommand('InsertOrderedList');
458 api.link = function() {
459 if ( s.has_tinymce && 'tinymce' === s.mode )
460 tinymce.execCommand('WP_Link');
465 api.unlink = function() {
466 if ( s.has_tinymce && 'tinymce' === s.mode )
467 tinymce.execCommand('unlink');
470 api.atd = function() {
471 if ( s.has_tinymce && 'tinymce' === s.mode )
472 tinymce.execCommand('mceWritingImprovementTool');
475 api.help = function() {
476 if ( s.has_tinymce && 'tinymce' === s.mode )
477 tinymce.execCommand('WP_Help');
480 api.blockquote = function() {
481 if ( s.has_tinymce && 'tinymce' === s.mode )
482 tinymce.execCommand('mceBlockQuote');
485 api.medialib = function() {
486 if ( typeof wp !== 'undefined' && wp.media && wp.media.editor )
487 wp.media.editor.open(s.editor_id);
490 api.refresh_buttons = function( fade ) {
491 fade = fade || false;
493 if ( s.mode === 'html' ) {
494 $('#wp-fullscreen-mode-bar').removeClass('wp-tmce-mode').addClass('wp-html-mode');
497 $('#wp-fullscreen-button-bar').fadeOut( 150, function(){
498 $(this).addClass('wp-html-mode').fadeIn( 150 );
501 $('#wp-fullscreen-button-bar').addClass('wp-html-mode');
503 } else if ( s.mode === 'tinymce' ) {
504 $('#wp-fullscreen-mode-bar').removeClass('wp-html-mode').addClass('wp-tmce-mode');
507 $('#wp-fullscreen-button-bar').fadeOut( 150, function(){
508 $(this).removeClass('wp-html-mode').fadeIn( 150 );
511 $('#wp-fullscreen-button-bar').removeClass('wp-html-mode');
518 * Used for transitioning between states.
522 var topbar = $('#fullscreen-topbar'), txtarea = $('#wp_mce_fullscreen'), last = 0;
524 s.toolbars = topbar.add( $('#wp-fullscreen-status') );
525 s.element = $('#fullscreen-fader');
526 s.textarea_obj = txtarea[0];
527 s.has_tinymce = typeof(tinymce) != 'undefined';
529 if ( !s.has_tinymce )
530 $('#wp-fullscreen-mode-bar').hide();
532 if ( wptitlehint && $('#wp-fullscreen-title').length )
533 wptitlehint('wp-fullscreen-title');
535 $(document).keyup(function(e){
536 var c = e.keyCode || e.charCode, a, data;
538 if ( !fullscreen.settings.visible )
541 if ( navigator.platform && navigator.platform.indexOf('Mac') != -1 )
542 a = e.ctrlKey; // Ctrl key for Mac
544 a = e.altKey; // Alt key for Win & Linux
546 if ( 27 == c ) { // Esc
551 condition: function(){
552 if ( $('#TB_window').is(':visible') || $('.wp-dialog').is(':visible') )
558 if ( ! jQuery(document).triggerHandler( 'wp_CloseOnEscape', [data] ) )
562 if ( a && (61 == c || 107 == c || 187 == c) ) { // +
567 if ( a && (45 == c || 109 == c || 189 == c) ) { // -
572 if ( a && 48 == c ) { // 0
578 // word count in Text mode
579 if ( typeof(wpWordCount) != 'undefined' ) {
581 txtarea.keyup( function(e) {
582 var k = e.keyCode || e.charCode;
587 if ( 13 == k || 8 == last || 46 == last )
588 $(document).triggerHandler('wpcountwords', [ txtarea.val() ]);
595 topbar.mouseenter(function(){
596 s.toolbars.addClass('fullscreen-make-sticky');
597 $( document ).unbind( '.fullscreen' );
598 clearTimeout( s.timer );
600 }).mouseleave(function(){
601 s.toolbars.removeClass('fullscreen-make-sticky');
604 $( document ).bind( 'mousemove.fullscreen', function(e) { bounder( 'showToolbar', 'hideToolbar', 2000, e ); } );
608 fade: function( before, during, after ) {
612 // If any callback bound to before returns false, bail.
613 if ( before && ! ps.publish( before ) )
616 api.fade.In( s.element, 600, function() {
618 ps.publish( during );
620 api.fade.Out( s.element, 600, function() {
629 transitionend: 'transitionend webkitTransitionEnd oTransitionEnd',
631 // Sensitivity to allow browsers to render the blank element before animating.
634 In: function( element, speed, callback, stop ) {
636 callback = callback || $.noop;
637 speed = speed || 400;
638 stop = stop || false;
640 if ( api.fade.transitions ) {
641 if ( element.is(':visible') ) {
642 element.addClass( 'fade-trigger' );
647 element.first().one( this.transitionend, function() {
650 setTimeout( function() { element.addClass( 'fade-trigger' ); }, this.sensitivity );
655 element.css( 'opacity', 1 );
656 element.first().fadeIn( speed, callback );
658 if ( element.length > 1 )
659 element.not(':first').fadeIn( speed );
665 Out: function( element, speed, callback, stop ) {
667 callback = callback || $.noop;
668 speed = speed || 400;
669 stop = stop || false;
671 if ( ! element.is(':visible') )
674 if ( api.fade.transitions ) {
675 element.first().one( api.fade.transitionend, function() {
676 if ( element.hasClass('fade-trigger') )
682 setTimeout( function() { element.removeClass( 'fade-trigger' ); }, this.sensitivity );
687 element.first().fadeOut( speed, callback );
689 if ( element.length > 1 )
690 element.not(':first').fadeOut( speed );
696 transitions: (function() { // Check if the browser supports CSS 3.0 transitions
697 var s = document.documentElement.style;
699 return ( typeof ( s.WebkitTransition ) == 'string' ||
700 typeof ( s.MozTransition ) == 'string' ||
701 typeof ( s.OTransition ) == 'string' ||
702 typeof ( s.transition ) == 'string' );
709 * Automatically updates textarea height.
712 api.bind_resize = function() {
713 $(s.textarea_obj).bind('keypress.grow click.grow paste.grow', function(){
714 setTimeout( api.resize_textarea, 200 );
719 api.resize_textarea = function() {
720 var txt = s.textarea_obj, newheight;
722 newheight = txt.scrollHeight > 300 ? txt.scrollHeight : 300;
724 if ( newheight != api.oldheight ) {
725 txt.style.height = newheight + 'px';
726 api.oldheight = newheight;