/* characterpicker-plugin.js is part of Aloha Editor project http://aloha-editor.org * * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria. * Contributors http://aloha-editor.org/contribution.php * * Aloha Editor 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 2 * of the License, or any later version. * * Aloha Editor 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * As an additional permission to the GNU GPL version 2, you may distribute * non-source (e.g., minimized or compacted) forms of the Aloha-Editor * source code without the copy of the GNU GPL normally required, * provided you include this license notice and a URL through which * recipients can access the Corresponding Source. */ define([ 'aloha', 'jquery', 'aloha/plugin', 'ui/ui', 'ui/button', 'ui/floating', 'PubSub', 'i18n!characterpicker/nls/i18n', 'i18n!aloha/nls/i18n' ], function(Aloha, jQuery, Plugin, Ui, Button, Floating, PubSub, i18n, i18nCore) { 'use strict'; var GENTICS = window.GENTICS; var overlayByConfig = {}; var _savedRange; function CharacterOverlay(onSelectCallback) { var self = this; self.$node = jQuery(''); // don't let the mousedown bubble up. otherwise there won't be an activeEditable self.$node.mousedown(function (e) { return false; }); self.onSelectCallback = onSelectCallback; self.$tbody = self.$node.find('tbody'); self.$node.appendTo(jQuery('body')); self._initHideOnDocumentClick(); self._initHideOnEsc(); self._initCursorFocus(onSelectCallback); self._initEvents(); } function calculateOffset(widget, $element) { var offset = $element.offset(); var calculatedOffset = { top: 0, left: 0 }; if ('fixed' === Floating.POSITION_STYLE) { offset.top -= jQuery(window).scrollTop(); offset.left -= jQuery(window).scrollLeft(); } calculatedOffset.top = widget.offset.top + (offset.top - widget.offset.top); calculatedOffset.left = widget.offset.left + (offset.left - widget.offset.left); return calculatedOffset; } CharacterOverlay.prototype = { offset: {top: 0, left: 0}, /** * Show the character overlay at the insert button's position * @param insertButton insert button */ show: function ($insertButton) { var self = this; // position the overlay relative to the insert-button self.$node.css(calculateOffset(self, $insertButton)); self.$node.css('position', Floating.POSITION_STYLE); self.$node.show(); // focus the first character self.$node.find('.focused').removeClass('focused'); jQuery(self.$node.find('td')[0]).addClass('focused'); self._overlayActive = true; }, hide: function() { this.$node.hide(); this._overlayActive = false; }, /** * Set the characters, that shall be selectable * @param {string} characters characters in a string, separated by spaces */ setCharacters: function (characters) { this._createCharacterButtons(characters); }, _initHideOnDocumentClick: function () { var self = this; // if the user clicks somewhere outside of the layer, the layer should be closed // stop bubbling the click on the create-dialog up to the body event self.$node.click(function (e) { e.stopPropagation(); }); var buttonSelector = '.aloha-icon-characterpicker'; // hide the layer if user clicks anywhere in the body jQuery('body').click(function (e) { if (!self._overlayActive) { return; } if (// don't consider clicks to the overlay itself e.target !== self.$node[0] // and don't consider clicks to the 'show' button. && !jQuery(e.target).is(buttonSelector) && !jQuery(e.target).find(buttonSelector).length) { self.hide(); } }); }, _initHideOnEsc: function () { var self = this; // escape closes the overlay jQuery(document).keyup(function (e) { var overlayVisibleAndEscapeKeyPressed = (self.$node.css('display') === 'table') && (e.keyCode === 27); if (overlayVisibleAndEscapeKeyPressed) { self.hide(); } }); }, _initCursorFocus: function (onSelectCallback) { var self = this; // you can navigate through the character table with the arrow keys // and select one with the enter key var $current, $next, $prev, $nextRow, $prevRow; var movements = { 13: function select() { $current = self.$node.find('.focused'); self.hide(); onSelectCallback($current.text()); }, 37: function left() { $current = self.$node.find('.focused'); $prev = $current.prev().addClass('focused'); if ($prev.length > 0) { $current.removeClass('focused'); } }, 38: function up() { $current = self.$node.find('.focused'); $prevRow = $current.parent().prev(); if ($prevRow.length > 0) { $prev = jQuery($prevRow.children()[$current.index()]).addClass('focused'); if ($prev.length > 0) { $current.removeClass('focused'); } } }, 39: function right() { $current = self.$node.find('.focused'); $next = $current.next().addClass('focused'); if ($next.length > 0) { $current.removeClass('focused'); } }, 40: function down() { $current = self.$node.find('.focused'); $nextRow = $current.parent().next(); if ($nextRow.length > 0) { $next = jQuery($nextRow.children()[$current.index()]).addClass('focused'); if ($next.length > 0) { $current.removeClass('focused'); } } } }; jQuery(document).keydown(function (e) { e.stopPropagation(); var isOverlayVisible = self.$node.css('display') === 'table'; if (isOverlayVisible) { // check if there is a move-command for the pressed key var moveCommand = movements[e.keyCode]; if (moveCommand) { moveCommand(); return false; } } }); }, _initEvents: function () { var self = this; // when the editable is deactivated, hide the layer Aloha.bind('aloha-editable-deactivated', function (event, rangeObject) { self.hide(); }); }, _createCharacterButtons: function (characters) { var self = this; // TODO: shouldn't we do jQuery('
' + characters + '
').text() here? var textarea = document.createElement('textarea'); textarea.innerHTML = characters; characters = textarea.value; var characterList = jQuery.grep( characters.split(' '), function filterOutEmptyOnces(e) { return e !== ''; } ); var charTable = ['']; var i = 0; var chr; while ((chr = characterList[i])) { // make a new row every 15 characters if (0 !== i && ((i % 15) === 0)) { charTable.push(''); } charTable.push('' + chr + ''); i++; } charTable.push(''); self.$tbody .empty() .append(charTable.join('')); self.$node.delegate('td', 'mouseover', function () { jQuery(this).addClass('mouseover'); }).delegate('td', 'mouseout', function () { jQuery(this).removeClass('mouseover'); }).delegate('td', 'click', function (e) { self.$node.hide(); var character = jQuery(this).text(); self.onSelectCallback(character); }); } }; return Plugin.create('characterpicker', { _constructor: function () { this._super('characterpicker'); }, /** * Default configuration */ config: '& " ¢ € £ ¥ © ® ™ ‰ µ · • … ′ ″ § ¶ ß ‹ › « » ‘ ’ “ ” ‚ „ < > ≤ ≥ – — ¯ ‾ ¤ ¦ ¨ ¡ ¿ ˆ ˜ ° − ± ÷ ⁄ × ¹ ² ³ ¼ ½ ¾ ƒ ∫ ∑ ∞ √ ∼ ≅ ≈ ≠ ≡ ∈ ∉ ∋ ∏ ∧ ∨ ¬ ∩ ∪ ∂ ∀ ∃ ∅ ∇ ∗ ∝ ∠ ´ ¸ ª º † ‡ À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö Ø Œ Š Ù Ú Û Ü Ý Ÿ Þ à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ø œ š ù ú û ü ý þ ÿ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ℵ ϖ ℜ ϑ ϒ ℘ ℑ ← ↑ → ↓ ↔ ↵ ⇐ ⇑ ⇒ ⇓ ⇔ ∴ ⊂ ⊃ ⊄ ⊆ ⊇ ⊕ ⊗ ⊥ ⋅ ⌈ ⌉ ⌊ ⌋ 〈 〉 ◊ ♠ ♣ ♥ ♦', init: function () { var self = this; if ( typeof Aloha.settings.plugins != 'undefined' && typeof Aloha.settings.plugins.characterpicker != 'undefined' ) { self.settings = Aloha.settings.plugins.characterpicker; } this._characterPickerButton = Ui.adopt("characterPicker", Button, { tooltip: i18n.t('button.addcharacter.tooltip'), icon: "aloha-icon-characterpicker", scope: 'Aloha.continuoustext', click: function() { if (false !== self.characterOverlay) { _savedRange = Aloha.Selection.rangeObject; self.characterOverlay.show(this.element); } } }); // Populate the cache lazily setTimeout(function(){ initCache(0); }, 100); function initCache(i) { if (i < Aloha.editables.length) { self.getOverlayForEditable(Aloha.editables[i]); setTimeout(function(){ initCache(i + 1); }, 100); } } Aloha.bind('aloha-editable-activated', function (event, data) { self.characterOverlay = self.getOverlayForEditable(data.editable); if (self.characterOverlay) { self._characterPickerButton.show(); } else { self._characterPickerButton.hide(); } }); PubSub.sub('aloha.floating.changed', function(message) { self.characterOverlay.offset = message.position.offset; self.characterOverlay.$node.css(calculateOffset(self.characterOverlay, self._characterPickerButton.element)); }); }, getOverlayForEditable: function(editable) { var that = this; // Each editable may have its own configuration and as // such may have its own overlay. var config = this.getEditableConfig(editable.obj), overlay; if ( ! config ) { return false; } if (jQuery.isArray(config)) { config = config.join(' '); } // We cache the overlay by configuration. If all editables // have the same configuration, only a single overlay will // be created that will be used by all editables. overlay = overlayByConfig[config]; if ( ! overlay ) { overlay = new CharacterOverlay(onCharacterSelect); overlay.setCharacters(config); overlayByConfig[config] = overlay; } return overlay; } }); /** * insert a character after selecting it from the list */ function onCharacterSelect (character) { if (Aloha.activeEditable) { //Select the range that was selected before the overlay was opened _savedRange.select(); Aloha.execCommand('insertHTML', false, character); } } });