|
@@ -0,0 +1,773 @@
|
|
|
+/**
|
|
|
+ * emojiarea - A rich textarea control that supports emojis, WYSIWYG-style.
|
|
|
+ * Copyright (c) 2012 DIY Co
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
|
|
+ * file except in compliance with the License. You may obtain a copy of the License at:
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software distributed under
|
|
|
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
|
+ * ANY KIND, either express or implied. See the License for the specific language
|
|
|
+ * governing permissions and limitations under the License.
|
|
|
+ *
|
|
|
+ * @author Brian Reavis <brian@diy.org>
|
|
|
+ */
|
|
|
+
|
|
|
+/**
|
|
|
+ * This file also contains some modifications by Igor Zhukov in order to add
|
|
|
+ * custom scrollbars to EmojiMenu See keyword `MODIFICATION` in source code.
|
|
|
+ */
|
|
|
+(function($, window, document) {
|
|
|
+
|
|
|
+ var ELEMENT_NODE = 1;
|
|
|
+ var TEXT_NODE = 3;
|
|
|
+ var TAGS_BLOCK = [ 'p', 'div', 'pre', 'form' ];
|
|
|
+ var KEY_ESC = 27;
|
|
|
+ var KEY_TAB = 9;
|
|
|
+ /* Keys that are not intercepted and canceled when the textbox has reached its max length:
|
|
|
+ Backspace, Tab, Ctrl, Alt, Left Arrow, Up Arrow, Right Arrow, Down Arrow, Cmd Key, Delete
|
|
|
+ */
|
|
|
+ var MAX_LENGTH_ALLOWED_KEYS = [8, 9, 17, 18, 37, 38, 39, 40, 91, 46];
|
|
|
+
|
|
|
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION START Options 'spritesheetPath', 'spritesheetDimens',
|
|
|
+ * 'iconSize' added by Andre Staltz.
|
|
|
+ */
|
|
|
+ $.emojiarea = {
|
|
|
+ assetsPath : '',
|
|
|
+ spriteSheetPath: '',
|
|
|
+ blankGifPath: '',
|
|
|
+ iconSize : 25,
|
|
|
+ icons : {},
|
|
|
+ };
|
|
|
+ var defaultRecentEmojis = ':joy:,:kissing_heart:,:heart:,:heart_eyes:,:blush:,:grin:,:+1:,:relaxed:,:pensive:,:smile:,:sob:,:kiss:,:unamused:,:flushed:,:stuck_out_tongue_winking_eye:,:see_no_evil:,:wink:,:smiley:,:cry:,:stuck_out_tongue_closed_eyes:,:scream:,:rage:,:smirk:,:disappointed:,:sweat_smile:,:kissing_closed_eyes:,:speak_no_evil:,:relieved:,:grinning:,:yum:,:laughing:,:ok_hand:,:neutral_face:,:confused:'
|
|
|
+ .split(',');
|
|
|
+ /* ! MODIFICATION END */
|
|
|
+
|
|
|
+ $.fn.emojiarea = function(options) {
|
|
|
+ options = $.extend({}, options);
|
|
|
+ return this
|
|
|
+ .each(function () {
|
|
|
+ var originalInput = $(this);
|
|
|
+ if ('contentEditable' in document.body
|
|
|
+ && options.wysiwyg !== false) {
|
|
|
+ var id = getGuid();
|
|
|
+ new EmojiArea_WYSIWYG(originalInput, id, $.extend({}, options));
|
|
|
+ } else {
|
|
|
+ var id = getGuid();
|
|
|
+ new EmojiArea_Plain(originalInput, id, options);
|
|
|
+ }
|
|
|
+ originalInput.attr(
|
|
|
+ {
|
|
|
+ 'data-emojiable': 'converted',
|
|
|
+ 'data-id': id,
|
|
|
+ 'data-type': 'original-input'
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+ var util = {};
|
|
|
+
|
|
|
+ util.restoreSelection = (function() {
|
|
|
+ if (window.getSelection) {
|
|
|
+ return function(savedSelection) {
|
|
|
+ var sel = window.getSelection();
|
|
|
+ sel.removeAllRanges();
|
|
|
+ for (var i = 0, len = savedSelection.length; i < len; ++i) {
|
|
|
+ sel.addRange(savedSelection[i]);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ } else if (document.selection && document.selection.createRange) {
|
|
|
+ return function(savedSelection) {
|
|
|
+ if (savedSelection) {
|
|
|
+ savedSelection.select();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ })();
|
|
|
+
|
|
|
+ util.saveSelection = (function() {
|
|
|
+ if (window.getSelection) {
|
|
|
+ return function() {
|
|
|
+ var sel = window.getSelection(), ranges = [];
|
|
|
+ if (sel.rangeCount) {
|
|
|
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
|
|
+ ranges.push(sel.getRangeAt(i));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ranges;
|
|
|
+ };
|
|
|
+ } else if (document.selection && document.selection.createRange) {
|
|
|
+ return function() {
|
|
|
+ var sel = document.selection;
|
|
|
+ return (sel.type.toLowerCase() !== 'none') ? sel.createRange()
|
|
|
+ : null;
|
|
|
+ };
|
|
|
+ }
|
|
|
+ })();
|
|
|
+
|
|
|
+ util.replaceSelection = (function() {
|
|
|
+ if (window.getSelection) {
|
|
|
+ return function(content) {
|
|
|
+ var range, sel = window.getSelection();
|
|
|
+ var node = typeof content === 'string' ? document
|
|
|
+ .createTextNode(content) : content;
|
|
|
+ if (sel.getRangeAt && sel.rangeCount) {
|
|
|
+ range = sel.getRangeAt(0);
|
|
|
+ range.deleteContents();
|
|
|
+ //range.insertNode(document.createTextNode(''));
|
|
|
+ range.insertNode(node);
|
|
|
+ range.setStart(node, 0);
|
|
|
+
|
|
|
+ window.setTimeout(function() {
|
|
|
+ range = document.createRange();
|
|
|
+ range.setStartAfter(node);
|
|
|
+ range.collapse(true);
|
|
|
+ sel.removeAllRanges();
|
|
|
+ sel.addRange(range);
|
|
|
+ }, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (document.selection && document.selection.createRange) {
|
|
|
+ return function(content) {
|
|
|
+ var range = document.selection.createRange();
|
|
|
+ if (typeof content === 'string') {
|
|
|
+ range.text = content;
|
|
|
+ } else {
|
|
|
+ range.pasteHTML(content.outerHTML);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })();
|
|
|
+
|
|
|
+ util.insertAtCursor = function(text, el) {
|
|
|
+ text = ' ' + text;
|
|
|
+ var val = el.value, endIndex, startIndex, range;
|
|
|
+ if (typeof el.selectionStart != 'undefined'
|
|
|
+ && typeof el.selectionEnd != 'undefined') {
|
|
|
+ startIndex = el.selectionStart;
|
|
|
+ endIndex = el.selectionEnd;
|
|
|
+ el.value = val.substring(0, startIndex) + text
|
|
|
+ + val.substring(el.selectionEnd);
|
|
|
+ el.selectionStart = el.selectionEnd = startIndex + text.length;
|
|
|
+ } else if (typeof document.selection != 'undefined'
|
|
|
+ && typeof document.selection.createRange != 'undefined') {
|
|
|
+ el.focus();
|
|
|
+ range = document.selection.createRange();
|
|
|
+ range.text = text;
|
|
|
+ range.select();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ util.extend = function(a, b) {
|
|
|
+ if (typeof a === 'undefined' || !a) {
|
|
|
+ a = {};
|
|
|
+ }
|
|
|
+ if (typeof b === 'object') {
|
|
|
+ for ( var key in b) {
|
|
|
+ if (b.hasOwnProperty(key)) {
|
|
|
+ a[key] = b[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return a;
|
|
|
+ };
|
|
|
+
|
|
|
+ util.escapeRegex = function(str) {
|
|
|
+ return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
|
|
|
+ };
|
|
|
+
|
|
|
+ util.htmlEntities = function(str) {
|
|
|
+ return String(str).replace(/&/g, '&').replace(/</g, '<')
|
|
|
+ .replace(/>/g, '>').replace(/"/g, '"');
|
|
|
+ };
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION START This function was added by Igor Zhukov to save
|
|
|
+ * recent used emojis.
|
|
|
+ */
|
|
|
+ util.emojiInserted = function(emojiKey, menu) {
|
|
|
+ ConfigStorage.get('emojis_recent', function(curEmojis) {
|
|
|
+ curEmojis = curEmojis || defaultRecentEmojis || [];
|
|
|
+
|
|
|
+ var pos = curEmojis.indexOf(emojiKey);
|
|
|
+ if (!pos) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (pos != -1) {
|
|
|
+ curEmojis.splice(pos, 1);
|
|
|
+ }
|
|
|
+ curEmojis.unshift(emojiKey);
|
|
|
+ if (curEmojis.length > 42) {
|
|
|
+ curEmojis = curEmojis.slice(42);
|
|
|
+ }
|
|
|
+
|
|
|
+ ConfigStorage.set({
|
|
|
+ emojis_recent : curEmojis
|
|
|
+ });
|
|
|
+ })
|
|
|
+ };
|
|
|
+ /* ! MODIFICATION END */
|
|
|
+
|
|
|
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+ var EmojiArea = function() {
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea.prototype.setup = function() {
|
|
|
+ var self = this;
|
|
|
+
|
|
|
+ this.$editor.on('focus', function() {
|
|
|
+ self.hasFocus = true;
|
|
|
+ });
|
|
|
+ this.$editor.on('blur', function() {
|
|
|
+ self.hasFocus = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Assign a unique instance of an emojiMenu to
|
|
|
+ self.emojiMenu = new EmojiMenu(self);
|
|
|
+
|
|
|
+ this.setupButton();
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea.prototype.setupButton = function() {
|
|
|
+ var self = this;
|
|
|
+ var $button = $('[data-id=' + this.id + '][data-type=picker]');
|
|
|
+
|
|
|
+ $button.on('click', function(e) {
|
|
|
+ self.emojiMenu.show(self);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$button = $button;
|
|
|
+ this.$dontHideOnClick = 'emoji-picker';
|
|
|
+ };
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION START This function was modified by Andre Staltz so that
|
|
|
+ * the icon is created from a spritesheet.
|
|
|
+ */
|
|
|
+ EmojiArea.createIcon = function(emoji, menu) {
|
|
|
+ var category = emoji[0];
|
|
|
+ var row = emoji[1];
|
|
|
+ var column = emoji[2];
|
|
|
+ var name = emoji[3];
|
|
|
+ var filename = $.emojiarea.spriteSheetPath ? $.emojiarea.spriteSheetPath : $.emojiarea.assetsPath + '/emoji_spritesheet_!.png';
|
|
|
+ var blankGifPath = $.emojiarea.blankGifPath ? $.emojiarea.blankGifPath : $.emojiarea.assetsPath + '/blank.gif';
|
|
|
+ var iconSize = menu && Config.Mobile ? 26 : $.emojiarea.iconSize
|
|
|
+ var xoffset = -(iconSize * column);
|
|
|
+ var yoffset = -(iconSize * row);
|
|
|
+ var scaledWidth = (Config.EmojiCategorySpritesheetDimens[category][1] * iconSize);
|
|
|
+ var scaledHeight = (Config.EmojiCategorySpritesheetDimens[category][0] * iconSize);
|
|
|
+
|
|
|
+ var style = 'display:inline-block;';
|
|
|
+ style += 'width:' + iconSize + 'px;';
|
|
|
+ style += 'height:' + iconSize + 'px;';
|
|
|
+ style += 'background:url(\'' + filename.replace('!', category) + '\') '
|
|
|
+ + xoffset + 'px ' + yoffset + 'px no-repeat;';
|
|
|
+ style += 'background-size:' + scaledWidth + 'px ' + scaledHeight
|
|
|
+ + 'px;';
|
|
|
+ return '<img src="' + blankGifPath + '" class="img" style="'
|
|
|
+ + style + '" alt="' + util.htmlEntities(name) + '">';
|
|
|
+ };
|
|
|
+
|
|
|
+ $.emojiarea.createIcon = EmojiArea.createIcon;
|
|
|
+ /* ! MODIFICATION END */
|
|
|
+
|
|
|
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+ /**
|
|
|
+ * Editor (plain-text)
|
|
|
+ *
|
|
|
+ * @constructor
|
|
|
+ * @param {object}
|
|
|
+ * $textarea
|
|
|
+ * @param {object}
|
|
|
+ * options
|
|
|
+ */
|
|
|
+
|
|
|
+ var EmojiArea_Plain = function($textarea, id, options) {
|
|
|
+ this.options = options;
|
|
|
+ this.$textarea = $textarea;
|
|
|
+ this.$editor = $textarea;
|
|
|
+ this.id = id;
|
|
|
+ this.setup();
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea_Plain.prototype.insert = function(emoji) {
|
|
|
+ if (!$.emojiarea.icons.hasOwnProperty(emoji))
|
|
|
+ return;
|
|
|
+ util.insertAtCursor(emoji, this.$textarea[0]);
|
|
|
+ /*
|
|
|
+ * MODIFICATION: Following line was added by Igor Zhukov, in order to
|
|
|
+ * save recent emojis
|
|
|
+ */
|
|
|
+ util.emojiInserted(emoji, this.menu);
|
|
|
+ this.$textarea.trigger('change');
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea_Plain.prototype.val = function() {
|
|
|
+ if (this.$textarea == '\n')
|
|
|
+ return '';
|
|
|
+ return this.$textarea.val();
|
|
|
+ };
|
|
|
+
|
|
|
+ util.extend(EmojiArea_Plain.prototype, EmojiArea.prototype);
|
|
|
+
|
|
|
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Editor (rich)
|
|
|
+ *
|
|
|
+ * @constructor
|
|
|
+ * @param {object}
|
|
|
+ * $textarea
|
|
|
+ * @param {object}
|
|
|
+ * options
|
|
|
+ */
|
|
|
+
|
|
|
+ var EmojiArea_WYSIWYG = function($textarea, id, options) {
|
|
|
+ var self = this;
|
|
|
+
|
|
|
+ this.options = options || {};
|
|
|
+ if ($($textarea).attr('data-emoji-input') === 'unicode')
|
|
|
+ this.options.inputMethod = 'unicode';
|
|
|
+ else
|
|
|
+ this.options.inputMethod = 'image';
|
|
|
+ this.id = id;
|
|
|
+ this.$textarea = $textarea;
|
|
|
+ this.emojiPopup = options.emojiPopup;
|
|
|
+ this.$editor = $('<div>').addClass('emoji-wysiwyg-editor').addClass($($textarea)[0].className);
|
|
|
+ this.$editor.data('self', this);
|
|
|
+
|
|
|
+ if ($textarea.attr('maxlength')) {
|
|
|
+ this.$editor.attr('maxlength', $textarea.attr('maxlength'));
|
|
|
+ }
|
|
|
+ this.$editor.height($textarea.outerHeight()); //auto adjust height
|
|
|
+ this.emojiPopup.appendUnicodeAsImageToElement(this.$editor, $textarea.val());
|
|
|
+
|
|
|
+ this.$editor.attr({
|
|
|
+ 'data-id': id,
|
|
|
+ 'data-type': 'input',
|
|
|
+ 'placeholder': $textarea.attr('placeholder'),
|
|
|
+ 'contenteditable': 'true',
|
|
|
+ });
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION START Following code was modified by Igor Zhukov, in
|
|
|
+ * order to improve rich text paste
|
|
|
+ */
|
|
|
+ var changeEvents = 'blur change';
|
|
|
+ if (!this.options.norealTime) {
|
|
|
+ changeEvents += ' keyup';
|
|
|
+ }
|
|
|
+ this.$editor.on(changeEvents, function(e) {
|
|
|
+ return self.onChange.apply(self, [ e ]);
|
|
|
+ });
|
|
|
+ /* ! MODIFICATION END */
|
|
|
+
|
|
|
+ this.$editor.on('mousedown focus', function() {
|
|
|
+ document.execCommand('enableObjectResizing', false, false);
|
|
|
+ });
|
|
|
+ this.$editor.on('blur', function() {
|
|
|
+ document.execCommand('enableObjectResizing', true, true);
|
|
|
+ });
|
|
|
+
|
|
|
+ var editorDiv = this.$editor;
|
|
|
+ this.$editor.on("change keydown keyup resize scroll", function(e) {
|
|
|
+ if(MAX_LENGTH_ALLOWED_KEYS.indexOf(e.which) == -1 &&
|
|
|
+ !((e.ctrlKey || e.metaKey) && e.which == 65) && // Ctrl + A
|
|
|
+ !((e.ctrlKey || e.metaKey) && e.which == 67) && // Ctrl + C
|
|
|
+ editorDiv.text().length + editorDiv.find('img').length >= editorDiv.attr('maxlength'))
|
|
|
+ {
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+ self.updateBodyPadding(editorDiv);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$editor.on("paste", function (e) {
|
|
|
+ e.preventDefault();
|
|
|
+ var content;
|
|
|
+ var charsRemaining = editorDiv.attr('maxlength') - (editorDiv.text().length + editorDiv.find('img').length);
|
|
|
+ if ((e.originalEvent || e).clipboardData) {
|
|
|
+ content = (e.originalEvent || e).clipboardData.getData('text/plain');
|
|
|
+ if (self.options.onPaste) {
|
|
|
+ content = self.options.onPaste(content);
|
|
|
+ }
|
|
|
+ if (charsRemaining < content.length) {
|
|
|
+ content = content.substring(0, charsRemaining);
|
|
|
+ }
|
|
|
+ document.execCommand('insertText', false, content);
|
|
|
+ }
|
|
|
+ else if (window.clipboardData) {
|
|
|
+ content = window.clipboardData.getData('Text');
|
|
|
+ if (self.options.onPaste) {
|
|
|
+ content = self.options.onPaste(content);
|
|
|
+ }
|
|
|
+ if (charsRemaining < content.length) {
|
|
|
+ content = content.substring(0, charsRemaining);
|
|
|
+ }
|
|
|
+ document.selection.createRange().pasteHTML(content);
|
|
|
+ }
|
|
|
+ editorDiv.scrollTop(editorDiv[0].scrollHeight);
|
|
|
+ });
|
|
|
+
|
|
|
+ $textarea.after("<i class='emoji-picker-icon emoji-picker " + this.options.popupButtonClasses + "' data-id='" + id + "' data-type='picker'></i>");
|
|
|
+
|
|
|
+ $textarea.hide().after(this.$editor);
|
|
|
+ this.setup();
|
|
|
+
|
|
|
+ /*
|
|
|
+ * MODIFICATION: Following line was modified by Igor Zhukov, in order to
|
|
|
+ * improve emoji insert behaviour
|
|
|
+ */
|
|
|
+ $(document.body).on('mousedown', function() {
|
|
|
+ if (self.hasFocus) {
|
|
|
+ self.selection = util.saveSelection();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea_WYSIWYG.prototype.updateBodyPadding = function(target) {
|
|
|
+ var emojiPicker = $('[data-id=' + this.id + '][data-type=picker]');
|
|
|
+ if ($(target).hasScrollbar()) {
|
|
|
+ if (!(emojiPicker.hasClass('parent-has-scroll')))
|
|
|
+ emojiPicker.addClass('parent-has-scroll');
|
|
|
+ if (!($(target).hasClass('parent-has-scroll')))
|
|
|
+ $(target).addClass('parent-has-scroll');
|
|
|
+ } else {
|
|
|
+ if ((emojiPicker.hasClass('parent-has-scroll')))
|
|
|
+ emojiPicker.removeClass('parent-has-scroll');
|
|
|
+ if (($(target).hasClass('parent-has-scroll')))
|
|
|
+ $(target).removeClass('parent-has-scroll');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea_WYSIWYG.prototype.onChange = function(e) {
|
|
|
+ var event = new CustomEvent('input', { bubbles: true });
|
|
|
+ this.$textarea.val(this.val())[0].dispatchEvent(event);
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea_WYSIWYG.prototype.insert = function(emoji) {
|
|
|
+ var content;
|
|
|
+ /*
|
|
|
+ * MODIFICATION: Following line was modified by Andre Staltz, to use new
|
|
|
+ * implementation of createIcon function.
|
|
|
+ */
|
|
|
+ var insertionContent = '';
|
|
|
+ if (this.options.inputMethod == 'unicode') {
|
|
|
+ insertionContent = this.emojiPopup.colonToUnicode(emoji);
|
|
|
+ } else {
|
|
|
+ var $img = $(EmojiArea.createIcon($.emojiarea.icons[emoji]));
|
|
|
+ if ($img[0].attachEvent) {
|
|
|
+ $img[0].attachEvent('onresizestart', function(e) {
|
|
|
+ e.returnValue = false;
|
|
|
+ }, false);
|
|
|
+ }
|
|
|
+ insertionContent = $img[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$editor.trigger('focus');
|
|
|
+ if (this.selection) {
|
|
|
+ util.restoreSelection(this.selection);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ util.replaceSelection(insertionContent);
|
|
|
+ } catch (e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * MODIFICATION: Following line was added by Igor Zhukov, in order to
|
|
|
+ * save recent emojis
|
|
|
+ */
|
|
|
+ util.emojiInserted(emoji, this.menu);
|
|
|
+
|
|
|
+ this.onChange();
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiArea_WYSIWYG.prototype.val = function() {
|
|
|
+ var lines = [];
|
|
|
+ var line = [];
|
|
|
+ var emojiPopup = this.emojiPopup;
|
|
|
+
|
|
|
+ var flush = function() {
|
|
|
+ lines.push(line.join(''));
|
|
|
+ line = [];
|
|
|
+ };
|
|
|
+
|
|
|
+ var sanitizeNode = function(node) {
|
|
|
+ if (node.nodeType === TEXT_NODE) {
|
|
|
+ line.push(node.nodeValue);
|
|
|
+ } else if (node.nodeType === ELEMENT_NODE) {
|
|
|
+ var tagName = node.tagName.toLowerCase();
|
|
|
+ var isBlock = TAGS_BLOCK.indexOf(tagName) !== -1;
|
|
|
+
|
|
|
+ if (isBlock && line.length)
|
|
|
+ flush();
|
|
|
+
|
|
|
+ if (tagName === 'img') {
|
|
|
+ var alt = node.getAttribute('alt') || '';
|
|
|
+ if (alt) {
|
|
|
+ line.push(alt);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ } else if (tagName === 'br') {
|
|
|
+ flush();
|
|
|
+ }
|
|
|
+
|
|
|
+ var children = node.childNodes;
|
|
|
+ for (var i = 0; i < children.length; i++) {
|
|
|
+ sanitizeNode(children[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isBlock && line.length)
|
|
|
+ flush();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ var children = this.$editor[0].childNodes;
|
|
|
+ for (var i = 0; i < children.length; i++) {
|
|
|
+ sanitizeNode(children[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (line.length)
|
|
|
+ flush();
|
|
|
+
|
|
|
+ var returnValue = lines.join('\n');
|
|
|
+ return emojiPopup.colonToUnicode(returnValue);
|
|
|
+ };
|
|
|
+
|
|
|
+ util.extend(EmojiArea_WYSIWYG.prototype, EmojiArea.prototype);
|
|
|
+
|
|
|
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+ jQuery.fn.hasScrollbar = function() {
|
|
|
+ var scrollHeight = this.get(0).scrollHeight;
|
|
|
+
|
|
|
+ //safari's scrollHeight includes padding
|
|
|
+ //if ($.browser.safari)
|
|
|
+// scrollHeight -= parseInt(this.css('padding-top')) + parseInt(this.css('padding-bottom'));
|
|
|
+ if (this.outerHeight() < scrollHeight)
|
|
|
+ return true;
|
|
|
+ else
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Emoji Dropdown Menu
|
|
|
+ *
|
|
|
+ * @constructor
|
|
|
+ * @param {object}
|
|
|
+ * emojiarea
|
|
|
+ */
|
|
|
+ var EmojiMenu = function(emojiarea) {
|
|
|
+ var self = this;
|
|
|
+ self.id = emojiarea.id;
|
|
|
+ var $body = $(document.body);
|
|
|
+ var $window = $(window);
|
|
|
+
|
|
|
+ this.visible = false;
|
|
|
+ this.emojiarea = emojiarea;
|
|
|
+ EmojiMenu.menuZIndex = 5000;
|
|
|
+ this.$menu = $('<div>');
|
|
|
+ this.$menu.addClass('emoji-menu');
|
|
|
+ this.$menu.attr('data-id', self.id);
|
|
|
+ this.$menu.attr('data-type', 'menu');
|
|
|
+ this.$menu.hide();
|
|
|
+
|
|
|
+ this.$itemsTailWrap = $('<div class="emoji-items-wrap1"></div>')
|
|
|
+ .appendTo(this.$menu);
|
|
|
+ this.$categoryTabs = $(
|
|
|
+ '<table class="emoji-menu-tabs"><tr>'
|
|
|
+ + '<td><a class="emoji-menu-tab icon-recent" ></a></td>'
|
|
|
+ + '<td><a class="emoji-menu-tab icon-smile" ></a></td>'
|
|
|
+ + '<td><a class="emoji-menu-tab icon-flower"></a></td>'
|
|
|
+ + '<td><a class="emoji-menu-tab icon-bell"></a></td>'
|
|
|
+ + '<td><a class="emoji-menu-tab icon-car"></a></td>'
|
|
|
+ + '<td><a class="emoji-menu-tab icon-grid"></a></td>'
|
|
|
+ + '</tr></table>').appendTo(this.$itemsTailWrap);
|
|
|
+ this.$itemsWrap = $(
|
|
|
+ '<div class="emoji-items-wrap mobile_scrollable_wrap"></div>')
|
|
|
+ .appendTo(this.$itemsTailWrap);
|
|
|
+ this.$items = $('<div class="emoji-items">').appendTo(
|
|
|
+ this.$itemsWrap);
|
|
|
+
|
|
|
+ this.emojiarea.$editor.after(this.$menu)
|
|
|
+
|
|
|
+ $body.on('keydown', function(e) {
|
|
|
+ if (e.keyCode === KEY_ESC || e.keyCode === KEY_TAB) {
|
|
|
+ self.hide();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION: Following 3 lines were added by Igor Zhukov, in order
|
|
|
+ * to hide menu on message submit with keyboard
|
|
|
+ */
|
|
|
+ $body.on('message_send', function(e) {
|
|
|
+ self.hide();
|
|
|
+ });
|
|
|
+
|
|
|
+ $body.on('mouseup', function(e) {
|
|
|
+ e = e.originalEvent || e;
|
|
|
+ var target = e.target || window;
|
|
|
+
|
|
|
+ if ($(target).hasClass(self.emojiarea.$dontHideOnClick)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (target && target != window) {
|
|
|
+ target = target.parentNode;
|
|
|
+ if (target == self.$menu[0] || self.emojiarea
|
|
|
+ && target == self.emojiarea.$button[0]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ self.hide();
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$menu.on('mouseup', 'a', function(e) {
|
|
|
+ e.stopPropagation();
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$menu.on('click', 'a', function(e) {
|
|
|
+
|
|
|
+ self.emojiarea.updateBodyPadding(self.emojiarea.$editor);
|
|
|
+ if ($(this).hasClass('emoji-menu-tab')) {
|
|
|
+ if (self.getTabIndex(this) !== self.currentCategory) {
|
|
|
+ self.selectCategory(self.getTabIndex(this));
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var emoji = $('.label', $(this)).text();
|
|
|
+ window.setTimeout(function() {
|
|
|
+ self.onItemSelected(emoji);
|
|
|
+ if (e.ctrlKey || e.metaKey) {
|
|
|
+ self.hide();
|
|
|
+ }
|
|
|
+ }, 0);
|
|
|
+ e.stopPropagation();
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.selectCategory(0);
|
|
|
+ };
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION START Following code was added by Andre Staltz, to
|
|
|
+ * implement category selection.
|
|
|
+ */
|
|
|
+ EmojiMenu.prototype.getTabIndex = function(tab) {
|
|
|
+ return this.$categoryTabs.find('.emoji-menu-tab').index(tab);
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiMenu.prototype.selectCategory = function(category) {
|
|
|
+ var self = this;
|
|
|
+ this.$categoryTabs.find('.emoji-menu-tab').each(function(index) {
|
|
|
+ if (index === category) {
|
|
|
+ this.className += '-selected';
|
|
|
+ } else {
|
|
|
+ this.className = this.className.replace('-selected', '');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.currentCategory = category;
|
|
|
+ this.load(category);
|
|
|
+ };
|
|
|
+ /* ! MODIFICATION END */
|
|
|
+
|
|
|
+ EmojiMenu.prototype.onItemSelected = function(emoji) {
|
|
|
+ if(this.emojiarea.$editor.text().length + this.emojiarea.$editor.find('img').length >= this.emojiarea.$editor.attr('maxlength'))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.emojiarea.insert(emoji);
|
|
|
+ };
|
|
|
+
|
|
|
+ /*
|
|
|
+ * MODIFICATION: The following function argument was modified by Andre
|
|
|
+ * Staltz, in order to load only icons from a category. Also function was
|
|
|
+ * modified by Igor Zhukov in order to display recent emojis from
|
|
|
+ * localStorage
|
|
|
+ */
|
|
|
+ EmojiMenu.prototype.load = function(category) {
|
|
|
+ var html = [];
|
|
|
+ var options = $.emojiarea.icons;
|
|
|
+ var path = $.emojiarea.assetsPath;
|
|
|
+ var self = this;
|
|
|
+ if (path.length && path.charAt(path.length - 1) !== '/') {
|
|
|
+ path += '/';
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * ! MODIFICATION: Following function was added by Igor Zhukov, in order
|
|
|
+ * to add scrollbars to EmojiMenu
|
|
|
+ */
|
|
|
+ var updateItems = function() {
|
|
|
+ self.$items.html(html.join(''));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (category > 0) {
|
|
|
+ for ( var key in options) {
|
|
|
+ /*
|
|
|
+ * MODIFICATION: The following 2 lines were modified by Andre
|
|
|
+ * Staltz, in order to load only icons from the specified
|
|
|
+ * category.
|
|
|
+ */
|
|
|
+ if (options.hasOwnProperty(key)
|
|
|
+ && options[key][0] === (category - 1)) {
|
|
|
+ html.push('<a href="javascript:void(0)" title="'
|
|
|
+ + util.htmlEntities(key) + '">'
|
|
|
+ + EmojiArea.createIcon(options[key], true)
|
|
|
+ + '<span class="label">' + util.htmlEntities(key)
|
|
|
+ + '</span></a>');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ updateItems();
|
|
|
+ } else {
|
|
|
+ ConfigStorage.get('emojis_recent', function(curEmojis) {
|
|
|
+ curEmojis = curEmojis || defaultRecentEmojis || [];
|
|
|
+ var key, i;
|
|
|
+ for (i = 0; i < curEmojis.length; i++) {
|
|
|
+ key = curEmojis[i]
|
|
|
+ if (options[key]) {
|
|
|
+ html.push('<a href="javascript:void(0)" title="'
|
|
|
+ + util.htmlEntities(key) + '">'
|
|
|
+ + EmojiArea.createIcon(options[key], true)
|
|
|
+ + '<span class="label">'
|
|
|
+ + util.htmlEntities(key) + '</span></a>');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ updateItems();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiMenu.prototype.hide = function(callback) {
|
|
|
+ this.visible = false;
|
|
|
+ this.$menu.hide("fast");
|
|
|
+ };
|
|
|
+
|
|
|
+ EmojiMenu.prototype.show = function(emojiarea) {
|
|
|
+ /*
|
|
|
+ * MODIFICATION: Following line was modified by Igor Zhukov, in order to
|
|
|
+ * improve EmojiMenu behaviour
|
|
|
+ */
|
|
|
+ if (this.visible)
|
|
|
+ return this.hide();
|
|
|
+ $(this.$menu).css('z-index', ++EmojiMenu.menuZIndex);
|
|
|
+ this.$menu.show("fast");
|
|
|
+ /*
|
|
|
+ * MODIFICATION: Following 3 lines were added by Igor Zhukov, in order
|
|
|
+ * to update EmojiMenu contents
|
|
|
+ */
|
|
|
+ if (!this.currentCategory) {
|
|
|
+ this.load(0);
|
|
|
+ }
|
|
|
+ this.visible = true;
|
|
|
+ };
|
|
|
+
|
|
|
+})(jQuery, window, document);
|