%PDF- %PDF-
Direktori : /home/vacivi36/ava/lib/editor/atto/yui/src/editor/js/ |
Current File : /home/vacivi36/ava/lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js |
// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * @module moodle-editor_atto-plugin * @submodule buttons */ /** * Button functions for an Atto Plugin. * * See {{#crossLink "M.editor_atto.EditorPlugin"}}{{/crossLink}} for details. * * @namespace M.editor_atto * @class EditorPluginButtons */ var MENUTEMPLATE = '' + '<button class="{{buttonClass}} atto_hasmenu" ' + 'id="{{id}}" ' + 'tabindex="-1" ' + 'title="{{title}}" ' + 'aria-label="{{title}}" ' + 'type="button" ' + 'aria-haspopup="true" ' + 'aria-controls="{{id}}_menu">' + '<span class="editor_atto_menu_icon"></span>' + '<span class="editor_atto_menu_expand"></span>' + '</button>'; var DISABLED = 'disabled', HIGHLIGHT = 'highlight', LOGNAME = 'moodle-editor_atto-editor-plugin', CSS = { EDITORWRAPPER: '.editor_atto_content', MENUICON: '.editor_atto_menu_icon', MENUEXPAND: '.editor_atto_menu_expand' }; function EditorPluginButtons() {} EditorPluginButtons.ATTRS = { }; EditorPluginButtons.prototype = { /** * All of the buttons that belong to this plugin instance. * * Buttons are stored by button name. * * @property buttons * @type object */ buttons: null, /** * A list of each of the button names. * * @property buttonNames * @type array */ buttonNames: null, /** * A read-only view of the current state for each button. Mappings are stored by name. * * Possible states are: * <ul> * <li>{{#crossLink "M.editor_atto.EditorPluginButtons/ENABLED:property"}}{{/crossLink}}; and</li> * <li>{{#crossLink "M.editor_atto.EditorPluginButtons/DISABLED:property"}}{{/crossLink}}.</li> * </ul> * * @property buttonStates * @type object */ buttonStates: null, /** * The menus belonging to this plugin instance. * * @property menus * @type object */ menus: null, /** * The state for a disabled button. * * @property DISABLED * @type Number * @static * @value 0 */ DISABLED: 0, /** * The state for an enabled button. * * @property ENABLED * @type Number * @static * @value 1 */ ENABLED: 1, /** * The list of Event Handlers for buttons. * * @property _buttonHandlers * @protected * @type array */ _buttonHandlers: null, /** * Hide handlers which are cancelled when the menu is hidden. * * @property _menuHideHandlers * @protected * @type array */ _menuHideHandlers: null, /** * A textual description of the primary keyboard shortcut for this * plugin. * * This will be null if no keyboard shortcut has been registered. * * @property _primaryKeyboardShortcut * @protected * @type String * @default null */ _primaryKeyboardShortcut: null, /** * An list of objects returned by Y.soon(). * * The keys will be the buttonName of the button, and the value the Y.soon() object. * * @property _highlightQueue * @protected * @type Object * @default null */ _highlightQueue: null, /** * Add a button for this plugin to the toolbar. * * @method addButton * @param {object} config The configuration for this button * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. * @param {string} [config.icon] The icon identifier. * @param {string} [config.iconComponent='core'] The icon component. * @param {string} [config.keys] The shortcut key that can call this plugin from the keyboard. * @param {string} [config.keyDescription] An optional description for the keyboard shortcuts. * If not specified, this is automatically generated based on config.keys. * If multiple key bindings are supplied to config.keys, then only the first is used. * If set to false, then no description is added to the title. * @param {string} [config.tags] The tags that trigger this button to be highlighted. * @param {boolean} [config.tagMatchRequiresAll=true] Working in combination with the tags parameter, when true * every tag of the selection has to match. When false, only one match is needed. Only set this to false when * necessary as it is much less efficient. * See {{#crossLink "M.editor_atto.EditorSelection/selectionFilterMatches:method"}}{{/crossLink}} for more information. * @param {string} [config.title=this.name] The string identifier in the plugin's language file. * @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if * specified, in the class for the button. * @param {function} config.callback A callback function to call when the button is clicked. * @param {object} [config.callbackArgs] Any arguments to pass to the callback. * @param {boolean} [config.inlineFormat] Delay callback for text input if selection is collapsed. * @return {Node} The Node representing the newly created button. */ addButton: function(config) { var group = this.get('group'), pluginname = this.name, buttonClass = 'atto_' + pluginname + '_button', button, host = this.get('host'); if (config.exec) { buttonClass = buttonClass + '_' + config.exec; } if (!config.buttonName) { // Set a default button name - this is used as an identifier in the button object. config.buttonName = config.exec || pluginname; } else { buttonClass = buttonClass + '_' + config.buttonName; } config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); if (!config.title) { config.title = 'pluginname'; } var title = M.util.get_string(config.title, 'atto_' + pluginname); // Create the actual button. button = Y.Node.create('<button type="button" class="' + buttonClass + '"' + 'tabindex="-1"></button>'); button.setAttribute('title', title); button.setAttribute('aria-label', title); window.require(['core/templates'], function(Templates) { // The button already has title and label, so no need to set them again on the icon. Templates.renderPix(config.icon, config.iconComponent, '').then(function(iconhtml) { button.append(iconhtml); }); }); // Append it to the group. group.append(button); var currentfocus = this.toolbar.getAttribute('aria-activedescendant'); if (!currentfocus) { // Initially set the first button in the toolbar to be the default on keyboard focus. button.setAttribute('tabindex', '0'); this.toolbar.setAttribute('aria-activedescendant', button.generateID()); this.get('host')._tabFocus = button; } // Normalize the callback parameters. config = this._normalizeCallback(config); // Add the standard click handler to the button. this._buttonHandlers.push( this.toolbar.delegate('click', config.callback, '.' + buttonClass, this) ); // Handle button click via shortcut key. if (config.keys) { if (typeof config.keyDescription !== 'undefined') { // A keyboard shortcut description was specified - use it. this._primaryKeyboardShortcut[buttonClass] = config.keyDescription; } this._addKeyboardListener(config.callback, config.keys, buttonClass); if (this._primaryKeyboardShortcut[buttonClass]) { // If we have a valid keyboard shortcut description, then set it with the title. title = M.util.get_string('plugin_title_shortcut', 'editor_atto', { title: title, shortcut: this._primaryKeyboardShortcut[buttonClass] }); button.setAttribute('title', title); button.setAttribute('aria-label', title); } } // Handle highlighting of the button. if (config.tags) { var tagMatchRequiresAll = true; if (typeof config.tagMatchRequiresAll === 'boolean') { tagMatchRequiresAll = config.tagMatchRequiresAll; } this._buttonHandlers.push( host.on(['atto:selectionchanged', 'change'], function(e) { if (typeof this._highlightQueue[config.buttonName] !== 'undefined') { this._highlightQueue[config.buttonName].cancel(); } // Async the highlighting. this._highlightQueue[config.buttonName] = Y.soon(Y.bind(function(e) { if (host.selectionFilterMatches(config.tags, e.selectedNodes, tagMatchRequiresAll)) { this.highlightButtons(config.buttonName); } else { this.unHighlightButtons(config.buttonName); } }, this, e)); }, this) ); } // Add the button reference to the buttons array for later reference. this.buttonNames.push(config.buttonName); this.buttons[config.buttonName] = button; this.buttonStates[config.buttonName] = this.ENABLED; return button; }, /** * Add a basic button which ties into the execCommand. * * See {{#crossLink "M.editor_atto.EditorPluginButtons/addButton:method"}}addButton{{/crossLink}} * for full details of the optional parameters. * * @method addBasicButton * @param {object} config The button configuration * @param {string} config.exec The execCommand to call on the document. * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. * @param {string} [config.icon] The icon identifier. * @param {string} [config.iconComponent='core'] The icon component. * @param {string} [config.keys] The shortcut key that can call this plugin from the keyboard. * @param {string} [config.tags] The tags that trigger this button to be highlighted. * @param {boolean} [config.tagMatchRequiresAll=false] Working in combination with the tags parameter, highlight * this button when any match is good enough. * * See {{#crossLink "M.editor_atto.EditorSelection/selectionFilterMatches:method"}}{{/crossLink}} for more information. * @param {string} [config.title=this.name] The string identifier in the plugin's language file. * @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if * specified, in the class for the button. * @return {Node} The Node representing the newly created button. */ addBasicButton: function(config) { if (!config.exec) { Y.log('No exec command specified. Cannot proceed.', 'warn', 'moodle-editor_atto-plugin'); return null; } // The default icon - true for most core plugins. if (!config.icon) { config.icon = 'e/' + config.exec; } // The default callback. config.callback = function() { document.execCommand(config.exec, false, null); // And mark the text area as updated. this.markUpdated(); }; // Return the newly created button. return this.addButton(config); }, /** * Add a menu for this plugin to the editor toolbar. * * @method addToolbarMenu * @param {object} config The configuration for this button * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. * @param {string} [config.icon] The icon identifier. * @param {string} [config.iconComponent='core'] The icon component. * @param {string} [config.title=this.name] The string identifier in the plugin's language file. * @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if * specified, in the class for the button. * @param {function} config.callback A callback function to call when the button is clicked. * @param {object} [config.callbackArgs] Any arguments to pass to the callback. * @param {boolean} [config.inlineFormat] Delay callback for text input if selection is collapsed. * @param {array} config.entries List of menu entries with the string (entry.text) and the handlers (entry.handler). * @param {number} [config.overlayWidth=14] The width of the menu. This will be suffixed with the 'em' unit. * @param {string} [config.menuColor] menu icon background color * @return {Node} The Node representing the newly created button. */ addToolbarMenu: function(config) { var group = this.get('group'), pluginname = this.name, buttonClass = 'atto_' + pluginname + '_button', button, currentFocus; if (!config.buttonName) { // Set a default button name - this is used as an identifier in the button object. config.buttonName = pluginname; } else { buttonClass = buttonClass + '_' + config.buttonName; } config.buttonClass = buttonClass; // Normalize icon configuration. config = this._normalizeIcon(config); if (!config.title) { config.title = 'pluginname'; } var title = M.util.get_string(config.title, 'atto_' + pluginname); if (!config.menuColor) { config.menuColor = 'transparent'; } // Create the actual button. var id = 'atto_' + pluginname + '_menubutton_' + Y.stamp(this); var template = Y.Handlebars.compile(MENUTEMPLATE); button = Y.Node.create(template({ buttonClass: buttonClass, config: config, title: title, id: id })); // Add this button id to the config. It will be used in the menu later. config.buttonId = id; window.require(['core/templates'], function(Templates) { // The button already has title and label, so no need to set them again on the icon. Templates.renderPix(config.icon, config.iconComponent, '').then(function(iconhtml) { button.one(CSS.MENUICON).append(iconhtml); }); Templates.renderPix('t/expanded', 'core', '').then(function(iconhtml) { button.one(CSS.MENUEXPAND).append(iconhtml); }); }); // Append it to the group. group.append(button); group.append(Y.Node.create('<div class="menuplaceholder" id="' + id + '_menu"></div>')); config.attachmentPoint = '#' + id + '_menu'; currentFocus = this.toolbar.getAttribute('aria-activedescendant'); if (!currentFocus) { // Initially set the first button in the toolbar to be the default on keyboard focus. button.setAttribute('tabindex', '0'); this.toolbar.setAttribute('aria-activedescendant', button.generateID()); } // Add the standard click handler to the menu. this._buttonHandlers.push( this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config), this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config) ); // Add the button reference to the buttons array for later reference. this.buttonNames.push(config.buttonName); this.buttons[config.buttonName] = button; this.buttonStates[config.buttonName] = this.ENABLED; return button; }, /** * Display a toolbar menu. * * @method _showToolbarMenu * @param {EventFacade} e * @param {object} config The configuration for the whole toolbar. * @param {Number} [config.overlayWidth=14] The width of the menu * @private */ _showToolbarMenu: function(e, config) { // Prevent default primarily to prevent arrow press changes. e.preventDefault(); if (!this.isEnabled()) { // Exit early if the plugin is disabled. return; } // Ensure menu button was clicked, and isn't itself disabled. var menuButton = e.currentTarget.ancestor('button', true); if (menuButton === null || menuButton.hasAttribute(DISABLED)) { return; } var menuDialogue; if (!this.menus[config.buttonClass]) { if (!config.overlayWidth) { config.overlayWidth = '14'; } if (!config.innerOverlayWidth) { config.innerOverlayWidth = parseInt(config.overlayWidth, 10) - 2 + 'em'; } config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em'; this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config); this.menus[config.buttonClass].get('contentBox').delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config); } // Clear the focusAfterHide for any other menus which may be open. Y.Array.each(this.get('host').openMenus, function(menu) { menu.set('focusAfterHide', null); }); // Ensure that we focus on this button next time. var creatorButton = this.buttons[config.buttonName]; creatorButton.focus(); this.get('host')._setTabFocus(creatorButton); // Get a reference to the menu dialogue. menuDialogue = this.menus[config.buttonClass]; // Focus on the button by default after hiding this menu. menuDialogue.set('focusAfterHide', creatorButton); // Display the menu. menuDialogue.show(); // Indicate that the menu is expanded. menuButton.setAttribute("aria-expanded", true); // Position it next to the button which opened it. menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); this.get('host').openMenus = [menuDialogue]; }, /** * Display a toolbar menu and focus upon the first item. * * @method _showToolbarMenuAndFocus * @param {EventFacade} e * @param {object} config The configuration for the whole toolbar. * @param {Number} [config.overlayWidth=14] The width of the menu * @private */ _showToolbarMenuAndFocus: function(e, config) { this._showToolbarMenu(e, config); // Focus on the first element in the menu. this.menus[config.buttonClass].get('boundingBox').one('a').focus(); }, /** * Select a menu item and call the appropriate callbacks. * * @method _chooseMenuItem * @param {EventFacade} e * @param {object} config * @param {M.core.dialogue} menuDialogue The Dialogue to hide. * @private */ _chooseMenuItem: function(e, config, menuDialogue) { // Get the index from the clicked anchor. var index = e.target.ancestor('a', true).getData('index'), // And the normalized callback configuration. buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig); menuDialogue = this.menus[config.buttonClass]; // Prevent the dialogue to be closed because of some browser weirdness. menuDialogue.set('preventHideMenu', true); // Call the callback for this button. buttonConfig.callback(e, buttonConfig._callback, buttonConfig.callbackArgs); // Cancel the hide menu prevention. menuDialogue.set('preventHideMenu', false); // Set the focus after hide so that focus is returned to the editor and changes are made correctly. menuDialogue.set('focusAfterHide', this.get('host').editor); menuDialogue.hide(e); }, /** * Normalize and sanitize the configuration variables relating to callbacks. * * @method _normalizeCallback * @param {object} config * @param {function} config.callback A callback function to call when the button is clicked. * @param {object} [config.callbackArgs] Any arguments to pass to the callback. * @param {boolean} [config.inlineFormat] Delay callback for text input if selection is collapsed. * @param {object} [inheritFrom] A parent configuration that this configuration may inherit from. * @return {object} The normalized configuration * @private */ _normalizeCallback: function(config, inheritFrom) { if (config._callbackNormalized) { // Return early if the callback has already been normalized. return config; } if (!inheritFrom) { // Create an empty inheritFrom to make life easier below. inheritFrom = {}; } // First we wrap the callback in function to handle formating of text inserted into collapsed selection. config.inlineFormat = config.inlineFormat || inheritFrom.inlineFormat; config._inlineCallback = config.callback || inheritFrom.callback; config._callback = config.callback || inheritFrom.callback; if (config.inlineFormat && typeof config._inlineCallback === 'function') { config._callback = function(e, args) { this.get('host').applyFormat(e, config._inlineCallback, this, args); }; } // We wrap the callback in function to prevent the default action, check whether the editor is // active and focus it, and then mark the field as updated. config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs); config._callbackNormalized = true; return config; }, /** * Normalize and sanitize the configuration variables relating to icons. * * @method _normalizeIcon * @param {object} config * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. * @param {string} [config.icon] The icon identifier. * @param {string} [config.iconComponent='core'] The icon component. * @return {object} The normalized configuration * @private */ _normalizeIcon: function(config) { if (!config.iconurl) { // The default icon component. if (!config.iconComponent || config.iconComponent == 'moodle') { config.iconComponent = 'core'; } config.iconurl = M.util.image_url(config.icon, config.iconComponent); } return config; }, /** * A wrapper in which to run the callbacks. * * This handles common functionality such as: * <ul> * <li>preventing the default action; and</li> * <li>focusing the editor if relevant.</li> * </ul> * * @method _callbackWrapper * @param {EventFacade} e * @param {Function} callback The function to call which makes the relevant changes. * @param {Array} [callbackArgs] The arguments passed to this callback. * @return {Mixed} The value returned by the callback. * @private */ _callbackWrapper: function(e, callback, callbackArgs) { e.preventDefault(); if (!this.isEnabled()) { // Exit early if the plugin is disabled. return; } var creatorButton = e.currentTarget.ancestor('button', true); if (creatorButton && creatorButton.hasAttribute(DISABLED)) { // Exit early if the clicked button was disabled. return; } if (!(YUI.Env.UA.android || this.get('host').isActive())) { // We must not focus for Android here, even if the editor is not active because the keyboard auto-completion // changes the cursor position. // If we save that change, then when we restore the change later we get put in the wrong place. // Android is fine to save the selection without the editor being in focus. this.get('host').focus(); } // Save the selection. this.get('host').saveSelection(); // Ensure that we focus on this button next time. if (creatorButton) { this.get('host')._setTabFocus(creatorButton); } // Build the arguments list, but remove the callback we're calling. var args = [e, callbackArgs]; // Restore selection before making changes. this.get('host').restoreSelection(); // Actually call the callback now. return callback.apply(this, args); }, /** * Add a keyboard listener to call the callback. * * The keyConfig will take either an array of keyConfigurations, in * which case _addKeyboardListener is called multiple times; an object * containing an optional eventtype, optional container, and a set of * keyCodes, or just a string containing the keyCodes. When keyConfig is * not an object, it is wrapped around a function that ensures that * only the expected key modifiers were used. For instance, it checks * that space+ctrl is not triggered when the user presses ctrl+shift+space. * When using an object, the developer should check that manually. * * @method _addKeyboardListener * @param {function} callback * @param {array|object|string} keyConfig * @param {string} [keyConfig.eventtype=key] The type of event * @param {string} [keyConfig.container=.editor_atto_content] The containing element. * @param {string} keyConfig.keyCodes The keycodes to user for the event. * @private * */ _addKeyboardListener: function(callback, keyConfig, buttonName) { var eventtype = 'key', container = CSS.EDITORWRAPPER, keys, handler, modifier; if (Y.Lang.isArray(keyConfig)) { // If an Array was specified, call the add function for each element. Y.Array.each(keyConfig, function(config) { this._addKeyboardListener(callback, config); }, this); return this; } else if (typeof keyConfig === "object") { if (keyConfig.eventtype) { eventtype = keyConfig.eventtype; } if (keyConfig.container) { container = keyConfig.container; } // Must be specified. keys = keyConfig.keyCodes; handler = callback; } else { modifier = this._getDefaultMetaKey(); keys = this._getKeyEvent() + keyConfig + '+' + modifier; if (typeof this._primaryKeyboardShortcut[buttonName] === 'undefined') { this._primaryKeyboardShortcut[buttonName] = this._getDefaultMetaKeyDescription(keyConfig); } // Wrap the callback into a handler to check if it uses the specified modifiers, not more. handler = Y.bind(function(modifiers, e) { if (this._eventUsesExactKeyModifiers(modifiers, e)) { callback.apply(this, [e]); } }, this, [modifier]); } this._buttonHandlers.push( this.editor.delegate( eventtype, handler, keys, container, this ) ); Y.log('Atto shortcut registered: ' + keys + ' now triggers for ' + buttonName, 'debug', LOGNAME); }, /** * Checks if a key event was strictly defined for the modifiers passed. * * @method _eventUsesExactKeyModifiers * @param {Array} modifiers List of key modifiers to check for (alt, ctrl, meta or shift). * @param {EventFacade} e The event facade. * @return {Boolean} True if the event was stricly using the modifiers specified. */ _eventUsesExactKeyModifiers: function(modifiers, e) { var exactMatch = true, hasKey; if (e.type !== 'key') { return false; } hasKey = Y.Array.indexOf(modifiers, 'alt') > -1; exactMatch = exactMatch && ((e.altKey && hasKey) || (!e.altKey && !hasKey)); hasKey = Y.Array.indexOf(modifiers, 'ctrl') > -1; exactMatch = exactMatch && ((e.ctrlKey && hasKey) || (!e.ctrlKey && !hasKey)); hasKey = Y.Array.indexOf(modifiers, 'meta') > -1; exactMatch = exactMatch && ((e.metaKey && hasKey) || (!e.metaKey && !hasKey)); hasKey = Y.Array.indexOf(modifiers, 'shift') > -1; exactMatch = exactMatch && ((e.shiftKey && hasKey) || (!e.shiftKey && !hasKey)); return exactMatch; }, /** * Determine if this plugin is enabled, based upon the state of it's buttons. * * @method isEnabled * @return {boolean} */ isEnabled: function() { // The first instance of an undisabled button will make this return true. var found = Y.Object.some(this.buttonStates, function(button) { return (button === this.ENABLED); }, this); return found; }, /** * Enable one button, or all buttons relating to this Plugin. * * If no button is specified, all buttons are disabled. * * @method disableButtons * @param {String} [button] The name of a specific plugin to enable. * @chainable */ disableButtons: function(button) { return this._setButtonState(false, button); }, /** * Enable one button, or all buttons relating to this Plugin. * * If no button is specified, all buttons are enabled. * * @method enableButtons * @param {String} [button] The name of a specific plugin to enable. * @chainable */ enableButtons: function(button) { return this._setButtonState(true, button); }, /** * Set the button state for one button, or all buttons associated with this plugin. * * @method _setButtonState * @param {Boolean} enable Whether to enable this button. * @param {String} [button] The name of a specific plugin to set state for. * @chainable * @private */ _setButtonState: function(enable, button) { var attributeChange = 'setAttribute'; if (enable) { attributeChange = 'removeAttribute'; } if (button) { if (this.buttons[button]) { this.buttons[button][attributeChange](DISABLED, DISABLED); this.buttonStates[button] = enable ? this.ENABLED : this.DISABLED; } } else { Y.Array.each(this.buttonNames, function(button) { this.buttons[button][attributeChange](DISABLED, DISABLED); this.buttonStates[button] = enable ? this.ENABLED : this.DISABLED; }, this); } this.get('host').checkTabFocus(); return this; }, /** * Highlight a button, or buttons in the toolbar. * * If no button is specified, all buttons are highlighted. * * @method highlightButtons * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. * @chainable */ highlightButtons: function(button) { return this._changeButtonHighlight(true, button); }, /** * Un-highlight a button, or buttons in the toolbar. * * If no button is specified, all buttons are un-highlighted. * * @method unHighlightButtons * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. * @chainable */ unHighlightButtons: function(button) { return this._changeButtonHighlight(false, button); }, /** * Highlight a button, or buttons in the toolbar. * * @method _changeButtonHighlight * @param {boolean} highlight true * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. * @protected * @chainable */ _changeButtonHighlight: function(highlight, button) { var method = 'addClass'; if (!highlight) { method = 'removeClass'; } if (button) { if (this.buttons[button]) { this.buttons[button][method](HIGHLIGHT); this.buttons[button].setAttribute('aria-pressed', highlight ? 'true' : 'false'); this._buttonHighlightToggled(button, highlight); } } else { Y.Object.each(this.buttons, function(button) { button[method](HIGHLIGHT); button.setAttribute('aria-pressed', highlight ? 'true' : 'false'); this._buttonHighlightToggled(button, highlight); }, this); } return this; }, /** * Fires a custom event that notifies listeners that a button's highlight has been toggled. * * @param {String} buttonName The button name. * @param {Boolean} highlight True when the button was highlighted. False, otherwise. * @private */ _buttonHighlightToggled: function(buttonName, highlight) { var toggledButton = this.buttons[buttonName]; if (toggledButton) { // Fire an event that the button highlight was toggled. require(['editor_atto/events'], function(attoEvents) { attoEvents.notifyButtonHighlightToggled(toggledButton.getDOMNode(), buttonName, highlight); }); } }, /** * Get the default meta key to use with keyboard events. * * On a Mac, this will be the 'meta' key for Command; otherwise it will * be the Control key. * * @method _getDefaultMetaKey * @return {string} * @private */ _getDefaultMetaKey: function() { if (Y.UA.os === 'macintosh') { return 'meta'; } else { return 'ctrl'; } }, /** * Get the user-visible description of the meta key to use with keyboard events. * * On a Mac, this will be 'Command' ; otherwise it will be 'Control'. * * @method _getDefaultMetaKeyDescription * @return {string} * @private */ _getDefaultMetaKeyDescription: function(keyCode) { if (Y.UA.os === 'macintosh') { return M.util.get_string('editor_command_keycode', 'editor_atto', String.fromCharCode(keyCode).toLowerCase()); } else { return M.util.get_string('editor_control_keycode', 'editor_atto', String.fromCharCode(keyCode).toLowerCase()); } }, /** * Get the standard key event to use for keyboard events. * * @method _getKeyEvent * @return {string} * @private */ _getKeyEvent: function() { return 'down:'; } }; Y.Base.mix(Y.M.editor_atto.EditorPlugin, [EditorPluginButtons]);