/** * Floating-Tools * Author: Philipp Stracker (2013) * Project page: http://stracker-phil.github.com/Floating-Tools/ */ (function() { var floatingtools = function() { this.dom = null; // the main toolbar-object this.toolbars = []; this.is_visible = false; this.hide_on_blur = true; //!TODO: Make this an configuration option this.toolbarsize = false; this.editoroffset = false; this.mousepos = {x:0, y:0}; }; CKEDITOR.plugins.add( 'floating-tools', { requires: 'toolbar', init: function( editor ) { /** * Create the UI elements required by this plugin * UI is the floating toolbar * Many parts of this function are taken from the toolbar plugin */ editor.on( 'uiSpace', function( event ) { // Create toolbar only once... event.removeListener(); editor.floatingtools = new floatingtools(); var labelId = CKEDITOR.tools.getNextId(); var output = [ // Did not find a nicer way to include the CSS required for the toolbar... '', '', editor.lang.toolbar.toolbars, '', '' ]; var groupStarted, pendingSeparator; var toolbars = editor.floatingtools.toolbars, toolbar = getFloatingToolbarConfig( editor ); // Build the toolbar for ( var r = 0; r < toolbar.length; r++ ) { var toolbarId, toolbarObj = 0, toolbarName, row = toolbar[ r ], items; // It's better to check if the row object is really // available because it's a common mistake to leave // an extra comma in the toolbar definition // settings, which leads on the editor not loading // at all in IE. (#3983) if ( !row ) continue; if ( groupStarted ) { output.push( '' ); groupStarted = 0; pendingSeparator = 0; } if ( row === '/' ) { output.push( '' ); continue; } items = row.items || row; // Create all items defined for this toolbar. for ( var i = 0; i < items.length; i++ ) { var item = items[ i ], canGroup; if ( item ) { if ( item.type == CKEDITOR.UI_SEPARATOR ) { // Do not add the separator immediately. Just save // it be included if we already have something in // the toolbar and if a new item is to be added (later). pendingSeparator = groupStarted && item; continue; } canGroup = item.canGroup !== false; // Initialize the toolbar first, if needed. if ( !toolbarObj ) { // Create the basic toolbar object. toolbarId = CKEDITOR.tools.getNextId(); toolbarObj = { id: toolbarId, items: [] }; toolbarName = row.name && ( editor.lang.toolbar.toolbarGroups[ row.name ] || row.name ); // Output the toolbar opener. output.push( '' ); // If a toolbar name is available, send the voice label. toolbarName && output.push( '', toolbarName, '' ); output.push( '' ); // Add the toolbar to the "editor.toolbox.toolbars" // array. var index = toolbars.push( toolbarObj ) - 1; // Create the next/previous reference. if ( index > 0 ) { toolbarObj.previous = toolbars[ index - 1 ]; toolbarObj.previous.next = toolbarObj; } } if ( canGroup ) { if ( !groupStarted ) { output.push( '' ); groupStarted = 1; } } else if ( groupStarted ) { output.push( '' ); groupStarted = 0; } function addItem( item ) { var itemObj = item.render( editor, output ); index = toolbarObj.items.push( itemObj ) - 1; if ( index > 0 ) { itemObj.previous = toolbarObj.items[ index - 1 ]; itemObj.previous.next = itemObj; } itemObj.toolbar = toolbarObj; // No need for keyboard handlers, the toolbar is only accessibly by mouse /* itemObj.onkey = itemKeystroke; // Fix for #3052: // Prevent JAWS from focusing the toolbar after document load. itemObj.onfocus = function() { if ( !editor.toolbox.focusCommandExecuted ) editor.focus(); }; */ } if ( pendingSeparator ) { addItem( pendingSeparator ); pendingSeparator = 0; } addItem( item ); } } if ( groupStarted ) { output.push( '' ); groupStarted = 0; pendingSeparator = 0; } if ( toolbarObj ) output.push( '' ); } output.push( '' ); event.data.html += output.join( '' ); }); /** * Do the magic: Attach eventhandlers to see if text is selected * When text is selected then show the floating toolbar, else hide it */ editor.on('contentDom', function( event ) { unfocus_toolbar(); /** * Attach an eventhandler to the mouse-up event */ editor.document.on('mouseup', function( mouse_event ) { // When user right-clicks, ctrl-clicks, etc. then do not show the toolbar data = mouse_event.data.$; if (data.button !== 0 || data.ctrlKey || data.altKey || data.shiftKey) return true; // When the user clears the selection by single-clicking in the editor then this event is fired before the selection is removed // So we add a short delay to give the browser a chance to remove the selection before we do anything setTimeout( function() { if (is_text_selected()) { // Save the current mouse-position set_mousepos (mouse_event.data.$); // when there is text selected after mouse-up: show the toolbar editor.execCommand('showFloatingTools'); } else { // when no text is selected then hide the toolbar editor.execCommand('hideFloatingTools'); } }, 100); }); /** * On keypress we will always hide the toolbar * The toolbar is only accessible via mouse */ editor.document.on('keyup', function( key_event ) { editor.execCommand('hideFloatingTools'); }); /** * On blur hide the toolbar (editor looses focus) */ editor.on('blur', function( e ) { if (editor.floatingtools.hide_on_blur) { hide_toolbar(); } }); /** * Attach the mouse-over event to the toolbar. * When cursor is above the toolbar then set opacity to 1 */ toolbar = get_element(); toolbar.on('mouseover', function( mouse_event ) { focus_toolbar(); }); /** * When the mouse moves out of the toolbar then make it transparent again */ toolbar.on('mouseout', function( mouse_event ) { unfocus_toolbar(); }) editor.container.addClass('pos-relative'); console.log(editor.container); }); /** * Display the floating toolbar */ editor.addCommand( 'showFloatingTools', { exec : function( editor ) { if (is_text_selected()) { toolbar = get_element(); unfocus_toolbar(); toolbar.show(); // Get the size of the toolbar size = get_toolbar_size() // Get the offset of the editor offset = get_editor_offset(); // Get the mouse position pos = get_mousepos(); // Calculate the position for the toolbar toolpos = calculate_position(pos, size, offset); toolbar.setStyles({ 'left' : toolpos.x + 'px', 'top' : toolpos.y + 'px' }); editor.floatingtools.is_visible = true; } } }); /** * Hide the floating toolbar */ editor.addCommand( 'hideFloatingTools', { exec : function( editor ) { hide_toolbar(); } }); /** * ===== Behind the scenes. Getters, setters, calculation, etc. */ hide_toolbar = function() { if (false != editor.floatingtools.is_visible) { toolbar = get_element(); toolbar.hide(); editor.floatingtools.is_visible = false; } } /** * Store the current mouse-position, so we can position the toolbar near the cursor */ set_mousepos = function(data) { editor.floatingtools.mousepos = { left: data.clientX, top: data.clientY }; } /** * Store the current mouse-position, so we can position the toolbar near the cursor */ get_mousepos = function() { return editor.floatingtools.mousepos; } /** * Returns the main toolbar-object (the parent of all items in the floating-toolbar) */ get_element = function() { if (! editor.floatingtools.dom) { var dom_id = editor.ui.spaceId( 'floatingtools' ); editor.floatingtools.dom = CKEDITOR.document.getById( dom_id ); } return editor.floatingtools.dom; } /** * Returns the offset of the editor area (effectively the height of the top-toolbar) */ get_editor_offset = function() { if (! editor.floatingtools.editoroffset) { var editor_id = editor.ui.spaceId( 'contents' ); var obj = CKEDITOR.document.getById( editor_id ); editor.floatingtools.editoroffset = { left: obj.$.offsetLeft, top: obj.$.offsetTop, width: obj.$.offsetWidth, height: obj.$.offsetHeight }; } return editor.floatingtools.editoroffset; } /** * Calculates the position for the toolbar */ calculate_position = function(pos, toolbar_size, offset) { toolpos = { x: pos.left + offset.left - (toolbar_size.width/2), y: pos.top + offset.top - (toolbar_size.height + 20) } // make sure toolbar does not extend out of the left CKEditor border if (toolpos.x < offset.left + 2) toolpos.x = offset.left + 2; // make sure toolbar does not extend out of the right CKEditor border if (pos.left + (toolbar_size.width/2) >= offset.left + offset.width-2 ) toolpos.x = offset.left + offset.width - toolbar_size.width - 2; // Make sure toolbar does no go into the top toolbar area if (toolpos.y < offset.top) toolpos.y = offset.top; // make sure toolbar does not cover the mouse-cursor when text in the top line is selected if (offset.top+pos.top > toolpos.y && offset.top+pos.top < toolpos.y+toolbar_size.height) toolpos.y = offset.top + pos.top + 24; // display toolbar below the cursor return toolpos; } /** * Returns the size of the floating toolbar */ get_toolbar_size = function() { if (! editor.floatingtools.toolbarsize) { var obj = get_element(); editor.floatingtools.toolbarsize = { width: obj.$.offsetWidth, height: obj.$.offsetHeight }; } return editor.floatingtools.toolbarsize; } /** * Check if text is selected. * Retrns true when there is at least 1 character selected in the editor */ is_text_selected = function () { var text = editor.getSelection().getSelectedText(); return text != ''; } /** * Make the toolbar opaque */ focus_toolbar = function() { obj = get_element(); obj.setOpacity(1); } /** * Make the toolbar transparent */ unfocus_toolbar = function() { obj = get_element(); obj.setOpacity(0.25); }, /** * Get the plugin configuration. * Kidnapped from the toolbar-plugin... */ getFloatingToolbarConfig = function( editor ) { var removeButtons = editor.config.removeButtons; removeButtons = removeButtons && removeButtons.split( ',' ); function buildToolbarConfig() { // Take the base for the new toolbar, which is basically a toolbar // definition without items. var toolbar = getPrivateFloatingToolbarGroups( editor ); return populateToolbarConfig( toolbar ); } // Returns an object containing all toolbar groups used by ui items. function getItemDefinedGroups() { var groups = {}, itemName, item, itemToolbar, group, order; for ( itemName in editor.ui.items ) { item = editor.ui.items[ itemName ]; itemToolbar = item.toolbar || 'others'; if ( itemToolbar ) { // Break the toolbar property into its parts: "group_name[,order]". itemToolbar = itemToolbar.split( ',' ); group = itemToolbar[ 0 ]; order = parseInt( itemToolbar[ 1 ] || -1, 10 ); // Initialize the group, if necessary. groups[ group ] || ( groups[ group ] = [] ); // Push the data used to build the toolbar later. groups[ group ].push( { name: itemName, order: order} ); } } // Put the items in the right order. for ( group in groups ) { groups[ group ] = groups[ group ].sort( function( a, b ) { return a.order == b.order ? 0 : b.order < 0 ? -1 : a.order < 0 ? 1 : a.order < b.order ? -1 : 1; }); } return groups; } function fillGroup( toolbarGroup, uiItems ) { if ( uiItems.length ) { if ( toolbarGroup.items ) toolbarGroup.items.push( editor.ui.create( '-' ) ); else toolbarGroup.items = []; var item, name; while ( ( item = uiItems.shift() ) ) { name = typeof item == 'string' ? item : item.name; // Ignore items that are configured to be removed. if ( !removeButtons || CKEDITOR.tools.indexOf( removeButtons, name ) == -1 ) { item = editor.ui.create( name ); if ( !item ) continue; if ( !editor.addFeature( item ) ) continue; toolbarGroup.items.push( item ); } } } } function populateToolbarConfig( config ) { var toolbar = [], i, group, newGroup; for ( i = 0; i < config.length; ++i ) { group = config[ i ]; newGroup = {}; if ( group == '/' ) toolbar.push( group ); else if ( CKEDITOR.tools.isArray( group) ) { fillGroup( newGroup, CKEDITOR.tools.clone( group ) ); toolbar.push( newGroup ); } else if ( group.items ) { fillGroup( newGroup, CKEDITOR.tools.clone( group.items ) ); newGroup.name = group.name; toolbar.push( newGroup); } } return toolbar; } var toolbar = editor.config.floatingtools; // If it is a string, return the relative "toolbar_name" config. if ( typeof toolbar == 'string' ) toolbar = editor.config[ 'floatingtools_' + toolbar ]; return ( editor.toolbar = toolbar ? populateToolbarConfig( toolbar ) : buildToolbarConfig() ); }, /** * Return the default toolbar configuration. */ getPrivateFloatingToolbarGroups = function( editor ) { return editor._.floatingToolsGroups || ( editor._.floatingToolsGroups = [ { name: 'styles', items: [ 'Font','FontSize' ]}, { name: 'format', items: [ 'Bold','Italic' ]}, { name: 'paragraph', items: [ 'JustifyCenter','Outdent','Indent','NumberedList','BulletedList' ]} ]); } } } ); })();