1 /*! 2 * This file is part of Aloha Editor Project http://aloha-editor.org 3 * Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com 4 * Contributors http://aloha-editor.org/contribution.php 5 * Licensed unter the terms of http://www.aloha-editor.org/license.html 6 *//* 7 * Aloha Editor is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU Affero General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version.* 11 * 12 * Aloha Editor is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 define( 21 ['aloha/core', 'aloha/jquery', 'aloha/ext', 'util/class', 'aloha/console', 'vendor/jquery.store', 'aloha/selection'], 22 function(Aloha, jQuery, Ext, Class, console) { 23 "use strict"; 24 var GENTICS = window.GENTICS; 25 26 /** 27 * Constructor for a floatingmenu tab 28 * @namespace Aloha.FloatingMenu 29 * @class Tab 30 * @constructor 31 * @param {String} label label of the tab 32 */ 33 var Tab = Class.extend({ 34 _constructor: function(label) { 35 this.label = label; 36 this.groups = []; 37 this.groupMap = {}; 38 this.visible = true; 39 }, 40 41 /** 42 * Get the group with given index. If it does not yet exist, create a new one 43 * @method 44 * @param {int} group group index of the group to get 45 * @return group object 46 */ 47 getGroup: function(group) { 48 var groupObject = this.groupMap[group]; 49 if (typeof groupObject === 'undefined') { 50 groupObject = new Group(); 51 this.groupMap[group] = groupObject; 52 this.groups.push(groupObject); 53 // TODO resort the groups 54 } 55 56 return groupObject; 57 }, 58 59 /** 60 * Get the EXT component representing the tab 61 * @return EXT component (EXT.Panel) 62 63 * @hide 64 */ 65 getExtComponent: function () { 66 var that = this; 67 68 if (!this.extPanel) { 69 this.extPanel = new Ext.Panel({ 70 'tbar' : [], 71 'title' : this.label, 72 'style': 'margin-top:0px', 73 'bodyStyle': 'display:none', 74 'autoScroll': true 75 }); 76 } 77 78 jQuery.each(this.groups, function(index, group) { 79 // let each group generate its ext component and add them to 80 // the panel once. 81 if (!group.extButtonGroup) { 82 that.extPanel.getTopToolbar().add(group.getExtComponent()); 83 } 84 }); 85 86 return this.extPanel; 87 }, 88 89 /** 90 * Recalculate the visibility of all groups within the tab 91 * @hide 92 */ 93 doLayout: function() { 94 var that = this; 95 96 if (Aloha.Log.isDebugEnabled()) { 97 Aloha.Log.debug(this, 'doLayout called for tab ' + this.label); 98 } 99 this.visible = false; 100 101 // check all groups in this tab 102 jQuery.each(this.groups, function(index, group) { 103 that.visible |= group.doLayout(); 104 }); 105 106 if (Aloha.Log.isDebugEnabled()) { 107 Aloha.Log.debug(this, 'tab ' + this.label + (this.visible ? ' is ' : ' is not ') + 'visible now'); 108 } 109 110 return this.visible; 111 } 112 }); 113 114 /** 115 * Constructor for a floatingmenu group 116 * @namespace Aloha.FloatingMenu 117 * @class Group 118 * @constructor 119 */ 120 var Group = Class.extend({ 121 _constructor: function() { 122 this.buttons = []; 123 this.fields = []; 124 }, 125 126 /** 127 * Add a button to this group 128 * @param {Button} buttonInfo to add to the group 129 */ 130 addButton: function(buttonInfo) { 131 if (buttonInfo.button instanceof Aloha.ui.AttributeField) { 132 if (this.fields.length < 2) { 133 this.fields.push(buttonInfo); 134 } else { 135 throw new Error("Too much fields in this group"); 136 } 137 } else { 138 // Every plugin API entryPoint (method) should be securised enough 139 // to avoid Aloha to block at startup even 140 // if a plugin is badly designed 141 if (typeof buttonInfo.button !== "undefined"){ 142 this.buttons.push(buttonInfo); 143 } 144 } 145 }, 146 147 /** 148 * Get the EXT component representing the group (Ext.ButtonGroup) 149 * @return the Ext.ButtonGroup 150 * @hide 151 */ 152 getExtComponent: function () { 153 var that = this, l, 154 items = [], 155 buttonCount = 0, 156 columnCount = 0, 157 len, idx, half; 158 159 160 if (typeof this.extButtonGroup === 'undefined') { 161 162 if (this.fields.length > 1) { 163 columnCount = 1; 164 } 165 166 jQuery.each(this.buttons, function(index, button) { 167 // count the number of buttons (large buttons count as 2) 168 buttonCount += button.button.size == 'small' ? 1 : 2; 169 }); 170 columnCount = columnCount + Math.ceil(buttonCount / 2); 171 172 len = this.buttons.length; 173 idx = 0; 174 half = Math.ceil(this.buttons.length / 2) - this.buttons.length % 2 ; 175 176 if (this.fields.length > 0) { 177 that.buttons.push(this.fields[0]); 178 items.push(this.fields[0].button.getExtConfigProperties()); 179 } 180 181 while (--len >= half) { 182 items.push(this.buttons[idx++].button.getExtConfigProperties()); 183 } 184 ++len; 185 if (this.fields.length > 1) { 186 that.buttons.push(this.fields[1]); 187 items.push(this.fields[1].button.getExtConfigProperties()); 188 } 189 while (--len >=0) { 190 items.push(this.buttons[idx++].button.getExtConfigProperties()); 191 } 192 193 this.extButtonGroup = new Ext.ButtonGroup({ 194 'columns' : columnCount, 195 'items': items 196 }); 197 198 // jQuery.each(this.fields, function(id, field){ 199 // that.buttons.push(field); 200 // }); 201 // now find the Ext.Buttons and set to the GENTICS buttons 202 jQuery.each(this.buttons, function(index, buttonInfo) { 203 buttonInfo.button.extButton = that.extButtonGroup.findById(buttonInfo.button.id); 204 // the following code is a work arround because ExtJS initializes later. 205 // The ui wrapper store the information and here we use it... ugly. 206 // if there are any listeners added before initializing the extButtons 207 if ( buttonInfo.button.listenerQueue && buttonInfo.button.listenerQueue.length > 0 ) { 208 while ( true ) { 209 l = buttonInfo.button.listenerQueue.shift(); 210 if ( !l ) {break;} 211 buttonInfo.button.extButton.addListener(l.eventName, l.handler, l.scope, l.options); 212 } 213 } 214 if (buttonInfo.button.extButton.setObjectTypeFilter) { 215 if (buttonInfo.button.objectTypeFilter) { 216 buttonInfo.button.extButton.noQuery = false; 217 } 218 if ( buttonInfo.button.objectTypeFilter == 'all' ) { 219 buttonInfo.button.objectTypeFilter = null; 220 } 221 buttonInfo.button.extButton.setObjectTypeFilter(buttonInfo.button.objectTypeFilter); 222 if ( buttonInfo.button.displayField) { 223 buttonInfo.button.extButton.displayField = buttonInfo.button.displayField; 224 } 225 if ( buttonInfo.button.tpl ) { 226 buttonInfo.button.extButton.tpl = buttonInfo.button.tpl; 227 } 228 } 229 }); 230 } 231 232 return this.extButtonGroup; 233 }, 234 235 /** 236 * Recalculate the visibility of the buttons and the group 237 * @hide 238 */ 239 doLayout: function () { 240 var groupVisible = false, 241 that = this; 242 jQuery.each(this.buttons, function(index, button) { 243 if (typeof button.button !== "undefined") { 244 var extButton = that.extButtonGroup.findById(button.button.id), 245 buttonVisible = button.button.isVisible() && button.scopeVisible; 246 247 if (!extButton) { 248 return; 249 } 250 251 if (buttonVisible && extButton.hidden) { 252 extButton.show(); 253 } else if (!buttonVisible && extButton && !extButton.hidden) { 254 extButton.hide(); 255 } 256 257 groupVisible |= buttonVisible; 258 } 259 }); 260 if (groupVisible && this.extButtonGroup.hidden) { 261 this.extButtonGroup.show(); 262 } else if (!groupVisible && !this.extButtonGroup.hidden) { 263 this.extButtonGroup.hide(); 264 } 265 266 return groupVisible; 267 268 } 269 }); 270 271 //========================================================================= 272 // 273 // Floating Menu 274 // 275 //========================================================================= 276 277 var lastFloatingMenuPos = { 278 top: null, 279 left: null 280 }; 281 282 /** 283 * Handler for window scroll event. Positions the floating menu 284 * appropriately. 285 * 286 * @param {Aloha.FloatingMenu} floatingmenu 287 */ 288 function onWindowScroll( floatingmenu ) { 289 if ( !Aloha.activeEditable ) { 290 return; 291 } 292 293 var element = floatingmenu.obj; 294 var editablePos = Aloha.activeEditable.obj.offset(); 295 var isTopAligned = floatingmenu.behaviour === 'topalign'; 296 var isAppended = floatingmenu.behaviour === 'append'; 297 var isManuallyPinned = floatingmenu.pinned 298 && ( parseInt( element.css( 'left' ), 10 ) 299 != ( editablePos.left 300 + floatingmenu.horizontalOffset 301 ) ); 302 303 // no calculation when pinned manually or has behaviour 'append' 304 if ( isTopAligned && isManuallyPinned || isAppended ) { 305 return; 306 } 307 308 var floatingmenuHeight = element.height(); 309 var scrollTop = jQuery( document ).scrollTop(); 310 311 // This value is what the top position of the floating menu *would* be 312 // if we tried to position it above the active editable. 313 var floatingmenuTop = editablePos.top - floatingmenuHeight 314 + floatingmenu.marginTop 315 - floatingmenu.topalignOffset; 316 317 // The floating menu does not fit in the space between the top of the 318 // viewport and the editable, so position it at the top of the viewport 319 // and over the editable. 320 if ( scrollTop > floatingmenuTop ) { 321 editablePos.top = isTopAligned 322 ? scrollTop + floatingmenu.marginTop 323 : floatingmenu.marginTop; 324 325 // There is enough space on top of the editable to fit the entire 326 // floating menu, so we do so. 327 } else if ( scrollTop <= floatingmenuTop ) { 328 editablePos.top -= floatingmenuHeight 329 + ( isTopAligned 330 ? floatingmenu.marginTop + 331 floatingmenu.topalignOffset 332 : 0 ); 333 } 334 335 floatingmenu.floatTo( editablePos ); 336 } 337 338 /** 339 * Aloha's Floating Menu 340 * @namespace Aloha 341 * @class FloatingMenu 342 * @singleton 343 */ 344 var FloatingMenu = Class.extend({ 345 /** 346 * Define the default scopes 347 * @property 348 * @type Object 349 */ 350 scopes: { 351 'Aloha.empty' : { 352 'name' : 'Aloha.empty', 353 'extendedScopes' : [], 354 'buttons' : [] 355 }, 356 'Aloha.global' : { 357 'name' : 'Aloha.global', 358 'extendedScopes' : ['Aloha.empty'], 359 'buttons' : [] 360 }, 361 'Aloha.continuoustext' : { 362 'name' : 'Aloha.continuoustext', 363 'extendedScopes' : ['Aloha.global'], 364 'buttons' : [] 365 } 366 }, 367 368 /** 369 * Array of tabs within the floatingmenu 370 * @hide 371 */ 372 tabs: [], 373 374 /** 375 * 'Map' of tabs (for easy access) 376 * @hide 377 */ 378 tabMap: {}, 379 380 /** 381 * Flag to mark whether the floatingmenu is initialized 382 * @hide 383 */ 384 initialized: false, 385 386 /** 387 * Array containing all buttons 388 * @hide 389 */ 390 allButtons: [], 391 392 /** 393 * top part of the floatingmenu position 394 * @hide 395 */ 396 top: 100, 397 398 /** 399 * left part of the floatingmenu position 400 * @hide 401 */ 402 left: 100, 403 404 /** 405 * store pinned status - true, if the FloatingMenu is pinned 406 * @property 407 * @type boolean 408 */ 409 pinned: false, 410 411 /** 412 * just a reference to the jQuery(window) object, which is used quite often 413 */ 414 window: jQuery(window), 415 416 /** 417 * Aloha.settings.floatingmenu.behaviour 418 * 419 * Is used to define the floating menu (fm) float behaviour. 420 * 421 * available: 422 * 'float' (default) the fm will float next to the position where the caret is, 423 * 'topalign' the fm is fixed above the contentEditable which is active, 424 * 'append' the fm is appended to the defined 'element' element position (top/left) 425 */ 426 behaviour: 'float', 427 428 /** 429 * Aloha.settings.floatingmenu.element 430 * 431 * Is used to define the element where the floating menu is positioned when 432 * Aloha.settings.floatingmenu.behaviour is set to 'append' 433 * 434 */ 435 element: 'floatingmenu', 436 437 /** 438 * topalign offset to be used for topalign behavior 439 */ 440 topalignOffset: 0, 441 442 /** 443 * topalign offset to be used for topalign behavior 444 */ 445 horizontalOffset: 0, 446 447 /** 448 * will only be hounoured when behaviour is set to 'topalign'. Adds a margin, 449 * so the floating menu is not directly attached to the top of the page 450 */ 451 marginTop: 10, 452 453 /** 454 * Define whether the floating menu shall be draggable or not via Aloha.settings.floatingmanu.draggable 455 * Default is: true 456 */ 457 draggable: true, 458 459 /** 460 * Define whether the floating menu shall be pinned or not via Aloha.settings.floatingmanu.pin 461 * Default is: false 462 */ 463 pin: false, 464 465 /** 466 * A list of all buttons that have been added to the floatingmenu 467 * This needs to be tracked, as adding buttons twice will break the fm 468 */ 469 buttonsAdded: [], 470 471 /** 472 * Will be initialized by checking Aloha.settings.toolbar, which will contain the config for 473 * the floating menu. If there is no config, tabs and groups will be generated programmatically 474 */ 475 fromConfig: false, 476 477 /** 478 * hide a tab 479 */ 480 hideTab: false, 481 482 /** 483 * Initialize the floatingmenu 484 * @hide 485 */ 486 init: function() { 487 488 // check for behaviour setting of the floating menu 489 if ( Aloha.settings.floatingmenu ) { 490 if ( typeof Aloha.settings.floatingmenu.draggable === 491 'boolean' ) { 492 this.draggable = Aloha.settings.floatingmenu.draggable; 493 } 494 495 if ( typeof Aloha.settings.floatingmenu.behaviour === 496 'string' ) { 497 this.behaviour = Aloha.settings.floatingmenu.behaviour; 498 } 499 500 if ( typeof Aloha.settings.floatingmenu.topalignOffset !== 501 'undefined' ) { 502 this.topalignOffset = parseInt( 503 Aloha.settings.floatingmenu.topalignOffset, 10 ); 504 } 505 506 if ( typeof Aloha.settings.floatingmenu.horizontalOffset !== 507 'undefined' ) { 508 this.horizontalOffset = parseInt( 509 Aloha.settings.floatingmenu.horizontalOffset , 10 ); 510 } 511 512 if ( typeof Aloha.settings.floatingmenu.marginTop === 513 'number' ) { 514 this.marginTop = parseInt( 515 Aloha.settings.floatingmenu.marginTop , 10 ); 516 } 517 518 if ( typeof Aloha.settings.floatingmenu.element === 519 'string' ) { 520 this.element = Aloha.settings.floatingmenu.element; 521 } 522 if ( typeof Aloha.settings.floatingmenu.pin === 523 'boolean' ) { 524 this.pin = Aloha.settings.floatingmenu.pin; 525 } 526 527 if ( typeof Aloha.settings.floatingmenu.width !== 528 'undefined' ) { 529 this.width = parseInt( Aloha.settings.floatingmenu.width, 530 10 ); 531 } 532 } 533 534 jQuery.storage = new jQuery.store(); 535 536 this.currentScope = 'Aloha.global'; 537 538 var that = this; 539 540 this.window.unload(function () { 541 // store fm position if the panel is pinned to be able to restore it next time 542 if (that.pinned) { 543 jQuery.storage.set('Aloha.FloatingMenu.pinned', 'true'); 544 jQuery.storage.set('Aloha.FloatingMenu.top', that.top); 545 jQuery.storage.set('Aloha.FloatingMenu.left', that.left); 546 if (Aloha.Log.isInfoEnabled()) { 547 Aloha.Log.info(this, 'stored FloatingMenu pinned position {' + that.left 548 + ', ' + that.top + '}'); 549 } 550 } else { 551 // delete old localStorages 552 jQuery.storage.del('Aloha.FloatingMenu.pinned'); 553 jQuery.storage.del('Aloha.FloatingMenu.top'); 554 jQuery.storage.del('Aloha.FloatingMenu.left'); 555 } 556 if (that.userActivatedTab) { 557 jQuery.storage.set('Aloha.FloatingMenu.activeTab', that.userActivatedTab); 558 } 559 }).resize(function () { 560 if (that.behaviour === 'float') { 561 if (that.pinned) { 562 that.fixPinnedPosition(); 563 that.refreshShadow(); 564 that.extTabPanel.setPosition(that.left, that.top); 565 } else { 566 var target = that.calcFloatTarget(Aloha.Selection.getRangeObject()); 567 if (target) { 568 that.floatTo(target); 569 } 570 } 571 } 572 }); 573 Aloha.bind('aloha-ready', function() { 574 that.generateComponent(); 575 that.initialized = true; 576 }); 577 578 if (typeof Aloha.settings.toolbar === 'object') { 579 this.fromConfig = true; 580 } 581 }, 582 583 /** 584 * jQuery reference to the extjs tabpanel 585 * @hide 586 */ 587 obj: null, 588 589 /** 590 * jQuery reference to the shadow obj 591 * @hide 592 */ 593 shadow: null, 594 595 /** 596 * jQuery reference to the panels body wrap div 597 * @hide 598 */ 599 panelBody: null, 600 601 /** 602 * The panels width 603 * @hide 604 */ 605 width: 400, 606 607 /** 608 * initialize tabs and groups according to the current configuration 609 */ 610 initTabsAndGroups: function () { 611 var that = this; 612 613 // if there is no toolbar config tabs and groups have been initialized before 614 if (!this.fromConfig) { 615 return; 616 } 617 618 jQuery.each(Aloha.settings.toolbar.tabs, function (tab, groups) { 619 // generate or retrieve tab 620 var tabObject = that.tabMap[tab]; 621 if (typeof tabObject === 'undefined') { 622 // the tab object does not yet exist, so create a new tab and add it to the list 623 tabObject = new Tab(tab); 624 that.tabs.push(tabObject); 625 that.tabMap[tab] = tabObject; 626 } 627 628 // generate groups for current tab 629 jQuery.each(groups, function (group, buttons) { 630 var groupObject = tabObject.getGroup(group), 631 i; 632 633 // now get all the buttons for that group 634 jQuery.each(buttons, function (j, button) { 635 if (jQuery.inArray(button, that.buttonsAdded) !== -1) { 636 // buttons must not be added twice 637 console.warn('Skipping button {' + button + '}. A button can\'t be added ' + 638 'to the floating menu twice. Config key: {Aloha.settings.toolbar.' + 639 tab + '.' + group + '}'); 640 return; 641 } 642 643 // now add the button to the group 644 for (i = 0; i < that.allButtons.length; i++) { 645 if (button === that.allButtons[i].button.name) { 646 groupObject.addButton(that.allButtons[i]); 647 // remember that we've added the button 648 that.buttonsAdded.push(that.allButtons[i].button.name); 649 break; 650 } 651 } 652 }); 653 }); 654 }); 655 }, 656 657 /** 658 * Generate the rendered component for the floatingmenu 659 * @hide 660 */ 661 generateComponent: function () { 662 var that = this, pinTab; 663 664 // initialize tabs and groups first 665 this.initTabsAndGroups(); 666 667 // Initialize and configure the tooltips 668 Ext.QuickTips.init(); 669 Ext.apply(Ext.QuickTips.getQuickTip(), { 670 minWidth : 10 671 }); 672 673 674 675 if (this.extTabPanel) { 676 // TODO dispose of the ext component 677 } else { 678 679 // Enable or disable the drag functionality 680 var dragConfiguration = false; 681 682 if ( that.draggable ) { 683 dragConfiguration = { 684 insertProxy: false, 685 onDrag : function(e) { 686 var pel = this.proxy.getEl(); 687 this.x = pel.getLeft(true); 688 this.y = pel.getTop(true); 689 this.panel.shadow.hide(); 690 }, 691 endDrag : function(e) { 692 var top = (that.pinned) ? this.y - jQuery(document).scrollTop() : this.y; 693 694 that.left = this.x; 695 that.top = top; 696 697 // store the last floating menu position when the floating menu was dragged around 698 lastFloatingMenuPos.left = that.left; 699 lastFloatingMenuPos.top = that.top; 700 701 this.panel.setPosition(this.x, top); 702 that.refreshShadow(); 703 this.panel.shadow.show(); 704 } 705 }; 706 } 707 // generate the tabpanel object 708 this.extTabPanel = new Ext.TabPanel({ 709 activeTab: 0, 710 width: that.width, // 336px this fits the multisplit button and 6 small buttons placed in 3 cols 711 plain: false, 712 draggable: dragConfiguration, 713 floating: {shadow: false}, 714 defaults: { 715 autoScroll: true 716 }, 717 layoutOnTabChange : true, 718 shadow: false, 719 cls: 'aloha-floatingmenu ext-root', 720 listeners : { 721 'tabchange' : { 722 'fn' : function(tabPanel, tab) { 723 if (tab.title != that.autoActivatedTab) { 724 if (Aloha.Log.isDebugEnabled()) { 725 Aloha.Log.debug(that, 'User selected tab ' + tab.title); 726 } 727 // remember the last user-selected tab 728 that.userActivatedTab = tab.title; 729 } else { 730 if (Aloha.Log.isDebugEnabled()) { 731 Aloha.Log.debug(that, 'Tab ' + tab.title + ' was activated automatically'); 732 } 733 } 734 that.autoActivatedTab = undefined; 735 736 // ok, this is kind of a hack: when the tab changes, we check all buttons for multisplitbuttons (which have the method setActiveDOMElement). 737 // if a DOM Element is queued to be set active, we try to do this now. 738 // the reason for this is that the active DOM element can only be set when the multisplit button is currently visible. 739 jQuery.each(that.allButtons, function(index, buttonInfo) { 740 if (typeof buttonInfo.button !== 'undefined' 741 742 && typeof buttonInfo.button.extButton !== 'undefined' 743 && buttonInfo.button.extButton !== null 744 && typeof buttonInfo.button.extButton.setActiveDOMElement === 'function') { 745 if (typeof buttonInfo.button.extButton.activeDOMElement !== 'undefined') { 746 buttonInfo.button.extButton.setActiveDOMElement(buttonInfo.button.extButton.activeDOMElement); 747 } 748 } 749 }); 750 751 // adapt the shadow 752 if (that.extTabPanel.isVisible()) { 753 that.extTabPanel.shadow.show(); 754 that.refreshShadow(); 755 } 756 } 757 } 758 }, 759 enableTabScroll : true 760 }); 761 762 763 } 764 765 // add the tabs 766 jQuery.each(this.tabs, function(index, tab) { 767 // let each tab generate its ext component and add them to the panel 768 try { 769 if (!tab.extPanel) { 770 // if the tab itself was not generated, we do this and add it to the panel 771 that.extTabPanel.add(tab.getExtComponent()); 772 } else { 773 // otherwise, we will make sure that probably missing groups are generated, but don't add the tab to the menu (again) 774 tab.getExtComponent(); 775 } 776 } catch(e) { 777 Aloha.Log.error(that,"Error while inserting tab: " + e); 778 } 779 }); 780 781 // add the dropshadow 782 if (!this.extTabPanel.shadow) { 783 this.extTabPanel.shadow = jQuery('<div id="aloha-floatingmenu-shadow" class="aloha-shadow"> </div>').hide(); 784 jQuery('body').append(this.extTabPanel.shadow); 785 } 786 787 // add an empty pin tab item, store reference 788 pinTab = this.extTabPanel.add({ 789 title : ' ' 790 }); 791 792 // finally render the panel to the body 793 this.extTabPanel.render(document.body); 794 795 // finish the pin element after the FM has rendered (before there are noe html contents to be manipulated 796 jQuery(pinTab.tabEl) 797 .addClass('aloha-floatingmenu-pin') 798 .html(' ') 799 .mousedown(function (e) { 800 that.togglePin(); 801 // Note: this event is deliberately stopped here, although normally, 802 // we would set the flag GENTICS.Aloha.eventHandled instead. 803 // But when the event bubbles up, no tab would be selected and 804 // the floatingmenu would be rather thin. 805 e.stopPropagation(); 806 }); 807 808 // a reference to the panels body needed for shadow size & position 809 this.panelBody = jQuery('div.aloha-floatingmenu div.x-tab-panel-bwrap'); 810 811 // do the visibility 812 this.doLayout(); 813 814 // bind jQuery reference to extjs obj 815 // this has to be done AFTER the tab panel has been rendered 816 this.obj = jQuery(this.extTabPanel.getEl().dom); 817 818 if (jQuery.storage.get('Aloha.FloatingMenu.pinned') == 'true') { 819 //this.togglePin(); 820 821 this.top = parseInt(jQuery.storage.get('Aloha.FloatingMenu.top'),10); 822 this.left = parseInt(jQuery.storage.get('Aloha.FloatingMenu.left'),10); 823 824 // do some positioning fixes 825 this.fixPinnedPosition(); 826 827 if (Aloha.Log.isInfoEnabled()) { 828 Aloha.Log.info(this, 'restored FloatingMenu pinned position {' + this.left + ', ' + this.top + '}'); 829 } 830 831 this.refreshShadow(); 832 } 833 834 // set the user activated tab stored in a localStorage 835 if (jQuery.storage.get('Aloha.FloatingMenu.activeTab')) { 836 this.userActivatedTab = jQuery.storage.get('Aloha.FloatingMenu.activeTab'); 837 } 838 839 // for now, position the panel somewhere 840 this.extTabPanel.setPosition(this.left, this.top); 841 842 // mark the event being handled by aloha, because we don't want to recognize 843 // a click into the floatingmenu to be a click into nowhere (which would 844 // deactivate the editables) 845 this.obj.mousedown(function (e) { 846 e.originalEvent.stopSelectionUpdate = true; 847 Aloha.eventHandled = true; 848 //e.stopSelectionUpdate = true; 849 }); 850 851 this.obj.mouseup(function (e) { 852 e.originalEvent.stopSelectionUpdate = true; 853 Aloha.eventHandled = false; 854 }); 855 856 jQuery( window ).scroll(function() { 857 onWindowScroll( that ); 858 }); 859 860 // don't display the drag handle bar / pin when floating menu is not draggable 861 if ( !that.draggable ) { 862 jQuery('.aloha-floatingmenu').hover( function() { 863 jQuery(this).css({background: 'none'}); 864 jQuery('.aloha-floatingmenu-pin').hide(); 865 }); 866 } 867 868 // adjust float behaviour 869 if (this.behaviour === 'float') { 870 // listen to selectionChanged event 871 Aloha.bind('aloha-selection-changed',function(event, rangeObject) { 872 if (!that.pinned) { 873 var pos = that.calcFloatTarget(rangeObject); 874 if (pos) { 875 that.floatTo(pos); 876 } 877 } 878 }); 879 } else if (this.behaviour === 'append' ) { 880 var p = jQuery( "#" + that.element ); 881 var position = p.offset(); 882 883 if ( !position ) { 884 Aloha.Log.warn(that, 'Invalid element HTML ID for floating menu: ' + that.element); 885 return false; 886 } 887 888 // set the position so that it does not float on the first editable activation 889 this.floatTo( position ); 890 891 if ( this.pin ) { 892 this.togglePin( true ); 893 } 894 895 Aloha.bind( 'aloha-editable-activated', function( event, data ) { 896 if ( that.pinned ) { 897 return; 898 } 899 that.floatTo( position ); 900 }); 901 902 } else if ( this.behaviour === 'topalign' ) { 903 // topalign will retain the user's pinned status 904 // TODO maybe the pin should be hidden in that case? 905 this.togglePin( false ); 906 907 // Float the menu to the editable that is activated. 908 Aloha.bind( 'aloha-editable-activated', function( event, data ) { 909 if ( that.pinned ) { 910 return; 911 } 912 913 // FIXME: that.obj.height() does not return the correct 914 // height of the editable. We need to fix this, and 915 // not hard-code the height as we currently do. 916 var editable = data.editable.obj; 917 var floatingmenuHeight = 90; 918 var editablePos = editable.offset(); 919 var isFloatingmenuAboveViewport = ( ( 920 editablePos.top - floatingmenuHeight ) 921 922 < jQuery( document ).scrollTop() ); 923 924 if ( isFloatingmenuAboveViewport ) { 925 // Since we don't have space to place the floatingmenu 926 // above the editable, we want to place it over the 927 // editable instead. But if the editable is shorter 928 // than the floatingmenu, it would be completely 929 // covered by it, and so, in such cases, we position 930 // the editable at the bottom of the short editable. 931 editablePos.top = ( editable.height() 932 < floatingmenuHeight ) 933 ? editablePos.top + editable.height() 934 : jQuery( document ).scrollTop(); 935 936 editablePos.top += that.marginTop; 937 } else { 938 editablePos.top -= floatingmenuHeight 939 + that.topalignOffset; 940 } 941 942 editablePos.left += that.horizontalOffset; 943 944 var HORIZONTAL_PADDING = 10; 945 // Calculate by how much the floating menu is pocking 946 // outside the width of the viewport. A positive number 947 // means that it is outside the viewport, negative means 948 // it is within the viewport. 949 var overhang = ( ( editablePos.left + that.width 950 + HORIZONTAL_PADDING ) - jQuery(window).width() ); 951 952 if ( overhang > 0 ) { 953 editablePos.left -= overhang; 954 } 955 956 that.floatTo( editablePos ); 957 }); 958 } 959 }, 960 961 /** 962 * Fix the position of the pinned floatingmenu to keep it visible 963 */ 964 fixPinnedPosition: function() { 965 // if not pinned, do not fix the position 966 if (!this.pinned) { 967 return; 968 } 969 970 // fix the position of the floatingmenu, to keep it visible 971 if (this.top < 30) { 972 // from top position, we want to have 30px margin 973 this.top = 30; 974 } else if (this.top > this.window.height() - this.extTabPanel.getHeight()) { 975 this.top = this.window.height() - this.extTabPanel.getHeight(); 976 } 977 if (this.left < 0) { 978 this.left = 0; 979 } else if (this.left > this.window.width() - this.extTabPanel.getWidth()) { 980 this.left = this.window.width() - this.extTabPanel.getWidth(); 981 } 982 }, 983 984 /** 985 * reposition & resize the shadow 986 * the shadow must not be repositioned outside this method! 987 * position calculation is based on this.top and this.left coordinates 988 * @method 989 */ 990 refreshShadow: function (resize) { 991 if (this.panelBody) { 992 var props = { 993 'top': this.top + 24, // 24px top offset to reflect tab bar height 994 'left': this.left 995 }; 996 997 if(typeof resize === 'undefined' || !resize) { 998 props.width = this.panelBody.width() + 'px'; 999 props.height = this.panelBody.height() + 'px'; 1000 } 1001 1002 this.extTabPanel.shadow.css(props); 1003 } 1004 }, 1005 1006 /** 1007 * toggles the pinned status of the floating menu 1008 * @method 1009 * @param {boolean} pinned set to true to activate pin, or set to false to deactivate pin. 1010 * leave undefined to toggle pin status automatically 1011 */ 1012 togglePin: function(pinned) { 1013 var el = jQuery('.aloha-floatingmenu-pin'); 1014 1015 if (typeof pinned === 'boolean') { 1016 this.pinned = !pinned; 1017 } 1018 1019 if (this.pinned) { 1020 el.removeClass('aloha-floatingmenu-pinned'); 1021 this.top = this.obj.offset().top; 1022 1023 this.obj.removeClass('fixed').css({ 1024 'top': this.top 1025 }); 1026 1027 this.extTabPanel.shadow.removeClass('fixed'); 1028 this.refreshShadow(); 1029 1030 this.pinned = false; 1031 } else { 1032 el.addClass('aloha-floatingmenu-pinned'); 1033 this.top = this.obj.offset().top - this.window.scrollTop(); 1034 1035 this.obj.addClass('fixed').css({ 1036 'top': this.top // update position for fixed position 1037 }); 1038 1039 // do the same for the shadow 1040 this.extTabPanel.shadow.addClass('fixed');//props.start 1041 this.refreshShadow(); 1042 1043 this.pinned = true; 1044 } 1045 }, 1046 1047 /** 1048 * Create a new scopes 1049 * @method 1050 * @param {String} scope name of the new scope (should be namespaced for uniqueness) 1051 * @param {String} extendedScopes Array of scopes this scope extends. Can also be a single String if 1052 * only one scope is extended, or omitted if the scope should extend 1053 * the empty scope 1054 */ 1055 createScope: function(scope, extendedScopes) { 1056 if (typeof extendedScopes === 'undefined') { 1057 extendedScopes = ['Aloha.empty']; 1058 } else if (typeof extendedScopes === 'string') { 1059 extendedScopes = [extendedScopes]; 1060 } 1061 1062 // TODO check whether the extended scopes already exist 1063 1064 if (this.scopes[scope]) { 1065 // TODO what if the scope already exists? 1066 } else { 1067 // generate the new scope 1068 this.scopes[scope] = {'name' : scope, 'extendedScopes' : extendedScopes, 'buttons' : []}; 1069 } 1070 }, 1071 1072 /** 1073 * Adds a button to the floatingmenu 1074 * @method 1075 * @param {String} scope the scope for the button, should be generated before (either by core or the plugin) 1076 * @param {Button} button instance of Aloha.ui.button to add at the floatingmenu 1077 * @param {String} tab label of the tab to which the button is added 1078 * @param {int} group index of the button group in the tab, lowest index is left 1079 */ 1080 addButton: function(scope, button, tab, group) { 1081 // check whether the scope exists 1082 var 1083 scopeObject = this.scopes[scope], 1084 buttonInfo, tabObject, groupObject; 1085 1086 if (!button.name) { 1087 console.warn('Added button with iconClass {' + button.iconClass + '} which has no property "name"'); 1088 } 1089 1090 if (typeof scopeObject === 'undefined') { 1091 Aloha.Log.error("Can't add button to given scope since the scope has not yet been initialized.", scope); 1092 return false; 1093 } 1094 1095 // generate a buttonInfo object 1096 buttonInfo = { 'button' : button, 'scopeVisible' : false }; 1097 1098 // add the button to the list of all buttons 1099 this.allButtons.push(buttonInfo); 1100 1101 // add the button to the scope 1102 scopeObject.buttons.push(buttonInfo); 1103 1104 // if there is no toolbar config tabs and groups will be generated right away 1105 if (!this.fromConfig) { 1106 // get the tab object 1107 tabObject = this.tabMap[tab]; 1108 if (typeof tabObject === 'undefined') { 1109 // the tab object does not yet exist, so create a new tab and add it to the list 1110 tabObject = new Tab(tab); 1111 this.tabs.push(tabObject); 1112 this.tabMap[tab] = tabObject; 1113 } 1114 1115 // get the group 1116 groupObject = tabObject.getGroup(group); 1117 1118 // now add the button to the group 1119 groupObject.addButton(buttonInfo); 1120 } 1121 1122 // finally, when the floatingmenu is already initialized, we need to create the ext component now 1123 if (this.initialized) { 1124 this.generateComponent(); 1125 } 1126 }, 1127 1128 /** 1129 * Recalculate the visibility of tabs, groups and buttons (depending on scope and button hiding) 1130 * @hide 1131 */ 1132 doLayout: function () { 1133 if (Aloha.Log.isDebugEnabled()) { 1134 Aloha.Log.debug(this, 'doLayout called for FloatingMenu, scope is ' + this.currentScope); 1135 } 1136 1137 // if there's no floatingmenu don't do anything 1138 if ( typeof this.extTabPanel === 'undefined' ) { 1139 return false; 1140 } 1141 1142 var that = this, 1143 firstVisibleTab = false, 1144 activeExtTab = this.extTabPanel.getActiveTab(), 1145 activeTab = false, 1146 floatingMenuVisible = false, 1147 showUserActivatedTab = false, 1148 pos; 1149 1150 // let the tabs layout themselves 1151 jQuery.each(this.tabs, function(index, tab) { 1152 1153 // remember the active tab 1154 if (tab.extPanel == activeExtTab) { 1155 activeTab = tab; 1156 } 1157 1158 // remember whether the tab is currently visible 1159 var tabVisible = tab.visible; 1160 1161 // let each tab generate its ext component and add them to the panel 1162 if (tab.doLayout()) { 1163 // found a visible tab, so the floatingmenu needs to be visible as well 1164 floatingMenuVisible = true; 1165 1166 // make sure the tabstrip is visible 1167 if (!tabVisible) { 1168 if (Aloha.Log.isDebugEnabled()) { 1169 Aloha.Log.debug(that, 'showing tab strip for tab ' + tab.label); 1170 } 1171 that.extTabPanel.unhideTabStripItem(tab.extPanel); 1172 } 1173 1174 // remember the first visible tab 1175 if (!firstVisibleTab) { 1176 // this is the first visible tab (in case we need to switch to it) 1177 firstVisibleTab = tab; 1178 } 1179 // check whether this visible tab is the last user activated tab and currently not active 1180 if (that.userActivatedTab == tab.extPanel.title && tab.extPanel != activeExtTab) { 1181 showUserActivatedTab = tab; 1182 } 1183 } else { 1184 // make sure the tabstrip is hidden 1185 if (tabVisible) { 1186 if (Aloha.Log.isDebugEnabled()) { 1187 Aloha.Log.debug(that, 'hiding tab strip for tab ' + tab.label); 1188 } 1189 that.extTabPanel.hideTabStripItem(tab.extPanel); 1190 } 1191 } 1192 1193 // hide a tab 1194 if ( tab.label == that.hideTab ) { 1195 that.extTabPanel.hideTabStripItem(tab.extPanel); 1196 1197 if ( activeExtTab.title == that.hideTab ) { 1198 showUserActivatedTab = firstVisibleTab; 1199 } 1200 } 1201 }); 1202 1203 // check whether the last tab which was selected by the user is visible and not the active tab 1204 if (showUserActivatedTab) { 1205 if (Aloha.Log.isDebugEnabled()) { 1206 Aloha.Log.debug(this, 'Setting active tab to ' + showUserActivatedTab.label); 1207 } 1208 this.extTabPanel.setActiveTab(showUserActivatedTab.extPanel); 1209 } else if (typeof activeTab === 'object' && typeof firstVisibleTab === 'object') { 1210 // now check the currently visible tab, whether it is visible and enabled 1211 if (!activeTab.visible) { 1212 if (Aloha.Log.isDebugEnabled()) { 1213 Aloha.Log.debug(this, 'Setting active tab to ' + firstVisibleTab.label); 1214 } 1215 this.autoActivatedTab = firstVisibleTab.extPanel.title; 1216 this.extTabPanel.setActiveTab(firstVisibleTab.extPanel); 1217 } 1218 } 1219 1220 // set visibility of floatingmenu 1221 if (floatingMenuVisible && this.extTabPanel.hidden) { 1222 // set the remembered position 1223 this.extTabPanel.show(); 1224 this.refreshShadow(); 1225 this.extTabPanel.shadow.show(); 1226 this.extTabPanel.setPosition(this.left, this.top); 1227 } else if (!floatingMenuVisible && !this.extTabPanel.hidden) { 1228 // remember the current position 1229 pos = this.extTabPanel.getPosition(true); 1230 // restore previous position if the fm was pinned 1231 this.left = pos[0] < 0 ? 100 : pos[0]; 1232 this.top = pos[1] < 0 ? 100 : pos[1]; 1233 this.extTabPanel.hide(); 1234 this.extTabPanel.shadow.hide(); 1235 } /*else { 1236 var target = that.calcFloatTarget(Aloha.Selection.getRangeObject()); 1237 if (target) { 1238 this.left = target.left; 1239 this.top = target.top; 1240 this.extTabPanel.show(); 1241 this.refreshShadow(); 1242 this.extTabPanel.shadow.show(); 1243 this.extTabPanel.setPosition(this.left, this.top); 1244 1245 that.floatTo(target); 1246 } 1247 }*/ 1248 1249 // let the Ext object render itself again 1250 this.extTabPanel.doLayout(); 1251 }, 1252 1253 /** 1254 * Set the current scope 1255 * @method 1256 * @param {String} scope name of the new current scope 1257 */ 1258 setScope: function(scope) { 1259 // get the scope object 1260 var scopeObject = this.scopes[scope]; 1261 1262 if (typeof scopeObject === 'undefined') { 1263 // TODO log an error 1264 } else if (this.currentScope != scope) { 1265 this.currentScope = scope; 1266 1267 // first hide all buttons 1268 jQuery.each(this.allButtons, function(index, buttonInfo) { 1269 buttonInfo.scopeVisible = false; 1270 }); 1271 1272 // now set the buttons in the given scope to be visible 1273 this.setButtonScopeVisibility(scopeObject); 1274 1275 // finally refresh the layout 1276 this.doLayout(); 1277 } 1278 }, 1279 1280 /** 1281 * Set the scope visibility of the buttons for the given scope. This method will call itself for the motherscopes of the given scope. 1282 * @param scopeObject scope object 1283 * @hide 1284 */ 1285 setButtonScopeVisibility: function(scopeObject) { 1286 var that = this; 1287 1288 // set all buttons in the given scope to be visible 1289 jQuery.each(scopeObject.buttons, function(index, buttonInfo) { 1290 buttonInfo.scopeVisible = true; 1291 }); 1292 1293 // now do the recursion for the motherscopes 1294 jQuery.each(scopeObject.extendedScopes, function(index, scopeName) { 1295 var motherScopeObject = that.scopes[scopeName]; 1296 if (typeof motherScopeObject === 'object') { 1297 that.setButtonScopeVisibility(motherScopeObject); 1298 } 1299 }); 1300 }, 1301 1302 /** 1303 * returns the next possible float target dom obj 1304 * the floating menu should only float to h1-h6, p, div, td and pre elements 1305 * if the current object is not valid, it's parentNode will be considered, until 1306 * the limit object is hit 1307 * @param obj the dom object to start from (commonly this would be the commonAncestorContainer) 1308 * @param limitObj the object that limits the range (this would be the editable) 1309 * @return dom object which qualifies as a float target 1310 * @hide 1311 */ 1312 nextFloatTargetObj: function (obj, limitObj) { 1313 // if we've hit the limit object we don't care for it's type 1314 if (!obj || obj == limitObj) { 1315 return obj; 1316 } 1317 1318 // fm will only float to h1-h6, p, div, td 1319 switch (obj.nodeName.toLowerCase()) { 1320 case 'h1': 1321 case 'h2': 1322 case 'h3': 1323 case 'h4': 1324 case 'h5': 1325 case 'h6': 1326 case 'p': 1327 case 'div': 1328 case 'td': 1329 case 'pre': 1330 case 'ul': 1331 case 'ol': 1332 return obj; 1333 default: 1334 return this.nextFloatTargetObj(obj.parentNode, limitObj); 1335 } 1336 }, 1337 1338 /** 1339 * calculates the float target coordinates for a range 1340 * @param range the fm should float to 1341 * @return object containing left and top coordinates, like { left : 20, top : 43 } 1342 * @hide 1343 */ 1344 calcFloatTarget: function(range) { 1345 var 1346 i, documentWidth, editableLength, left, target, 1347 targetObj, scrollTop, top; 1348 1349 // TODO in IE8 somteimes a broken range is handed to this function - investigate this 1350 if (!Aloha.activeEditable || typeof range.getCommonAncestorContainer === 'undefined') { 1351 return false; 1352 } 1353 1354 // check if the designated editable is disabled 1355 for ( i = 0, editableLength = Aloha.editables.length; i < editableLength; i++) { 1356 if (Aloha.editables[i].obj.get(0) == range.limitObject && 1357 Aloha.editables[i].isDisabled()) { 1358 return false; 1359 } 1360 } 1361 1362 target = this.nextFloatTargetObj(range.getCommonAncestorContainer(), range.limitObject); 1363 if ( ! target ) { 1364 return false; 1365 } 1366 1367 targetObj = jQuery(target); 1368 scrollTop = GENTICS.Utils.Position.Scroll.top; 1369 if (!targetObj || !targetObj.offset()) { 1370 return false; 1371 } 1372 top = targetObj.offset().top - this.obj.height() - 50; // 50px offset above the current obj to have some space above 1373 1374 // if the floating menu would be placed higher than the top of the screen... 1375 if ( top < scrollTop) { 1376 top += 80 + GENTICS.Utils.Position.ScrollCorrection.top; 1377 // 80px if editable element is eg h1; 50px was fine for p; 1378 // todo: maybe just use GENTICS.Utils.Position.ScrollCorrection.top with a better value? 1379 // check where this is also used ... 1380 } 1381 1382 // if the floating menu would float off the bottom of the screen 1383 // we don't want it to move, so we'll return false 1384 if (top > this.window.height() + this.window.scrollTop()) { 1385 return false; 1386 } 1387 1388 // check if the floating menu does not float off the right side 1389 left = Aloha.activeEditable.obj.offset().left; 1390 documentWidth = jQuery(document).width(); 1391 if ( documentWidth - this.width < left ) { 1392 left = documentWidth - this.width - GENTICS.Utils.Position.ScrollCorrection.left; 1393 } 1394 1395 return { 1396 left : left, 1397 top : top 1398 }; 1399 }, 1400 1401 /** 1402 * float the fm to the desired position 1403 * the floating menu won't float if it is pinned 1404 * @method 1405 * @param {Object} coordinate object which has a left and top property 1406 */ 1407 floatTo: function(position) { 1408 // no floating if the panel is pinned 1409 if (this.pinned) { 1410 return; 1411 } 1412 1413 var floatingmenu = this, 1414 fmpos = this.obj.offset(), 1415 lastLeft, 1416 lastTop; 1417 1418 if ( lastFloatingMenuPos.left === null ) { 1419 lastLeft = fmpos.left; 1420 lastTop = fmpos.top; 1421 } else { 1422 lastLeft = lastFloatingMenuPos.left; 1423 lastTop = lastFloatingMenuPos.top; 1424 } 1425 1426 // Place the floatingmenu to the last place the user had seen it, 1427 // then animate it into its new position. 1428 if ( lastLeft != position.left || lastTop != position.top ) { 1429 this.obj.offset({ 1430 left: lastLeft, 1431 top: lastTop 1432 }); 1433 1434 this.obj.animate( { 1435 top: position.top, 1436 left: position.left 1437 }, { 1438 queue : false, 1439 step : function( step, props ) { 1440 // update position reference 1441 if ( props.prop === 'top' ) { 1442 floatingmenu.top = props.now; 1443 } else if ( props.prop === 'left' ) { 1444 floatingmenu.left = props.now; 1445 } 1446 1447 floatingmenu.refreshShadow( false ); 1448 }, 1449 complete: function() { 1450 // When the animation is over, remember the floatingmenu's 1451 // final resting position. 1452 lastFloatingMenuPos.left = floatingmenu.left; 1453 lastFloatingMenuPos.top = floatingmenu.top; 1454 } 1455 }); 1456 } 1457 }, 1458 1459 /** 1460 * Hide the floatingmenu 1461 */ 1462 hide: function() { 1463 if (this.obj) { 1464 this.obj.hide(); 1465 } 1466 if (this.shadow) { 1467 this.shadow.hide(); 1468 } 1469 }, 1470 1471 /** 1472 * Activate the tab containing the button with given name. 1473 * If the button with given name is not found, nothing changes 1474 * @param name name of the button 1475 */ 1476 activateTabOfButton: function(name) { 1477 var tabOfButton = null; 1478 1479 // find the tab containing the button 1480 for (var t = 0; t < this.tabs.length && !tabOfButton; t++) { 1481 var tab = this.tabs[t]; 1482 for (var g = 0; g < tab.groups.length && !tabOfButton; g++) { 1483 var group = tab.groups[g]; 1484 for (var b = 0; b < group.buttons.length && !tabOfButton; b++) { 1485 var button = group.buttons[b]; 1486 if (button.button.name == name) { 1487 tabOfButton = tab; 1488 break; 1489 } 1490 } 1491 } 1492 } 1493 1494 if (tabOfButton) { 1495 this.userActivatedTab = tabOfButton.label; 1496 this.doLayout(); 1497 } 1498 } 1499 }); 1500 1501 var menu = new FloatingMenu(); 1502 menu.init(); 1503 1504 // set scope to empty if deactivated 1505 Aloha.bind('aloha-editable-deactivated', function() { 1506 menu.setScope('Aloha.empty'); 1507 }); 1508 1509 Aloha.bind('aloha-selection-changed-before', function(event, rangeObject, originalEvent) { 1510 // Only set the specific scope if an event was provided, which means 1511 // that somehow an editable was selected 1512 if (originalEvent !== undefined) { 1513 // Initiallly set the scope to 'continuoustext' 1514 menu.setScope('Aloha.continuoustext'); 1515 } 1516 }); 1517 1518 // set scope to empty if the user selectes a non contenteditable area 1519 Aloha.bind('aloha-selection-changed', function() { 1520 if ( !Aloha.Selection.isSelectionEditable() && !Aloha.Selection.isFloatingMenuVisible() ) { 1521 menu.setScope('Aloha.empty'); 1522 } 1523 }); 1524 1525 return menu; 1526 }); 1527