diff --git a/src/ui/yui/config/yui-modules.js b/src/ui/yui/config/yui-modules.js index 7c1fca1ac8..1cf4f6d1bc 100644 --- a/src/ui/yui/config/yui-modules.js +++ b/src/ui/yui/config/yui-modules.js @@ -55,7 +55,7 @@ 'button-a': { group: 'AlloyEditor', path: 'buttons/button-a.js', - requires: ['button-base', 'event-valuechange', 'node-focusmanager'] + requires: ['button-base', 'event-valuechange'] }, 'button-h1': { @@ -133,7 +133,7 @@ 'toolbar-base': { group: 'AlloyEditor', path: 'toolbars/toolbar-base.js', - requires: ['plugin', 'node-base', 'transition'] + requires: ['plugin', 'node-base', 'node-focusmanager', 'transition'] }, 'toolbar-position': { @@ -145,19 +145,13 @@ 'toolbar-add': { group: 'AlloyEditor', path: 'toolbars/toolbar-add.js', - requires: ['widget-base', 'widget-position', 'widget-position-constrain', 'widget-position-align', 'toolbar-base'] + requires: ['toolbar-base', 'toolbar-position', 'widget-base', 'widget-position', 'widget-position-constrain', 'widget-position-align'] }, 'toolbar-styles': { group: 'AlloyEditor', path: 'toolbars/toolbar-styles.js', - requires: ['toolbar-base', 'widget-base', 'widget-position', 'widget-position-constrain'] - }, - - 'toolbar-image': { - group: 'AlloyEditor', - path: 'toolbars/toolbar-image.js', - requires: ['dom-screen', 'widget-base', 'widget-position', 'widget-position-constrain', 'toolbar-base'] + requires: ['toolbar-base', 'toolbar-position', 'widget-base', 'widget-position', 'widget-position-constrain', 'widget-position-align'] }, 'selector-patch': { diff --git a/src/ui/yui/demo/index.html b/src/ui/yui/demo/index.html index f2a45e5850..137d31bd39 100755 --- a/src/ui/yui/demo/index.html +++ b/src/ui/yui/demo/index.html @@ -89,11 +89,12 @@

Technical details

var editor2 = new Y.AlloyEditor({ srcNode: '#headline', - toolbars: {} + widgets: [] }); var editor3 = new Y.AlloyEditor({ - srcNode: '#description' + srcNode: '#description', + widgets: [] }); }); diff --git a/src/ui/yui/src/adapter/yui.js b/src/ui/yui/src/adapter/yui.js index 4d27a62b08..e8a563f77b 100644 --- a/src/ui/yui/src/adapter/yui.js +++ b/src/ui/yui/src/adapter/yui.js @@ -4,6 +4,7 @@ YUI.add('alloy-editor', function(Y) { 'use strict'; var Lang = Y.Lang, + YArray = Y.Array, AlloyEditor, KEY_ESC = 27, @@ -42,8 +43,6 @@ YUI.add('alloy-editor', function(Y) { editor.config.allowedContent = this.get('allowedContent'); - editor.config.toolbars = this.get('toolbars'); - editor.config.removePlugins = this.get('removePlugins'); editor.config.extraPlugins = this.get('extraPlugins'); editor.config.placeholderClass = this.get('placeholderClass'); @@ -53,6 +52,8 @@ YUI.add('alloy-editor', function(Y) { Y.mix(editor.config, config); + this._initializeWidgets(editor); + this._editor = editor; eventsDelay = this.get('eventsDelay'); @@ -74,16 +75,66 @@ YUI.add('alloy-editor', function(Y) { // Custom events will be attached automatically, there is no need to put them in to the list // with event handles - editor.on('toolbarKey', this._onToolbarKey, this); + editor.on('toolbarKey', this._onWidgetKey, this); + editor.on('widgetKey', this._onWidgetKey, this); - editor.on('toolbarActive', this._onToolbarActive, this); + editor.on('toolbarActive', this._onWidgetActive, this); + editor.on('widgetActive', this._onWidgetActive, this); - editor.on('afterCommandExec', this._returnFocusToToolbar, this); + editor.on('afterCommandExec', this._returnFocusToWidget, this); + }, + + /** + * [_initializeWidgets description] + * @param {[type]} editor [description] + * @return {[type]} [description] + */ + _initializeWidgets: function(editor) { + var self = this, + modules, + widget, + widgetConfig, + widgets; + + modules = []; + widgets = []; + + YArray.each( + this.get('widgets'), + function(item) { + if (Lang.isFunction(item.fn)) { + widgetConfig = Y.merge(item.cfg, { editor: editor }); + + widget = new item.fn(widgetConfig); + } + + if (Lang.isFunction(widget.getModules)) { + modules = modules.concat(widget.getModules()); + } + + if (Lang.isFunction(widget.setEditor)) { + widget.setEditor(editor); + } + + widgets.push(widget); + } + ); + + Y.use(modules, function() { + YArray.each(widgets, function(widget) { + widget.render(); + }); + + editor.fire('widgetsReady', { widgets: widgets }); + }); + + + this._widgets = widgets; }, /** * Destructor lifecycle implementation for the AlloyEdtor class. Destroys the CKEditor - * instance and destroys all created toolbars. + * instance and destroys all created widgets. * * @method destructor * @protected @@ -93,11 +144,11 @@ YUI.add('alloy-editor', function(Y) { editorInstance = CKEDITOR.instances[this.get('srcNode').get('id')]; - if (editorInstance) { - Y.Object.each(editorInstance.config.toolbars, function(value) { - value.destroy(); - }); + YArray.each(this._widgets, function(widget) { + widget.destroy(); + }); + if (editorInstance) { editorInstance.destroy(); } @@ -105,37 +156,28 @@ YUI.add('alloy-editor', function(Y) { }, /** - * Searches among toolbars to find the next toolbar that should be focused. + * Searches among widgets to find the next widget that should be focused. * - * @method _focusNextToolbar + * @method _focusNextWidget * @protected */ - _focusNextToolbar: function() { - var activeToolbar, - currentToolbarIndex, + _focusNextWidget: function() { + var activeWidget, + currentWidgetIndex, lastPart, - toolbars; + widgets; - activeToolbar = this._activeToolbar; + activeWidget = this._activeWidget; - toolbars = this._editor.config.toolbars; - - //We need to convert toolbars to an array so we can reorder them. - toolbars = Y.Object.keys(toolbars).map(function(item) { - return toolbars[item]; - }); + widgets = this._widgets; - currentToolbarIndex = Y.Array.indexOf(toolbars, activeToolbar); + currentWidgetIndex = YArray.indexOf(widgets, activeWidget); - lastPart = toolbars.splice(currentToolbarIndex); + YArray.some(widgets, function(widget) { + if (widget !== activeWidget && widget.focus()) { + this._activeWidget = widget; - toolbars = lastPart.concat(toolbars); - - Y.Array.some(toolbars, function(toolbar) { - if (toolbar !== activeToolbar && toolbar.focus()) { - this._activeToolbar = toolbar; - - this._focusedToolbar = toolbar; + this._focusedWidget = widget; return true; } @@ -143,20 +185,20 @@ YUI.add('alloy-editor', function(Y) { }, /** - * Focuses the first visible toolbar in editor or if there is not any, focuses the last of the other - * toolbars which accept the request for focusing. + * Focuses the first visible widget in editor or if there is not any, focuses the last of the other + * widgets which accept the request for focusing. * - * @method _focusVisibleToolbar + * @method _focusVisibleWidget * @protected */ - _focusVisibleToolbar: function(currentButton) { - Y.Object.some(this._editor.config.toolbars, function(toolbar) { - if (toolbar != this._activeToolbar && toolbar.focus(currentButton)) { - this._activeToolbar = toolbar; + _focusVisibleWidget: function(currentButton) { + YArray.some(this._widgets, function(widget) { + if (widget != this._activeWidget && widget.focus(currentButton)) { + this._activeWidget = widget; - this._focusedToolbar = toolbar; + this._focusedWidget = widget; - return toolbar.get('visible'); + return widget.get('visible'); } }, this); }, @@ -175,17 +217,17 @@ YUI.add('alloy-editor', function(Y) { /** * Hide all visible toolbars in editor * - * @method _hideToolbars + * @method _hideWidgets * @protected */ - _hideToolbars: function() { - Y.Object.each(this._editor.config.toolbars, function(toolbar) { - toolbar.hide(); + _hideWidgets: function() { + YArray.each(this._widgets, function(widget) { + widget.hide(); }); }, /** - * Fires toolbarsHide event if none of the toolbars or their child nodes is the element user is + * Fires widgetsHide event if none of the toolbars or their child nodes is the element user is * currently interacting. * * @method _onDocInteract @@ -193,29 +235,26 @@ YUI.add('alloy-editor', function(Y) { * @param {EventFacade} event EventFacade object */ _onDocInteract: function(event) { - var editorInstance, - result, + var result, srcNode; srcNode = this.get('srcNode'); result = (srcNode === event.target) || (srcNode.contains(event.target)); - editorInstance = CKEDITOR.instances[srcNode.get('id')]; - - result = result || Y.some(editorInstance.config.toolbars, function(toolbar) { - return toolbar.ownsNode(event.target); + result = result || Y.some(this._widgets, function(widget) { + return widget.ownsNode(event.target); }); if (!result) { - this._editor.fire('toolbarsHide'); + this._editor.fire('widgetsHide'); } }, /** * Handles key events in the editor: - * - ALT + F10: focus the toolbar - * - ESC: hide visible toolbars + * - ALT + F10: focus the widget + * - ESC: hide visible widgets * * @method _onEditorKey * @param {EventFacade} event Event that triggered when user pressed a key inside the editor. @@ -223,60 +262,58 @@ YUI.add('alloy-editor', function(Y) { */ _onEditorKey: function(event) { if (event.altKey && event.keyCode === KEY_F10) { - this._focusVisibleToolbar(); + this._focusVisibleWidget(); } else if (event.keyCode === KEY_ESC) { - this._hideToolbars(); + this._hideWidgets(); } }, /** - * Handles activating a toolbar. + * Handles activating a widget. * * @method _onToolbarActive * @protected */ - _onToolbarActive: function(event) { - this._activeToolbar = event.data; + _onWidgetActive: function(event) { + this._activeWidget = event.data; }, /** - * Handles key events in the toolbar: - * - TAB: focus next toolbar + * Handles key events in the widget: + * - TAB: focus next widget * - ESC: focus the editor * - * @method _onToolbarKey + * @method _onWidgetKey * @param {Event} event keyboard event * @protected */ - _onToolbarKey: function(event) { + _onWidgetKey: function(event) { if (event.data.keyCode === KEY_TAB) { event.data.preventDefault(); - this._focusNextToolbar(); + this._focusNextWidget(); } else if (event.data.keyCode === KEY_ESC) { - this._activeToolbar.blur(); - this._activeToolbar = null; - this._focusedToolbar = null; + this._activeWidget.blur(); + this._activeWidget = null; + this._focusedWidget = null; - this._hideToolbars(); + this._hideWidgets(); } }, /** - * Checks if a toolbar was focused by keyboard, + * Checks if a widget was focused by keyboard, * and if so, returns the focus to it. * - * @method _returnFocusToToolbar + * @method _returnFocusToWidget * @protected */ - _returnFocusToToolbar: function() { - var toolbar = this._focusedToolbar; + _returnFocusToWidget: function() { + var widget = this._focusedWidget; - if (toolbar) { - var currentButton = toolbar.getActiveButton(); - - this._focusVisibleToolbar(currentButton); + if (widget) { + widget.focus(widget.getActiveButton()); } }, @@ -294,15 +331,15 @@ YUI.add('alloy-editor', function(Y) { }, /** - * Validates toolbars attribute. May be empty string or null, which means the current instance of AlloyEdtor - * shouldn't have any toolbars, or Object, which properties are the desired toolbars. + * Validates widgets attribute. May be empty string or null, which means the current instance of AlloyEdtor + * shouldn't have any widgets, or Object, which properties are the desired widgets. * - * @method _validateToolbars + * @method _validateWidgets * @protected * @param {Any} value The value which should be validated. * @return {Boolean} True if the value was accepted, false otherwise. */ - _validateToolbars: function(value) { + _validateWidgets: function(value) { return (value === '' || Lang.isObject(value) || Lang.isNull(value)); } }, { @@ -339,13 +376,13 @@ YUI.add('alloy-editor', function(Y) { * make AlloyEditor to work properly. * * @attribute extraPlugins - * @default 'uicore,selectionregion,dropimages,placeholder,linktooltip,uiloader' + * @default 'uicore,selectionregion,dropimages,placeholder,linktooltip' * @writeOnce * @type {String} */ extraPlugins: { validator: Lang.isString, - value: 'uicore,selectionregion,dropimages,placeholder,linktooltip,uiloader', + value: 'uicore,selectionregion,dropimages,placeholder,linktooltip', writeOnce: true }, @@ -412,44 +449,72 @@ YUI.add('alloy-editor', function(Y) { }, /** - * Specifies the Toolbars of the current editor instance. The value should be an object - * with one or more properties. Each of these will represent a toolbar. The value of these properties - * can be string or Object. If String, the value of the string should represent the buttons. If object, - * one of these properties should be 'buttons', which will represent the buttons of this Toolbar and - * all the other properties will be different configuration parameters of the Toolbar. Example: + * Specifies the Widgets of the current editor instance. The value should be an array + * with one or more items. Each of these will represent a widget. The value of these items + * can be an instance of the widget or a configuration Object. If object, it should contain an 'fn' + * property with the constructor of the Widget and optionally a 'cfg' object to be passed to the + * class on instantiation. Example: + * *

-             *     toolbars: {
-             *         table: { // this would be a toolbar configuration, which specifies both Toolbar attributes and its buttons
-             *             buttons: ['button1', button2],
-             *             width: 25
+             *     widgets: [
+             *         {
+             *             fn: Y.ToolbarStyles,
+             *             cfg: {
+             *                 visible: false
+             *             }
              *         },
-             *         add: ['image', 'code'], // here we specify only the buttons of this toolbar and leave the other options unmodified.
-             *         image: ['left', 'right'],
-             *         styles: ['strong', 'em', 'u', 'h1', 'h2', 'a', 'twitter']
-             *     }
+             *         {
+             *             fn: Y.ToolbarAdd
+             *         },
+             *         new Y.CustomWidget()
+             *     ]
              * 
* + * Widgets may take part in the focus rotation for accessibility purposes. In general, the should expose + * the following methods: + * + * - focus() To gain focus on the widget + * - setEditor(editor) To receive the native editor instance on already created Widgets. When passing the + * wiget constructor, an 'editor' attribute with the nativeEditor will be passed down in the config object. + * * @default - * toolbars { - * add: ['image'], - * image: ['left', 'right'], - * styles: ['strong', 'em', 'u', 'h1', 'h2', 'a', 'twitter'] - * } - * @attribute toolbars - * @type Object + * widgets: [ + * { + * fn: Y.ToolbarStyles, + * cfg: { + * visible: false + * } + * }, + * { + * fn: Y.ToolbarAdd, + * cfg: { + * visible: false + * } + * } + * ] + * @type Array */ - toolbars: { - validator: '_validateToolbars', - value: { - add: ['image'], - image: ['left', 'right'], - styles: ['strong', 'em', 'u', 'h1', 'h2', 'a', 'twitter'] - } + widgets: { + validator: '_validateWidgets', + value: [ + { + fn: Y.ToolbarStyles, + cfg: { + visible: false + } + }, + { + fn: Y.ToolbarAdd, + cfg: { + visible: false + } + } + ] } } }); Y.AlloyEditor = AlloyEditor; }, '', { - requires: ['base-build', 'node-base'] + requires: ['base-build', 'node-base', 'toolbar-add', 'toolbar-styles'] }); \ No newline at end of file diff --git a/src/ui/yui/src/assets/css/alloy-editor-core.css b/src/ui/yui/src/assets/css/alloy-editor-core.css index 38017b43d5..f7b09e8000 100644 --- a/src/ui/yui/src/assets/css/alloy-editor-core.css +++ b/src/ui/yui/src/assets/css/alloy-editor-core.css @@ -1,3 +1,4 @@ +/* line 1, ../sass/alloy-editor-core.scss */ .yui3-toolbarstyles-hidden, .yui3-toolbaradd-hidden, .yui3-toolbaraddtrigger-hidden, .yui3-toolbarimage-hidden, .yui3-linktooltip-hidden { display: none; } @@ -7,9 +8,7 @@ z-index: 1; } -.alloy-editor-toolbar { - padding: 10px; -} +/* line 11, ../sass/alloy-editor-core.scss */ .alloy-editor-toolbar .alloy-editor-button { height: 24px; margin: 0 10px; @@ -25,9 +24,7 @@ outline: 2px solid #44b6e0 !important; } -.alloy-editor-toolbar-styles .link-wrapper .alloy-editor-button { - margin-left: 10px; -} +/* line 20, ../sass/alloy-editor-core.scss */ .alloy-editor-toolbar-styles .link-wrapper .input-container { display: inline-block; padding: 2px; @@ -54,15 +51,7 @@ position: relative; } -.alloy-editor-toolbar-add-trigger { - padding: 0; -} -.alloy-editor-toolbar-add-trigger .alloy-editor-button { - height: 24px; - padding: 5px; - width: 24px; -} - +/* line 48, ../sass/alloy-editor-core.scss */ .alloy-editor-toolbar-add .alloy-editor-button { font-size: 20px; } diff --git a/src/ui/yui/src/assets/css/skin/dark/alloy-editor-skin.css b/src/ui/yui/src/assets/css/skin/dark/alloy-editor-skin.css index d1ef8d9c4e..e5e89152b7 100644 --- a/src/ui/yui/src/assets/css/skin/dark/alloy-editor-skin.css +++ b/src/ui/yui/src/assets/css/skin/dark/alloy-editor-skin.css @@ -66,17 +66,13 @@ } .alloy-editor-toolbar .alloy-editor-button { - -webkit-box-shadow: none; -moz-box-shadow: none; + -webkit-box-shadow: none; box-shadow: none; - -webkit-border-radius: 0; - -moz-border-radius: 0; - -ms-border-radius: 0; - -o-border-radius: 0; - border-radius: 0; -webkit-transition: color 0.1s ease-out; -moz-transition: color 0.1s ease-out; -o-transition: color 0.1s ease-out; + -webkit-transition: color 0.1s ease-out; transition: color 0.1s ease-out; background-color: transparent; background-image: none; @@ -87,8 +83,8 @@ color: #44b6e0; } .alloy-editor-toolbar .alloy-editor-button.yui3-button-selected, .alloy-editor-toolbar .alloy-editor-button.yui3-button-active, .alloy-editor-toolbar .alloy-editor-button.yui3-button:active { - -webkit-box-shadow: none; -moz-box-shadow: none; + -webkit-box-shadow: none; box-shadow: none; background-color: #2f323d; color: #44b6e0; @@ -105,46 +101,57 @@ color: #fff; } +/* line 54, ../../../sass/skin/dark/alloy-editor-skin.scss */ .alloy-editor-toolbar-styles .link-wrapper { -webkit-border-radius: 4px; -moz-border-radius: 4px; - -ms-border-radius: 4px; - -o-border-radius: 4px; + -webkit-border-radius: 4px; border-radius: 4px; background-color: #2f323d; margin-left: 5px; } +/* line 60, ../../../sass/skin/dark/alloy-editor-skin.scss */ .alloy-editor-toolbar-styles .link-wrapper input { border: none; } +/* line 64, ../../../sass/skin/dark/alloy-editor-skin.scss */ .alloy-editor-toolbar-styles .link-wrapper .input-container { background-color: #fff; color: #2f232d; } +/* line 70, ../../../sass/skin/dark/alloy-editor-skin.scss */ .alloy-editor-toolbar-styles .link-wrapper .input-container .input-clear { color: #b1b1b1; } +/* line 76, ../../../sass/skin/dark/alloy-editor-skin.scss */ +.alloy-editor-toolbar-styles .link-wrapper .input-close-container .close-link { + color: #27be0e; +} +/* line 79, ../../../sass/skin/dark/alloy-editor-skin.scss */ +.alloy-editor-toolbar-styles .link-wrapper .input-close-container .close-link.yui3-button[disabled], .alloy-editor-toolbar-styles .link-wrapper .input-close-container .close-link.yui3-button-disabled { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + color: #fff; + opacity: 1; +} .alloy-editor-toolbar-add-trigger { - -webkit-box-shadow: none; -moz-box-shadow: none; + -webkit-box-shadow: none; box-shadow: none; background-color: transparent; } .alloy-editor-toolbar-add-trigger .alloy-editor-button { - -webkit-border-radius: 13px; -moz-border-radius: 13px; - -ms-border-radius: 13px; - -o-border-radius: 13px; + -webkit-border-radius: 13px; border-radius: 13px; background-color: #2f323d; } .alloy-editor-tooltip-link { - -webkit-border-radius: 4px; -moz-border-radius: 4px; - -ms-border-radius: 4px; - -o-border-radius: 4px; + -webkit-border-radius: 4px; border-radius: 4px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=95); opacity: 0.95; @@ -162,6 +169,12 @@ background-color: #ffffe0; } +/* line 120, ../../../sass/skin/dark/alloy-editor-skin.scss */ +.alloy-editor-toolbar .alloy-editor-button.focus { + color: #44b6e0; +} + +/* line 124, ../../../sass/skin/dark/alloy-editor-skin.scss */ .alloy-editor-twitter-link { background-color: #f8f8f8; } diff --git a/src/ui/yui/src/assets/sass/alloy-editor-core.scss b/src/ui/yui/src/assets/sass/alloy-editor-core.scss index e53d30f489..b3da3c9479 100644 --- a/src/ui/yui/src/assets/sass/alloy-editor-core.scss +++ b/src/ui/yui/src/assets/sass/alloy-editor-core.scss @@ -1,4 +1,4 @@ -.yui3-toolbarstyles-hidden, .yui3-toolbaradd-hidden, .yui3-toolbaraddtrigger-hidden, .yui3-toolbarimage-hidden, .yui3-linktooltip-hidden { +.yui3-toolbarstyles-hidden, .yui3-toolbaradd-hidden, .yui3-toolbaraddtrigger-hidden, .yui3-linktooltip-hidden { display: none; } @@ -29,6 +29,14 @@ } .alloy-editor-toolbar-styles { + .alloy-editor-toolbar-buttons { + display: none; + + &.active { + display: block; + } + } + .link-wrapper { .alloy-editor-button { margin-left: 10px; diff --git a/src/ui/yui/src/assets/sass/skin/dark/alloy-editor-skin.scss b/src/ui/yui/src/assets/sass/skin/dark/alloy-editor-skin.scss index c72f9aab78..7c2730e5f6 100644 --- a/src/ui/yui/src/assets/sass/skin/dark/alloy-editor-skin.scss +++ b/src/ui/yui/src/assets/sass/skin/dark/alloy-editor-skin.scss @@ -60,7 +60,7 @@ $prefixes: ("-webkit-", "-moz-", "-o-", ""); } } -.alloy-editor-toolbar-styles { +.alloy-editor-toolbar-context { .link-wrapper { @include border-radius(4px); background-color: #2f323d; diff --git a/src/ui/yui/src/toolbars/toolbar-add.js b/src/ui/yui/src/toolbars/toolbar-add.js index e9370521f3..7f59145758 100644 --- a/src/ui/yui/src/toolbars/toolbar-add.js +++ b/src/ui/yui/src/toolbars/toolbar-add.js @@ -2,6 +2,7 @@ YUI.add('toolbar-add', function(Y) { 'use strict'; var Lang = Y.Lang, + YArray = Y.Array, YNode = Y.Node, ARROW_BOX_CLASSES = [ @@ -33,8 +34,6 @@ YUI.add('toolbar-add', function(Y) { editorNode = Y.one(this.get('editor').element.$); this._editorDOMNode = editorNode.getDOMNode(); - - this._toolbars = {}; }, /** @@ -52,8 +51,8 @@ YUI.add('toolbar-add', function(Y) { this._triggerButton.on('click', this._showToolbarAddContent, this); this.on('visibleChange', this._onVisibleChange, this); - editor.on('toolbarsReady', this._onToolbarsReady, this); - editor.on('toolbarsHide', this._onToolbarsHide, this); + editor.on('widgetsReady', this._onWidgetsReady, this); + editor.on('widgetsHide', this._onWidgetsHide, this); }, /** @@ -89,6 +88,8 @@ YUI.add('toolbar-add', function(Y) { var buttonsContainer = this.get('buttonsContainer'); if (this.get('visible')) { + buttonsContainer.focusManager.refresh(); + buttonsContainer.focusManager.focus(0); } else { this._triggerButton.focus(); @@ -97,6 +98,29 @@ YUI.add('toolbar-add', function(Y) { return true; }, + /** + * Resolves all required modules based on the current toolbar configuration. + * + * @method getModules + * @return {Array} An Array with all the module names required by + * the current toolbar configuration. + */ + getModules: function() { + var i, + modules, + buttonsConfig; + + modules = []; + + buttonsConfig = this.get('buttons'); + + for (i in buttonsConfig) { + modules.push('button-' + this._getButtonName(buttonsConfig[i])); + } + + return modules; + }, + /** * Returns true if the passed node is a child node of the toolbar, false otherwise. * @@ -167,11 +191,12 @@ YUI.add('toolbar-add', function(Y) { * @protected */ _hideEditorToolbars: function() { - var editorToolbars; + var editorWidgets; - editorToolbars = this._toolbars; + editorWidgets = this._widgets; - Y.Object.each(editorToolbars, function(item) { + Y.Array.each(editorWidgets, function(item) { + // TODO Should distinguish if it's actually a Toolbar if (this !== item) { item.hide(); } @@ -219,9 +244,9 @@ YUI.add('toolbar-add', function(Y) { /** * Hides the toolbar and the trigger in case of toolbarsHide event. * - * @method _onToolbarsHide + * @method _onWidgetsHide */ - _onToolbarsHide: function() { + _onWidgetsHide: function() { this.hide(); this._trigger.hide(); @@ -234,8 +259,8 @@ YUI.add('toolbar-add', function(Y) { * @protected * @param {EventFacade} event Event that triggered when all editor toolbars are initialized. */ - _onToolbarsReady: function(event) { - this._toolbars = event.data.toolbars; + _onWidgetsReady: function(event) { + this._widgets = event.data.widgets; }, /** @@ -266,6 +291,11 @@ YUI.add('toolbar-add', function(Y) { contentBox.appendChild(buttonsContainer); + YArray.each( + instance.get('buttons'), + Y.bind('_appendButton', instance, buttonsContainer) + ); + instance._buttonsContainer = buttonsContainer; }, @@ -317,8 +347,6 @@ YUI.add('toolbar-add', function(Y) { this._trigger.hide(); - this._editorNode.focus(); - this.focus(); this.get('editor').fire('toolbarActive', this); diff --git a/src/ui/yui/src/toolbars/toolbar-base.js b/src/ui/yui/src/toolbars/toolbar-base.js index 306b3c6fec..661ff97d59 100644 --- a/src/ui/yui/src/toolbars/toolbar-base.js +++ b/src/ui/yui/src/toolbars/toolbar-base.js @@ -32,27 +32,6 @@ YUI.add('toolbar-base', function(Y) { instance._editorNode = Y.one(editor.element.$); - YArray.each( - instance.get('buttons'), - function(item) { - var buttonName, - cfg, - instanceName; - - buttonName = Lang.isObject(item) ? item.name : item; - - instanceName = instance._getButtonInstanceName(buttonName); - - cfg = Lang.isObject(item) ? item.cfg : null; - - instance.plug(Y[instanceName], cfg); - - // Each button will fire actionPerformed when user interacts with it. Here we will - // re-fire this event to the other buttons so they will be able to update their UI too. - instance[Y[instanceName].NS].after('actionPerformed', instance._afterActionPerformed, instance); - } - ); - instance.after('render', instance._afterRender, instance); instance.after('visibleChange', instance._afterVisibleChange, instance); @@ -86,6 +65,8 @@ YUI.add('toolbar-base', function(Y) { visible = this.get('visible'); if (visible) { + buttonsContainer.focusManager.refresh(); + buttonsContainer.focusManager.focus(index); } @@ -127,7 +108,7 @@ YUI.add('toolbar-base', function(Y) { buttonsContainer.plug(Y.Plugin.NodeFocusManager, { activeDescendant: 0, circular: true, - descendants: 'button', + descendants: 'button:visible', focusClass: 'focus', keys: { next: 'down:' + KEY_ARROW_RIGHT, @@ -159,6 +140,38 @@ YUI.add('toolbar-base', function(Y) { }); }, + /** + * Adds a button to a given container inside the toolbar. + * + * @method _appendButton + * @protected + * @param {Node} buttonsContainer Container for the buttons + * @param {String|Object} item String or object describing the button + */ + _appendButton: function(buttonsContainer, item) { + var instance = this, + buttonName, + cfg, + defaultCfg, + instanceName; + + defaultCfg = { + container: buttonsContainer + }; + + buttonName = Lang.isObject(item) ? item.name : item; + + instanceName = instance._getButtonInstanceName(buttonName); + + cfg = Lang.isObject(item) ? item.cfg : null; + + instance.plug(Y[instanceName], Y.merge(defaultCfg, cfg)); + + // Each button will fire actionPerformed when user interacts with it. Here we will + // re-fire this event to the other buttons so they will be able to update their UI too. + instance[Y[instanceName].NS].after('actionPerformed', instance._afterActionPerformed, instance); + }, + /** * Applies transition specified via {{#crossLink "ToolbarBase/transition:attribute"}}{{/crossLink}} attribute. * @@ -260,6 +273,25 @@ YUI.add('toolbar-base', function(Y) { return 'Button' + buttonName.substring(0, 1).toUpperCase() + buttonName.substring(1); }, + /** + * Resolves the name of a button module passed through configuration. + * + * @method _getButtonName + * @protected + * @param {String|Object} button A string representing the button or an object + * with a name attribute. + * @return {String} The name of the button. + */ + _getButtonName: function(button) { + var buttonName = button; + + if (typeof button !== 'string') { + buttonName = button.name; + } + + return buttonName + }, + /** * Detects if the current line is empty * @@ -386,5 +418,5 @@ YUI.add('toolbar-base', function(Y) { Y.ToolbarBase = ToolbarBase; }, '', { - requires: ['node-focusmanager', 'node-base', 'plugin'] + requires: ['node-focusmanager', 'node', 'plugin', 'transition'] }); diff --git a/src/ui/yui/src/toolbars/toolbar-image.js b/src/ui/yui/src/toolbars/toolbar-image.js deleted file mode 100644 index fb36fe8c6c..0000000000 --- a/src/ui/yui/src/toolbars/toolbar-image.js +++ /dev/null @@ -1,204 +0,0 @@ -YUI.add('toolbar-image', function(Y) { - 'use strict'; - - var Lang = Y.Lang, - YNode = Y.Node, - - /** - * The ToolbarImage class hosts the buttons for aligning and manipulating an image. - * - * @class ToolbarImage - */ - ToolbarImage = Y.Base.create('toolbarimage', Y.Widget, [Y.WidgetPosition, Y.WidgetPositionConstrain, Y.ToolbarBase, Y.ToolbarPosition], { - /** - * Creates the container where buttons, attached to the instance of Toolbar should render. - * - * @method renderUI - * @protected - */ - renderUI: function() { - var instance = this, - buttonsContainer, - contentBox; - - buttonsContainer = YNode.create(instance.TPL_BUTTONS_CONTAINER); - - this.get('boundingBox').addClass('arrow-box arrow-box-bottom'); - - contentBox = this.get('contentBox'); - - contentBox.addClass('btn-toolbar'); - - contentBox.appendChild(buttonsContainer); - - instance._buttonsContainer = buttonsContainer; - }, - - /** - * Attaches listeners to actionPerformed and toolbarsHide events. - * - * @method bindUI - * @protected - */ - bindUI: function() { - this.on('actionPerformed', this._onActionPerformed, this); - - this.get('editor').on('toolbarsHide', this._onToolbarsHide, this); - }, - - /** - * Calculates and sets the position of the toolbar. - * - * @method showAtPoint - * @param {Number} left The left offset in page coordinates. - * @param {Number} top The top offset in page coordinates. - * @param {Number} direction The direction of the selection. Can be one of these: - * 1. CKEDITOR.SELECTION_TOP_TO_BOTTOM - * 2. CKEDITOR.SELECTION_BOTTOM_TO_TOP - */ - showAtPoint: function(left, top, direction) { - var xy, - visible; - - visible = this.get('visible'); - - if (!visible) { - this.show(); - } - - xy = this._getToolbarXYPoint(left, top, direction); - - this._moveToPoint(this.getConstrainedXY(xy), direction, { - visible: visible - }); - }, - - /** - * After changing the attributes of the image, updates the position of the Toolbar. - * - * @method _onActionPerformed - * @protected - */ - _onActionPerformed: function() { - var editor, - element; - - editor = this.get('editor'); - - element = editor.getSelection().getSelectedElement(); - - this._updateUI(element); - }, - - /** - * Once after user interacts with the editor, shows or hides the Toolbar. - * The Toolbar will be hidden if the currently selected element is not an image. - * - * @method _onEditorInteraction - * @protected - * @param {EventFacade} event Event that triggered when user interacted with the editor. - */ - _onEditorInteraction: function(event) { - var element, - name, - selectionData; - - selectionData = event.data.selectionData; - - element = selectionData.element; - - name = element ? element.getName() : null; - - if (name === 'img') { - this._updateUI(element); - } else { - this.hide(); - } - }, - - /** - * Hides the toolbar in case of toolbarsHide event. - * - * @method _onToolbarsHide - */ - _onToolbarsHide: function() { - this.hide(); - }, - - /** - * Moves the Toolbar to specified position. - * - * @method _updateUI - * @protected - * @param {CKEDITOR.dom.element} element The selected image element from the editor. - */ - _updateUI: function(element) { - var region; - - if (element) { - region = Y.DOM.region(element.$); - - this.showAtPoint(region.left + (region.right - region.left) / 2, region.top, - CKEDITOR.SELECTION_BOTTOM_TO_TOP); - - this.fire('positionChange', this); - - this.get('editor').fire('toolbarActive', this); - } - }, - - BOUNDING_TEMPLATE: '
', - - CONTENT_TEMPLATE: '
', - - TPL_BUTTONS_CONTAINER: '
' - }, { - ATTRS: { - /** - * Specifies the buttons, which will be attached to the current instance of the toolbar. - * A button configuration can be simple string with the name of the button, or an object - * with properties, like this: - *

-                 *     buttons: ['left']
-                 * 
- * or: - *

-                 *     buttons: [
-                 *         {
-                 *             name: 'left',
-                 *             cfg: {
-                 *                 zIndex: 1024,
-                 *                 property2: 1024
-                 *             }
-                 *         }
-                 *     ]
-                 * 
- * - * @attribute buttons - * @default ['left', 'right'] - * @type Array - */ - buttons: { - validator: Lang.isArray, - value: ['left', 'right'] - }, - - /** - * Specifies whether the toolbar show be constrained to some node or to the viewport. - * - * @attribute constrain - * @default true (will be constrained to the viewport) - * @type Boolean - */ - constrain: { - validator: Lang.isBoolean, - value: true - } - } - }); - - Y.ToolbarImage = ToolbarImage; -}, '', { - requires: ['dom-screen', 'widget-base', 'widget-position', 'widget-position-constrain', 'toolbar-base', 'toolbar-position'] -}); diff --git a/src/ui/yui/src/toolbars/toolbar-position.js b/src/ui/yui/src/toolbars/toolbar-position.js index c1a2ee861f..55731f5add 100644 --- a/src/ui/yui/src/toolbars/toolbar-position.js +++ b/src/ui/yui/src/toolbars/toolbar-position.js @@ -71,6 +71,25 @@ YUI.add('toolbar-position', function(Y) { }; }, + _getModules: function() { + var i, + j, + modules, + selectionsConfig; + + modules = []; + + selectionsConfig = this.get('selections'); + + for (i in selectionsConfig) { + for (j = selectionsConfig[i].buttons.length - 1; j >= 0; j--) { // put button modules + modules.push('button-' + this._getButtonName(selectionsConfig[i].buttons[j])); + } + } + + return modules; + }, + /** * Returns the position of the Toolbar taking in consideration the * {{#crossLink "ToolbarStyles/gutter:attribute"}}{{/crossLink}} attribute. diff --git a/src/ui/yui/src/toolbars/toolbar-styles.js b/src/ui/yui/src/toolbars/toolbar-styles.js index 279b52d84d..1b822c7345 100644 --- a/src/ui/yui/src/toolbars/toolbar-styles.js +++ b/src/ui/yui/src/toolbars/toolbar-styles.js @@ -2,10 +2,15 @@ YUI.add('toolbar-styles', function(Y) { 'use strict'; var Lang = Y.Lang, - YNode = Y.Node, + YArray = Y.Array, + + STR_SELECTION_TEXT = 'text', + STR_SELECTION_IMAGE = 'image', + + SELECTION_TYPES = {}, /** - * The ToolbarStyles class hosts the buttons for styling text selection. + * The ToolbarStyles class hosts the buttons for handling editor selection selections. * * @class ToolbarStyles */ @@ -19,13 +24,37 @@ YUI.add('toolbar-styles', function(Y) { renderUI: function() { var instance = this, buttonsContainer, + selectionContainer, contentBox; - buttonsContainer = YNode.create(instance.TPL_BUTTONS_CONTAINER); + this.get('boundingBox').addClass('arrow-box arrow-box-bottom'); contentBox = this.get('contentBox'); - contentBox.appendChild(buttonsContainer); + contentBox.addClass('btn-toolbar'); + + buttonsContainer = contentBox.one('.selections'); + + YArray.each( + instance._getSelectionsConfig(), + function(selection) { + selectionContainer = Y.Node.create( + Y.Lang.sub( + instance.TPL_BUTTONS_CONTAINER, + { + selectionType: selection.type + } + ) + ); + + buttonsContainer.appendChild(selectionContainer); + + YArray.each( + selection.buttons, + Y.bind('_appendButton', instance, selectionContainer) + ); + } + ); instance._buttonsContainer = buttonsContainer; }, @@ -40,14 +69,43 @@ YUI.add('toolbar-styles', function(Y) { this.get('editor').on('toolbarsHide', this._onToolbarsHide, this); }, + /** + * Resolves all required modules based on the current toolbar configuration. + * + * @method getModules + * @return {Array} An Array with all the module names required by + * the current toolbar configuration. + */ + getModules: function() { + var self = this, + modules; + + modules = []; + + YArray.each( + this._getSelectionsConfig(), + function(selection) { + YArray.each( + selection.buttons, + function(button) { + modules.push('button-' + self._getButtonName(button)); + } + ); + } + ); + + return modules; + }, + /** * Calculates and sets the position of the toolbar. * * @method showAtPoint - * @param {Number} left The left offset in page coordinates where Toolbar should be shown. - * @param {Number} top The right offset in page coordinates where Toolbar should be shown. - * @param {Number} direction The direction of the selection. May be one of the following: - * CKEDITOR.SELECTION_BOTTOM_TO_TOP or CKEDITOR.SELECTION_TOP_TO_BOTTOM + * @param {Number} left The left offset in page coordinates. + * @param {Number} top The top offset in page coordinates. + * @param {Number} direction The direction of the selection. Can be one of these: + * 1. CKEDITOR.SELECTION_TOP_TO_BOTTOM + * 2. CKEDITOR.SELECTION_BOTTOM_TO_TOP */ showAtPoint: function(left, top, direction) { var boundingBox, @@ -75,6 +133,43 @@ YUI.add('toolbar-styles', function(Y) { }); }, + /** + * Resolves the different shortcuts available for the selections configuration and + * produces a final configuration array with all the required options. + * + * @method _getSelectionsConfig + * @protected + * @return {Array} An array with the selections final configuration for the toolbar. + */ + _getSelectionsConfig: function() { + var defaultSelection, + selectionConfig, + selectionsConfig = []; + + if (!this._selectionsConfig) { + YArray.each( + this.get('selections'), + function(selection) { + if (Lang.isString(selection)) { + selectionConfig = SELECTION_TYPES[selection]; + } else { + defaultSelection = SELECTION_TYPES[selection.type]; + + selectionConfig = defaultSelection ? Y.merge(defaultSelection, selection) : selection; + } + + if (selectionConfig) { + selectionsConfig.push(selectionConfig); + } + } + ); + + this._selectionsConfig = selectionsConfig; + } + + return this._selectionsConfig; + }, + /** * Calculates the most appropriate position where the Toolbar should be displayed and shows it. * @@ -85,32 +180,49 @@ YUI.add('toolbar-styles', function(Y) { * information. */ _onEditorInteraction: function(event) { - var editor, + var self = this, + editor, + contentBox, + nativeEvent, position, - selectionData, - selectionEmpty, - nativeEvent; + matchedSelection, + selectionData; editor = this.get('editor'); - selectionEmpty = editor.isSelectionEmpty(); - selectionData = event.data.selectionData; nativeEvent = event.data.nativeEvent; - if (!selectionData.element && selectionData.region && !selectionEmpty) { - position = this._calculatePosition(selectionData, { - x: nativeEvent.pageX, - y: nativeEvent.pageY - }); + matchedSelection = YArray.some( + this._getSelectionsConfig(), + function(selection) { + if (selection.test(selectionData, editor)) { + contentBox = self.get('contentBox'); - this.showAtPoint(position.x, position.y, position.direction); + contentBox.all('.alloy-editor-toolbar-buttons').removeClass('active'); - this.fire('positionChange', this); + contentBox.one('.alloy-editor-toolbar-buttons[data-selection=' + selection.type + ']').addClass('active'); - editor.fire('toolbarActive', this); - } else { + position = self._calculatePosition(selectionData, { + x: nativeEvent.pageX, + y: nativeEvent.pageY + }); + + self.showAtPoint(position.x, position.y, position.direction); + + self.fire('positionChange'); + + editor.fire('toolbarActive', self); + + return true; + } + + return false; + } + ); + + if (!matchedSelection) { this.hide(); } }, @@ -124,41 +236,56 @@ YUI.add('toolbar-styles', function(Y) { this.hide(); }, - BOUNDING_TEMPLATE: '
' + - '
', + BOUNDING_TEMPLATE: '
', - CONTENT_TEMPLATE: '
', + CONTENT_TEMPLATE: '
', - TPL_BUTTONS_CONTAINER: '
' + TPL_BUTTONS_CONTAINER: '
' }, { ATTRS: { - /** - * Specifies the buttons, which will be attached to the current instance of the toolbar. - * A button configuration can be simple string with the name of the button, or an object - * with properties, like this: + /* + * Specifies the selections that can be handled by the current instance of the toolbar. + * A Selection configuration is an object like + *

+                 *     {
+                 *         type: 'text',
+                 *         buttons: ['strong', 'em', 'u', 'h1', 'h2'],
+                 *         test: function(selectionData, editor) { ... }
+                 *     }
+                 * 
+ * + * A button configuration can be a simple string with the type of the selection. In this case, + * the toolbar will use the default buttons and test given for that type of selection, like this: *

-                 *     buttons: ['strong']
+                 *     selections: ['image', 'text']
                  * 
- * or: + * + * In addition, it's possible to pass only partials of a Selection configuration. In this case, + * the rest of properties will be inherited from the default selection type configuration: *

-                 *     buttons: [
+                 *     selections: [
+                 *         {
+                 *             type: 'image',
+                 *             buttons: ['left']
+                 *         },
                  *         {
-                 *             name: 'strong',
-                 *             cfg: {
-                 *                 zIndex: 1024,
-                 *                 property2: 1024
-                 *             }
+                 *             type: 'text',
+                 *             test: function() { // custom logic }
                  *         }
                  *     ]
                  * 
* - * @attribute buttons - * @default ['strong', 'em', 'u', 'h1', 'h2', 'a', 'twitter'] + * In the example, when a selection of type 'image' is done, only the 'left' button will be shown, and + * the decission of wether a selection is of type 'text' or not, will be handled by the custom provided + * code, but will show the default selection buttons. + * + * @attribute selections + * @default ['image', 'text'] * @type Array */ - buttons: { + selections: { validator: Lang.isArray, - value: ['strong', 'em', 'u', 'h1', 'h2', 'a', 'twitter'] + value: [STR_SELECTION_IMAGE, STR_SELECTION_TEXT] }, /** @@ -175,6 +302,22 @@ YUI.add('toolbar-styles', function(Y) { } }); + SELECTION_TYPES[STR_SELECTION_IMAGE] = { + type: STR_SELECTION_IMAGE, + buttons: ['left', 'right'], + test: function(selectionData, editor) { + return (selectionData.element && selectionData.element.getName() === 'img'); + } + }; + + SELECTION_TYPES[STR_SELECTION_TEXT] = { + type: STR_SELECTION_TEXT, + buttons: ['strong', 'em', 'u', 'h1', 'h2', 'a', 'twitter'], + test: function(selectionData, editor) { + return (!selectionData.element && selectionData.region && !editor.isSelectionEmpty()); + } + }; + Y.ToolbarStyles = ToolbarStyles; }, '', { requires: ['toolbar-base', 'toolbar-position', 'widget-base', 'widget-position', 'widget-position-constrain']