WordPress/wp-admin/js/wp-fullscreen.js
2014-08-01 18:10:16 +00:00

710 lines
16 KiB
JavaScript

/* global deleteUserSetting, setUserSetting, switchEditors, tinymce, tinyMCEPreInit */
/**
* Distraction Free Writing
* (wp-fullscreen)
*
* Access the API globally using the window.wp.editor.fullscreen variable.
*/
( function( $, window ) {
var api, ps, s, toggleUI, uiTimer, PubSub,
uiScrollTop = 0,
transitionend = 'transitionend webkitTransitionEnd',
$body = $( document.body ),
$document = $( document );
/**
* PubSub
*
* A lightweight publish/subscribe implementation.
*
* @access private
*/
PubSub = function() {
this.topics = {};
this.subscribe = function( topic, callback ) {
if ( ! this.topics[ topic ] )
this.topics[ topic ] = [];
this.topics[ topic ].push( callback );
return callback;
};
this.unsubscribe = function( topic, callback ) {
var i, l,
topics = this.topics[ topic ];
if ( ! topics )
return callback || [];
// Clear matching callbacks
if ( callback ) {
for ( i = 0, l = topics.length; i < l; i++ ) {
if ( callback == topics[i] )
topics.splice( i, 1 );
}
return callback;
// Clear all callbacks
} else {
this.topics[ topic ] = [];
return topics;
}
};
this.publish = function( topic, args ) {
var i, l, broken,
topics = this.topics[ topic ];
if ( ! topics )
return;
args = args || [];
for ( i = 0, l = topics.length; i < l; i++ ) {
broken = ( topics[i].apply( null, args ) === false || broken );
}
return ! broken;
};
};
// Initialize the fullscreen/api object
api = {};
// Create the PubSub (publish/subscribe) interface.
ps = api.pubsub = new PubSub();
s = api.settings = { // Settings
visible: false,
mode: 'tinymce',
id: '',
title_id: '',
timer: 0,
toolbar_shown: false
};
function _hideUI() {
$body.removeClass('wp-dfw-show-ui');
}
/**
* toggleUI
*
* Toggle the CSS class to show/hide the toolbar, borders and statusbar.
*/
toggleUI = api.toggleUI = function( show ) {
clearTimeout( uiTimer );
if ( ! $body.hasClass('wp-dfw-show-ui') || show === 'show' ) {
$body.addClass('wp-dfw-show-ui');
} else if ( show !== 'autohide' ) {
$body.removeClass('wp-dfw-show-ui');
}
if ( show === 'autohide' ) {
uiTimer = setTimeout( _hideUI, 2000 );
}
};
function resetCssPosition( add ) {
s.$dfwWrap.parents().each( function( i, parent ) {
var cssPosition, $parent = $(parent);
if ( add ) {
if ( parent.style.position ) {
$parent.data( 'wp-dfw-css-position', parent.style.position );
}
$parent.css( 'position', 'static' );
} else {
cssPosition = $parent.data( 'wp-dfw-css-position' );
cssPosition = cssPosition || '';
$parent.css( 'position', cssPosition );
}
if ( parent.nodeName === 'BODY' ) {
return false;
}
});
}
/**
* on()
*
* Turns fullscreen on.
*
* @param string mode Optional. Switch to the given mode before opening.
*/
api.on = function() {
var id, $dfwWrap, titleId;
if ( s.visible ) {
return;
}
if ( ! s.$fullscreenFader ) {
api.ui.init();
}
// Settings can be added or changed by defining "wp_fullscreen_settings" JS object.
if ( typeof window.wp_fullscreen_settings === 'object' )
$.extend( s, window.wp_fullscreen_settings );
id = s.id || window.wpActiveEditor;
if ( ! id ) {
if ( s.hasTinymce ) {
id = tinymce.activeEditor.id;
} else {
return;
}
}
s.id = id;
$dfwWrap = s.$dfwWrap = $( '#wp-' + id + '-wrap' );
if ( ! $dfwWrap.length ) {
return;
}
s.$dfwTextarea = $( '#' + id );
s.$editorContainer = $dfwWrap.find( '.wp-editor-container' );
uiScrollTop = $document.scrollTop();
if ( s.hasTinymce ) {
s.editor = tinymce.get( id );
}
if ( s.editor && ! s.editor.isHidden() ) {
s.origHeight = $( '#' + id + '_ifr' ).height();
s.mode = 'tinymce';
} else {
s.origHeight = s.$dfwTextarea.height();
s.mode = 'html';
}
// Try to find title field
if ( typeof window.adminpage !== 'undefined' &&
( window.adminpage === 'post-php' || window.adminpage === 'post-new-php' ) ) {
titleId = 'title';
} else {
titleId = id + '-title';
}
s.$dfwTitle = $( '#' + titleId );
if ( ! s.$dfwTitle.length ) {
s.$dfwTitle = null;
}
$( '#fullscreen-overlay' ).css( 'background-color', s.editor.dom.getStyle( s.editor.getBody(), 'background-color', true ) );
s.$dfwTitle.add( s.$editorContainer ).css( 'color', s.editor.dom.getStyle( s.editor.getBody(), 'color', true ) );
api.ui.fade( 'show', 'showing', 'shown' );
};
/**
* off()
*
* Turns fullscreen off.
*/
api.off = function() {
if ( ! s.visible )
return;
api.ui.fade( 'hide', 'hiding', 'hidden' );
};
/**
* switchmode()
*
* @return string - The current mode.
*
* @param string to - The fullscreen mode to switch to.
* @event switchMode
* @eventparam string to - The new mode.
* @eventparam string from - The old mode.
*/
api.switchmode = function( to ) {
var from = s.mode;
if ( ! to || ! s.visible || ! s.hasTinymce || typeof switchEditors === 'undefined' ) {
return from;
}
// Don't switch if the mode is the same.
if ( from == to )
return from;
if ( to === 'tinymce' && ! s.editor ) {
s.editor = tinymce.get( s.id );
if ( ! s.editor && typeof tinyMCEPreInit !== 'undefined' &&
tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[ s.id ] ) {
// If the TinyMCE instance hasn't been created, set the "wp_fulscreen" flag on creating it
tinyMCEPreInit.mceInit[ s.id ].wp_fullscreen = true;
}
}
s.mode = to;
switchEditors.go( s.id, to );
api.refreshButtons( true );
if ( to === 'html' ) {
setTimeout( api.resizeTextarea, 200 );
}
return to;
};
/**
* General
*/
api.save = function() {
var $hidden = $('#hiddenaction'),
oldVal = $hidden.val(),
$spinner = $('#wp-fullscreen-save .spinner'),
$saveMessage = $('#wp-fullscreen-save .wp-fullscreen-saved-message'),
$errorMessage = $('#wp-fullscreen-save .wp-fullscreen-error-message');
$spinner.show();
$errorMessage.hide();
$saveMessage.hide();
$hidden.val('wp-fullscreen-save-post');
if ( s.editor && ! s.editor.isHidden() ) {
s.editor.save();
}
$.ajax({
url: window.ajaxurl,
type: 'post',
data: $('form#post').serialize(),
dataType: 'json'
}).done( function( response ) {
$spinner.hide();
if ( response && response.success ) {
$saveMessage.show();
setTimeout( function() {
$saveMessage.fadeOut(300);
}, 3000 );
if ( response.data && response.data.last_edited ) {
$('#wp-fullscreen-save input').attr( 'title', response.data.last_edited );
}
} else {
$errorMessage.show();
}
}).fail( function() {
$spinner.hide();
$errorMessage.show();
});
$hidden.val( oldVal );
};
api.dfwWidth = function( pixels, total ) {
var width;
if ( pixels && pixels.toString().indexOf('%') !== -1 ) {
s.$editorContainer.css( 'width', pixels );
s.$statusbar.css( 'width', pixels );
if ( s.$dfwTitle ) {
s.$dfwTitle.css( 'width', pixels );
}
return;
}
if ( ! pixels ) {
// Reset to theme width
width = $('#wp-fullscreen-body').data('theme-width') || 800;
s.$editorContainer.width( width );
s.$statusbar.width( width );
if ( s.$dfwTitle ) {
s.$dfwTitle.width( width - 16 );
}
deleteUserSetting('dfw_width');
return;
}
if ( total ) {
width = pixels;
} else {
width = s.$editorContainer.width();
width += pixels;
}
if ( width < 200 || width > 1200 ) {
// sanity check
return;
}
s.$editorContainer.width( width );
s.$statusbar.width( width );
if ( s.$dfwTitle ) {
s.$dfwTitle.width( width - 16 );
}
setUserSetting( 'dfw_width', width );
};
// This event occurs before the overlay blocks the UI.
ps.subscribe( 'show', function() {
var title = $('#last-edit').text();
if ( title ) {
$('#wp-fullscreen-save input').attr( 'title', title );
}
});
// This event occurs while the overlay blocks the UI.
ps.subscribe( 'showing', function() {
$body.addClass( 'wp-fullscreen-active' );
s.$dfwWrap.addClass( 'wp-fullscreen-wrap' );
if ( s.$dfwTitle ) {
s.$dfwTitle.after( '<span id="wp-fullscreen-title-placeholder">' );
s.$dfwWrap.prepend( s.$dfwTitle.addClass('wp-fullscreen-title') );
}
api.refreshButtons();
resetCssPosition( true );
$('#wpadminbar').hide();
// Show the UI for 2 sec. when opening
toggleUI('autohide');
api.bind_resize();
if ( s.editor ) {
s.editor.execCommand( 'wpFullScreenOn' );
}
if ( 'ontouchstart' in window ) {
api.dfwWidth( '90%' );
} else {
api.dfwWidth( $( '#wp-fullscreen-body' ).data('dfw-width') || 800, true );
}
// scroll to top so the user is not disoriented
scrollTo(0, 0);
});
// This event occurs after the overlay unblocks the UI
ps.subscribe( 'shown', function() {
s.visible = true;
if ( s.editor && ! s.editor.isHidden() ) {
s.editor.execCommand( 'wpAutoResize' );
} else {
api.resizeTextarea( 'force' );
}
});
ps.subscribe( 'hide', function() { // This event occurs before the overlay blocks DFW.
$document.unbind( '.fullscreen' );
s.$dfwTextarea.unbind('.wp-dfw-resize');
});
ps.subscribe( 'hiding', function() { // This event occurs while the overlay blocks the DFW UI.
s.$dfwTitle.add( s.$editorContainer ).css( 'color', '' );
$body.removeClass( 'wp-fullscreen-active' );
if ( s.$dfwTitle ) {
$( '#wp-fullscreen-title-placeholder' ).before( s.$dfwTitle.removeClass('wp-fullscreen-title').css( 'width', '' ) ).remove();
}
s.$dfwWrap.removeClass( 'wp-fullscreen-wrap' );
s.$editorContainer.css( 'width', '' );
s.$dfwTextarea.add( '#' + s.id + '_ifr' ).height( s.origHeight );
if ( s.editor ) {
s.editor.execCommand( 'wpFullScreenOff' );
}
resetCssPosition( false );
window.scrollTo( 0, uiScrollTop );
$('#wpadminbar').show();
});
// This event occurs after DFW is removed.
ps.subscribe( 'hidden', function() {
s.visible = false;
});
api.refreshButtons = function( fade ) {
if ( s.mode === 'html' ) {
$('#wp-fullscreen-mode-bar').removeClass('wp-tmce-mode').addClass('wp-html-mode')
.find('a').removeClass( 'active' ).filter('.wp-fullscreen-mode-html').addClass( 'active' );
if ( fade ) {
$('#wp-fullscreen-button-bar').fadeOut( 150, function(){
$(this).addClass('wp-html-mode').fadeIn( 150 );
});
} else {
$('#wp-fullscreen-button-bar').addClass('wp-html-mode');
}
} else if ( s.mode === 'tinymce' ) {
$('#wp-fullscreen-mode-bar').removeClass('wp-html-mode').addClass('wp-tmce-mode')
.find('a').removeClass( 'active' ).filter('.wp-fullscreen-mode-tinymce').addClass( 'active' );
if ( fade ) {
$('#wp-fullscreen-button-bar').fadeOut( 150, function(){
$(this).removeClass('wp-html-mode').fadeIn( 150 );
});
} else {
$('#wp-fullscreen-button-bar').removeClass('wp-html-mode');
}
}
};
/**
* UI Elements
*
* Used for transitioning between states.
*/
api.ui = {
init: function() {
var toolbar;
s.toolbar = toolbar = $('#fullscreen-topbar');
s.$fullscreenFader = $('#fullscreen-fader');
s.$statusbar = $('#wp-fullscreen-status');
s.hasTinymce = typeof tinymce !== 'undefined';
if ( ! s.hasTinymce )
$('#wp-fullscreen-mode-bar').hide();
$document.keyup( function(e) {
var c = e.keyCode || e.charCode, modKey;
if ( ! s.visible ) {
return;
}
if ( navigator.platform && navigator.platform.indexOf('Mac') !== -1 ) {
modKey = e.ctrlKey; // Ctrl key for Mac
} else {
modKey = e.altKey; // Alt key for Win & Linux
}
if ( modKey && ( 61 === c || 107 === c || 187 === c ) ) { // +
api.dfwWidth( 25 );
e.preventDefault();
}
if ( modKey && ( 45 === c || 109 === c || 189 === c ) ) { // -
api.dfwWidth( -25 );
e.preventDefault();
}
if ( modKey && 48 === c ) { // 0
api.dfwWidth( 0 );
e.preventDefault();
}
});
$( window ).on( 'keydown.wp-fullscreen', function( event ) {
// Turn fullscreen off when Esc is pressed.
if ( 27 === event.keyCode && s.visible ) {
api.off();
event.stopImmediatePropagation();
}
});
if ( 'ontouchstart' in window ) {
$body.addClass('wp-dfw-touch');
}
toolbar.on( 'mouseenter', function() {
toggleUI('show');
}).on( 'mouseleave', function() {
toggleUI('autohide');
});
// Bind buttons
$('#wp-fullscreen-buttons').on( 'click.wp-fullscreen', 'button', function( event ) {
var command = event.currentTarget.id ? event.currentTarget.id.substr(6) : null;
if ( s.editor && 'tinymce' === s.mode ) {
switch( command ) {
case 'bold':
s.editor.execCommand('Bold');
break;
case 'italic':
s.editor.execCommand('Italic');
break;
case 'bullist':
s.editor.execCommand('InsertUnorderedList');
break;
case 'numlist':
s.editor.execCommand('InsertOrderedList');
break;
case 'link':
s.editor.execCommand('WP_Link');
break;
case 'unlink':
s.editor.execCommand('unlink');
break;
case 'help':
s.editor.execCommand('WP_Help');
break;
case 'blockquote':
s.editor.execCommand('mceBlockQuote');
break;
}
} else if ( command === 'link' && window.wpLink ) {
window.wpLink.open();
}
if ( command === 'wp-media-library' && typeof wp !== 'undefined' && wp.media && wp.media.editor ) {
wp.media.editor.open( s.id );
}
});
},
fade: function( before, during, after ) {
if ( ! s.$fullscreenFader ) {
api.ui.init();
}
// If any callback bound to before returns false, bail.
if ( before && ! ps.publish( before ) ) {
return;
}
api.fade.In( s.$fullscreenFader, 200, function() {
if ( during ) {
ps.publish( during );
}
api.fade.Out( s.$fullscreenFader, 200, function() {
if ( after ) {
ps.publish( after );
}
});
});
}
};
api.fade = {
// Sensitivity to allow browsers to render the blank element before animating.
sensitivity: 100,
In: function( element, speed, callback, stop ) {
callback = callback || $.noop;
speed = speed || 400;
stop = stop || false;
if ( api.fade.transitions ) {
if ( element.is(':visible') ) {
element.addClass( 'fade-trigger' );
return element;
}
element.show();
element.first().one( transitionend, function() {
callback();
});
setTimeout( function() { element.addClass( 'fade-trigger' ); }, this.sensitivity );
} else {
if ( stop ) {
element.stop();
}
element.css( 'opacity', 1 );
element.first().fadeIn( speed, callback );
if ( element.length > 1 ) {
element.not(':first').fadeIn( speed );
}
}
return element;
},
Out: function( element, speed, callback, stop ) {
callback = callback || $.noop;
speed = speed || 400;
stop = stop || false;
if ( ! element.is(':visible') ) {
return element;
}
if ( api.fade.transitions ) {
element.first().one( transitionend, function() {
if ( element.hasClass('fade-trigger') ) {
return;
}
element.hide();
callback();
});
setTimeout( function() { element.removeClass( 'fade-trigger' ); }, this.sensitivity );
} else {
if ( stop ) {
element.stop();
}
element.first().fadeOut( speed, callback );
if ( element.length > 1 ) {
element.not(':first').fadeOut( speed );
}
}
return element;
},
// Check if the browser supports CSS 3.0 transitions
transitions: ( function() {
var style = document.documentElement.style;
return ( typeof style.WebkitTransition === 'string' ||
typeof style.MozTransition === 'string' ||
typeof style.OTransition === 'string' ||
typeof style.transition === 'string' );
})()
};
/**
* Resize API
*
* Automatically updates textarea height.
*/
api.bind_resize = function() {
s.$dfwTextarea.on( 'keydown.wp-dfw-resize click.wp-dfw-resize paste.wp-dfw-resize', function() {
api.resizeTextarea();
});
};
api.resizeTextarea = function() {
var node = s.$dfwTextarea[0];
if ( node.scrollHeight > node.clientHeight ) {
node.style.height = node.scrollHeight + 50 + 'px';
}
};
// Export
window.wp = window.wp || {};
window.wp.editor = window.wp.editor || {};
window.wp.editor.fullscreen = api;
})( jQuery, window );