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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); 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);