github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/public/jquery-emojiarea/jquery.emojiarea.js (about)

     1  /**
     2   * emojiarea - A rich textarea control that supports emojis, WYSIWYG-style.
     3   * Copyright (c) 2012 DIY Co
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
     6   * file except in compliance with the License. You may obtain a copy of the License at:
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software distributed under
    10   * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
    11   * ANY KIND, either express or implied. See the License for the specific language
    12   * governing permissions and limitations under the License.
    13   *
    14   * @author Brian Reavis <brian@diy.org>
    15   */
    16  
    17  (function($, window, document) {
    18  
    19  	var ELEMENT_NODE = 1;
    20  	var TEXT_NODE = 3;
    21  	var TAGS_BLOCK = ['p', 'div', 'pre', 'form'];
    22  	var KEY_ESC = 27;
    23  	var KEY_TAB = 9;
    24  
    25  	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    26  
    27  	$.emojiarea = {
    28  		path: '',
    29  		icons: {},
    30  		defaults: {
    31  			button: null,
    32  			buttonLabel: 'Emojis',
    33  			buttonPosition: 'after'
    34  		}
    35  	};
    36  
    37  	$.fn.emojiarea = function(options) {
    38  		options = $.extend({}, $.emojiarea.defaults, options);
    39  		return this.each(function() {
    40  			var $textarea = $(this);
    41  			if ('contentEditable' in document.body && options.wysiwyg !== false) {
    42  				new EmojiArea_WYSIWYG($textarea, options);
    43  			} else {
    44  				new EmojiArea_Plain($textarea, options);
    45  			}
    46  		});
    47  	};
    48  
    49  	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    50  
    51  	var util = {};
    52  
    53  	util.restoreSelection = (function() {
    54  		if (window.getSelection) {
    55  			return function(savedSelection) {
    56  				var sel = window.getSelection();
    57  				sel.removeAllRanges();
    58  				for (var i = 0, len = savedSelection.length; i < len; ++i) {
    59  					sel.addRange(savedSelection[i]);
    60  				}
    61  			};
    62  		} else if (document.selection && document.selection.createRange) {
    63  			return function(savedSelection) {
    64  				if (savedSelection) {
    65  					savedSelection.select();
    66  				}
    67  			};
    68  		}
    69  	})();
    70  
    71  	util.saveSelection = (function() {
    72  		if (window.getSelection) {
    73  			return function() {
    74  				var sel = window.getSelection(), ranges = [];
    75  				if (sel.rangeCount) {
    76  					for (var i = 0, len = sel.rangeCount; i < len; ++i) {
    77  						ranges.push(sel.getRangeAt(i));
    78  					}
    79  				}
    80  				return ranges;
    81  			};
    82  		} else if (document.selection && document.selection.createRange) {
    83  			return function() {
    84  				var sel = document.selection;
    85  				return (sel.type.toLowerCase() !== 'none') ? sel.createRange() : null;
    86  			};
    87  		}
    88  	})();
    89  
    90  	util.replaceSelection = (function() {
    91  		if (window.getSelection) {
    92  			return function(content) {
    93  				var range, sel = window.getSelection();
    94  				var node = typeof content === 'string' ? document.createTextNode(content) : content;
    95  				if (sel.getRangeAt && sel.rangeCount) {
    96  					range = sel.getRangeAt(0);
    97  					range.deleteContents();
    98  					range.insertNode(document.createTextNode(' '));
    99  					range.insertNode(node);
   100  					range.setStart(node, 0);
   101  
   102  					window.setTimeout(function() {
   103  						range = document.createRange();
   104  						range.setStartAfter(node);
   105  						range.collapse(true);
   106  						sel.removeAllRanges();
   107  						sel.addRange(range);
   108  					}, 0);
   109  				}
   110  			}
   111  		} else if (document.selection && document.selection.createRange) {
   112  			return function(content) {
   113  				var range = document.selection.createRange();
   114  				if (typeof content === 'string') {
   115  					range.text = content;
   116  				} else {
   117  					range.pasteHTML(content.outerHTML);
   118  				}
   119  			}
   120  		}
   121  	})();
   122  
   123  	util.insertAtCursor = function(text, el) {
   124  		text = ' ' + text;
   125  		var val = el.value, endIndex, startIndex, range;
   126  		if (typeof el.selectionStart != 'undefined' && typeof el.selectionEnd != 'undefined') {
   127  			startIndex = el.selectionStart;
   128  			endIndex = el.selectionEnd;
   129  			el.value = val.substring(0, startIndex) + text + val.substring(el.selectionEnd);
   130  			el.selectionStart = el.selectionEnd = startIndex + text.length;
   131  		} else if (typeof document.selection != 'undefined' && typeof document.selection.createRange != 'undefined') {
   132  			el.focus();
   133  			range = document.selection.createRange();
   134  			range.text = text;
   135  			range.select();
   136  		}
   137  	};
   138  
   139  	util.extend = function(a, b) {
   140  		if (typeof a === 'undefined' || !a) { a = {}; }
   141  		if (typeof b === 'object') {
   142  			for (var key in b) {
   143  				if (b.hasOwnProperty(key)) {
   144  					a[key] = b[key];
   145  				}
   146  			}
   147  		}
   148  		return a;
   149  	};
   150  
   151  	util.escapeRegex = function(str) {
   152  		return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
   153  	};
   154  
   155  	util.htmlEntities = function(str) {
   156  		return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
   157  	};
   158  
   159  	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   160  
   161  	var EmojiArea = function() {};
   162  
   163  	EmojiArea.prototype.setup = function() {
   164  		var self = this;
   165  
   166  		this.$editor.on('focus', function() { self.hasFocus = true; });
   167  		this.$editor.on('blur', function() { self.hasFocus = false; });
   168  
   169  		this.setupButton();
   170  	};
   171  
   172  	EmojiArea.prototype.setupButton = function() {
   173  		var self = this;
   174  		var $button;
   175  
   176  		if (this.options.button) {
   177  			$button = $(this.options.button);
   178  		} else if (this.options.button !== false) {
   179  			$button = $('<a href="javascript:void(0)">');
   180  			$button.html(this.options.buttonLabel);
   181  			$button.addClass('emoji-button');
   182  			$button.attr({title: this.options.buttonLabel});
   183  			this.$editor[this.options.buttonPosition]($button);
   184  		} else {
   185  			$button = $('');
   186  		}
   187  
   188  		$button.on('click', function(e) {
   189  			EmojiMenu.show(self);
   190  			e.stopPropagation();
   191  		});
   192  
   193  		this.$button = $button;
   194  	};
   195  
   196  	EmojiArea.createIcon = function(group, emoji) {
   197  		var filename = $.emojiarea.icons[group]['icons'][emoji];
   198  		var path = $.emojiarea.path || '';
   199  		if (path.length && path.charAt(path.length - 1) !== '/') {
   200  			path += '/';
   201  		}
   202  		return '<img src="' + path + filename + '" alt="' + util.htmlEntities(emoji) + '">';
   203  	};
   204  
   205  	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   206  
   207  	/**
   208  	 * Editor (plain-text)
   209  	 *
   210  	 * @constructor
   211  	 * @param {object} $textarea
   212  	 * @param {object} options
   213  	 */
   214  
   215  	var EmojiArea_Plain = function($textarea, options) {
   216  		this.options = options;
   217  		this.$textarea = $textarea;
   218  		this.$editor = $textarea;
   219  		this.setup();
   220  	};
   221  
   222  	EmojiArea_Plain.prototype.insert = function(group, emoji) {
   223  		if (!$.emojiarea.icons[group]['icons'].hasOwnProperty(emoji)) return;
   224  		util.insertAtCursor(emoji, this.$textarea[0]);
   225  		this.$textarea.trigger('change');
   226  	};
   227  
   228  	EmojiArea_Plain.prototype.val = function() {
   229  		return this.$textarea.val();
   230  	};
   231  
   232  	util.extend(EmojiArea_Plain.prototype, EmojiArea.prototype);
   233  
   234  	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   235  
   236  	/**
   237  	 * Editor (rich)
   238  	 *
   239  	 * @constructor
   240  	 * @param {object} $textarea
   241  	 * @param {object} options
   242  	 */
   243  
   244  	var EmojiArea_WYSIWYG = function($textarea, options) {
   245  		var self = this;
   246  
   247  		this.options = options;
   248  		this.$textarea = $textarea;
   249  		this.$editor = $('<div>').addClass('emoji-wysiwyg-editor');
   250  		this.$editor.text($textarea.val());
   251  		this.$editor.attr({contenteditable: 'true'});
   252  		this.$editor.on('blur keyup paste', function() { return self.onChange.apply(self, arguments); });
   253  		this.$editor.on('mousedown focus', function() { document.execCommand('enableObjectResizing', false, false); });
   254  		this.$editor.on('blur', function() { document.execCommand('enableObjectResizing', true, true); });
   255  
   256  		var html = this.$editor.text();
   257  		var emojis = $.emojiarea.icons;
   258  		for (var group in emojis) {
   259  			for (var key in emojis[group]['icons']) {
   260  				if (emojis[group]['icons'].hasOwnProperty(key)) {
   261  					html = html.replace(new RegExp(util.escapeRegex(key), 'g'), EmojiArea.createIcon(group, key));
   262  				}
   263  			}
   264  		}
   265  		this.$editor.html(html);
   266  
   267  		$textarea.hide().after(this.$editor);
   268  
   269  		this.setup();
   270  
   271  		this.$button.on('mousedown', function() {
   272  			if (self.hasFocus) {
   273  				self.selection = util.saveSelection();
   274  			}
   275  		});
   276  	};
   277  
   278  	EmojiArea_WYSIWYG.prototype.onChange = function() {
   279  		this.$textarea.val(this.val()).trigger('change');
   280  	};
   281  
   282  	EmojiArea_WYSIWYG.prototype.insert = function(group, emoji) {
   283  		var content;
   284  		var $img = $(EmojiArea.createIcon(group, emoji));
   285  		if ($img[0].attachEvent) {
   286  			$img[0].attachEvent('onresizestart', function(e) { e.returnValue = false; }, false);
   287  		}
   288  
   289  		this.$editor.trigger('focus');
   290  		if (this.selection) {
   291  			util.restoreSelection(this.selection);
   292  		}
   293  		try { util.replaceSelection($img[0]); } catch (e) {}
   294  		this.onChange();
   295  	};
   296  
   297  	EmojiArea_WYSIWYG.prototype.val = function() {
   298  		var lines = [];
   299  		var line  = [];
   300  
   301  		var flush = function() {
   302  			lines.push(line.join(''));
   303  			line = [];
   304  		};
   305  
   306  		var sanitizeNode = function(node) {
   307  			if (node.nodeType === TEXT_NODE) {
   308  				line.push(node.nodeValue);
   309  			} else if (node.nodeType === ELEMENT_NODE) {
   310  				var tagName = node.tagName.toLowerCase();
   311  				var isBlock = TAGS_BLOCK.indexOf(tagName) !== -1;
   312  
   313  				if (isBlock && line.length) flush();
   314  
   315  				if (tagName === 'img') {
   316  					var alt = node.getAttribute('alt') || '';
   317  					if (alt) line.push(alt);
   318  					return;
   319  				} else if (tagName === 'br') {
   320  					flush();
   321  				}
   322  
   323  				var children = node.childNodes;
   324  				for (var i = 0; i < children.length; i++) {
   325  					sanitizeNode(children[i]);
   326  				}
   327  
   328  				if (isBlock && line.length) flush();
   329  			}
   330  		};
   331  
   332  		var children = this.$editor[0].childNodes;
   333  		for (var i = 0; i < children.length; i++) {
   334  			sanitizeNode(children[i]);
   335  		}
   336  
   337  		if (line.length) flush();
   338  
   339  		return lines.join('\n');
   340  	};
   341  
   342  	util.extend(EmojiArea_WYSIWYG.prototype, EmojiArea.prototype);
   343  
   344  	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   345  
   346  	/**
   347  	 * Emoji Dropdown Menu
   348  	 *
   349  	 * @constructor
   350  	 * @param {object} emojiarea
   351  	 */
   352  	var EmojiMenu = function() {
   353  		var self = this;
   354  		var $body = $(document.body);
   355  		var $window = $(window);
   356  
   357  		this.visible = false;
   358  		this.emojiarea = null;
   359  		this.$menu = $('<div>');
   360  		this.$menu.addClass('emoji-menu');
   361  		this.$menu.hide();
   362  		this.$items = $('<div>').appendTo(this.$menu);
   363  
   364  		$body.append(this.$menu);
   365  
   366  		$body.on('keydown', function(e) {
   367  			if (e.keyCode === KEY_ESC || e.keyCode === KEY_TAB) {
   368  				self.hide();
   369  			}
   370  		});
   371  
   372  		$body.on('mouseup', function() {
   373  			self.hide();
   374  		});
   375  
   376  		$window.on('resize', function() {
   377  			if (self.visible) self.reposition();
   378  		});
   379  
   380  		this.$menu.on('mouseup', 'a', function(e) {
   381  			e.stopPropagation();
   382  			return false;
   383  		});
   384  
   385  		this.$menu.on('click', 'a', function(e) {
   386  			var emoji = $('.label', $(this)).text();
   387  			var group = $('.label', $(this)).parent().parent().attr('group');
   388  			if(group && emoji !== ''){
   389  				window.setTimeout(function() {
   390  					self.onItemSelected.apply(self, [group, emoji]);
   391  				}, 0);
   392  				e.stopPropagation();
   393  				return false;
   394  			}
   395  		});
   396  
   397  		this.load();
   398  	};
   399  
   400  	EmojiMenu.prototype.onItemSelected = function(group, emoji) {
   401  		this.emojiarea.insert(group, emoji);
   402  		this.hide();
   403  	};
   404  
   405  	EmojiMenu.prototype.load = function() {
   406  		var html = [];
   407  		var groups = [];
   408  		var options = $.emojiarea.icons;
   409  		var path = $.emojiarea.path;
   410  		if (path.length && path.charAt(path.length - 1) !== '/') {
   411  			path += '/';
   412  		}
   413  		groups.push('<ul class="group-selector">');
   414  		for (var group in options) {
   415  		groups.push('<a href="#group_' + group + '" class="tab_switch"><li>' + options[group]['name'] + '</li></a>');
   416  		html.push('<div class="select_group" group="' + group + '" id="group_' + group + '">');
   417  			for (var key in options[group]['icons']) {
   418  				if (options[group]['icons'].hasOwnProperty(key)) {
   419  					var filename = options[key];
   420  					html.push('<a href="javascript:void(0)" title="' + util.htmlEntities(key) + '">' + EmojiArea.createIcon(group, key) + '<span class="label">' + util.htmlEntities(key) + '</span></a>');
   421  				}
   422  			}
   423  		html.push('</div>');
   424  		}
   425  		groups.push('</ul>');
   426  		this.$items.html(html.join(''));
   427  		this.$menu.prepend(groups.join(''));
   428  		this.$menu.find('.tab_switch').each(function(i) {
   429  			if (i != 0) {
   430  				var select = $(this).attr('href');
   431  				$(select).hide();
   432  			} else {
   433  				$(this).addClass('active');
   434  			}
   435  			$(this).click(function() {
   436  				$(this).addClass('active');
   437  				$(this).siblings().removeClass('active');
   438  				$('.select_group').hide();
   439  				var select = $(this).attr('href');
   440  				$(select).show();
   441  			});
   442  		});
   443  	};
   444  
   445  	EmojiMenu.prototype.reposition = function() {
   446  		var $button = this.emojiarea.$button;
   447  		var offset = $button.offset();
   448  		offset.top += $button.outerHeight();
   449  		offset.left += Math.round($button.outerWidth() / 2);
   450  
   451  		this.$menu.css({
   452  			top: offset.top,
   453  			left: offset.left
   454  		});
   455  	};
   456  
   457  	EmojiMenu.prototype.hide = function(callback) {
   458  		if (this.emojiarea) {
   459  			this.emojiarea.menu = null;
   460  			this.emojiarea.$button.removeClass('on');
   461  			this.emojiarea = null;
   462  		}
   463  		this.visible = false;
   464  		this.$menu.hide();
   465  	};
   466  
   467  	EmojiMenu.prototype.show = function(emojiarea) {
   468  		if (this.emojiarea && this.emojiarea === emojiarea) return;
   469  		this.emojiarea = emojiarea;
   470  		this.emojiarea.menu = this;
   471  
   472  		this.reposition();
   473  		this.$menu.show();
   474  		this.visible = true;
   475  	};
   476  
   477  	EmojiMenu.show = (function() {
   478  		var menu = null;
   479  		return function(emojiarea) {
   480  			menu = menu || new EmojiMenu();
   481  			menu.show(emojiarea);
   482  		};
   483  	})();
   484  
   485  })(jQuery, window, document);