d9447f6224
EmojiMenu click scroll fix EmojiMenu tail Emojiarea improve paste hadling Emoji, attach icons styles (opacity + color on active state) Message better placeholder styles Notifications click improve (now focus field) Send message from modal now closes all modals Send message now scrolls down Message drafts now support line breaks Correct display ‘f’, ’n’, ‘no’ messages Group info click causes double modal window fixed Document is now downloaded on icon click
513 lines
14 KiB
JavaScript
513 lines
14 KiB
JavaScript
/**
|
|
* 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;
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
$.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(e) { return self.onChange.apply(self, [e]); });
|
|
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(e) {
|
|
if (e && e.type == 'paste') {
|
|
var text = (e.originalEvent || e).clipboardData.getData('text/plain'),
|
|
self = this;
|
|
setTimeout(function () {
|
|
self.onChange();
|
|
}, 0);
|
|
if (text.length) {
|
|
document.execCommand('insertText', false, text);
|
|
return cancelEvent();
|
|
}
|
|
return true;
|
|
}
|
|
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();
|
|
|
|
/*! MODIFICATION START
|
|
Following code was modified by Igor Zhukov, in order to add scrollbars and tail to EmojiMenu
|
|
*/
|
|
this.$itemsTailWrap = $('<div class="emoji-items-wrap1"></div>').appendTo(this.$menu);
|
|
this.$itemsWrap = $('<div class="emoji-items-wrap nano"></div>').appendTo(this.$itemsTailWrap);
|
|
this.$items = $('<div class="emoji-items content">').appendTo(this.$itemsWrap);
|
|
$('<div class="emoji-menu-tail">').appendTo(this.$menu);
|
|
/*! MODIFICATION END */
|
|
|
|
$body.append(this.$menu);
|
|
|
|
/*! MODIFICATION: Following line is added by Igor Zhukov, in order to add scrollbars to EmojiMenu */
|
|
this.$itemsWrap.nanoScroller({preventPageScrolling: true, tabIndex: -1});
|
|
|
|
$body.on('keydown', function(e) {
|
|
if (e.keyCode === KEY_ESC || e.keyCode === KEY_TAB) {
|
|
self.hide();
|
|
}
|
|
});
|
|
|
|
$body.on('mouseup', function(e) {
|
|
/*! MODIFICATION START
|
|
Following code was added by Igor Zhukov, in order to prevent close on click on EmojiMenu scrollbar
|
|
*/
|
|
e = e.originalEvent || e;
|
|
var target = e.originalTarget || e.target || window;
|
|
while (target && target != window) {
|
|
target = target.parentNode;
|
|
if (target == self.$menu[0]) {
|
|
return;
|
|
}
|
|
}
|
|
/*! MODIFICATION END */
|
|
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() {
|
|
/*! MODIFICATION START
|
|
Following code was modified by Igor Zhukov, in order to prevent close on shift-, ctrl-, alt- emoji select
|
|
*/
|
|
self.onItemSelected(emoji);
|
|
if (!e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
self.hide();
|
|
}
|
|
/*! MODIFICATION END */
|
|
}, 0);
|
|
e.stopPropagation();
|
|
return false;
|
|
});
|
|
|
|
this.load();
|
|
};
|
|
|
|
EmojiMenu.prototype.onItemSelected = function(emoji) {
|
|
this.emojiarea.insert(emoji);
|
|
};
|
|
|
|
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(''));
|
|
|
|
/*! MODIFICATION: Following 4 lines were added by Igor Zhukov, in order to add scrollbars to EmojiMenu */
|
|
var self = this;
|
|
setTimeout(function () {
|
|
self.$itemsWrap.nanoScroller();
|
|
}, 100);
|
|
};
|
|
|
|
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;
|
|
emojiarea.$button.addClass('on');
|
|
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); |