123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- /**
- * 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>
- */
- (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;
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- $.emojiarea = {
- path: '',
- icons: {},
- defaults: {
- button: null,
- buttonLabel: 'Emojis',
- buttonPosition: 'after'
- }
- };
- $.fn.emojiarea = function(options) {
- options = $.extend({}, $.emojiarea.defaults, options);
- return this.each(function() {
- var $textarea = $(this);
- if ('contentEditable' in document.body && options.wysiwyg !== false) {
- new EmojiArea_WYSIWYG($textarea, options);
- } else {
- new EmojiArea_Plain($textarea, options);
- }
- });
- };
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 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, '"');
- };
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 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; });
-
- this.setupButton();
- };
- EmojiArea.prototype.setupButton = function() {
- var self = this;
- var $button;
- if (this.options.button) {
- $button = $(this.options.button);
- } else if (this.options.button !== false) {
- $button = $('<a href="javascript:void(0)">');
- $button.html(this.options.buttonLabel);
- $button.addClass('emoji-button');
- $button.attr({title: this.options.buttonLabel});
- this.$editor[this.options.buttonPosition]($button);
- } else {
- $button = $('');
- }
- $button.on('click', function(e) {
- EmojiMenu.show(self);
- e.stopPropagation();
- });
-
- this.$button = $button;
- };
- EmojiArea.createIcon = function(emoji) {
- var filename = $.emojiarea.icons[emoji];
- var path = $.emojiarea.path || '';
- if (path.length && path.charAt(path.length - 1) !== '/') {
- path += '/';
- }
- return '<img src="' + path + filename + '" alt="' + util.htmlEntities(emoji) + '">';
- };
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- /**
- * Editor (plain-text)
- *
- * @constructor
- * @param {object} $textarea
- * @param {object} options
- */
- var EmojiArea_Plain = function($textarea, options) {
- this.options = options;
- this.$textarea = $textarea;
- this.$editor = $textarea;
- this.setup();
- };
- EmojiArea_Plain.prototype.insert = function(emoji) {
- if (!$.emojiarea.icons.hasOwnProperty(emoji)) return;
- util.insertAtCursor(emoji, this.$textarea[0]);
- this.$textarea.trigger('change');
- };
- EmojiArea_Plain.prototype.val = function() {
- 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, options) {
- var self = this;
- this.options = options;
- this.$textarea = $textarea;
- this.$editor = $('<div>').addClass('emoji-wysiwyg-editor');
- this.$editor.text($textarea.val());
- this.$editor.attr({contenteditable: 'true'});
- this.$editor.on('blur keyup paste', function() { return self.onChange.apply(self, arguments); });
- this.$editor.on('mousedown focus', function() { document.execCommand('enableObjectResizing', false, false); });
- this.$editor.on('blur', function() { document.execCommand('enableObjectResizing', true, true); });
- var html = this.$editor.text();
- var emojis = $.emojiarea.icons;
- for (var key in emojis) {
- if (emojis.hasOwnProperty(key)) {
- html = html.replace(new RegExp(util.escapeRegex(key), 'g'), EmojiArea.createIcon(key));
- }
- }
- this.$editor.html(html);
- $textarea.hide().after(this.$editor);
- this.setup();
- this.$button.on('mousedown', function() {
- if (self.hasFocus) {
- self.selection = util.saveSelection();
- }
- });
- };
- EmojiArea_WYSIWYG.prototype.onChange = function() {
- this.$textarea.val(this.val()).trigger('change');
- };
- EmojiArea_WYSIWYG.prototype.insert = function(emoji) {
- var content;
- var $img = $(EmojiArea.createIcon(emoji));
- if ($img[0].attachEvent) {
- $img[0].attachEvent('onresizestart', function(e) { e.returnValue = false; }, false);
- }
-
- this.$editor.trigger('focus');
- if (this.selection) {
- util.restoreSelection(this.selection);
- }
- try { util.replaceSelection($img[0]); } catch (e) {}
- this.onChange();
- };
- EmojiArea_WYSIWYG.prototype.val = function() {
- var lines = [];
- var line = [];
- 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();
- return lines.join('\n');
- };
- util.extend(EmojiArea_WYSIWYG.prototype, EmojiArea.prototype);
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- /**
- * Emoji Dropdown Menu
- *
- * @constructor
- * @param {object} emojiarea
- */
- var EmojiMenu = function() {
- var self = this;
- var $body = $(document.body);
- var $window = $(window);
- this.visible = false;
- this.emojiarea = null;
- this.$menu = $('<div>');
- this.$menu.addClass('emoji-menu');
- this.$menu.hide();
- this.$items = $('<div>').appendTo(this.$menu);
- $body.append(this.$menu);
- $body.on('keydown', function(e) {
- if (e.keyCode === KEY_ESC || e.keyCode === KEY_TAB) {
- self.hide();
- }
- });
- $body.on('mouseup', function() {
- self.hide();
- });
- $window.on('resize', function() {
- if (self.visible) self.reposition();
- });
- this.$menu.on('mouseup', 'a', function(e) {
- e.stopPropagation();
- return false;
- });
- this.$menu.on('click', 'a', function(e) {
- var emoji = $('.label', $(this)).text();
- window.setTimeout(function() {
- self.onItemSelected.apply(self, [emoji]);
- }, 0);
- e.stopPropagation();
- return false;
- });
- this.load();
- };
- EmojiMenu.prototype.onItemSelected = function(emoji) {
- this.emojiarea.insert(emoji);
- this.hide();
- };
- EmojiMenu.prototype.load = function() {
- var html = [];
- var options = $.emojiarea.icons;
- var path = $.emojiarea.path;
- if (path.length && path.charAt(path.length - 1) !== '/') {
- path += '/';
- }
- for (var key in options) {
- if (options.hasOwnProperty(key)) {
- var filename = options[key];
- html.push('<a href="javascript:void(0)" title="' + util.htmlEntities(key) + '">' + EmojiArea.createIcon(key) + '<span class="label">' + util.htmlEntities(key) + '</span></a>');
- }
- }
- this.$items.html(html.join(''));
- };
- EmojiMenu.prototype.reposition = function() {
- var $button = this.emojiarea.$button;
- var offset = $button.offset();
- offset.top += $button.outerHeight();
- offset.left += Math.round($button.outerWidth() / 2);
-
- this.$menu.css({
- top: offset.top,
- left: offset.left
- });
- };
- EmojiMenu.prototype.hide = function(callback) {
- if (this.emojiarea) {
- this.emojiarea.menu = null;
- this.emojiarea.$button.removeClass('on');
- this.emojiarea = null;
- }
- this.visible = false;
- this.$menu.hide();
- };
- EmojiMenu.prototype.show = function(emojiarea) {
- if (this.emojiarea && this.emojiarea === emojiarea) return;
- this.emojiarea = emojiarea;
- this.emojiarea.menu = this;
- this.reposition();
- this.$menu.show();
- this.visible = true;
- };
- EmojiMenu.show = (function() {
- var menu = null;
- return function(emojiarea) {
- menu = menu || new EmojiMenu();
- menu.show(emojiarea);
- };
- })();
- })(jQuery, window, document);
|