diff --git a/.eslintrc.json b/.eslintrc.json index 2d75e6ebd..5bf8e03c7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,7 @@ { "parserOptions": { - "ecmaVersion": 2017 + "ecmaVersion": 2017, + "sourceType": "module" }, "env": { "browser": true, diff --git a/CHANGES.md b/CHANGES.md index 2d0f246b4..ea06b9886 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changelog +## 4.1.0 (Unreleased) + +- Use [Lerna](https://lernajs.io/) to create the @converse/headless package +- Use ES2015 modules instead of UMD. - #1257: Prefer 'probably' over 'maybe' when evaluating audio play support. ## 4.0.3 (2018-10-22) diff --git a/Makefile b/Makefile index 36364497c..bb61e9cf0 100644 --- a/Makefile +++ b/Makefile @@ -157,12 +157,16 @@ watchcss: dev $(SASS) --watch -I $(BOURBON) -I $(BOOTSTRAP) sass:css .PHONY: watchjs -watchjs: dev +watchjs: dev dist/converse-headless.js ./node_modules/.bin/npx webpack --mode=development --watch +.PHONY: watchjsheadless +watchjsheadless: dev + ./node_modules/.bin/npx webpack --mode=development --watch --type=headless + .PHONY: watch watch: dev - make -j 2 watchjs watchcss + make -j 3 watchcss watchjsheadless watchjs .PHONY: logo logo: logo/conversejs-transparent16.png \ diff --git a/dist/converse.js b/dist/converse.js index 0f410f804..879d45366 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -57298,449 +57298,446 @@ exports["filterCSS"] = (filterCSS); /*!**************************************!*\ !*** ./src/converse-autocomplete.js ***! \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js // http://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) // This plugin started as a fork of Lea Verou's Awesomplete // https://leaverou.github.io/awesomplete/ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - const _converse$env = converse.env, - _ = _converse$env._, - Backbone = _converse$env.Backbone, - u = converse.env.utils; - converse.plugins.add("converse-autocomplete", { - initialize() { - const _converse = this._converse; - _converse.FILTER_CONTAINS = function (text, input) { - return RegExp(helpers.regExpEscape(input.trim()), "i").test(text); - }; +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + _ = _converse$env._, + Backbone = _converse$env.Backbone, + u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add("converse-autocomplete", { + initialize() { + const _converse = this._converse; - _converse.FILTER_STARTSWITH = function (text, input) { - return RegExp("^" + helpers.regExpEscape(input.trim()), "i").test(text); - }; + _converse.FILTER_CONTAINS = function (text, input) { + return RegExp(helpers.regExpEscape(input.trim()), "i").test(text); + }; - const SORT_BYLENGTH = function SORT_BYLENGTH(a, b) { - if (a.length !== b.length) { - return a.length - b.length; + _converse.FILTER_STARTSWITH = function (text, input) { + return RegExp("^" + helpers.regExpEscape(input.trim()), "i").test(text); + }; + + const SORT_BYLENGTH = function SORT_BYLENGTH(a, b) { + if (a.length !== b.length) { + return a.length - b.length; + } + + return a < b ? -1 : 1; + }; + + const ITEM = (text, input) => { + input = input.trim(); + const element = document.createElement("li"); + element.setAttribute("aria-selected", "false"); + const regex = new RegExp("(" + input + ")", "ig"); + const parts = input ? text.split(regex) : [text]; + parts.forEach(txt => { + if (input && txt.match(regex)) { + const match = document.createElement("mark"); + match.textContent = txt; + element.appendChild(match); + } else { + element.appendChild(document.createTextNode(txt)); + } + }); + return element; + }; + + class AutoComplete { + constructor(el, config = {}) { + this.is_opened = false; + + if (u.hasClass('.suggestion-box', el)) { + this.container = el; + } else { + this.container = el.querySelector('.suggestion-box'); } - return a < b ? -1 : 1; - }; + this.input = this.container.querySelector('.suggestion-box__input'); + this.input.setAttribute("autocomplete", "off"); + this.input.setAttribute("aria-autocomplete", "list"); + this.ul = this.container.querySelector('.suggestion-box__results'); + this.status = this.container.querySelector('.suggestion-box__additions'); - const ITEM = (text, input) => { - input = input.trim(); - const element = document.createElement("li"); - element.setAttribute("aria-selected", "false"); - const regex = new RegExp("(" + input + ")", "ig"); - const parts = input ? text.split(regex) : [text]; - parts.forEach(txt => { - if (input && txt.match(regex)) { - const match = document.createElement("mark"); - match.textContent = txt; - element.appendChild(match); - } else { - element.appendChild(document.createTextNode(txt)); - } - }); - return element; - }; + _.assignIn(this, { + 'match_current_word': false, + // Match only the current word, otherwise all input is matched + 'match_on_tab': false, + // Whether matching should only start when tab's pressed + 'trigger_on_at': false, + // Whether @ should trigger autocomplete + 'min_chars': 2, + 'max_items': 10, + 'auto_evaluate': true, + 'auto_first': false, + 'data': _.identity, + 'filter': _converse.FILTER_CONTAINS, + 'sort': config.sort === false ? false : SORT_BYLENGTH, + 'item': ITEM + }, config); - class AutoComplete { - constructor(el, config = {}) { - this.is_opened = false; + this.index = -1; + this.bindEvents(); - if (u.hasClass('.suggestion-box', el)) { - this.container = el; - } else { - this.container = el.querySelector('.suggestion-box'); - } + if (this.input.hasAttribute("list")) { + this.list = "#" + this.input.getAttribute("list"); + this.input.removeAttribute("list"); + } else { + this.list = this.input.getAttribute("data-list") || config.list || []; + } + } - this.input = this.container.querySelector('.suggestion-box__input'); - this.input.setAttribute("autocomplete", "off"); - this.input.setAttribute("aria-autocomplete", "list"); - this.ul = this.container.querySelector('.suggestion-box__results'); - this.status = this.container.querySelector('.suggestion-box__additions'); + bindEvents() { + // Bind events + const input = { + "blur": () => this.close({ + 'reason': 'blur' + }) + }; - _.assignIn(this, { - 'match_current_word': false, - // Match only the current word, otherwise all input is matched - 'match_on_tab': false, - // Whether matching should only start when tab's pressed - 'trigger_on_at': false, - // Whether @ should trigger autocomplete - 'min_chars': 2, - 'max_items': 10, - 'auto_evaluate': true, - 'auto_first': false, - 'data': _.identity, - 'filter': _converse.FILTER_CONTAINS, - 'sort': config.sort === false ? false : SORT_BYLENGTH, - 'item': ITEM - }, config); - - this.index = -1; - this.bindEvents(); - - if (this.input.hasAttribute("list")) { - this.list = "#" + this.input.getAttribute("list"); - this.input.removeAttribute("list"); - } else { - this.list = this.input.getAttribute("data-list") || config.list || []; - } + if (this.auto_evaluate) { + input["input"] = () => this.evaluate(); } - bindEvents() { - // Bind events - const input = { - "blur": () => this.close({ - 'reason': 'blur' + this._events = { + 'input': input, + 'form': { + "submit": () => this.close({ + 'reason': 'submit' }) - }; - - if (this.auto_evaluate) { - input["input"] = () => this.evaluate(); + }, + 'ul': { + "mousedown": ev => this.onMouseDown(ev), + "mouseover": ev => this.onMouseOver(ev) } + }; + helpers.bind(this.input, this._events.input); + helpers.bind(this.input.form, this._events.form); + helpers.bind(this.ul, this._events.ul); + } - this._events = { - 'input': input, - 'form': { - "submit": () => this.close({ - 'reason': 'submit' - }) - }, - 'ul': { - "mousedown": ev => this.onMouseDown(ev), - "mouseover": ev => this.onMouseOver(ev) - } - }; - helpers.bind(this.input, this._events.input); - helpers.bind(this.input.form, this._events.form); - helpers.bind(this.ul, this._events.ul); - } + set list(list) { + if (Array.isArray(list) || typeof list === "function") { + this._list = list; + } else if (typeof list === "string" && _.includes(list, ",")) { + this._list = list.split(/\s*,\s*/); + } else { + // Element or CSS selector + list = helpers.getElement(list); - set list(list) { - if (Array.isArray(list) || typeof list === "function") { - this._list = list; - } else if (typeof list === "string" && _.includes(list, ",")) { - this._list = list.split(/\s*,\s*/); - } else { - // Element or CSS selector - list = helpers.getElement(list); + if (list && list.children) { + const items = []; + slice.apply(list.children).forEach(function (el) { + if (!el.disabled) { + const text = el.textContent.trim(), + value = el.value || text, + label = el.label || text; - if (list && list.children) { - const items = []; - slice.apply(list.children).forEach(function (el) { - if (!el.disabled) { - const text = el.textContent.trim(), - value = el.value || text, - label = el.label || text; - - if (value !== "") { - items.push({ - label: label, - value: value - }); - } + if (value !== "") { + items.push({ + label: label, + value: value + }); } - }); - this._list = items; - } - } - - if (document.activeElement === this.input) { - this.evaluate(); - } - } - - get selected() { - return this.index > -1; - } - - get opened() { - return this.is_opened; - } - - close(o) { - if (!this.opened) { - return; - } - - this.ul.setAttribute("hidden", ""); - this.is_opened = false; - this.index = -1; - this.trigger("suggestion-box-close", o || {}); - } - - insertValue(suggestion) { - let value; - - if (this.match_current_word) { - u.replaceCurrentWord(this.input, suggestion.value); - } else { - this.input.value = suggestion.value; - } - } - - open() { - this.ul.removeAttribute("hidden"); - this.is_opened = true; - - if (this.auto_first && this.index === -1) { - this.goto(0); - } - - this.trigger("suggestion-box-open"); - } - - destroy() { - //remove events from the input and its form - helpers.unbind(this.input, this._events.input); - helpers.unbind(this.input.form, this._events.form); //move the input out of the suggestion-box container and remove the container and its children - - const parentNode = this.container.parentNode; - parentNode.insertBefore(this.input, this.container); - parentNode.removeChild(this.container); //remove autocomplete and aria-autocomplete attributes - - this.input.removeAttribute("autocomplete"); - this.input.removeAttribute("aria-autocomplete"); - } - - next() { - const count = this.ul.children.length; - this.goto(this.index < count - 1 ? this.index + 1 : count ? 0 : -1); - } - - previous() { - const count = this.ul.children.length, - pos = this.index - 1; - this.goto(this.selected && pos !== -1 ? pos : count - 1); - } - - goto(i) { - // Should not be used directly, highlights specific item without any checks! - const list = this.ul.children; - - if (this.selected) { - list[this.index].setAttribute("aria-selected", "false"); - } - - this.index = i; - - if (i > -1 && list.length > 0) { - list[i].setAttribute("aria-selected", "true"); - list[i].focus(); - this.status.textContent = list[i].textContent; // scroll to highlighted element in case parent's height is fixed - - this.ul.scrollTop = list[i].offsetTop - this.ul.clientHeight + list[i].clientHeight; - this.trigger("suggestion-box-highlight", { - 'text': this.suggestions[this.index] + } }); + this._list = items; } } - select(selected, origin) { - if (selected) { - this.index = u.siblingIndex(selected); - } else { - selected = this.ul.children[this.index]; - } + if (document.activeElement === this.input) { + this.evaluate(); + } + } - if (selected) { - const suggestion = this.suggestions[this.index]; - this.insertValue(suggestion); + get selected() { + return this.index > -1; + } + + get opened() { + return this.is_opened; + } + + close(o) { + if (!this.opened) { + return; + } + + this.ul.setAttribute("hidden", ""); + this.is_opened = false; + this.index = -1; + this.trigger("suggestion-box-close", o || {}); + } + + insertValue(suggestion) { + let value; + + if (this.match_current_word) { + u.replaceCurrentWord(this.input, suggestion.value); + } else { + this.input.value = suggestion.value; + } + } + + open() { + this.ul.removeAttribute("hidden"); + this.is_opened = true; + + if (this.auto_first && this.index === -1) { + this.goto(0); + } + + this.trigger("suggestion-box-open"); + } + + destroy() { + //remove events from the input and its form + helpers.unbind(this.input, this._events.input); + helpers.unbind(this.input.form, this._events.form); //move the input out of the suggestion-box container and remove the container and its children + + const parentNode = this.container.parentNode; + parentNode.insertBefore(this.input, this.container); + parentNode.removeChild(this.container); //remove autocomplete and aria-autocomplete attributes + + this.input.removeAttribute("autocomplete"); + this.input.removeAttribute("aria-autocomplete"); + } + + next() { + const count = this.ul.children.length; + this.goto(this.index < count - 1 ? this.index + 1 : count ? 0 : -1); + } + + previous() { + const count = this.ul.children.length, + pos = this.index - 1; + this.goto(this.selected && pos !== -1 ? pos : count - 1); + } + + goto(i) { + // Should not be used directly, highlights specific item without any checks! + const list = this.ul.children; + + if (this.selected) { + list[this.index].setAttribute("aria-selected", "false"); + } + + this.index = i; + + if (i > -1 && list.length > 0) { + list[i].setAttribute("aria-selected", "true"); + list[i].focus(); + this.status.textContent = list[i].textContent; // scroll to highlighted element in case parent's height is fixed + + this.ul.scrollTop = list[i].offsetTop - this.ul.clientHeight + list[i].clientHeight; + this.trigger("suggestion-box-highlight", { + 'text': this.suggestions[this.index] + }); + } + } + + select(selected, origin) { + if (selected) { + this.index = u.siblingIndex(selected); + } else { + selected = this.ul.children[this.index]; + } + + if (selected) { + const suggestion = this.suggestions[this.index]; + this.insertValue(suggestion); + this.close({ + 'reason': 'select' + }); + this.auto_completing = false; + this.trigger("suggestion-box-selectcomplete", { + 'text': suggestion + }); + } + } + + onMouseOver(ev) { + const li = u.ancestor(ev.target, 'li'); + + if (li) { + this.goto(Array.prototype.slice.call(this.ul.children).indexOf(li)); + } + } + + onMouseDown(ev) { + if (ev.button !== 0) { + return; // Only select on left click + } + + const li = u.ancestor(ev.target, 'li'); + + if (li) { + ev.preventDefault(); + this.select(li, ev.target); + } + } + + keyPressed(ev) { + if (this.opened) { + if (_.includes([_converse.keycodes.ENTER, _converse.keycodes.TAB], ev.keyCode) && this.selected) { + ev.preventDefault(); + ev.stopPropagation(); + this.select(); + return true; + } else if (ev.keyCode === _converse.keycodes.ESCAPE) { this.close({ - 'reason': 'select' + 'reason': 'esc' }); - this.auto_completing = false; - this.trigger("suggestion-box-selectcomplete", { - 'text': suggestion - }); - } - } - - onMouseOver(ev) { - const li = u.ancestor(ev.target, 'li'); - - if (li) { - this.goto(Array.prototype.slice.call(this.ul.children).indexOf(li)); - } - } - - onMouseDown(ev) { - if (ev.button !== 0) { - return; // Only select on left click - } - - const li = u.ancestor(ev.target, 'li'); - - if (li) { + return true; + } else if (_.includes([_converse.keycodes.UP_ARROW, _converse.keycodes.DOWN_ARROW], ev.keyCode)) { ev.preventDefault(); - this.select(li, ev.target); + ev.stopPropagation(); + this[ev.keyCode === _converse.keycodes.UP_ARROW ? "previous" : "next"](); + return true; } } - keyPressed(ev) { - if (this.opened) { - if (_.includes([_converse.keycodes.ENTER, _converse.keycodes.TAB], ev.keyCode) && this.selected) { - ev.preventDefault(); - ev.stopPropagation(); - this.select(); - return true; - } else if (ev.keyCode === _converse.keycodes.ESCAPE) { - this.close({ - 'reason': 'esc' - }); - return true; - } else if (_.includes([_converse.keycodes.UP_ARROW, _converse.keycodes.DOWN_ARROW], ev.keyCode)) { - ev.preventDefault(); - ev.stopPropagation(); - this[ev.keyCode === _converse.keycodes.UP_ARROW ? "previous" : "next"](); - return true; - } - } - - if (_.includes([_converse.keycodes.SHIFT, _converse.keycodes.META, _converse.keycodes.META_RIGHT, _converse.keycodes.ESCAPE, _converse.keycodes.ALT], ev.keyCode)) { - return; - } - - if (this.match_on_tab && ev.keyCode === _converse.keycodes.TAB) { - ev.preventDefault(); - this.auto_completing = true; - } else if (this.trigger_on_at && ev.keyCode === _converse.keycodes.AT) { - this.auto_completing = true; - } + if (_.includes([_converse.keycodes.SHIFT, _converse.keycodes.META, _converse.keycodes.META_RIGHT, _converse.keycodes.ESCAPE, _converse.keycodes.ALT], ev.keyCode)) { + return; } - evaluate(ev) { - const arrow_pressed = ev.keyCode === _converse.keycodes.UP_ARROW || ev.keyCode === _converse.keycodes.DOWN_ARROW; + if (this.match_on_tab && ev.keyCode === _converse.keycodes.TAB) { + ev.preventDefault(); + this.auto_completing = true; + } else if (this.trigger_on_at && ev.keyCode === _converse.keycodes.AT) { + this.auto_completing = true; + } + } - if (!this.auto_completing || this.selected && arrow_pressed) { - return; + evaluate(ev) { + const arrow_pressed = ev.keyCode === _converse.keycodes.UP_ARROW || ev.keyCode === _converse.keycodes.DOWN_ARROW; + + if (!this.auto_completing || this.selected && arrow_pressed) { + return; + } + + const list = typeof this._list === "function" ? this._list() : this._list; + + if (list.length === 0) { + return; + } + + let value = this.match_current_word ? u.getCurrentWord(this.input) : this.input.value; + let ignore_min_chars = false; + + if (this.trigger_on_at && value.startsWith('@')) { + ignore_min_chars = true; + value = value.slice('1'); + } + + if (value.length >= this.min_chars || ignore_min_chars) { + this.index = -1; // Populate list with options that match + + this.ul.innerHTML = ""; + this.suggestions = list.map(item => new Suggestion(this.data(item, value))).filter(item => this.filter(item, value)); + + if (this.sort !== false) { + this.suggestions = this.suggestions.sort(this.sort); } - const list = typeof this._list === "function" ? this._list() : this._list; + this.suggestions = this.suggestions.slice(0, this.max_items); + this.suggestions.forEach(text => this.ul.appendChild(this.item(text, value))); - if (list.length === 0) { - return; - } - - let value = this.match_current_word ? u.getCurrentWord(this.input) : this.input.value; - let ignore_min_chars = false; - - if (this.trigger_on_at && value.startsWith('@')) { - ignore_min_chars = true; - value = value.slice('1'); - } - - if (value.length >= this.min_chars || ignore_min_chars) { - this.index = -1; // Populate list with options that match - - this.ul.innerHTML = ""; - this.suggestions = list.map(item => new Suggestion(this.data(item, value))).filter(item => this.filter(item, value)); - - if (this.sort !== false) { - this.suggestions = this.suggestions.sort(this.sort); - } - - this.suggestions = this.suggestions.slice(0, this.max_items); - this.suggestions.forEach(text => this.ul.appendChild(this.item(text, value))); - - if (this.ul.children.length === 0) { - this.close({ - 'reason': 'nomatches' - }); - } else { - this.open(); - } - } else { + if (this.ul.children.length === 0) { this.close({ 'reason': 'nomatches' }); - this.auto_completing = false; + } else { + this.open(); } + } else { + this.close({ + 'reason': 'nomatches' + }); + this.auto_completing = false; } - - } // Make it an event emitter - - - _.extend(AutoComplete.prototype, Backbone.Events); // Private functions - - - function Suggestion(data) { - const o = Array.isArray(data) ? { - label: data[0], - value: data[1] - } : typeof data === "object" && "label" in data && "value" in data ? data : { - label: data, - value: data - }; - this.label = o.label || o.value; - this.value = o.value; } - Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { - get: function get() { - return this.label.length; - } - }); - - Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { - return "" + this.label; - }; // Helpers + } // Make it an event emitter - var slice = Array.prototype.slice; - const helpers = { - getElement(expr, el) { - return typeof expr === "string" ? (el || document).querySelector(expr) : expr || null; - }, + _.extend(AutoComplete.prototype, Backbone.Events); // Private functions - bind(element, o) { - if (element) { - for (var event in o) { - if (!Object.prototype.hasOwnProperty.call(o, event)) { - continue; - } - - const callback = o[event]; - event.split(/\s+/).forEach(event => element.addEventListener(event, callback)); - } - } - }, - - unbind(element, o) { - if (element) { - for (var event in o) { - if (!Object.prototype.hasOwnProperty.call(o, event)) { - continue; - } - - const callback = o[event]; - event.split(/\s+/).forEach(event => element.removeEventListener(event, callback)); - } - } - }, - - regExpEscape(s) { - return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); - } + function Suggestion(data) { + const o = Array.isArray(data) ? { + label: data[0], + value: data[1] + } : typeof data === "object" && "label" in data && "value" in data ? data : { + label: data, + value: data }; - _converse.AutoComplete = AutoComplete; + this.label = o.label || o.value; + this.value = o.value; } - }); + Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { + get: function get() { + return this.label.length; + } + }); + + Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { + return "" + this.label; + }; // Helpers + + + var slice = Array.prototype.slice; + const helpers = { + getElement(expr, el) { + return typeof expr === "string" ? (el || document).querySelector(expr) : expr || null; + }, + + bind(element, o) { + if (element) { + for (var event in o) { + if (!Object.prototype.hasOwnProperty.call(o, event)) { + continue; + } + + const callback = o[event]; + event.split(/\s+/).forEach(event => element.addEventListener(event, callback)); + } + } + }, + + unbind(element, o) { + if (element) { + for (var event in o) { + if (!Object.prototype.hasOwnProperty.call(o, event)) { + continue; + } + + const callback = o[event]; + event.split(/\s+/).forEach(event => element.removeEventListener(event, callback)); + } + } + }, + + regExpEscape(s) { + return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); + } + + }; + _converse.AutoComplete = AutoComplete; + } + }); /***/ }), @@ -57749,10 +57746,22 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!***********************************!*\ !*** ./src/converse-bookmarks.js ***! \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var _converse_headless_converse_muc__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"); +/* harmony import */ var templates_bookmark_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! templates/bookmark.html */ "./src/templates/bookmark.html"); +/* harmony import */ var templates_bookmark_html__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(templates_bookmark_html__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var templates_bookmarks_list_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/bookmarks_list.html */ "./src/templates/bookmarks_list.html"); +/* harmony import */ var templates_bookmarks_list_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_bookmarks_list_html__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var templates_chatroom_bookmark_form_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/chatroom_bookmark_form.html */ "./src/templates/chatroom_bookmark_form.html"); +/* harmony import */ var templates_chatroom_bookmark_form_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_bookmark_form_html__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var templates_chatroom_bookmark_toggle_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/chatroom_bookmark_toggle.html */ "./src/templates/chatroom_bookmark_toggle.html"); +/* harmony import */ var templates_chatroom_bookmark_toggle_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_bookmark_toggle_html__WEBPACK_IMPORTED_MODULE_5__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand @@ -57764,633 +57773,632 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /* This is a Converse.js plugin which add support for bookmarks specified * in XEP-0048. */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"), __webpack_require__(/*! templates/chatroom_bookmark_form.html */ "./src/templates/chatroom_bookmark_form.html"), __webpack_require__(/*! templates/chatroom_bookmark_toggle.html */ "./src/templates/chatroom_bookmark_toggle.html"), __webpack_require__(/*! templates/bookmark.html */ "./src/templates/bookmark.html"), __webpack_require__(/*! templates/bookmarks_list.html */ "./src/templates/bookmarks_list.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, muc, tpl_chatroom_bookmark_form, tpl_chatroom_bookmark_toggle, tpl_bookmark, tpl_bookmarks_list) { - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - $iq = _converse$env.$iq, - b64_sha1 = _converse$env.b64_sha1, - sizzle = _converse$env.sizzle, - _ = _converse$env._; - const u = converse.env.utils; - converse.plugins.add('converse-bookmarks', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatboxes", "@converse/headless/converse-muc", "converse-muc-views"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - ChatRoomView: { - events: { - 'click .toggle-bookmark': 'toggleBookmark' - }, - initialize() { - this.__super__.initialize.apply(this, arguments); - this.model.on('change:bookmarked', this.onBookmarked, this); - this.setBookmarkState(); - }, - renderBookmarkToggle() { - if (this.el.querySelector('.chat-head .toggle-bookmark')) { - return; - } - const _converse = this.__super__._converse, - __ = _converse.__; - const bookmark_button = tpl_chatroom_bookmark_toggle(_.assignIn(this.model.toJSON(), { - info_toggle_bookmark: __('Bookmark this groupchat'), - bookmarked: this.model.get('bookmarked') - })); - const close_button = this.el.querySelector('.close-chatbox-button'); - close_button.insertAdjacentHTML('afterend', bookmark_button); - }, - renderHeading() { - this.__super__.renderHeading.apply(this, arguments); - const _converse = this.__super__._converse; +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq, + b64_sha1 = _converse$env.b64_sha1, + sizzle = _converse$env.sizzle, + _ = _converse$env._; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-bookmarks', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatboxes", "converse-muc", "converse-muc-views"], + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + ChatRoomView: { + events: { + 'click .toggle-bookmark': 'toggleBookmark' + }, - if (_converse.allow_bookmarks) { - _converse.checkBookmarksSupport().then(supported => { - if (supported) { - this.renderBookmarkToggle(); - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - }, + initialize() { + this.__super__.initialize.apply(this, arguments); - checkForReservedNick() { - /* Check if the user has a bookmark with a saved nickanme - * for this groupchat, and if so use it. - * Otherwise delegate to the super method. - */ - const _converse = this.__super__._converse; + this.model.on('change:bookmarked', this.onBookmarked, this); + this.setBookmarkState(); + }, - if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) { - return this.__super__.checkForReservedNick.apply(this, arguments); - } + renderBookmarkToggle() { + if (this.el.querySelector('.chat-head .toggle-bookmark')) { + return; + } - const model = _converse.bookmarks.findWhere({ - 'jid': this.model.get('jid') - }); + const _converse = this.__super__._converse, + __ = _converse.__; + const bookmark_button = templates_chatroom_bookmark_toggle_html__WEBPACK_IMPORTED_MODULE_5___default()(_.assignIn(this.model.toJSON(), { + info_toggle_bookmark: __('Bookmark this groupchat'), + bookmarked: this.model.get('bookmarked') + })); + const close_button = this.el.querySelector('.close-chatbox-button'); + close_button.insertAdjacentHTML('afterend', bookmark_button); + }, - if (!_.isUndefined(model) && model.get('nick')) { - this.join(model.get('nick')); - } else { - return this.__super__.checkForReservedNick.apply(this, arguments); - } - }, + renderHeading() { + this.__super__.renderHeading.apply(this, arguments); - onBookmarked() { - const icon = this.el.querySelector('.toggle-bookmark'); + const _converse = this.__super__._converse; - if (_.isNull(icon)) { - return; - } - - if (this.model.get('bookmarked')) { - icon.classList.add('button-on'); - } else { - icon.classList.remove('button-on'); - } - }, - - setBookmarkState() { - /* Set whether the groupchat is bookmarked or not. - */ - const _converse = this.__super__._converse; - - if (!_.isUndefined(_converse.bookmarks)) { - const models = _converse.bookmarks.where({ - 'jid': this.model.get('jid') - }); - - if (!models.length) { - this.model.save('bookmarked', false); - } else { - this.model.save('bookmarked', true); + if (_converse.allow_bookmarks) { + _converse.checkBookmarksSupport().then(supported => { + if (supported) { + this.renderBookmarkToggle(); } - } - }, + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + }, - renderBookmarkForm() { - const _converse = this.__super__._converse, - __ = _converse.__, - body = this.el.querySelector('.chatroom-body'); + checkForReservedNick() { + /* Check if the user has a bookmark with a saved nickanme + * for this groupchat, and if so use it. + * Otherwise delegate to the super method. + */ + const _converse = this.__super__._converse; - _.each(body.children, child => child.classList.add('hidden')); + if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) { + return this.__super__.checkForReservedNick.apply(this, arguments); + } - _.each(body.querySelectorAll('.chatroom-form-container'), u.removeElement); + const model = _converse.bookmarks.findWhere({ + 'jid': this.model.get('jid') + }); - body.insertAdjacentHTML('beforeend', tpl_chatroom_bookmark_form({ - 'default_nick': this.model.get('nick'), - 'heading': __('Bookmark this groupchat'), - 'label_autojoin': __('Would you like this groupchat to be automatically joined upon startup?'), - 'label_cancel': __('Cancel'), - 'label_name': __('The name for this bookmark:'), - 'label_nick': __('What should your nickname for this groupchat be?'), - 'label_submit': __('Save'), - 'name': this.model.get('name') - })); - const form = body.querySelector('form.chatroom-form'); - form.addEventListener('submit', ev => this.onBookmarkFormSubmitted(ev)); - form.querySelector('.button-cancel').addEventListener('click', () => this.closeForm()); - }, + if (!_.isUndefined(model) && model.get('nick')) { + this.join(model.get('nick')); + } else { + return this.__super__.checkForReservedNick.apply(this, arguments); + } + }, - onBookmarkFormSubmitted(ev) { - ev.preventDefault(); - const _converse = this.__super__._converse; + onBookmarked() { + const icon = this.el.querySelector('.toggle-bookmark'); - _converse.bookmarks.createBookmark({ - 'jid': this.model.get('jid'), - 'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false, - 'name': _.get(ev.target.querySelector('input[name=name]'), 'value'), - 'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value') - }); + if (_.isNull(icon)) { + return; + } - u.removeElement(this.el.querySelector('div.chatroom-form-container')); - this.renderAfterTransition(); - }, + if (this.model.get('bookmarked')) { + icon.classList.add('button-on'); + } else { + icon.classList.remove('button-on'); + } + }, - toggleBookmark(ev) { - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - - const _converse = this.__super__._converse; + setBookmarkState() { + /* Set whether the groupchat is bookmarked or not. + */ + const _converse = this.__super__._converse; + if (!_.isUndefined(_converse.bookmarks)) { const models = _converse.bookmarks.where({ 'jid': this.model.get('jid') }); if (!models.length) { - this.renderBookmarkForm(); + this.model.save('bookmarked', false); } else { - _.forEach(models, function (model) { - model.destroy(); - }); - - this.el.querySelector('.toggle-bookmark').classList.remove('button-on'); + this.model.save('bookmarked', true); } } + }, + renderBookmarkForm() { + const _converse = this.__super__._converse, + __ = _converse.__, + body = this.el.querySelector('.chatroom-body'); + + _.each(body.children, child => child.classList.add('hidden')); + + _.each(body.querySelectorAll('.chatroom-form-container'), u.removeElement); + + body.insertAdjacentHTML('beforeend', templates_chatroom_bookmark_form_html__WEBPACK_IMPORTED_MODULE_4___default()({ + 'default_nick': this.model.get('nick'), + 'heading': __('Bookmark this groupchat'), + 'label_autojoin': __('Would you like this groupchat to be automatically joined upon startup?'), + 'label_cancel': __('Cancel'), + 'label_name': __('The name for this bookmark:'), + 'label_nick': __('What should your nickname for this groupchat be?'), + 'label_submit': __('Save'), + 'name': this.model.get('name') + })); + const form = body.querySelector('form.chatroom-form'); + form.addEventListener('submit', ev => this.onBookmarkFormSubmitted(ev)); + form.querySelector('.button-cancel').addEventListener('click', () => this.closeForm()); + }, + + onBookmarkFormSubmitted(ev) { + ev.preventDefault(); + const _converse = this.__super__._converse; + + _converse.bookmarks.createBookmark({ + 'jid': this.model.get('jid'), + 'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false, + 'name': _.get(ev.target.querySelector('input[name=name]'), 'value'), + 'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value') + }); + + u.removeElement(this.el.querySelector('div.chatroom-form-container')); + this.renderAfterTransition(); + }, + + toggleBookmark(ev) { + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + const _converse = this.__super__._converse; + + const models = _converse.bookmarks.where({ + 'jid': this.model.get('jid') + }); + + if (!models.length) { + this.renderBookmarkForm(); + } else { + _.forEach(models, function (model) { + model.destroy(); + }); + + this.el.querySelector('.toggle-bookmark').classList.remove('button-on'); + } } - }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; // Configuration values for this plugin - // ==================================== - // Refer to docs/source/configuration.rst for explanations of these - // configuration settings. + } + }, - _converse.api.settings.update({ - allow_bookmarks: true, - allow_public_bookmarks: false, - hide_open_bookmarks: true - }); // Promises exposed by this plugin + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; // Configuration values for this plugin + // ==================================== + // Refer to docs/source/configuration.rst for explanations of these + // configuration settings. + + _converse.api.settings.update({ + allow_bookmarks: true, + allow_public_bookmarks: false, + hide_open_bookmarks: true + }); // Promises exposed by this plugin - _converse.api.promises.add('bookmarksInitialized'); // Pure functions on the _converse object + _converse.api.promises.add('bookmarksInitialized'); // Pure functions on the _converse object - _.extend(_converse, { - removeBookmarkViaEvent(ev) { - /* Remove a bookmark as determined by the passed in - * event. - */ - ev.preventDefault(); - const name = ev.target.getAttribute('data-bookmark-name'); - const jid = ev.target.getAttribute('data-room-jid'); + _.extend(_converse, { + removeBookmarkViaEvent(ev) { + /* Remove a bookmark as determined by the passed in + * event. + */ + ev.preventDefault(); + const name = ev.target.getAttribute('data-bookmark-name'); + const jid = ev.target.getAttribute('data-room-jid'); - if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) { - _.invokeMap(_converse.bookmarks.where({ - 'jid': jid - }), Backbone.Model.prototype.destroy); + if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) { + _.invokeMap(_converse.bookmarks.where({ + 'jid': jid + }), Backbone.Model.prototype.destroy); + } + }, + + addBookmarkViaEvent(ev) { + /* Add a bookmark as determined by the passed in + * event. + */ + ev.preventDefault(); + const jid = ev.target.getAttribute('data-room-jid'); + + const chatroom = _converse.api.rooms.open(jid, { + 'bring_to_foreground': true + }); + + _converse.chatboxviews.get(jid).renderBookmarkForm(); + } + + }); + + _converse.Bookmark = Backbone.Model; + _converse.Bookmarks = Backbone.Collection.extend({ + model: _converse.Bookmark, + comparator: item => item.get('name').toLowerCase(), + + initialize() { + this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked)); + this.on('remove', this.markRoomAsUnbookmarked, this); + this.on('remove', this.sendBookmarkStanza, this); + + const storage = _converse.config.get('storage'), + cache_key = `converse.room-bookmarks${_converse.bare_jid}`; + + this.fetched_flag = b64_sha1(cache_key + 'fetched'); + this.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(cache_key)); + }, + + openBookmarkedRoom(bookmark) { + if (bookmark.get('autojoin')) { + const groupchat = _converse.api.rooms.create(bookmark.get('jid'), bookmark.get('nick')); + + if (!groupchat.get('hidden')) { + groupchat.trigger('show'); } - }, - - addBookmarkViaEvent(ev) { - /* Add a bookmark as determined by the passed in - * event. - */ - ev.preventDefault(); - const jid = ev.target.getAttribute('data-room-jid'); - - const chatroom = _converse.api.rooms.open(jid, { - 'bring_to_foreground': true - }); - - _converse.chatboxviews.get(jid).renderBookmarkForm(); } - }); + return bookmark; + }, - _converse.Bookmark = Backbone.Model; - _converse.Bookmarks = Backbone.Collection.extend({ - model: _converse.Bookmark, - comparator: item => item.get('name').toLowerCase(), + fetchBookmarks() { + const deferred = u.getResolveablePromise(); - initialize() { - this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked)); - this.on('remove', this.markRoomAsUnbookmarked, this); - this.on('remove', this.sendBookmarkStanza, this); + if (this.browserStorage.records.length > 0) { + this.fetch({ + 'success': _.bind(this.onCachedBookmarksFetched, this, deferred), + 'error': _.bind(this.onCachedBookmarksFetched, this, deferred) + }); + } else if (!window.sessionStorage.getItem(this.fetched_flag)) { + // There aren't any cached bookmarks and the + // `fetched_flag` is off, so we query the XMPP server. + // If nothing is returned from the XMPP server, we set + // the `fetched_flag` to avoid calling the server again. + this.fetchBookmarksFromServer(deferred); + } else { + deferred.resolve(); + } - const storage = _converse.config.get('storage'), - cache_key = `converse.room-bookmarks${_converse.bare_jid}`; + return deferred; + }, - this.fetched_flag = b64_sha1(cache_key + 'fetched'); - this.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(cache_key)); - }, + onCachedBookmarksFetched(deferred) { + return deferred.resolve(); + }, - openBookmarkedRoom(bookmark) { - if (bookmark.get('autojoin')) { - const groupchat = _converse.api.rooms.create(bookmark.get('jid'), bookmark.get('nick')); + createBookmark(options) { + this.create(options); + this.sendBookmarkStanza().catch(iq => this.onBookmarkError(iq, options)); + }, - if (!groupchat.get('hidden')) { - groupchat.trigger('show'); - } - } + sendBookmarkStanza() { + const stanza = $iq({ + 'type': 'set', + 'from': _converse.connection.jid + }).c('pubsub', { + 'xmlns': Strophe.NS.PUBSUB + }).c('publish', { + 'node': 'storage:bookmarks' + }).c('item', { + 'id': 'current' + }).c('storage', { + 'xmlns': 'storage:bookmarks' + }); + this.each(model => { + stanza.c('conference', { + 'name': model.get('name'), + 'autojoin': model.get('autojoin'), + 'jid': model.get('jid') + }).c('nick').t(model.get('nick')).up().up(); + }); + stanza.up().up().up(); + stanza.c('publish-options').c('x', { + 'xmlns': Strophe.NS.XFORM, + 'type': 'submit' + }).c('field', { + 'var': 'FORM_TYPE', + 'type': 'hidden' + }).c('value').t('http://jabber.org/protocol/pubsub#publish-options').up().up().c('field', { + 'var': 'pubsub#persist_items' + }).c('value').t('true').up().up().c('field', { + 'var': 'pubsub#access_model' + }).c('value').t('whitelist'); + return _converse.api.sendIQ(stanza); + }, - return bookmark; - }, + onBookmarkError(iq, options) { + _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR); - fetchBookmarks() { - const deferred = u.getResolveablePromise(); + _converse.log(iq); - if (this.browserStorage.records.length > 0) { - this.fetch({ - 'success': _.bind(this.onCachedBookmarksFetched, this, deferred), - 'error': _.bind(this.onCachedBookmarksFetched, this, deferred) - }); - } else if (!window.sessionStorage.getItem(this.fetched_flag)) { - // There aren't any cached bookmarks and the - // `fetched_flag` is off, so we query the XMPP server. - // If nothing is returned from the XMPP server, we set - // the `fetched_flag` to avoid calling the server again. - this.fetchBookmarksFromServer(deferred); - } else { - deferred.resolve(); - } + _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, something went wrong while trying to save your bookmark.")]); - return deferred; - }, + this.findWhere({ + 'jid': options.jid + }).destroy(); + }, - onCachedBookmarksFetched(deferred) { + fetchBookmarksFromServer(deferred) { + const stanza = $iq({ + 'from': _converse.connection.jid, + 'type': 'get' + }).c('pubsub', { + 'xmlns': Strophe.NS.PUBSUB + }).c('items', { + 'node': 'storage:bookmarks' + }); + + _converse.api.sendIQ(stanza).then(iq => this.onBookmarksReceived(deferred, iq)).catch(iq => this.onBookmarksReceivedError(deferred, iq)); + }, + + markRoomAsBookmarked(bookmark) { + const groupchat = _converse.chatboxes.get(bookmark.get('jid')); + + if (!_.isUndefined(groupchat)) { + groupchat.save('bookmarked', true); + } + }, + + markRoomAsUnbookmarked(bookmark) { + const groupchat = _converse.chatboxes.get(bookmark.get('jid')); + + if (!_.isUndefined(groupchat)) { + groupchat.save('bookmarked', false); + } + }, + + createBookmarksFromStanza(stanza) { + const bookmarks = sizzle('items[node="storage:bookmarks"] ' + 'item#current ' + 'storage[xmlns="storage:bookmarks"] ' + 'conference', stanza); + + _.forEach(bookmarks, bookmark => { + const jid = bookmark.getAttribute('jid'); + this.create({ + 'jid': jid, + 'name': bookmark.getAttribute('name') || jid, + 'autojoin': bookmark.getAttribute('autojoin') === 'true', + 'nick': _.get(bookmark.querySelector('nick'), 'textContent') + }); + }); + }, + + onBookmarksReceived(deferred, iq) { + this.createBookmarksFromStanza(iq); + + if (!_.isUndefined(deferred)) { return deferred.resolve(); - }, + } + }, - createBookmark(options) { - this.create(options); - this.sendBookmarkStanza().catch(iq => this.onBookmarkError(iq, options)); - }, + onBookmarksReceivedError(deferred, iq) { + window.sessionStorage.setItem(this.fetched_flag, true); - sendBookmarkStanza() { - const stanza = $iq({ - 'type': 'set', - 'from': _converse.connection.jid - }).c('pubsub', { - 'xmlns': Strophe.NS.PUBSUB - }).c('publish', { - 'node': 'storage:bookmarks' - }).c('item', { - 'id': 'current' - }).c('storage', { - 'xmlns': 'storage:bookmarks' - }); - this.each(model => { - stanza.c('conference', { - 'name': model.get('name'), - 'autojoin': model.get('autojoin'), - 'jid': model.get('jid') - }).c('nick').t(model.get('nick')).up().up(); - }); - stanza.up().up().up(); - stanza.c('publish-options').c('x', { - 'xmlns': Strophe.NS.XFORM, - 'type': 'submit' - }).c('field', { - 'var': 'FORM_TYPE', - 'type': 'hidden' - }).c('value').t('http://jabber.org/protocol/pubsub#publish-options').up().up().c('field', { - 'var': 'pubsub#persist_items' - }).c('value').t('true').up().up().c('field', { - 'var': 'pubsub#access_model' - }).c('value').t('whitelist'); - return _converse.api.sendIQ(stanza); - }, + _converse.log('Error while fetching bookmarks', Strophe.LogLevel.WARN); - onBookmarkError(iq, options) { - _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR); + _converse.log(iq.outerHTML, Strophe.LogLevel.DEBUG); - _converse.log(iq); - - _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, something went wrong while trying to save your bookmark.")]); - - this.findWhere({ - 'jid': options.jid - }).destroy(); - }, - - fetchBookmarksFromServer(deferred) { - const stanza = $iq({ - 'from': _converse.connection.jid, - 'type': 'get' - }).c('pubsub', { - 'xmlns': Strophe.NS.PUBSUB - }).c('items', { - 'node': 'storage:bookmarks' - }); - - _converse.api.sendIQ(stanza).then(iq => this.onBookmarksReceived(deferred, iq)).catch(iq => this.onBookmarksReceivedError(deferred, iq)); - }, - - markRoomAsBookmarked(bookmark) { - const groupchat = _converse.chatboxes.get(bookmark.get('jid')); - - if (!_.isUndefined(groupchat)) { - groupchat.save('bookmarked', true); - } - }, - - markRoomAsUnbookmarked(bookmark) { - const groupchat = _converse.chatboxes.get(bookmark.get('jid')); - - if (!_.isUndefined(groupchat)) { - groupchat.save('bookmarked', false); - } - }, - - createBookmarksFromStanza(stanza) { - const bookmarks = sizzle('items[node="storage:bookmarks"] ' + 'item#current ' + 'storage[xmlns="storage:bookmarks"] ' + 'conference', stanza); - - _.forEach(bookmarks, bookmark => { - const jid = bookmark.getAttribute('jid'); - this.create({ - 'jid': jid, - 'name': bookmark.getAttribute('name') || jid, - 'autojoin': bookmark.getAttribute('autojoin') === 'true', - 'nick': _.get(bookmark.querySelector('nick'), 'textContent') - }); - }); - }, - - onBookmarksReceived(deferred, iq) { - this.createBookmarksFromStanza(iq); - - if (!_.isUndefined(deferred)) { + if (!_.isNil(deferred)) { + if (iq.querySelector('error[type="cancel"] item-not-found')) { + // Not an exception, the user simply doesn't have + // any bookmarks. return deferred.resolve(); - } - }, - - onBookmarksReceivedError(deferred, iq) { - window.sessionStorage.setItem(this.fetched_flag, true); - - _converse.log('Error while fetching bookmarks', Strophe.LogLevel.WARN); - - _converse.log(iq.outerHTML, Strophe.LogLevel.DEBUG); - - if (!_.isNil(deferred)) { - if (iq.querySelector('error[type="cancel"] item-not-found')) { - // Not an exception, the user simply doesn't have - // any bookmarks. - return deferred.resolve(); - } else { - return deferred.reject(new Error("Could not fetch bookmarks")); - } - } - } - - }); - _converse.BookmarksList = Backbone.Model.extend({ - defaults: { - "toggle-state": _converse.OPENED - } - }); - _converse.BookmarkView = Backbone.VDOMView.extend({ - toHTML() { - return tpl_bookmark({ - 'hidden': _converse.hide_open_bookmarks && _converse.chatboxes.where({ - 'jid': this.model.get('jid') - }).length, - 'bookmarked': true, - 'info_leave_room': __('Leave this groupchat'), - 'info_remove': __('Remove this bookmark'), - 'info_remove_bookmark': __('Unbookmark this groupchat'), - 'info_title': __('Show more information on this groupchat'), - 'jid': this.model.get('jid'), - 'name': Strophe.xmlunescape(this.model.get('name')), - 'open_title': __('Click to open this groupchat') - }); - } - - }); - _converse.BookmarksView = Backbone.OrderedListView.extend({ - tagName: 'div', - className: 'bookmarks-list list-container rooms-list-container', - events: { - 'click .add-bookmark': 'addBookmark', - 'click .bookmarks-toggle': 'toggleBookmarksList', - 'click .remove-bookmark': 'removeBookmark', - 'click .open-room': 'openRoom' - }, - listSelector: '.rooms-list', - ItemView: _converse.BookmarkView, - subviewIndex: 'jid', - - initialize() { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - this.model.on('add', this.showOrHide, this); - this.model.on('remove', this.showOrHide, this); - - _converse.chatboxes.on('add', this.renderBookmarkListElement, this); - - _converse.chatboxes.on('remove', this.renderBookmarkListElement, this); - - const storage = _converse.config.get('storage'), - id = b64_sha1(`converse.room-bookmarks${_converse.bare_jid}-list-model`); - - this.list_model = new _converse.BookmarksList({ - 'id': id - }); - this.list_model.browserStorage = new Backbone.BrowserStorage[storage](id); - this.list_model.fetch(); - this.render(); - this.sortAndPositionAllItems(); - }, - - render() { - this.el.innerHTML = tpl_bookmarks_list({ - 'toggle_state': this.list_model.get('toggle-state'), - 'desc_bookmarks': __('Click to toggle the bookmarks list'), - 'label_bookmarks': __('Bookmarks'), - '_converse': _converse - }); - this.showOrHide(); - this.insertIntoControlBox(); - return this; - }, - - insertIntoControlBox() { - const controlboxview = _converse.chatboxviews.get('controlbox'); - - if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) { - const el = controlboxview.el.querySelector('.bookmarks-list'); - - if (!_.isNull(el)) { - el.parentNode.replaceChild(this.el, el); - } - } - }, - - openRoom(ev) { - ev.preventDefault(); - const name = ev.target.textContent; - const jid = ev.target.getAttribute('data-room-jid'); - const data = { - 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid - }; - - _converse.api.rooms.open(jid, data); - }, - - removeBookmark: _converse.removeBookmarkViaEvent, - addBookmark: _converse.addBookmarkViaEvent, - - renderBookmarkListElement(chatbox) { - const bookmarkview = this.get(chatbox.get('jid')); - - if (_.isNil(bookmarkview)) { - // A chat box has been closed, but we don't have a - // bookmark for it, so nothing further to do here. - return; - } - - bookmarkview.render(); - this.showOrHide(); - }, - - showOrHide(item) { - if (_converse.hide_open_bookmarks) { - const bookmarks = this.model.filter(bookmark => !_converse.chatboxes.get(bookmark.get('jid'))); - - if (!bookmarks.length) { - u.hideElement(this.el); - return; - } - } - - if (this.model.models.length) { - u.showElement(this.el); - } - }, - - toggleBookmarksList(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const icon_el = ev.target.querySelector('.fa'); - - if (u.hasClass('fa-caret-down', icon_el)) { - u.slideIn(this.el.querySelector('.bookmarks')); - this.list_model.save({ - 'toggle-state': _converse.CLOSED - }); - icon_el.classList.remove("fa-caret-down"); - icon_el.classList.add("fa-caret-right"); } else { - icon_el.classList.remove("fa-caret-right"); - icon_el.classList.add("fa-caret-down"); - u.slideOut(this.el.querySelector('.bookmarks')); - this.list_model.save({ - 'toggle-state': _converse.OPENED - }); + return deferred.reject(new Error("Could not fetch bookmarks")); } } + } - }); + }); + _converse.BookmarksList = Backbone.Model.extend({ + defaults: { + "toggle-state": _converse.OPENED + } + }); + _converse.BookmarkView = Backbone.VDOMView.extend({ + toHTML() { + return templates_bookmark_html__WEBPACK_IMPORTED_MODULE_2___default()({ + 'hidden': _converse.hide_open_bookmarks && _converse.chatboxes.where({ + 'jid': this.model.get('jid') + }).length, + 'bookmarked': true, + 'info_leave_room': __('Leave this groupchat'), + 'info_remove': __('Remove this bookmark'), + 'info_remove_bookmark': __('Unbookmark this groupchat'), + 'info_title': __('Show more information on this groupchat'), + 'jid': this.model.get('jid'), + 'name': Strophe.xmlunescape(this.model.get('name')), + 'open_title': __('Click to open this groupchat') + }); + } - _converse.checkBookmarksSupport = function () { - return new Promise((resolve, reject) => { - Promise.all([_converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid), _converse.api.disco.supports(Strophe.NS.PUBSUB + '#publish-options', _converse.bare_jid)]).then(args => { - resolve(args[0] && (args[1].length || _converse.allow_public_bookmarks)); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }; + }); + _converse.BookmarksView = Backbone.OrderedListView.extend({ + tagName: 'div', + className: 'bookmarks-list list-container rooms-list-container', + events: { + 'click .add-bookmark': 'addBookmark', + 'click .bookmarks-toggle': 'toggleBookmarksList', + 'click .remove-bookmark': 'removeBookmark', + 'click .open-room': 'openRoom' + }, + listSelector: '.rooms-list', + ItemView: _converse.BookmarkView, + subviewIndex: 'jid', - const initBookmarks = function initBookmarks() { - if (!_converse.allow_bookmarks) { + initialize() { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + this.model.on('add', this.showOrHide, this); + this.model.on('remove', this.showOrHide, this); + + _converse.chatboxes.on('add', this.renderBookmarkListElement, this); + + _converse.chatboxes.on('remove', this.renderBookmarkListElement, this); + + const storage = _converse.config.get('storage'), + id = b64_sha1(`converse.room-bookmarks${_converse.bare_jid}-list-model`); + + this.list_model = new _converse.BookmarksList({ + 'id': id + }); + this.list_model.browserStorage = new Backbone.BrowserStorage[storage](id); + this.list_model.fetch(); + this.render(); + this.sortAndPositionAllItems(); + }, + + render() { + this.el.innerHTML = templates_bookmarks_list_html__WEBPACK_IMPORTED_MODULE_3___default()({ + 'toggle_state': this.list_model.get('toggle-state'), + 'desc_bookmarks': __('Click to toggle the bookmarks list'), + 'label_bookmarks': __('Bookmarks'), + '_converse': _converse + }); + this.showOrHide(); + this.insertIntoControlBox(); + return this; + }, + + insertIntoControlBox() { + const controlboxview = _converse.chatboxviews.get('controlbox'); + + if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) { + const el = controlboxview.el.querySelector('.bookmarks-list'); + + if (!_.isNull(el)) { + el.parentNode.replaceChild(this.el, el); + } + } + }, + + openRoom(ev) { + ev.preventDefault(); + const name = ev.target.textContent; + const jid = ev.target.getAttribute('data-room-jid'); + const data = { + 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid + }; + + _converse.api.rooms.open(jid, data); + }, + + removeBookmark: _converse.removeBookmarkViaEvent, + addBookmark: _converse.addBookmarkViaEvent, + + renderBookmarkListElement(chatbox) { + const bookmarkview = this.get(chatbox.get('jid')); + + if (_.isNil(bookmarkview)) { + // A chat box has been closed, but we don't have a + // bookmark for it, so nothing further to do here. return; } - _converse.checkBookmarksSupport().then(supported => { - if (supported) { - _converse.bookmarks = new _converse.Bookmarks(); - _converse.bookmarksview = new _converse.BookmarksView({ - 'model': _converse.bookmarks - }); + bookmarkview.render(); + this.showOrHide(); + }, - _converse.bookmarks.fetchBookmarks().catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)).then(() => _converse.emit('bookmarksInitialized')); - } else { - _converse.emit('bookmarksInitialized'); + showOrHide(item) { + if (_converse.hide_open_bookmarks) { + const bookmarks = this.model.filter(bookmark => !_converse.chatboxes.get(bookmark.get('jid'))); + + if (!bookmarks.length) { + u.hideElement(this.el); + return; } - }); - }; + } - u.onMultipleEvents([{ - 'object': _converse, - 'event': 'chatBoxesFetched' - }, { - 'object': _converse, - 'event': 'roomsPanelRendered' - }], initBookmarks); + if (this.model.models.length) { + u.showElement(this.el); + } + }, - _converse.on('clearSession', () => { - if (!_.isUndefined(_converse.bookmarks)) { - _converse.bookmarks.browserStorage._clear(); + toggleBookmarksList(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } - window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag); + const icon_el = ev.target.querySelector('.fa'); + + if (u.hasClass('fa-caret-down', icon_el)) { + u.slideIn(this.el.querySelector('.bookmarks')); + this.list_model.save({ + 'toggle-state': _converse.CLOSED + }); + icon_el.classList.remove("fa-caret-down"); + icon_el.classList.add("fa-caret-right"); + } else { + icon_el.classList.remove("fa-caret-right"); + icon_el.classList.add("fa-caret-down"); + u.slideOut(this.el.querySelector('.bookmarks')); + this.list_model.save({ + 'toggle-state': _converse.OPENED + }); + } + } + + }); + + _converse.checkBookmarksSupport = function () { + return new Promise((resolve, reject) => { + Promise.all([_converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid), _converse.api.disco.supports(Strophe.NS.PUBSUB + '#publish-options', _converse.bare_jid)]).then(args => { + resolve(args[0] && (args[1].length || _converse.allow_public_bookmarks)); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }; + + const initBookmarks = function initBookmarks() { + if (!_converse.allow_bookmarks) { + return; + } + + _converse.checkBookmarksSupport().then(supported => { + if (supported) { + _converse.bookmarks = new _converse.Bookmarks(); + _converse.bookmarksview = new _converse.BookmarksView({ + 'model': _converse.bookmarks + }); + + _converse.bookmarks.fetchBookmarks().catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)).then(() => _converse.emit('bookmarksInitialized')); + } else { + _converse.emit('bookmarksInitialized'); } }); + }; - _converse.on('reconnected', initBookmarks); + u.onMultipleEvents([{ + 'object': _converse, + 'event': 'chatBoxesFetched' + }, { + 'object': _converse, + 'event': 'roomsPanelRendered' + }], initBookmarks); - _converse.on('connected', () => { - // Add a handler for bookmarks pushed from other connected clients - // (from the same user obviously) - _converse.connection.addHandler(message => { - if (sizzle('event[xmlns="' + Strophe.NS.PUBSUB + '#event"] items[node="storage:bookmarks"]', message).length) { - _converse.api.waitUntil('bookmarksInitialized').then(() => _converse.bookmarks.createBookmarksFromStanza(message)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - }, null, 'message', 'headline', null, _converse.bare_jid); - }); - } + _converse.on('clearSession', () => { + if (!_.isUndefined(_converse.bookmarks)) { + _converse.bookmarks.browserStorage._clear(); + + window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag); + } + }); + + _converse.on('reconnected', initBookmarks); + + _converse.on('connected', () => { + // Add a handler for bookmarks pushed from other connected clients + // (from the same user obviously) + _converse.connection.addHandler(message => { + if (sizzle('event[xmlns="' + Strophe.NS.PUBSUB + '#event"] items[node="storage:bookmarks"]', message).length) { + _converse.api.waitUntil('bookmarksInitialized').then(() => _converse.bookmarks.createBookmarksFromStanza(message)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + }, null, 'message', 'headline', null, _converse.bare_jid); + }); + } - }); }); /***/ }), @@ -58399,75 +58407,72 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!******************************!*\ !*** ./src/converse-caps.js ***! \******************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js // http://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - $build = _converse$env.$build, - _ = _converse$env._, - b64_sha1 = _converse$env.b64_sha1; - Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps"); - function propertySort(array, property) { - return array.sort((a, b) => { - return a[property] > b[property] ? -1 : 1; - }); - } +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Strophe = _converse$env.Strophe, + $build = _converse$env.$build, + _ = _converse$env._, + b64_sha1 = _converse$env.b64_sha1; +Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps"); - function generateVerificationString(_converse) { - const identities = _converse.api.disco.own.identities.get(), - features = _converse.api.disco.own.features.get(); - - if (identities.length > 1) { - propertySort(identities, "category"); - propertySort(identities, "type"); - propertySort(identities, "lang"); - } - - let S = _.reduce(identities, (result, id) => `${result}${id.category}/${id.type}/${_.get(id, 'lang', '')}/${id.name}<`, ""); - - features.sort(); - S = _.reduce(features, (result, feature) => `${result}${feature}<`, S); - return b64_sha1(S); - } - - function createCapsNode(_converse) { - return $build("c", { - 'xmlns': Strophe.NS.CAPS, - 'hash': "sha-1", - 'node': "https://conversejs.org", - 'ver': generateVerificationString(_converse) - }).nodeTree; - } - - converse.plugins.add('converse-caps', { - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - XMPPStatus: { - constructPresence() { - const presence = this.__super__.constructPresence.apply(this, arguments); - - presence.root().cnode(createCapsNode(this.__super__._converse)); - return presence; - } - - } - } +function propertySort(array, property) { + return array.sort((a, b) => { + return a[property] > b[property] ? -1 : 1; }); +} + +function generateVerificationString(_converse) { + const identities = _converse.api.disco.own.identities.get(), + features = _converse.api.disco.own.features.get(); + + if (identities.length > 1) { + propertySort(identities, "category"); + propertySort(identities, "type"); + propertySort(identities, "lang"); + } + + let S = _.reduce(identities, (result, id) => `${result}${id.category}/${id.type}/${_.get(id, 'lang', '')}/${id.name}<`, ""); + + features.sort(); + S = _.reduce(features, (result, feature) => `${result}${feature}<`, S); + return b64_sha1(S); +} + +function createCapsNode(_converse) { + return $build("c", { + 'xmlns': Strophe.NS.CAPS, + 'hash': "sha-1", + 'node': "https://conversejs.org", + 'ver': generateVerificationString(_converse) + }).nodeTree; +} + +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-caps', { + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + XMPPStatus: { + constructPresence() { + const presence = this.__super__.constructPresence.apply(this, arguments); + + presence.root().cnode(createCapsNode(this.__super__._converse)); + return presence; + } + + } + } }); /***/ }), @@ -58476,193 +58481,198 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**************************************!*\ !*** ./src/converse-chatboxviews.js ***! \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_chatboxes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-chatboxes */ "./src/headless/converse-chatboxes.js"); +/* harmony import */ var backbone_nativeview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! backbone.nativeview */ "./node_modules/backbone.nativeview/backbone.nativeview.js"); +/* harmony import */ var backbone_nativeview__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(backbone_nativeview__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var backbone_overview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js"); +/* harmony import */ var backbone_overview__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(backbone_overview__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/chatboxes.html */ "./src/templates/chatboxes.html"); +/* harmony import */ var templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4__); +// Converse.js // http://conversejs.org // // Copyright (c) 2012-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/chatboxes.html */ "./src/templates/chatboxes.html"), __webpack_require__(/*! @converse/headless/converse-chatboxes */ "./src/headless/converse-chatboxes.js"), __webpack_require__(/*! backbone.nativeview */ "./node_modules/backbone.nativeview/backbone.nativeview.js"), __webpack_require__(/*! backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_chatboxes) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - _ = _converse$env._; - const AvatarMixin = { - renderAvatar(el) { - el = el || this.el; - const canvas_el = el.querySelector('canvas'); - if (_.isNull(canvas_el)) { - return; - } - const image_type = this.model.vcard.get('image_type'), - image = this.model.vcard.get('image'), - img_src = "data:" + image_type + ";base64," + image, - img = new Image(); - return new Promise((resolve, reject) => { - img.onload = () => { - const ctx = canvas_el.getContext('2d'), - ratio = img.width / img.height; - ctx.clearRect(0, 0, canvas_el.width, canvas_el.height); - if (ratio < 1) { - const scaled_img_with = canvas_el.width * ratio, - x = Math.floor((canvas_el.width - scaled_img_with) / 2); - ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height); - } else { - ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio); - } - resolve(); - }; +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].env, + Backbone = _converse$env.Backbone, + _ = _converse$env._; +const AvatarMixin = { + renderAvatar(el) { + el = el || this.el; + const canvas_el = el.querySelector('canvas'); - img.src = img_src; - }); + if (_.isNull(canvas_el)) { + return; } - }; - converse.plugins.add('converse-chatboxviews', { - dependencies: ["converse-chatboxes"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - initStatus: function initStatus(reconnecting) { - const _converse = this.__super__._converse; + const image_type = this.model.vcard.get('image_type'), + image = this.model.vcard.get('image'), + img_src = "data:" + image_type + ";base64," + image, + img = new Image(); + return new Promise((resolve, reject) => { + img.onload = () => { + const ctx = canvas_el.getContext('2d'), + ratio = img.width / img.height; + ctx.clearRect(0, 0, canvas_el.width, canvas_el.height); - if (!reconnecting) { - _converse.chatboxviews.closeAllChatBoxes(); + if (ratio < 1) { + const scaled_img_with = canvas_el.width * ratio, + x = Math.floor((canvas_el.width - scaled_img_with) / 2); + ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height); + } else { + ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio); } - return this.__super__.initStatus.apply(this, arguments); + resolve(); + }; + + img.src = img_src; + }); + } + +}; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins.add('converse-chatboxviews', { + dependencies: ["converse-chatboxes"], + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + initStatus: function initStatus(reconnecting) { + const _converse = this.__super__._converse; + + if (!reconnecting) { + _converse.chatboxviews.closeAllChatBoxes(); } - }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; + return this.__super__.initStatus.apply(this, arguments); + } + }, - _converse.api.promises.add(['chatBoxViewsInitialized']); + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; - _converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin); - _converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin); - _converse.ChatBoxViews = Backbone.Overview.extend({ - _ensureElement() { - /* Override method from backbone.js - * If the #conversejs element doesn't exist, create it. - */ - if (!this.el) { - let el = _converse.root.querySelector('#conversejs'); + _converse.api.promises.add(['chatBoxViewsInitialized']); - if (_.isNull(el)) { - el = document.createElement('div'); - el.setAttribute('id', 'conversejs'); + _converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin); + _converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin); + _converse.ChatBoxViews = Backbone.Overview.extend({ + _ensureElement() { + /* Override method from backbone.js + * If the #conversejs element doesn't exist, create it. + */ + if (!this.el) { + let el = _converse.root.querySelector('#conversejs'); - const body = _converse.root.querySelector('body'); + if (_.isNull(el)) { + el = document.createElement('div'); + el.setAttribute('id', 'conversejs'); - if (body) { - body.appendChild(el); - } else { - // Perhaps inside a web component? - _converse.root.appendChild(el); - } + const body = _converse.root.querySelector('body'); + + if (body) { + body.appendChild(el); + } else { + // Perhaps inside a web component? + _converse.root.appendChild(el); } - - el.innerHTML = ''; - this.setElement(el, false); - } else { - this.setElement(_.result(this, 'el'), false); - } - }, - - initialize() { - this.model.on("destroy", this.removeChat, this); - this.el.classList.add(`converse-${_converse.view_mode}`); - this.render(); - }, - - render() { - try { - this.el.innerHTML = tpl_chatboxes(); - } catch (e) { - this._ensureElement(); - - this.el.innerHTML = tpl_chatboxes(); } - this.row_el = this.el.querySelector('.row'); - }, + el.innerHTML = ''; + this.setElement(el, false); + } else { + this.setElement(_.result(this, 'el'), false); + } + }, - insertRowColumn(el) { - /* Add a new DOM element (likely a chat box) into the - * the row managed by this overview. - */ - this.row_el.insertAdjacentElement('afterBegin', el); - }, + initialize() { + this.model.on("destroy", this.removeChat, this); + this.el.classList.add(`converse-${_converse.view_mode}`); + this.render(); + }, - removeChat(item) { - this.remove(item.get('id')); - }, + render() { + try { + this.el.innerHTML = templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4___default()(); + } catch (e) { + this._ensureElement(); - closeAllChatBoxes() { - /* This method gets overridden in src/converse-controlbox.js if - * the controlbox plugin is active. - */ - this.each(function (view) { - view.close(); - }); - return this; - }, - - chatBoxMayBeShown(chatbox) { - return this.model.chatBoxMayBeShown(chatbox); + this.el.innerHTML = templates_chatboxes_html__WEBPACK_IMPORTED_MODULE_4___default()(); } - }); - /************************ BEGIN Event Handlers ************************/ + this.row_el = this.el.querySelector('.row'); + }, - _converse.api.waitUntil('rosterContactsFetched').then(() => { - _converse.roster.on('add', contact => { - /* When a new contact is added, check if we already have a - * chatbox open for it, and if so attach it to the chatbox. - */ - const chatbox = _converse.chatboxes.findWhere({ - 'jid': contact.get('jid') - }); + insertRowColumn(el) { + /* Add a new DOM element (likely a chat box) into the + * the row managed by this overview. + */ + this.row_el.insertAdjacentElement('afterBegin', el); + }, - if (chatbox) { - chatbox.addRelatedContact(contact); - } + removeChat(item) { + this.remove(item.get('id')); + }, + + closeAllChatBoxes() { + /* This method gets overridden in src/converse-controlbox.js if + * the controlbox plugin is active. + */ + this.each(function (view) { + view.close(); }); - }); + return this; + }, - _converse.api.listen.on('chatBoxesInitialized', () => { - _converse.chatboxviews = new _converse.ChatBoxViews({ - 'model': _converse.chatboxes + chatBoxMayBeShown(chatbox) { + return this.model.chatBoxMayBeShown(chatbox); + } + + }); + /************************ BEGIN Event Handlers ************************/ + + _converse.api.waitUntil('rosterContactsFetched').then(() => { + _converse.roster.on('add', contact => { + /* When a new contact is added, check if we already have a + * chatbox open for it, and if so attach it to the chatbox. + */ + const chatbox = _converse.chatboxes.findWhere({ + 'jid': contact.get('jid') }); - _converse.emit('chatBoxViewsInitialized'); + if (chatbox) { + chatbox.addRelatedContact(contact); + } + }); + }); + + _converse.api.listen.on('chatBoxesInitialized', () => { + _converse.chatboxviews = new _converse.ChatBoxViews({ + 'model': _converse.chatboxes }); - _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes()); - /************************ END Event Handlers ************************/ + _converse.emit('chatBoxViewsInitialized'); + }); - } + _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes()); + /************************ END Event Handlers ************************/ + + } - }); - return converse; }); /***/ }), @@ -58671,1383 +58681,1435 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**********************************!*\ !*** ./src/converse-chatview.js ***! \**********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_chatboxviews__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-chatboxviews */ "./src/converse-chatboxviews.js"); +/* harmony import */ var converse_message_view__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! converse-message-view */ "./src/converse-message-view.js"); +/* harmony import */ var converse_modal__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"); +/* harmony import */ var twemoji__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! twemoji */ "./node_modules/twemoji/2/esm.js"); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(bootstrap__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_chatbox_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/chatbox.html */ "./src/templates/chatbox.html"); +/* harmony import */ var templates_chatbox_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_chatbox_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_chatbox_head_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/chatbox_head.html */ "./src/templates/chatbox_head.html"); +/* harmony import */ var templates_chatbox_head_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_chatbox_head_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var templates_chatbox_message_form_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/chatbox_message_form.html */ "./src/templates/chatbox_message_form.html"); +/* harmony import */ var templates_chatbox_message_form_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_chatbox_message_form_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var templates_emojis_html__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! templates/emojis.html */ "./src/templates/emojis.html"); +/* harmony import */ var templates_emojis_html__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(templates_emojis_html__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var templates_error_message_html__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! templates/error_message.html */ "./src/templates/error_message.html"); +/* harmony import */ var templates_error_message_html__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(templates_error_message_html__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var templates_help_message_html__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! templates/help_message.html */ "./src/templates/help_message.html"); +/* harmony import */ var templates_help_message_html__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(templates_help_message_html__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"); +/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(templates_info_html__WEBPACK_IMPORTED_MODULE_12__); +/* harmony import */ var templates_new_day_html__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! templates/new_day.html */ "./src/templates/new_day.html"); +/* harmony import */ var templates_new_day_html__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(templates_new_day_html__WEBPACK_IMPORTED_MODULE_13__); +/* harmony import */ var templates_spinner_html__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"); +/* harmony import */ var templates_spinner_html__WEBPACK_IMPORTED_MODULE_14___default = /*#__PURE__*/__webpack_require__.n(templates_spinner_html__WEBPACK_IMPORTED_MODULE_14__); +/* harmony import */ var templates_spoiler_button_html__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! templates/spoiler_button.html */ "./src/templates/spoiler_button.html"); +/* harmony import */ var templates_spoiler_button_html__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(templates_spoiler_button_html__WEBPACK_IMPORTED_MODULE_15__); +/* harmony import */ var templates_status_message_html__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! templates/status_message.html */ "./src/templates/status_message.html"); +/* harmony import */ var templates_status_message_html__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(templates_status_message_html__WEBPACK_IMPORTED_MODULE_16__); +/* harmony import */ var templates_toolbar_html__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! templates/toolbar.html */ "./src/templates/toolbar.html"); +/* harmony import */ var templates_toolbar_html__WEBPACK_IMPORTED_MODULE_17___default = /*#__PURE__*/__webpack_require__.n(templates_toolbar_html__WEBPACK_IMPORTED_MODULE_17__); +/* harmony import */ var templates_toolbar_fileupload_html__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! templates/toolbar_fileupload.html */ "./src/templates/toolbar_fileupload.html"); +/* harmony import */ var templates_toolbar_fileupload_html__WEBPACK_IMPORTED_MODULE_18___default = /*#__PURE__*/__webpack_require__.n(templates_toolbar_fileupload_html__WEBPACK_IMPORTED_MODULE_18__); +/* harmony import */ var templates_user_details_modal_html__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! templates/user_details_modal.html */ "./src/templates/user_details_modal.html"); +/* harmony import */ var templates_user_details_modal_html__WEBPACK_IMPORTED_MODULE_19___default = /*#__PURE__*/__webpack_require__.n(templates_user_details_modal_html__WEBPACK_IMPORTED_MODULE_19__); +/* harmony import */ var utils_emoji__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! utils/emoji */ "./src/headless/utils/emoji.js"); +/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"); +/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_21___default = /*#__PURE__*/__webpack_require__.n(xss__WEBPACK_IMPORTED_MODULE_21__); +// Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! utils/emoji */ "./src/headless/utils/emoji.js"), __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! twemoji */ "./node_modules/twemoji/2/esm.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! templates/chatbox.html */ "./src/templates/chatbox.html"), __webpack_require__(/*! templates/chatbox_head.html */ "./src/templates/chatbox_head.html"), __webpack_require__(/*! templates/chatbox_message_form.html */ "./src/templates/chatbox_message_form.html"), __webpack_require__(/*! templates/emojis.html */ "./src/templates/emojis.html"), __webpack_require__(/*! templates/error_message.html */ "./src/templates/error_message.html"), __webpack_require__(/*! templates/help_message.html */ "./src/templates/help_message.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/new_day.html */ "./src/templates/new_day.html"), __webpack_require__(/*! templates/user_details_modal.html */ "./src/templates/user_details_modal.html"), __webpack_require__(/*! templates/toolbar_fileupload.html */ "./src/templates/toolbar_fileupload.html"), __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"), __webpack_require__(/*! templates/spoiler_button.html */ "./src/templates/spoiler_button.html"), __webpack_require__(/*! templates/status_message.html */ "./src/templates/status_message.html"), __webpack_require__(/*! templates/toolbar.html */ "./src/templates/toolbar.html"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"), __webpack_require__(/*! converse-chatboxviews */ "./src/converse-chatboxviews.js"), __webpack_require__(/*! converse-message-view */ "./src/converse-message-view.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (u, converse, bootstrap, twemoji, xss, tpl_chatbox, tpl_chatbox_head, tpl_chatbox_message_form, tpl_emojis, tpl_error_message, tpl_help_message, tpl_info, tpl_new_day, tpl_user_details_modal, tpl_toolbar_fileupload, tpl_spinner, tpl_spoiler_button, tpl_status_message, tpl_toolbar) { - "use strict"; - const _converse$env = converse.env, - $msg = _converse$env.$msg, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - _ = _converse$env._, - b64_sha1 = _converse$env.b64_sha1, - f = _converse$env.f, - sizzle = _converse$env.sizzle, - moment = _converse$env.moment; - converse.plugins.add('converse-chatview', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. + + + + + + + + + + + + + + + + + + + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].env, + $msg = _converse$env.$msg, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + _ = _converse$env._, + b64_sha1 = _converse$env.b64_sha1, + f = _converse$env.f, + sizzle = _converse$env.sizzle, + moment = _converse$env.moment; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins.add('converse-chatview', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"], + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. */ - dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"], + const _converse = this._converse, + __ = _converse.__; - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; + _converse.api.settings.update({ + 'emoji_image_path': twemoji__WEBPACK_IMPORTED_MODULE_3__["default"].base, + 'show_send_button': false, + 'show_toolbar': true, + 'time_format': 'HH:mm', + 'use_system_emojis': true, + 'visible_toolbar_buttons': { + 'call': false, + 'clear': true, + 'emoji': true, + 'spoiler': true + } + }); - _converse.api.settings.update({ - 'emoji_image_path': twemoji.default.base, - 'show_send_button': false, - 'show_toolbar': true, - 'time_format': 'HH:mm', - 'use_system_emojis': true, - 'visible_toolbar_buttons': { - 'call': false, - 'clear': true, - 'emoji': true, - 'spoiler': true + twemoji__WEBPACK_IMPORTED_MODULE_3__["default"].base = _converse.emoji_image_path; + + function onWindowStateChanged(data) { + if (_converse.chatboxviews) { + _converse.chatboxviews.each(view => { + if (view.model.get('id') !== 'controlbox') { + view.onWindowStateChanged(data.state); + } + }); + } + } + + _converse.api.listen.on('windowStateChanged', onWindowStateChanged); + + _converse.EmojiPicker = Backbone.Model.extend({ + defaults: { + 'current_category': 'people', + 'current_skintone': '', + 'scroll_position': 0 + } + }); + _converse.EmojiPickerView = Backbone.VDOMView.extend({ + className: 'emoji-picker-container', + events: { + 'click .emoji-category-picker li.emoji-category': 'chooseCategory', + 'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone' + }, + + initialize() { + this.model.on('change:current_skintone', this.render, this); + this.model.on('change:current_category', this.render, this); + }, + + toHTML() { + return templates_emojis_html__WEBPACK_IMPORTED_MODULE_9___default()(_.extend(this.model.toJSON(), { + '_': _, + 'transform': utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getEmojiRenderer(_converse), + 'emojis_by_category': utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getEmojisByCategory(_converse), + 'toned_emojis': utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getTonedEmojis(_converse), + 'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'], + 'shouldBeHidden': this.shouldBeHidden + })); + }, + + shouldBeHidden(shortname, current_skintone, toned_emojis) { + /* Helper method for the template which decides whether an + * emoji should be hidden, based on which skin tone is + * currently being applied. + */ + if (_.includes(shortname, '_tone')) { + if (!current_skintone || !_.includes(shortname, current_skintone)) { + return true; + } + } else { + if (current_skintone && _.includes(toned_emojis, shortname)) { + return true; + } } - }); - twemoji.default.base = _converse.emoji_image_path; + return false; + }, - function onWindowStateChanged(data) { - if (_converse.chatboxviews) { - _converse.chatboxviews.each(view => { - if (view.model.get('id') !== 'controlbox') { - view.onWindowStateChanged(data.state); - } + chooseSkinTone(ev) { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; + const skintone = target.getAttribute("data-skintone").trim(); + + if (this.model.get('current_skintone') === skintone) { + this.model.save({ + 'current_skintone': '' + }); + } else { + this.model.save({ + 'current_skintone': skintone + }); + } + }, + + chooseCategory(ev) { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; + const category = target.getAttribute("data-category").trim(); + this.model.save({ + 'current_category': category, + 'scroll_position': 0 + }); + } + + }); + _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({ + initialize() { + this.model.on('change:status', this.onStatusMessageChanged, this); + this.model.vcard.on('change', this.render, this); + }, + + render() { + this.el.innerHTML = templates_chatbox_head_html__WEBPACK_IMPORTED_MODULE_7___default()(_.extend(this.model.vcard.toJSON(), this.model.toJSON(), { + '_converse': _converse, + 'info_close': __('Close this chat box') + })); + this.renderAvatar(); + return this; + }, + + onStatusMessageChanged(item) { + this.render(); + + _converse.emit('contactStatusMessageChanged', { + 'contact': item.attributes, + 'message': item.get('status') + }); + } + + }); + _converse.UserDetailsModal = _converse.BootstrapModal.extend({ + events: { + 'click button.remove-contact': 'removeContact', + 'click button.refresh-contact': 'refreshContact', + 'click .fingerprint-trust .btn input': 'toggleDeviceTrust' + }, + + initialize() { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + + this.model.on('contactAdded', this.registerContactEventHandlers, this); + this.model.on('change', this.render, this); + this.registerContactEventHandlers(); + + _converse.emit('userDetailsModalInitialized', this.model); + }, + + toHTML() { + return templates_user_details_modal_html__WEBPACK_IMPORTED_MODULE_19___default()(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { + '_': _, + '__': __, + 'view': this, + '_converse': _converse, + 'allow_contact_removal': _converse.allow_contact_removal, + 'display_name': this.model.getDisplayName(), + 'is_roster_contact': !_.isUndefined(this.model.contact), + 'utils': utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"] + })); + }, + + registerContactEventHandlers() { + if (!_.isUndefined(this.model.contact)) { + this.model.contact.on('change', this.render, this); + this.model.contact.vcard.on('change', this.render, this); + this.model.contact.on('destroy', () => { + delete this.model.contact; + this.render(); + }); + } + }, + + refreshContact(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + const refresh_icon = this.el.querySelector('.fa-refresh'); + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].addClass('fa-spin', refresh_icon); + + _converse.api.vcard.update(this.model.contact.vcard, true).then(() => utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].removeClass('fa-spin', refresh_icon)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + removeContact(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + if (!_converse.allow_contact_removal) { + return; + } + + const result = confirm(__("Are you sure you want to remove this contact?")); + + if (result === true) { + this.modal.hide(); + this.model.contact.removeFromRoster(iq => { + this.model.contact.destroy(); + }, err => { + _converse.log(err, Strophe.LogLevel.ERROR); + + _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__('Sorry, there was an error while trying to remove %1$s as a contact.', this.model.contact.getDisplayName())]); }); } } - _converse.api.listen.on('windowStateChanged', onWindowStateChanged); + }); + _converse.ChatBoxView = Backbone.NativeView.extend({ + length: 200, + className: 'chatbox hidden', + is_chatroom: false, + // Leaky abstraction from MUC + events: { + 'change input.fileupload': 'onFileSelection', + 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', + 'click .chatbox-navback': 'showControlBox', + 'click .close-chatbox-button': 'close', + 'click .new-msgs-indicator': 'viewUnreadMessages', + 'click .send-button': 'onFormSubmitted', + 'click .show-user-details-modal': 'showUserDetailsModal', + 'click .spoiler-toggle': 'toggleSpoilerMessage', + 'click .toggle-call': 'toggleCall', + 'click .toggle-clear': 'clearMessages', + 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage', + 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', + 'click .toggle-smiley': 'toggleEmojiMenu', + 'click .upload-file': 'toggleFileUpload', + 'input .chat-textarea': 'inputChanged', + 'keydown .chat-textarea': 'keyPressed' + }, - _converse.EmojiPicker = Backbone.Model.extend({ - defaults: { - 'current_category': 'people', - 'current_skintone': '', - 'scroll_position': 0 + initialize() { + this.initDebounced(); + this.model.messages.on('add', this.onMessageAdded, this); + this.model.messages.on('rendered', this.scrollDown, this); + this.model.on('show', this.show, this); + this.model.on('destroy', this.remove, this); + this.model.presence.on('change:show', this.onPresenceChanged, this); + this.model.on('showHelpMessages', this.showHelpMessages, this); + this.render(); + this.fetchMessages(); + + _converse.emit('chatBoxOpened', this); + + _converse.emit('chatBoxInitialized', this); + }, + + initDebounced() { + this.scrollDown = _.debounce(this._scrollDown, 250); + this.markScrolled = _.debounce(this._markScrolled, 100); + this.show = _.debounce(this._show, 250, { + 'leading': true + }); + }, + + render() { + // XXX: Is this still needed? + this.el.setAttribute('id', this.model.get('box_id')); + this.el.innerHTML = templates_chatbox_html__WEBPACK_IMPORTED_MODULE_6___default()(_.extend(this.model.toJSON(), { + 'unread_msgs': __('You have unread messages') + })); + this.content = this.el.querySelector('.chat-content'); + this.renderMessageForm(); + this.insertHeading(); + return this; + }, + + renderToolbar(toolbar, options) { + if (!_converse.show_toolbar) { + return this; } - }); - _converse.EmojiPickerView = Backbone.VDOMView.extend({ - className: 'emoji-picker-container', - events: { - 'click .emoji-category-picker li.emoji-category': 'chooseCategory', - 'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone' - }, - initialize() { - this.model.on('change:current_skintone', this.render, this); - this.model.on('change:current_category', this.render, this); - }, + toolbar = toolbar || templates_toolbar_html__WEBPACK_IMPORTED_MODULE_17___default.a; + options = _.assign(this.model.toJSON(), this.getToolbarOptions(options || {})); + this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options); + this.addSpoilerButton(options); + this.addFileUploadButton(); - toHTML() { - return tpl_emojis(_.extend(this.model.toJSON(), { - '_': _, - 'transform': u.getEmojiRenderer(_converse), - 'emojis_by_category': u.getEmojisByCategory(_converse), - 'toned_emojis': u.getTonedEmojis(_converse), - 'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'], - 'shouldBeHidden': this.shouldBeHidden - })); - }, + _converse.emit('renderToolbar', this); - shouldBeHidden(shortname, current_skintone, toned_emojis) { - /* Helper method for the template which decides whether an - * emoji should be hidden, based on which skin tone is - * currently being applied. - */ - if (_.includes(shortname, '_tone')) { - if (!current_skintone || !_.includes(shortname, current_skintone)) { - return true; - } - } else { - if (current_skintone && _.includes(toned_emojis, shortname)) { - return true; - } - } + return this; + }, - return false; - }, + renderMessageForm() { + let placeholder; - chooseSkinTone(ev) { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; - const skintone = target.getAttribute("data-skintone").trim(); + if (this.model.get('composing_spoiler')) { + placeholder = __('Hidden message'); + } else { + placeholder = __('Message'); + } - if (this.model.get('current_skintone') === skintone) { - this.model.save({ - 'current_skintone': '' - }); - } else { - this.model.save({ - 'current_skintone': skintone - }); - } - }, + const form_container = this.el.querySelector('.message-form-container'); + form_container.innerHTML = templates_chatbox_message_form_html__WEBPACK_IMPORTED_MODULE_8___default()(_.extend(this.model.toJSON(), { + 'hint_value': _.get(this.el.querySelector('.spoiler-hint'), 'value'), + 'label_message': placeholder, + 'label_send': __('Send'), + 'label_spoiler_hint': __('Optional hint'), + 'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'), + 'show_send_button': _converse.show_send_button, + 'show_toolbar': _converse.show_toolbar, + 'unread_msgs': __('You have unread messages') + })); + this.renderToolbar(); + }, - chooseCategory(ev) { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; - const category = target.getAttribute("data-category").trim(); - this.model.save({ - 'current_category': category, - 'scroll_position': 0 + showControlBox() { + // Used in mobile view, to navigate back to the controlbox + const view = _converse.chatboxviews.get('controlbox'); + + view.show(); + this.hide(); + }, + + showUserDetailsModal(ev) { + ev.preventDefault(); + + if (_.isUndefined(this.user_details_modal)) { + this.user_details_modal = new _converse.UserDetailsModal({ + model: this.model }); } - }); - _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({ - initialize() { - this.model.on('change:status', this.onStatusMessageChanged, this); - this.model.vcard.on('change', this.render, this); - }, + this.user_details_modal.show(ev); + }, - render() { - this.el.innerHTML = tpl_chatbox_head(_.extend(this.model.vcard.toJSON(), this.model.toJSON(), { - '_converse': _converse, - 'info_close': __('Close this chat box') - })); - this.renderAvatar(); - return this; - }, + toggleFileUpload(ev) { + this.el.querySelector('input.fileupload').click(); + }, - onStatusMessageChanged(item) { - this.render(); + onFileSelection(evt) { + this.model.sendFiles(evt.target.files); + }, - _converse.emit('contactStatusMessageChanged', { - 'contact': item.attributes, - 'message': item.get('status') - }); + addFileUploadButton(options) { + _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(result => { + if (result.length) { + this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', templates_toolbar_fileupload_html__WEBPACK_IMPORTED_MODULE_18___default()({ + 'tooltip_upload_file': __('Choose a file to send') + })); + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + addSpoilerButton(options) { + /* Asynchronously adds a button for writing spoiler + * messages, based on whether the contact's client supports + * it. + */ + if (!options.show_spoiler_button || this.model.get('type') === 'chatroom') { + return; } - }); - _converse.UserDetailsModal = _converse.BootstrapModal.extend({ - events: { - 'click button.remove-contact': 'removeContact', - 'click button.refresh-contact': 'refreshContact', - 'click .fingerprint-trust .btn input': 'toggleDeviceTrust' - }, + const contact_jid = this.model.get('jid'); + const resources = this.model.presence.get('resources'); - initialize() { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - - this.model.on('contactAdded', this.registerContactEventHandlers, this); - this.model.on('change', this.render, this); - this.registerContactEventHandlers(); - - _converse.emit('userDetailsModalInitialized', this.model); - }, - - toHTML() { - return tpl_user_details_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { - '_': _, - '__': __, - 'view': this, - '_converse': _converse, - 'allow_contact_removal': _converse.allow_contact_removal, - 'display_name': this.model.getDisplayName(), - 'is_roster_contact': !_.isUndefined(this.model.contact), - 'utils': u - })); - }, - - registerContactEventHandlers() { - if (!_.isUndefined(this.model.contact)) { - this.model.contact.on('change', this.render, this); - this.model.contact.vcard.on('change', this.render, this); - this.model.contact.on('destroy', () => { - delete this.model.contact; - this.render(); - }); - } - }, - - refreshContact(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const refresh_icon = this.el.querySelector('.fa-refresh'); - u.addClass('fa-spin', refresh_icon); - - _converse.api.vcard.update(this.model.contact.vcard, true).then(() => u.removeClass('fa-spin', refresh_icon)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - removeContact(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - if (!_converse.allow_contact_removal) { - return; - } - - const result = confirm(__("Are you sure you want to remove this contact?")); - - if (result === true) { - this.modal.hide(); - this.model.contact.removeFromRoster(iq => { - this.model.contact.destroy(); - }, err => { - _converse.log(err, Strophe.LogLevel.ERROR); - - _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__('Sorry, there was an error while trying to remove %1$s as a contact.', this.model.contact.getDisplayName())]); - }); - } + if (_.isEmpty(resources)) { + return; } - }); - _converse.ChatBoxView = Backbone.NativeView.extend({ - length: 200, - className: 'chatbox hidden', - is_chatroom: false, - // Leaky abstraction from MUC - events: { - 'change input.fileupload': 'onFileSelection', - 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', - 'click .chatbox-navback': 'showControlBox', - 'click .close-chatbox-button': 'close', - 'click .new-msgs-indicator': 'viewUnreadMessages', - 'click .send-button': 'onFormSubmitted', - 'click .show-user-details-modal': 'showUserDetailsModal', - 'click .spoiler-toggle': 'toggleSpoilerMessage', - 'click .toggle-call': 'toggleCall', - 'click .toggle-clear': 'clearMessages', - 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage', - 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', - 'click .toggle-smiley': 'toggleEmojiMenu', - 'click .upload-file': 'toggleFileUpload', - 'input .chat-textarea': 'inputChanged', - 'keydown .chat-textarea': 'keyPressed' - }, + Promise.all(_.map(_.keys(resources), resource => _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`))).then(results => { + if (_.filter(results, 'length').length) { + const html = templates_spoiler_button_html__WEBPACK_IMPORTED_MODULE_15___default()(this.model.toJSON()); - initialize() { - this.initDebounced(); - this.model.messages.on('add', this.onMessageAdded, this); - this.model.messages.on('rendered', this.scrollDown, this); - this.model.on('show', this.show, this); - this.model.on('destroy', this.remove, this); - this.model.presence.on('change:show', this.onPresenceChanged, this); - this.model.on('showHelpMessages', this.showHelpMessages, this); - this.render(); - this.fetchMessages(); - - _converse.emit('chatBoxOpened', this); - - _converse.emit('chatBoxInitialized', this); - }, - - initDebounced() { - this.scrollDown = _.debounce(this._scrollDown, 250); - this.markScrolled = _.debounce(this._markScrolled, 100); - this.show = _.debounce(this._show, 250, { - 'leading': true - }); - }, - - render() { - // XXX: Is this still needed? - this.el.setAttribute('id', this.model.get('box_id')); - this.el.innerHTML = tpl_chatbox(_.extend(this.model.toJSON(), { - 'unread_msgs': __('You have unread messages') - })); - this.content = this.el.querySelector('.chat-content'); - this.renderMessageForm(); - this.insertHeading(); - return this; - }, - - renderToolbar(toolbar, options) { - if (!_converse.show_toolbar) { - return this; - } - - toolbar = toolbar || tpl_toolbar; - options = _.assign(this.model.toJSON(), this.getToolbarOptions(options || {})); - this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options); - this.addSpoilerButton(options); - this.addFileUploadButton(); - - _converse.emit('renderToolbar', this); - - return this; - }, - - renderMessageForm() { - let placeholder; - - if (this.model.get('composing_spoiler')) { - placeholder = __('Hidden message'); - } else { - placeholder = __('Message'); - } - - const form_container = this.el.querySelector('.message-form-container'); - form_container.innerHTML = tpl_chatbox_message_form(_.extend(this.model.toJSON(), { - 'hint_value': _.get(this.el.querySelector('.spoiler-hint'), 'value'), - 'label_message': placeholder, - 'label_send': __('Send'), - 'label_spoiler_hint': __('Optional hint'), - 'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'), - 'show_send_button': _converse.show_send_button, - 'show_toolbar': _converse.show_toolbar, - 'unread_msgs': __('You have unread messages') - })); - this.renderToolbar(); - }, - - showControlBox() { - // Used in mobile view, to navigate back to the controlbox - const view = _converse.chatboxviews.get('controlbox'); - - view.show(); - this.hide(); - }, - - showUserDetailsModal(ev) { - ev.preventDefault(); - - if (_.isUndefined(this.user_details_modal)) { - this.user_details_modal = new _converse.UserDetailsModal({ - model: this.model - }); - } - - this.user_details_modal.show(ev); - }, - - toggleFileUpload(ev) { - this.el.querySelector('input.fileupload').click(); - }, - - onFileSelection(evt) { - this.model.sendFiles(evt.target.files); - }, - - addFileUploadButton(options) { - _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(result => { - if (result.length) { - this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', tpl_toolbar_fileupload({ - 'tooltip_upload_file': __('Choose a file to send') - })); - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - addSpoilerButton(options) { - /* Asynchronously adds a button for writing spoiler - * messages, based on whether the contact's client supports - * it. - */ - if (!options.show_spoiler_button || this.model.get('type') === 'chatroom') { - return; - } - - const contact_jid = this.model.get('jid'); - const resources = this.model.presence.get('resources'); - - if (_.isEmpty(resources)) { - return; - } - - Promise.all(_.map(_.keys(resources), resource => _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`))).then(results => { - if (_.filter(results, 'length').length) { - const html = tpl_spoiler_button(this.model.toJSON()); - - if (_converse.visible_toolbar_buttons.emoji) { - this.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html); - } else { - this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html); - } - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - insertHeading() { - this.heading = new _converse.ChatBoxHeading({ - 'model': this.model - }); - this.heading.render(); - this.heading.chatview = this; - - if (!_.isUndefined(this.model.contact)) { - this.model.contact.on('destroy', this.heading.render, this); - } - - const flyout = this.el.querySelector('.flyout'); - flyout.insertBefore(this.heading.el, flyout.querySelector('.chat-body')); - return this; - }, - - getToolbarOptions(options) { - let label_toggle_spoiler; - - if (this.model.get('composing_spoiler')) { - label_toggle_spoiler = __('Click to write as a normal (non-spoiler) message'); - } else { - label_toggle_spoiler = __('Click to write your message as a spoiler'); - } - - return _.extend(options || {}, { - 'label_clear': __('Clear all messages'), - 'tooltip_insert_smiley': __('Insert emojis'), - 'tooltip_start_call': __('Start a call'), - 'label_toggle_spoiler': label_toggle_spoiler, - 'show_call_button': _converse.visible_toolbar_buttons.call, - 'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler, - 'use_emoji': _converse.visible_toolbar_buttons.emoji - }); - }, - - afterMessagesFetched() { - this.insertIntoDOM(); - this.scrollDown(); - this.content.addEventListener('scroll', this.markScrolled.bind(this)); - - _converse.emit('afterMessagesFetched', this); - }, - - fetchMessages() { - this.model.messages.fetch({ - 'add': true, - 'success': this.afterMessagesFetched.bind(this), - 'error': this.afterMessagesFetched.bind(this) - }); - return this; - }, - - insertIntoDOM() { - /* This method gets overridden in src/converse-controlbox.js - * as well as src/converse-muc.js (if those plugins are - * enabled). - */ - _converse.chatboxviews.insertRowColumn(this.el); - - return this; - }, - - showChatEvent(message) { - const isodate = moment().format(); - this.content.insertAdjacentHTML('beforeend', tpl_info({ - 'extra_classes': 'chat-event', - 'message': message, - 'isodate': isodate - })); - this.insertDayIndicator(this.content.lastElementChild); - this.scrollDown(); - return isodate; - }, - - showErrorMessage(message) { - this.content.insertAdjacentHTML('beforeend', tpl_error_message({ - 'message': message, - 'isodate': moment().format() - })); - this.scrollDown(); - }, - - addSpinner(append = false) { - if (_.isNull(this.el.querySelector('.spinner'))) { - if (append) { - this.content.insertAdjacentHTML('beforeend', tpl_spinner()); - this.scrollDown(); + if (_converse.visible_toolbar_buttons.emoji) { + this.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html); } else { - this.content.insertAdjacentHTML('afterbegin', tpl_spinner()); + this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html); } } - }, + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, - clearSpinner() { - _.each(this.content.querySelectorAll('span.spinner'), el => el.parentNode.removeChild(el)); - }, + insertHeading() { + this.heading = new _converse.ChatBoxHeading({ + 'model': this.model + }); + this.heading.render(); + this.heading.chatview = this; - insertDayIndicator(next_msg_el) { - /* Inserts an indicator into the chat area, showing the - * day as given by the passed in date. - * - * The indicator is only inserted if necessary. - * - * Parameters: - * (HTMLElement) next_msg_el - The message element before - * which the day indicator element must be inserted. - * This element must have a "data-isodate" attribute - * which specifies its creation date. - */ - const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"), - prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'), - next_msg_date = next_msg_el.getAttribute('data-isodate'); + if (!_.isUndefined(this.model.contact)) { + this.model.contact.on('destroy', this.heading.render, this); + } - if (_.isNull(prev_msg_date) || moment(next_msg_date).isAfter(prev_msg_date, 'day')) { - const day_date = moment(next_msg_date).startOf('day'); - next_msg_el.insertAdjacentHTML('beforeBegin', tpl_new_day({ - 'isodate': day_date.format(), - 'datestring': day_date.format("dddd MMM Do YYYY") - })); - } - }, + const flyout = this.el.querySelector('.flyout'); + flyout.insertBefore(this.heading.el, flyout.querySelector('.chat-body')); + return this; + }, - getLastMessageDate(cutoff) { - /* Return the ISO8601 format date of the latest message. - * - * Parameters: - * (Object) cutoff: Moment Date cutoff date. The last - * message received cutoff this date will be returned. - */ - const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'), - oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null; + getToolbarOptions(options) { + let label_toggle_spoiler; - if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) { - return null; - } + if (this.model.get('composing_spoiler')) { + label_toggle_spoiler = __('Click to write as a normal (non-spoiler) message'); + } else { + label_toggle_spoiler = __('Click to write your message as a spoiler'); + } - const last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'), - most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null; + return _.extend(options || {}, { + 'label_clear': __('Clear all messages'), + 'tooltip_insert_smiley': __('Insert emojis'), + 'tooltip_start_call': __('Start a call'), + 'label_toggle_spoiler': label_toggle_spoiler, + 'show_call_button': _converse.visible_toolbar_buttons.call, + 'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler, + 'use_emoji': _converse.visible_toolbar_buttons.emoji + }); + }, - if (_.isNull(most_recent_date) || moment(most_recent_date).isBefore(cutoff)) { - return most_recent_date; - } - /* XXX: We avoid .chat-state-notification messages, since they are - * temporary and get removed once a new element is - * inserted into the chat area, so we don't query for - * them here, otherwise we get a null reference later - * upon element insertion. - */ + afterMessagesFetched() { + this.insertIntoDOM(); + this.scrollDown(); + this.content.addEventListener('scroll', this.markScrolled.bind(this)); + _converse.emit('afterMessagesFetched', this); + }, - const msg_dates = _.invokeMap(sizzle('.message:not(.chat-state-notification)', this.content), Element.prototype.getAttribute, 'data-isodate'); + fetchMessages() { + this.model.messages.fetch({ + 'add': true, + 'success': this.afterMessagesFetched.bind(this), + 'error': this.afterMessagesFetched.bind(this) + }); + return this; + }, - if (_.isObject(cutoff)) { - cutoff = cutoff.format(); - } + insertIntoDOM() { + /* This method gets overridden in src/converse-controlbox.js + * as well as src/converse-muc.js (if those plugins are + * enabled). + */ + _converse.chatboxviews.insertRowColumn(this.el); - msg_dates.push(cutoff); - msg_dates.sort(); - const idx = msg_dates.lastIndexOf(cutoff); + return this; + }, - if (idx === 0) { - return null; + showChatEvent(message) { + const isodate = moment().format(); + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_12___default()({ + 'extra_classes': 'chat-event', + 'message': message, + 'isodate': isodate + })); + this.insertDayIndicator(this.content.lastElementChild); + this.scrollDown(); + return isodate; + }, + + showErrorMessage(message) { + this.content.insertAdjacentHTML('beforeend', templates_error_message_html__WEBPACK_IMPORTED_MODULE_10___default()({ + 'message': message, + 'isodate': moment().format() + })); + this.scrollDown(); + }, + + addSpinner(append = false) { + if (_.isNull(this.el.querySelector('.spinner'))) { + if (append) { + this.content.insertAdjacentHTML('beforeend', templates_spinner_html__WEBPACK_IMPORTED_MODULE_14___default()()); + this.scrollDown(); } else { - return msg_dates[idx - 1]; + this.content.insertAdjacentHTML('afterbegin', templates_spinner_html__WEBPACK_IMPORTED_MODULE_14___default()()); } - }, + } + }, - setScrollPosition(message_el) { - /* Given a newly inserted message, determine whether we - * should keep the scrollbar in place (so as to not scroll - * up when using infinite scroll). - */ - if (this.model.get('scrolled')) { - const next_msg_el = u.getNextElement(message_el, ".chat-msg"); + clearSpinner() { + _.each(this.content.querySelectorAll('span.spinner'), el => el.parentNode.removeChild(el)); + }, - if (next_msg_el) { - // The currently received message is not new, there - // are newer messages after it. So let's see if we - // should maintain our current scroll position. - if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) { - const top_visible_message = this.model.get('top_visible_message') || next_msg_el; - this.model.set('top_visible_message', top_visible_message); - this.content.scrollTop = top_visible_message.offsetTop - 30; + insertDayIndicator(next_msg_el) { + /* Inserts an indicator into the chat area, showing the + * day as given by the passed in date. + * + * The indicator is only inserted if necessary. + * + * Parameters: + * (HTMLElement) next_msg_el - The message element before + * which the day indicator element must be inserted. + * This element must have a "data-isodate" attribute + * which specifies its creation date. + */ + const prev_msg_el = utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"), + prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'), + next_msg_date = next_msg_el.getAttribute('data-isodate'); + + if (_.isNull(prev_msg_date) || moment(next_msg_date).isAfter(prev_msg_date, 'day')) { + const day_date = moment(next_msg_date).startOf('day'); + next_msg_el.insertAdjacentHTML('beforeBegin', templates_new_day_html__WEBPACK_IMPORTED_MODULE_13___default()({ + 'isodate': day_date.format(), + 'datestring': day_date.format("dddd MMM Do YYYY") + })); + } + }, + + getLastMessageDate(cutoff) { + /* Return the ISO8601 format date of the latest message. + * + * Parameters: + * (Object) cutoff: Moment Date cutoff date. The last + * message received cutoff this date will be returned. + */ + const first_msg = utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getFirstChildElement(this.content, '.message:not(.chat-state-notification)'), + oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null; + + if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) { + return null; + } + + const last_msg = utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getLastChildElement(this.content, '.message:not(.chat-state-notification)'), + most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null; + + if (_.isNull(most_recent_date) || moment(most_recent_date).isBefore(cutoff)) { + return most_recent_date; + } + /* XXX: We avoid .chat-state-notification messages, since they are + * temporary and get removed once a new element is + * inserted into the chat area, so we don't query for + * them here, otherwise we get a null reference later + * upon element insertion. + */ + + + const msg_dates = _.invokeMap(sizzle('.message:not(.chat-state-notification)', this.content), Element.prototype.getAttribute, 'data-isodate'); + + if (_.isObject(cutoff)) { + cutoff = cutoff.format(); + } + + msg_dates.push(cutoff); + msg_dates.sort(); + const idx = msg_dates.lastIndexOf(cutoff); + + if (idx === 0) { + return null; + } else { + return msg_dates[idx - 1]; + } + }, + + setScrollPosition(message_el) { + /* Given a newly inserted message, determine whether we + * should keep the scrollbar in place (so as to not scroll + * up when using infinite scroll). + */ + if (this.model.get('scrolled')) { + const next_msg_el = utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].getNextElement(message_el, ".chat-msg"); + + if (next_msg_el) { + // The currently received message is not new, there + // are newer messages after it. So let's see if we + // should maintain our current scroll position. + if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) { + const top_visible_message = this.model.get('top_visible_message') || next_msg_el; + this.model.set('top_visible_message', top_visible_message); + this.content.scrollTop = top_visible_message.offsetTop - 30; + } + } + } else { + this.scrollDown(); + } + }, + + showHelpMessages(msgs, type, spinner) { + _.each(msgs, msg => { + this.content.insertAdjacentHTML('beforeend', templates_help_message_html__WEBPACK_IMPORTED_MODULE_11___default()({ + 'isodate': moment().format(), + 'type': type, + 'message': xss__WEBPACK_IMPORTED_MODULE_21___default.a.filterXSS(msg, { + 'whiteList': { + 'strong': [] } - } - } else { - this.scrollDown(); - } - }, + }) + })); + }); - showHelpMessages(msgs, type, spinner) { - _.each(msgs, msg => { - this.content.insertAdjacentHTML('beforeend', tpl_help_message({ - 'isodate': moment().format(), - 'type': type, - 'message': xss.filterXSS(msg, { - 'whiteList': { - 'strong': [] - } - }) - })); - }); + if (spinner === true) { + this.addSpinner(); + } else if (spinner === false) { + this.clearSpinner(); + } - if (spinner === true) { - this.addSpinner(); - } else if (spinner === false) { - this.clearSpinner(); - } + return this.scrollDown(); + }, - return this.scrollDown(); - }, + clearChatStateNotification(message, isodate) { + if (isodate) { + _.each(sizzle(`.chat-state-notification[data-csn="${message.get('from')}"][data-isodate="${isodate}"]`, this.content), utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].removeElement); + } else { + _.each(sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content), utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].removeElement); + } + }, - clearChatStateNotification(message, isodate) { - if (isodate) { - _.each(sizzle(`.chat-state-notification[data-csn="${message.get('from')}"][data-isodate="${isodate}"]`, this.content), u.removeElement); - } else { - _.each(sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content), u.removeElement); - } - }, + shouldShowOnTextMessage() { + return !utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isVisible(this.el); + }, - shouldShowOnTextMessage() { - return !u.isVisible(this.el); - }, - - insertMessage(view) { - /* Given a view representing a message, insert it into the - * content area of the chat box. - * - * Parameters: - * (Backbone.View) message: The message Backbone.View - */ - if (view.model.get('type') === 'error') { - const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); - - if (previous_msg_el) { - previous_msg_el.insertAdjacentElement('afterend', view.el); - return this.trigger('messageInserted', view.el); - } - } - - const current_msg_date = moment(view.model.get('time')) || moment, - previous_msg_date = this.getLastMessageDate(current_msg_date); - - if (_.isNull(previous_msg_date)) { - this.content.insertAdjacentElement('afterbegin', view.el); - } else { - const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop(); - - if (view.model.get('type') === 'error' && u.hasClass('chat-error', previous_msg_el) && previous_msg_el.textContent === view.model.get('message')) { - // We don't show a duplicate error message - return; - } + insertMessage(view) { + /* Given a view representing a message, insert it into the + * content area of the chat box. + * + * Parameters: + * (Backbone.View) message: The message Backbone.View + */ + if (view.model.get('type') === 'error') { + const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); + if (previous_msg_el) { previous_msg_el.insertAdjacentElement('afterend', view.el); - this.markFollowups(view.el); + return this.trigger('messageInserted', view.el); } + } - return this.trigger('messageInserted', view.el); - }, + const current_msg_date = moment(view.model.get('time')) || moment, + previous_msg_date = this.getLastMessageDate(current_msg_date); - markFollowups(el) { - /* Given a message element, determine wether it should be - * marked as a followup message to the previous element. - * - * Also determine whether the element following it is a - * followup message or not. - * - * Followup messages are subsequent ones written by the same - * author with no other conversation elements inbetween and - * posted within 10 minutes of one another. - * - * Parameters: - * (HTMLElement) el - The message element. - */ - const from = el.getAttribute('data-from'), - previous_el = el.previousElementSibling, - date = moment(el.getAttribute('data-isodate')), - next_el = el.nextElementSibling; + if (_.isNull(previous_msg_date)) { + this.content.insertAdjacentElement('afterbegin', view.el); + } else { + const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop(); - if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) && previous_el.getAttribute('data-from') === from && date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) && el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) { - u.addClass('chat-msg--followup', el); - } - - if (!next_el) { + if (view.model.get('type') === 'error' && utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].hasClass('chat-error', previous_msg_el) && previous_msg_el.textContent === view.model.get('message')) { + // We don't show a duplicate error message return; } - if (!u.hasClass('chat-msg--action', 'el') && next_el.getAttribute('data-from') === from && moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) { - u.addClass('chat-msg--followup', next_el); - } else { - u.removeClass('chat-msg--followup', next_el); + previous_msg_el.insertAdjacentElement('afterend', view.el); + this.markFollowups(view.el); + } + + return this.trigger('messageInserted', view.el); + }, + + markFollowups(el) { + /* Given a message element, determine wether it should be + * marked as a followup message to the previous element. + * + * Also determine whether the element following it is a + * followup message or not. + * + * Followup messages are subsequent ones written by the same + * author with no other conversation elements inbetween and + * posted within 10 minutes of one another. + * + * Parameters: + * (HTMLElement) el - The message element. + */ + const from = el.getAttribute('data-from'), + previous_el = el.previousElementSibling, + date = moment(el.getAttribute('data-isodate')), + next_el = el.nextElementSibling; + + if (!utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].hasClass('chat-msg--action', el) && !utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].hasClass('chat-msg--action', previous_el) && previous_el.getAttribute('data-from') === from && date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) && el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) { + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].addClass('chat-msg--followup', el); + } + + if (!next_el) { + return; + } + + if (!utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].hasClass('chat-msg--action', 'el') && next_el.getAttribute('data-from') === from && moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) { + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].addClass('chat-msg--followup', next_el); + } else { + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].removeClass('chat-msg--followup', next_el); + } + }, + + async showMessage(message) { + /* Inserts a chat message into the content area of the chat box. + * + * Will also insert a new day indicator if the message is on a + * different day. + * + * Parameters: + * (Backbone.Model) message: The message object + */ + const view = new _converse.MessageView({ + 'model': message + }); + await view.render(); + this.clearChatStateNotification(message); + this.insertMessage(view); + this.insertDayIndicator(view.el); + this.setScrollPosition(view.el); + + if (utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isNewMessage(message)) { + if (message.get('sender') === 'me') { + // We remove the "scrolled" flag so that the chat area + // gets scrolled down. We always want to scroll down + // when the user writes a message as opposed to when a + // message is received. + this.model.set('scrolled', false); + } else if (this.model.get('scrolled', true) && !utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isOnlyChatStateNotification(message)) { + this.showNewMessagesIndicator(); } - }, + } - async showMessage(message) { - /* Inserts a chat message into the content area of the chat box. - * - * Will also insert a new day indicator if the message is on a - * different day. - * - * Parameters: - * (Backbone.Model) message: The message object - */ - const view = new _converse.MessageView({ - 'model': message - }); - await view.render(); - this.clearChatStateNotification(message); - this.insertMessage(view); - this.insertDayIndicator(view.el); - this.setScrollPosition(view.el); + if (this.shouldShowOnTextMessage()) { + this.show(); + } else { + this.scrollDown(); + } + }, - if (u.isNewMessage(message)) { - if (message.get('sender') === 'me') { - // We remove the "scrolled" flag so that the chat area - // gets scrolled down. We always want to scroll down - // when the user writes a message as opposed to when a - // message is received. - this.model.set('scrolled', false); - } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) { - this.showNewMessagesIndicator(); - } + onMessageAdded(message) { + /* Handler that gets called when a new message object is created. + * + * Parameters: + * (Object) message - The message Backbone object that was added. + */ + this.showMessage(message); + + if (message.get('correcting')) { + this.insertIntoTextArea(message.get('message'), true, true); + } + + _converse.emit('messageAdded', { + 'message': message, + 'chatbox': this.model + }); + }, + + parseMessageForCommands(text) { + const match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/); + + if (match) { + if (match[1] === "clear") { + this.clearMessages(); + return true; + } else if (match[1] === "help") { + const msgs = [`/clear: ${__('Remove messages')}`, `/me: ${__('Write in the third person')}`, `/help: ${__('Show this menu')}`]; + this.showHelpMessages(msgs); + return true; } + } + }, - if (this.shouldShowOnTextMessage()) { - this.show(); - } else { - this.scrollDown(); - } - }, + onMessageSubmitted(text, spoiler_hint) { + /* This method gets called once the user has typed a message + * and then pressed enter in a chat box. + * + * Parameters: + * (String) text - The chat message text. + * (String) spoiler_hint - A hint in case the message + * text is a hidden/spoiler message. See XEP-0382 + */ + if (!_converse.connection.authenticated) { + return this.showHelpMessages(['Sorry, the connection has been lost, ' + 'and your message could not be sent'], 'error'); + } - onMessageAdded(message) { - /* Handler that gets called when a new message object is created. - * - * Parameters: - * (Object) message - The message Backbone object that was added. - */ - this.showMessage(message); + if (this.parseMessageForCommands(text)) { + return; + } - if (message.get('correcting')) { - this.insertIntoTextArea(message.get('message'), true, true); - } + const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); + this.model.sendMessage(attrs); + }, - _converse.emit('messageAdded', { - 'message': message, - 'chatbox': this.model - }); - }, + setChatState(state, options) { + /* Mutator for setting the chat state of this chat session. + * Handles clearing of any chat state notification timeouts and + * setting new ones if necessary. + * Timeouts are set when the state being set is COMPOSING or PAUSED. + * After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE. + * See XEP-0085 Chat State Notifications. + * + * Parameters: + * (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE) + */ + if (!_.isUndefined(this.chat_state_timeout)) { + window.clearTimeout(this.chat_state_timeout); + delete this.chat_state_timeout; + } - parseMessageForCommands(text) { - const match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/); + if (state === _converse.COMPOSING) { + this.chat_state_timeout = window.setTimeout(this.setChatState.bind(this), _converse.TIMEOUTS.PAUSED, _converse.PAUSED); + } else if (state === _converse.PAUSED) { + this.chat_state_timeout = window.setTimeout(this.setChatState.bind(this), _converse.TIMEOUTS.INACTIVE, _converse.INACTIVE); + } - if (match) { - if (match[1] === "clear") { - this.clearMessages(); - return true; - } else if (match[1] === "help") { - const msgs = [`/clear: ${__('Remove messages')}`, `/me: ${__('Write in the third person')}`, `/help: ${__('Show this menu')}`]; - this.showHelpMessages(msgs); - return true; - } - } - }, + this.model.set('chat_state', state, options); + return this; + }, - onMessageSubmitted(text, spoiler_hint) { - /* This method gets called once the user has typed a message - * and then pressed enter in a chat box. - * - * Parameters: - * (String) text - The chat message text. - * (String) spoiler_hint - A hint in case the message - * text is a hidden/spoiler message. See XEP-0382 - */ - if (!_converse.connection.authenticated) { - return this.showHelpMessages(['Sorry, the connection has been lost, ' + 'and your message could not be sent'], 'error'); - } + onFormSubmitted(ev) { + ev.preventDefault(); + const textarea = this.el.querySelector('.chat-textarea'), + message = textarea.value; - if (this.parseMessageForCommands(text)) { + if (!message.replace(/\s/g, '').length) { + return; + } + + let spoiler_hint; + + if (this.model.get('composing_spoiler')) { + const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); + spoiler_hint = hint_el.value; + hint_el.value = ''; + } + + textarea.value = ''; + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].removeClass('correcting', textarea); + textarea.focus(); // Trigger input event, so that the textarea resizes + + const event = document.createEvent('Event'); + event.initEvent('input', true, true); + textarea.dispatchEvent(event); + this.onMessageSubmitted(message, spoiler_hint); + + _converse.emit('messageSend', message); // Suppress events, otherwise superfluous CSN gets set + // immediately after the message, causing rate-limiting issues. + + + this.setChatState(_converse.ACTIVE, { + 'silent': true + }); + }, + + keyPressed(ev) { + /* Event handler for when a key is pressed in a chat box textarea. + */ + if (ev.ctrlKey) { + // When ctrl is pressed, no chars are entered into the textarea. + return; + } + + if (!ev.shiftKey && !ev.altKey) { + if (ev.keyCode === _converse.keycodes.FORWARD_SLASH) { + // Forward slash is used to run commands. Nothing to do here. return; + } else if (ev.keyCode === _converse.keycodes.ESCAPE) { + return this.onEscapePressed(ev); + } else if (ev.keyCode === _converse.keycodes.ENTER) { + if (this.emoji_dropdown && utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) { + this.emoji_dropdown.toggle(); + } + + return this.onFormSubmitted(ev); + } else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) { + return this.editEarlierMessage(); + } else if (ev.keyCode === _converse.keycodes.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) { + return this.editLaterMessage(); + } + } + + if (_.includes([_converse.keycodes.SHIFT, _converse.keycodes.META, _converse.keycodes.META_RIGHT, _converse.keycodes.ESCAPE, _converse.keycodes.ALT], ev.keyCode)) { + return; + } + + if (this.model.get('chat_state') !== _converse.COMPOSING) { + // Set chat state to composing if keyCode is not a forward-slash + // (which would imply an internal command and not a message). + this.setChatState(_converse.COMPOSING); + } + }, + + getOwnMessages() { + return f(this.model.messages.filter({ + 'sender': 'me' + })); + }, + + onEscapePressed(ev) { + ev.preventDefault(); + const idx = this.model.messages.findLastIndex('correcting'), + message = idx >= 0 ? this.model.messages.at(idx) : null; + + if (message) { + message.save('correcting', false); + } + + this.insertIntoTextArea('', true, false); + }, + + onMessageEditButtonClicked(ev) { + ev.preventDefault(); + const idx = this.model.messages.findLastIndex('correcting'), + currently_correcting = idx >= 0 ? this.model.messages.at(idx) : null, + message_el = utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].ancestor(ev.target, '.chat-msg'), + message = this.model.messages.findWhere({ + 'msgid': message_el.getAttribute('data-msgid') + }); + + if (currently_correcting !== message) { + if (!_.isNil(currently_correcting)) { + currently_correcting.save('correcting', false); } - const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); - this.model.sendMessage(attrs); - }, + message.save('correcting', true); + this.insertIntoTextArea(utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].prefixMentions(message), true, true); + } else { + message.save('correcting', false); + this.insertIntoTextArea('', true, false); + } + }, - setChatState(state, options) { - /* Mutator for setting the chat state of this chat session. - * Handles clearing of any chat state notification timeouts and - * setting new ones if necessary. - * Timeouts are set when the state being set is COMPOSING or PAUSED. - * After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE. - * See XEP-0085 Chat State Notifications. - * - * Parameters: - * (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE) - */ - if (!_.isUndefined(this.chat_state_timeout)) { - window.clearTimeout(this.chat_state_timeout); - delete this.chat_state_timeout; + editLaterMessage() { + let message; + let idx = this.model.messages.findLastIndex('correcting'); + + if (idx >= 0) { + this.model.messages.at(idx).save('correcting', false); + + while (idx < this.model.messages.length - 1) { + idx += 1; + const candidate = this.model.messages.at(idx); + + if (candidate.get('sender') === 'me' && candidate.get('message')) { + message = candidate; + break; + } } + } - if (state === _converse.COMPOSING) { - this.chat_state_timeout = window.setTimeout(this.setChatState.bind(this), _converse.TIMEOUTS.PAUSED, _converse.PAUSED); - } else if (state === _converse.PAUSED) { - this.chat_state_timeout = window.setTimeout(this.setChatState.bind(this), _converse.TIMEOUTS.INACTIVE, _converse.INACTIVE); + if (message) { + this.insertIntoTextArea(message.get('message'), true, true); + message.save('correcting', true); + } else { + this.insertIntoTextArea('', true, false); + } + }, + + editEarlierMessage() { + let message; + let idx = this.model.messages.findLastIndex('correcting'); + + if (idx >= 0) { + this.model.messages.at(idx).save('correcting', false); + + while (idx > 0) { + idx -= 1; + const candidate = this.model.messages.at(idx); + + if (candidate.get('sender') === 'me' && candidate.get('message')) { + message = candidate; + break; + } } + } - this.model.set('chat_state', state, options); - return this; - }, + message = message || this.getOwnMessages().findLast(msg => msg.get('message')); - onFormSubmitted(ev) { + if (message) { + this.insertIntoTextArea(message.get('message'), true, true); + message.save('correcting', true); + } + }, + + inputChanged(ev) { + ev.target.style.height = 'auto'; // Fixes weirdness + + ev.target.style.height = ev.target.scrollHeight + 'px'; + }, + + clearMessages(ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); - const textarea = this.el.querySelector('.chat-textarea'), - message = textarea.value; + } - if (!message.replace(/\s/g, '').length) { - return; - } + const result = confirm(__("Are you sure you want to clear the messages from this conversation?")); - let spoiler_hint; + if (result === true) { + this.content.innerHTML = ''; + this.model.messages.reset(); - if (this.model.get('composing_spoiler')) { - const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); - spoiler_hint = hint_el.value; - hint_el.value = ''; + this.model.messages.browserStorage._clear(); + } + + return this; + }, + + insertIntoTextArea(value, replace = false, correcting = false) { + const textarea = this.el.querySelector('.chat-textarea'); + + if (correcting) { + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].addClass('correcting', textarea); + } else { + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].removeClass('correcting', textarea); + } + + if (replace) { + textarea.value = ''; + textarea.value = value; + } else { + let existing = textarea.value; + + if (existing && existing[existing.length - 1] !== ' ') { + existing = existing + ' '; } textarea.value = ''; - u.removeClass('correcting', textarea); - textarea.focus(); // Trigger input event, so that the textarea resizes + textarea.value = existing + value + ' '; + } - const event = document.createEvent('Event'); - event.initEvent('input', true, true); - textarea.dispatchEvent(event); - this.onMessageSubmitted(message, spoiler_hint); + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].putCurserAtEnd(textarea); + }, - _converse.emit('messageSend', message); // Suppress events, otherwise superfluous CSN gets set - // immediately after the message, causing rate-limiting issues. + createEmojiPicker() { + if (_.isUndefined(_converse.emojipicker)) { + const storage = _converse.config.get('storage'), + id = `converse.emoji-${_converse.bare_jid}`; + _converse.emojipicker = new _converse.EmojiPicker({ + 'id': id + }); + _converse.emojipicker.browserStorage = new Backbone.BrowserStorage[storage](id); - this.setChatState(_converse.ACTIVE, { + _converse.emojipicker.fetch(); + } + + this.emoji_picker_view = new _converse.EmojiPickerView({ + 'model': _converse.emojipicker + }); + }, + + insertEmoji(ev) { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; + this.insertIntoTextArea(target.getAttribute('data-emoji')); + }, + + toggleEmojiMenu(ev) { + if (_.isUndefined(this.emoji_dropdown)) { + ev.stopPropagation(); + this.createEmojiPicker(); + this.insertEmojiPicker(); + this.renderEmojiPicker(); + const dropdown_el = this.el.querySelector('.toggle-smiley.dropup'); + this.emoji_dropdown = new bootstrap__WEBPACK_IMPORTED_MODULE_4___default.a.Dropdown(dropdown_el, true); + this.emoji_dropdown.el = dropdown_el; + this.emoji_dropdown.toggle(); + } + }, + + toggleCall(ev) { + ev.stopPropagation(); + + _converse.emit('callButtonClicked', { + connection: _converse.connection, + model: this.model + }); + }, + + toggleComposeSpoilerMessage() { + this.model.set('composing_spoiler', !this.model.get('composing_spoiler')); + this.renderMessageForm(); + this.focus(); + }, + + toggleSpoilerMessage(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + const toggle_el = ev.target, + icon_el = toggle_el.firstElementChild; + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].slideToggleElement(toggle_el.parentElement.parentElement.querySelector('.spoiler')); + + if (toggle_el.getAttribute("data-toggle-state") == "closed") { + toggle_el.textContent = 'Show less'; + icon_el.classList.remove("fa-eye"); + icon_el.classList.add("fa-eye-slash"); + toggle_el.insertAdjacentElement('afterBegin', icon_el); + toggle_el.setAttribute("data-toggle-state", "open"); + } else { + toggle_el.textContent = 'Show more'; + icon_el.classList.remove("fa-eye-slash"); + icon_el.classList.add("fa-eye"); + toggle_el.insertAdjacentElement('afterBegin', icon_el); + toggle_el.setAttribute("data-toggle-state", "closed"); + } + }, + + onPresenceChanged(item) { + const show = item.get('show'), + fullname = this.model.getDisplayName(); + let text; + + if (utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isVisible(this.el)) { + if (show === 'offline') { + text = __('%1$s has gone offline', fullname); + } else if (show === 'away') { + text = __('%1$s has gone away', fullname); + } else if (show === 'dnd') { + text = __('%1$s is busy', fullname); + } else if (show === 'online') { + text = __('%1$s is online', fullname); + } + + if (text) { + this.content.insertAdjacentHTML('beforeend', templates_status_message_html__WEBPACK_IMPORTED_MODULE_16___default()({ + 'message': text, + 'isodate': moment().format() + })); + this.scrollDown(); + } + } + }, + + close(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + if (Backbone.history.getFragment() === "converse/chat?jid=" + this.model.get('jid')) { + _converse.router.navigate(''); + } + + if (_converse.connection.connected) { + // Immediately sending the chat state, because the + // model is going to be destroyed afterwards. + this.setChatState(_converse.INACTIVE); + this.model.sendChatState(); + } + + try { + this.model.destroy(); + } catch (e) { + _converse.log(e, Strophe.LogLevel.ERROR); + } + + this.remove(); + + _converse.emit('chatBoxClosed', this); + + return this; + }, + + renderEmojiPicker() { + this.emoji_picker_view.render(); + }, + + insertEmojiPicker() { + var picker_el = this.el.querySelector('.emoji-picker'); + + if (!_.isNull(picker_el)) { + picker_el.innerHTML = ''; + picker_el.appendChild(this.emoji_picker_view.el); + } + }, + + focus() { + const textarea_el = this.el.querySelector('.chat-textarea'); + + if (!_.isNull(textarea_el)) { + textarea_el.focus(); + + _converse.emit('chatBoxFocused', this); + } + + return this; + }, + + hide() { + this.el.classList.add('hidden'); + return this; + }, + + afterShown() { + this.model.clearUnreadMsgCounter(); + this.setChatState(_converse.ACTIVE); + this.scrollDown(); + this.focus(); + }, + + _show(f) { + /* Inner show method that gets debounced */ + if (utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isVisible(this.el)) { + this.focus(); + return; + } + + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].fadeIn(this.el, _.bind(this.afterShown, this)); + }, + + showNewMessagesIndicator() { + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].showElement(this.el.querySelector('.new-msgs-indicator')); + }, + + hideNewMessagesIndicator() { + const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator'); + + if (!_.isNull(new_msgs_indicator)) { + new_msgs_indicator.classList.add('hidden'); + } + }, + + _markScrolled: function _markScrolled(ev) { + /* Called when the chat content is scrolled up or down. + * We want to record when the user has scrolled away from + * the bottom, so that we don't automatically scroll away + * from what the user is reading when new messages are + * received. + */ + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + let scrolled = true; + const is_at_bottom = this.content.scrollTop + this.content.clientHeight >= this.content.scrollHeight - 62; // sigh... + + if (is_at_bottom) { + scrolled = false; + this.onScrolledDown(); + } + + utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].safeSave(this.model, { + 'scrolled': scrolled, + 'top_visible_message': null + }); + }, + + viewUnreadMessages() { + this.model.save({ + 'scrolled': false, + 'top_visible_message': null + }); + this.scrollDown(); + }, + + _scrollDown() { + /* Inner method that gets debounced */ + if (_.isUndefined(this.content)) { + return; + } + + if (utils_emoji__WEBPACK_IMPORTED_MODULE_20__["default"].isVisible(this.content) && !this.model.get('scrolled')) { + this.content.scrollTop = this.content.scrollHeight; + } + }, + + onScrolledDown() { + this.hideNewMessagesIndicator(); + + if (_converse.windowState !== 'hidden') { + this.model.clearUnreadMsgCounter(); + } + + _converse.emit('chatBoxScrolledDown', { + 'chatbox': this.model + }); + }, + + onWindowStateChanged(state) { + if (state === 'visible') { + if (!this.model.isHidden()) { + this.setChatState(_converse.ACTIVE); + + if (this.model.get('num_unread', 0)) { + this.model.clearUnreadMsgCounter(); + } + } + } else if (state === 'hidden') { + this.setChatState(_converse.INACTIVE, { 'silent': true }); - }, + this.model.sendChatState(); - keyPressed(ev) { - /* Event handler for when a key is pressed in a chat box textarea. - */ - if (ev.ctrlKey) { - // When ctrl is pressed, no chars are entered into the textarea. - return; - } + _converse.connection.flush(); + } + } - if (!ev.shiftKey && !ev.altKey) { - if (ev.keyCode === _converse.keycodes.FORWARD_SLASH) { - // Forward slash is used to run commands. Nothing to do here. - return; - } else if (ev.keyCode === _converse.keycodes.ESCAPE) { - return this.onEscapePressed(ev); - } else if (ev.keyCode === _converse.keycodes.ENTER) { - if (this.emoji_dropdown && u.isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) { - this.emoji_dropdown.toggle(); - } + }); - return this.onFormSubmitted(ev); - } else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) { - return this.editEarlierMessage(); - } else if (ev.keyCode === _converse.keycodes.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) { - return this.editLaterMessage(); - } - } + _converse.on('chatBoxViewsInitialized', () => { + const that = _converse.chatboxviews; - if (_.includes([_converse.keycodes.SHIFT, _converse.keycodes.META, _converse.keycodes.META_RIGHT, _converse.keycodes.ESCAPE, _converse.keycodes.ALT], ev.keyCode)) { - return; - } - - if (this.model.get('chat_state') !== _converse.COMPOSING) { - // Set chat state to composing if keyCode is not a forward-slash - // (which would imply an internal command and not a message). - this.setChatState(_converse.COMPOSING); - } - }, - - getOwnMessages() { - return f(this.model.messages.filter({ - 'sender': 'me' + _converse.chatboxes.on('add', item => { + if (!that.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) { + that.add(item.get('id'), new _converse.ChatBoxView({ + model: item })); - }, - - onEscapePressed(ev) { - ev.preventDefault(); - const idx = this.model.messages.findLastIndex('correcting'), - message = idx >= 0 ? this.model.messages.at(idx) : null; - - if (message) { - message.save('correcting', false); - } - - this.insertIntoTextArea('', true, false); - }, - - onMessageEditButtonClicked(ev) { - ev.preventDefault(); - const idx = this.model.messages.findLastIndex('correcting'), - currently_correcting = idx >= 0 ? this.model.messages.at(idx) : null, - message_el = u.ancestor(ev.target, '.chat-msg'), - message = this.model.messages.findWhere({ - 'msgid': message_el.getAttribute('data-msgid') - }); - - if (currently_correcting !== message) { - if (!_.isNil(currently_correcting)) { - currently_correcting.save('correcting', false); - } - - message.save('correcting', true); - this.insertIntoTextArea(u.prefixMentions(message), true, true); - } else { - message.save('correcting', false); - this.insertIntoTextArea('', true, false); - } - }, - - editLaterMessage() { - let message; - let idx = this.model.messages.findLastIndex('correcting'); - - if (idx >= 0) { - this.model.messages.at(idx).save('correcting', false); - - while (idx < this.model.messages.length - 1) { - idx += 1; - const candidate = this.model.messages.at(idx); - - if (candidate.get('sender') === 'me' && candidate.get('message')) { - message = candidate; - break; - } - } - } - - if (message) { - this.insertIntoTextArea(message.get('message'), true, true); - message.save('correcting', true); - } else { - this.insertIntoTextArea('', true, false); - } - }, - - editEarlierMessage() { - let message; - let idx = this.model.messages.findLastIndex('correcting'); - - if (idx >= 0) { - this.model.messages.at(idx).save('correcting', false); - - while (idx > 0) { - idx -= 1; - const candidate = this.model.messages.at(idx); - - if (candidate.get('sender') === 'me' && candidate.get('message')) { - message = candidate; - break; - } - } - } - - message = message || this.getOwnMessages().findLast(msg => msg.get('message')); - - if (message) { - this.insertIntoTextArea(message.get('message'), true, true); - message.save('correcting', true); - } - }, - - inputChanged(ev) { - ev.target.style.height = 'auto'; // Fixes weirdness - - ev.target.style.height = ev.target.scrollHeight + 'px'; - }, - - clearMessages(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const result = confirm(__("Are you sure you want to clear the messages from this conversation?")); - - if (result === true) { - this.content.innerHTML = ''; - this.model.messages.reset(); - - this.model.messages.browserStorage._clear(); - } - - return this; - }, - - insertIntoTextArea(value, replace = false, correcting = false) { - const textarea = this.el.querySelector('.chat-textarea'); - - if (correcting) { - u.addClass('correcting', textarea); - } else { - u.removeClass('correcting', textarea); - } - - if (replace) { - textarea.value = ''; - textarea.value = value; - } else { - let existing = textarea.value; - - if (existing && existing[existing.length - 1] !== ' ') { - existing = existing + ' '; - } - - textarea.value = ''; - textarea.value = existing + value + ' '; - } - - u.putCurserAtEnd(textarea); - }, - - createEmojiPicker() { - if (_.isUndefined(_converse.emojipicker)) { - const storage = _converse.config.get('storage'), - id = `converse.emoji-${_converse.bare_jid}`; - - _converse.emojipicker = new _converse.EmojiPicker({ - 'id': id - }); - _converse.emojipicker.browserStorage = new Backbone.BrowserStorage[storage](id); - - _converse.emojipicker.fetch(); - } - - this.emoji_picker_view = new _converse.EmojiPickerView({ - 'model': _converse.emojipicker - }); - }, - - insertEmoji(ev) { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; - this.insertIntoTextArea(target.getAttribute('data-emoji')); - }, - - toggleEmojiMenu(ev) { - if (_.isUndefined(this.emoji_dropdown)) { - ev.stopPropagation(); - this.createEmojiPicker(); - this.insertEmojiPicker(); - this.renderEmojiPicker(); - const dropdown_el = this.el.querySelector('.toggle-smiley.dropup'); - this.emoji_dropdown = new bootstrap.Dropdown(dropdown_el, true); - this.emoji_dropdown.el = dropdown_el; - this.emoji_dropdown.toggle(); - } - }, - - toggleCall(ev) { - ev.stopPropagation(); - - _converse.emit('callButtonClicked', { - connection: _converse.connection, - model: this.model - }); - }, - - toggleComposeSpoilerMessage() { - this.model.set('composing_spoiler', !this.model.get('composing_spoiler')); - this.renderMessageForm(); - this.focus(); - }, - - toggleSpoilerMessage(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const toggle_el = ev.target, - icon_el = toggle_el.firstElementChild; - u.slideToggleElement(toggle_el.parentElement.parentElement.querySelector('.spoiler')); - - if (toggle_el.getAttribute("data-toggle-state") == "closed") { - toggle_el.textContent = 'Show less'; - icon_el.classList.remove("fa-eye"); - icon_el.classList.add("fa-eye-slash"); - toggle_el.insertAdjacentElement('afterBegin', icon_el); - toggle_el.setAttribute("data-toggle-state", "open"); - } else { - toggle_el.textContent = 'Show more'; - icon_el.classList.remove("fa-eye-slash"); - icon_el.classList.add("fa-eye"); - toggle_el.insertAdjacentElement('afterBegin', icon_el); - toggle_el.setAttribute("data-toggle-state", "closed"); - } - }, - - onPresenceChanged(item) { - const show = item.get('show'), - fullname = this.model.getDisplayName(); - let text; - - if (u.isVisible(this.el)) { - if (show === 'offline') { - text = __('%1$s has gone offline', fullname); - } else if (show === 'away') { - text = __('%1$s has gone away', fullname); - } else if (show === 'dnd') { - text = __('%1$s is busy', fullname); - } else if (show === 'online') { - text = __('%1$s is online', fullname); - } - - if (text) { - this.content.insertAdjacentHTML('beforeend', tpl_status_message({ - 'message': text, - 'isodate': moment().format() - })); - this.scrollDown(); - } - } - }, - - close(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - if (Backbone.history.getFragment() === "converse/chat?jid=" + this.model.get('jid')) { - _converse.router.navigate(''); - } - - if (_converse.connection.connected) { - // Immediately sending the chat state, because the - // model is going to be destroyed afterwards. - this.setChatState(_converse.INACTIVE); - this.model.sendChatState(); - } - - try { - this.model.destroy(); - } catch (e) { - _converse.log(e, Strophe.LogLevel.ERROR); - } - - this.remove(); - - _converse.emit('chatBoxClosed', this); - - return this; - }, - - renderEmojiPicker() { - this.emoji_picker_view.render(); - }, - - insertEmojiPicker() { - var picker_el = this.el.querySelector('.emoji-picker'); - - if (!_.isNull(picker_el)) { - picker_el.innerHTML = ''; - picker_el.appendChild(this.emoji_picker_view.el); - } - }, - - focus() { - const textarea_el = this.el.querySelector('.chat-textarea'); - - if (!_.isNull(textarea_el)) { - textarea_el.focus(); - - _converse.emit('chatBoxFocused', this); - } - - return this; - }, - - hide() { - this.el.classList.add('hidden'); - return this; - }, - - afterShown() { - this.model.clearUnreadMsgCounter(); - this.setChatState(_converse.ACTIVE); - this.scrollDown(); - this.focus(); - }, - - _show(f) { - /* Inner show method that gets debounced */ - if (u.isVisible(this.el)) { - this.focus(); - return; - } - - u.fadeIn(this.el, _.bind(this.afterShown, this)); - }, - - showNewMessagesIndicator() { - u.showElement(this.el.querySelector('.new-msgs-indicator')); - }, - - hideNewMessagesIndicator() { - const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator'); - - if (!_.isNull(new_msgs_indicator)) { - new_msgs_indicator.classList.add('hidden'); - } - }, - - _markScrolled: function _markScrolled(ev) { - /* Called when the chat content is scrolled up or down. - * We want to record when the user has scrolled away from - * the bottom, so that we don't automatically scroll away - * from what the user is reading when new messages are - * received. - */ - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - let scrolled = true; - const is_at_bottom = this.content.scrollTop + this.content.clientHeight >= this.content.scrollHeight - 62; // sigh... - - if (is_at_bottom) { - scrolled = false; - this.onScrolledDown(); - } - - u.safeSave(this.model, { - 'scrolled': scrolled, - 'top_visible_message': null - }); - }, - - viewUnreadMessages() { - this.model.save({ - 'scrolled': false, - 'top_visible_message': null - }); - this.scrollDown(); - }, - - _scrollDown() { - /* Inner method that gets debounced */ - if (_.isUndefined(this.content)) { - return; - } - - if (u.isVisible(this.content) && !this.model.get('scrolled')) { - this.content.scrollTop = this.content.scrollHeight; - } - }, - - onScrolledDown() { - this.hideNewMessagesIndicator(); - - if (_converse.windowState !== 'hidden') { - this.model.clearUnreadMsgCounter(); - } - - _converse.emit('chatBoxScrolledDown', { - 'chatbox': this.model - }); - }, - - onWindowStateChanged(state) { - if (state === 'visible') { - if (!this.model.isHidden()) { - this.setChatState(_converse.ACTIVE); - - if (this.model.get('num_unread', 0)) { - this.model.clearUnreadMsgCounter(); - } - } - } else if (state === 'hidden') { - this.setChatState(_converse.INACTIVE, { - 'silent': true - }); - this.model.sendChatState(); - - _converse.connection.flush(); - } } - }); + }); - _converse.on('chatBoxViewsInitialized', () => { - const that = _converse.chatboxviews; - - _converse.chatboxes.on('add', item => { - if (!that.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) { - that.add(item.get('id'), new _converse.ChatBoxView({ - model: item - })); - } - }); - }); - - _converse.on('connected', () => { - // Advertise that we support XEP-0382 Message Spoilers - _converse.api.disco.own.features.add(Strophe.NS.SPOILER); - }); - /************************ BEGIN API ************************/ + _converse.on('connected', () => { + // Advertise that we support XEP-0382 Message Spoilers + _converse.api.disco.own.features.add(Strophe.NS.SPOILER); + }); + /************************ BEGIN API ************************/ - _.extend(_converse.api, { + _.extend(_converse.api, { + /** + * The "chatview" namespace groups methods pertaining to views + * for one-on-one chats. + * + * @namespace _converse.api.chatviews + * @memberOf _converse.api + */ + 'chatviews': { /** - * The "chatview" namespace groups methods pertaining to views - * for one-on-one chats. + * Get the view of an already open chat. * - * @namespace _converse.api.chatviews - * @memberOf _converse.api + * @method _converse.api.chatviews.get + * @returns {ChatBoxView} A [Backbone.View](http://backbonejs.org/#View) instance. + * The chat should already be open, otherwise `undefined` will be returned. + * + * @example + * // To return a single view, provide the JID of the contact: + * _converse.api.chatviews.get('buddy@example.com') + * + * @example + * // To return an array of views, provide an array of JIDs: + * _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com']) */ - 'chatviews': { - /** - * Get the view of an already open chat. - * - * @method _converse.api.chatviews.get - * @returns {ChatBoxView} A [Backbone.View](http://backbonejs.org/#View) instance. - * The chat should already be open, otherwise `undefined` will be returned. - * - * @example - * // To return a single view, provide the JID of the contact: - * _converse.api.chatviews.get('buddy@example.com') - * - * @example - * // To return an array of views, provide an array of JIDs: - * _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com']) - */ - 'get'(jids) { - if (_.isUndefined(jids)) { - _converse.log("chats.create: You need to provide at least one JID", Strophe.LogLevel.ERROR); + 'get'(jids) { + if (_.isUndefined(jids)) { + _converse.log("chats.create: You need to provide at least one JID", Strophe.LogLevel.ERROR); - return null; - } - - if (_.isString(jids)) { - return _converse.chatboxviews.get(jids); - } - - return _.map(jids, jid => _converse.chatboxviews.get(jids)); + return null; } + if (_.isString(jids)) { + return _converse.chatboxviews.get(jids); + } + + return _.map(jids, jid => _converse.chatboxviews.get(jids)); } - }); - /************************ END API ************************/ - } + } + }); + /************************ END API ************************/ + + } - }); - return converse; }); /***/ }), @@ -60056,10 +60118,30 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!************************************!*\ !*** ./src/converse-controlbox.js ***! \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_chatview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"); +/* harmony import */ var converse_profile__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! converse-profile */ "./src/converse-profile.js"); +/* harmony import */ var converse_rosterview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! converse-rosterview */ "./src/converse-rosterview.js"); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(formdata_polyfill__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(bootstrap__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var _converse_headless_lodash_fp__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @converse/headless/lodash.fp */ "./src/headless/lodash.fp.js"); +/* harmony import */ var _converse_headless_lodash_fp__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_converse_headless_lodash_fp__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_converse_brand_heading_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/converse_brand_heading.html */ "./src/templates/converse_brand_heading.html"); +/* harmony import */ var templates_converse_brand_heading_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_converse_brand_heading_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var templates_controlbox_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/controlbox.html */ "./src/templates/controlbox.html"); +/* harmony import */ var templates_controlbox_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_controlbox_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var templates_controlbox_toggle_html__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! templates/controlbox_toggle.html */ "./src/templates/controlbox_toggle.html"); +/* harmony import */ var templates_controlbox_toggle_html__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(templates_controlbox_toggle_html__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var templates_login_panel_html__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! templates/login_panel.html */ "./src/templates/login_panel.html"); +/* harmony import */ var templates_login_panel_html__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(templates_login_panel_html__WEBPACK_IMPORTED_MODULE_10__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand @@ -60067,657 +60149,659 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ // /*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"), __webpack_require__(/*! @converse/headless/lodash.fp */ "./src/headless/lodash.fp.js"), __webpack_require__(/*! templates/converse_brand_heading.html */ "./src/templates/converse_brand_heading.html"), __webpack_require__(/*! templates/controlbox.html */ "./src/templates/controlbox.html"), __webpack_require__(/*! templates/controlbox_toggle.html */ "./src/templates/controlbox_toggle.html"), __webpack_require__(/*! templates/login_panel.html */ "./src/templates/login_panel.html"), __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"), __webpack_require__(/*! converse-rosterview */ "./src/converse-rosterview.js"), __webpack_require__(/*! converse-profile */ "./src/converse-profile.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, bootstrap, _FormData, fp, tpl_brand_heading, tpl_controlbox, tpl_controlbox_toggle, tpl_login_panel) { - "use strict"; - const CHATBOX_TYPE = 'chatbox'; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - _ = _converse$env._, - moment = _converse$env.moment; - const u = converse.env.utils; - const CONNECTION_STATUS_CSS_CLASS = { - 'Error': 'error', - 'Connecting': 'info', - 'Connection failure': 'error', - 'Authenticating': 'info', - 'Authentication failure': 'error', - 'Connected': 'info', - 'Disconnected': 'error', - 'Disconnecting': 'warn', - 'Attached': 'info', - 'Redirect': 'info', - 'Reconnecting': 'warn' - }; - const PRETTY_CONNECTION_STATUS = { - 0: 'Error', - 1: 'Connecting', - 2: 'Connection failure', - 3: 'Authenticating', - 4: 'Authentication failure', - 5: 'Connected', - 6: 'Disconnected', - 7: 'Disconnecting', - 8: 'Attached', - 9: 'Redirect', - 10: 'Reconnecting' - }; - const REPORTABLE_STATUSES = [0, // ERROR' - 1, // CONNECTING - 2, // CONNFAIL - 3, // AUTHENTICATING - 4, // AUTHFAIL - 7, // DISCONNECTING - 10 // RECONNECTING - ]; - converse.plugins.add('converse-controlbox', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-modal", "converse-chatboxes", "converse-rosterview", "converse-chatview"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - tearDown() { - this.__super__.tearDown.apply(this, arguments); - if (this.rosterview) { - // Removes roster groups - this.rosterview.model.off().reset(); - this.rosterview.each(function (groupview) { - groupview.removeAll(); - groupview.remove(); - }); - this.rosterview.removeAll().remove(); - } - }, - ChatBoxes: { - chatBoxMayBeShown(chatbox) { - return this.__super__.chatBoxMayBeShown.apply(this, arguments) && chatbox.get('id') !== 'controlbox'; - } - }, - ChatBoxViews: { - closeAllChatBoxes() { - const _converse = this.__super__._converse; - this.each(function (view) { - if (view.model.get('id') === 'controlbox' && (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) { - return; - } - view.close(); - }); - return this; - }, - getChatBoxWidth(view) { - const _converse = this.__super__._converse; - const controlbox = this.get('controlbox'); - if (view.model.get('id') === 'controlbox') { - /* We return the width of the controlbox or its toggle, - * depending on which is visible. - */ - if (!controlbox || !u.isVisible(controlbox.el)) { - return u.getOuterWidth(_converse.controlboxtoggle.el, true); - } else { - return u.getOuterWidth(controlbox.el, true); - } - } else { - return this.__super__.getChatBoxWidth.apply(this, arguments); - } - } - }, - ChatBox: { - initialize() { - if (this.get('id') === 'controlbox') { - this.set({ - 'time_opened': moment(0).valueOf() - }); - } else { - this.__super__.initialize.apply(this, arguments); - } - } - }, - ChatBoxView: { - insertIntoDOM() { - const view = this.__super__._converse.chatboxviews.get("controlbox"); - if (view) { - view.el.insertAdjacentElement('afterend', this.el); - } else { - this.__super__.insertIntoDOM.apply(this, arguments); - } - return this; - } +const CHATBOX_TYPE = 'chatbox'; +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].env, + Strophe = _converse$env.Strophe, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + _ = _converse$env._, + moment = _converse$env.moment; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].env.utils; +const CONNECTION_STATUS_CSS_CLASS = { + 'Error': 'error', + 'Connecting': 'info', + 'Connection failure': 'error', + 'Authenticating': 'info', + 'Authentication failure': 'error', + 'Connected': 'info', + 'Disconnected': 'error', + 'Disconnecting': 'warn', + 'Attached': 'info', + 'Redirect': 'info', + 'Reconnecting': 'warn' +}; +const PRETTY_CONNECTION_STATUS = { + 0: 'Error', + 1: 'Connecting', + 2: 'Connection failure', + 3: 'Authenticating', + 4: 'Authentication failure', + 5: 'Connected', + 6: 'Disconnected', + 7: 'Disconnecting', + 8: 'Attached', + 9: 'Redirect', + 10: 'Reconnecting' +}; +const REPORTABLE_STATUSES = [0, // ERROR' +1, // CONNECTING +2, // CONNFAIL +3, // AUTHENTICATING +4, // AUTHFAIL +7, // DISCONNECTING +10 // RECONNECTING +]; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins.add('converse-controlbox', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-modal", "converse-chatboxes", "converse-rosterview", "converse-chatview"], + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + tearDown() { + this.__super__.tearDown.apply(this, arguments); + if (this.rosterview) { + // Removes roster groups + this.rosterview.model.off().reset(); + this.rosterview.each(function (groupview) { + groupview.removeAll(); + groupview.remove(); + }); + this.rosterview.removeAll().remove(); } }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; + ChatBoxes: { + chatBoxMayBeShown(chatbox) { + return this.__super__.chatBoxMayBeShown.apply(this, arguments) && chatbox.get('id') !== 'controlbox'; + } - _converse.api.settings.update({ - allow_logout: true, - default_domain: undefined, - locked_domain: undefined, - show_controlbox_by_default: false, - sticky_controlbox: false - }); + }, + ChatBoxViews: { + closeAllChatBoxes() { + const _converse = this.__super__._converse; + this.each(function (view) { + if (view.model.get('id') === 'controlbox' && (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) { + return; + } - _converse.api.promises.add('controlboxInitialized'); - - _converse.addControlBox = () => { - return _converse.chatboxes.add({ - 'id': 'controlbox', - 'box_id': 'controlbox', - 'type': _converse.CONTROLBOX_TYPE, - 'closed': !_converse.show_controlbox_by_default + view.close(); }); - }; + return this; + }, - _converse.ControlBoxView = _converse.ChatBoxView.extend({ - tagName: 'div', - className: 'chatbox', - id: 'controlbox', - events: { - 'click a.close-chatbox-button': 'close' - }, + getChatBoxWidth(view) { + const _converse = this.__super__._converse; + const controlbox = this.get('controlbox'); - initialize() { - if (_.isUndefined(_converse.controlboxtoggle)) { - _converse.controlboxtoggle = new _converse.ControlBoxToggle(); - } - - _converse.controlboxtoggle.el.insertAdjacentElement('afterend', this.el); - - this.model.on('change:connected', this.onConnected, this); - this.model.on('destroy', this.hide, this); - this.model.on('hide', this.hide, this); - this.model.on('show', this.show, this); - this.model.on('change:closed', this.ensureClosedState, this); - this.render(); - - if (this.model.get('connected')) { - this.insertRoster(); - } - - _converse.emit('controlboxInitialized', this); - }, - - render() { - if (this.model.get('connected')) { - if (_.isUndefined(this.model.get('closed'))) { - this.model.set('closed', !_converse.show_controlbox_by_default); - } - } - - this.el.innerHTML = tpl_controlbox(_.extend(this.model.toJSON())); - - if (!this.model.get('closed')) { - this.show(); + if (view.model.get('id') === 'controlbox') { + /* We return the width of the controlbox or its toggle, + * depending on which is visible. + */ + if (!controlbox || !u.isVisible(controlbox.el)) { + return u.getOuterWidth(_converse.controlboxtoggle.el, true); } else { - this.hide(); + return u.getOuterWidth(controlbox.el, true); } + } else { + return this.__super__.getChatBoxWidth.apply(this, arguments); + } + } - if (!_converse.connection.connected || !_converse.connection.authenticated || _converse.connection.disconnecting) { - this.renderLoginPanel(); - } else if (this.model.get('connected') && (!this.controlbox_pane || !u.isVisible(this.controlbox_pane.el))) { - this.renderControlBoxPane(); - } - - return this; - }, - - onConnected() { - if (this.model.get('connected')) { - this.render(); - this.insertRoster(); - } - }, - - insertRoster() { - if (_converse.authentication === _converse.ANONYMOUS) { - return; - } - /* Place the rosterview inside the "Contacts" panel. */ - - - _converse.api.waitUntil('rosterViewInitialized').then(() => this.controlbox_pane.el.insertAdjacentElement('beforeEnd', _converse.rosterview.el)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - createBrandHeadingHTML() { - return tpl_brand_heading({ - 'sticky_controlbox': _converse.sticky_controlbox + }, + ChatBox: { + initialize() { + if (this.get('id') === 'controlbox') { + this.set({ + 'time_opened': moment(0).valueOf() }); - }, + } else { + this.__super__.initialize.apply(this, arguments); + } + } - insertBrandHeading() { - const heading_el = this.el.querySelector('.brand-heading-container'); + }, + ChatBoxView: { + insertIntoDOM() { + const view = this.__super__._converse.chatboxviews.get("controlbox"); - if (_.isNull(heading_el)) { - const el = this.el.querySelector('.controlbox-head'); - el.insertAdjacentHTML('beforeend', this.createBrandHeadingHTML()); - } else { - heading_el.outerHTML = this.createBrandHeadingHTML(); + if (view) { + view.el.insertAdjacentElement('afterend', this.el); + } else { + this.__super__.insertIntoDOM.apply(this, arguments); + } + + return this; + } + + } + }, + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + + _converse.api.settings.update({ + allow_logout: true, + default_domain: undefined, + locked_domain: undefined, + show_controlbox_by_default: false, + sticky_controlbox: false + }); + + _converse.api.promises.add('controlboxInitialized'); + + _converse.addControlBox = () => { + return _converse.chatboxes.add({ + 'id': 'controlbox', + 'box_id': 'controlbox', + 'type': _converse.CONTROLBOX_TYPE, + 'closed': !_converse.show_controlbox_by_default + }); + }; + + _converse.ControlBoxView = _converse.ChatBoxView.extend({ + tagName: 'div', + className: 'chatbox', + id: 'controlbox', + events: { + 'click a.close-chatbox-button': 'close' + }, + + initialize() { + if (_.isUndefined(_converse.controlboxtoggle)) { + _converse.controlboxtoggle = new _converse.ControlBoxToggle(); + } + + _converse.controlboxtoggle.el.insertAdjacentElement('afterend', this.el); + + this.model.on('change:connected', this.onConnected, this); + this.model.on('destroy', this.hide, this); + this.model.on('hide', this.hide, this); + this.model.on('show', this.show, this); + this.model.on('change:closed', this.ensureClosedState, this); + this.render(); + + if (this.model.get('connected')) { + this.insertRoster(); + } + + _converse.emit('controlboxInitialized', this); + }, + + render() { + if (this.model.get('connected')) { + if (_.isUndefined(this.model.get('closed'))) { + this.model.set('closed', !_converse.show_controlbox_by_default); } - }, + } - renderLoginPanel() { - this.el.classList.add("logged-out"); + this.el.innerHTML = templates_controlbox_html__WEBPACK_IMPORTED_MODULE_8___default()(_.extend(this.model.toJSON())); - if (_.isNil(this.loginpanel)) { - this.loginpanel = new _converse.LoginPanel({ - 'model': new _converse.LoginPanelModel() - }); - const panes = this.el.querySelector('.controlbox-panes'); - panes.innerHTML = ''; - panes.appendChild(this.loginpanel.render().el); - this.insertBrandHeading(); - } else { - this.loginpanel.render(); - } + if (!this.model.get('closed')) { + this.show(); + } else { + this.hide(); + } - this.loginpanel.initPopovers(); - return this; - }, + if (!_converse.connection.connected || !_converse.connection.authenticated || _converse.connection.disconnecting) { + this.renderLoginPanel(); + } else if (this.model.get('connected') && (!this.controlbox_pane || !u.isVisible(this.controlbox_pane.el))) { + this.renderControlBoxPane(); + } - renderControlBoxPane() { - /* Renders the "Contacts" panel of the controlbox. - * - * This will only be called after the user has already been - * logged in. - */ - if (this.loginpanel) { - this.loginpanel.remove(); - delete this.loginpanel; - } + return this; + }, - this.el.classList.remove("logged-out"); - this.controlbox_pane = new _converse.ControlBoxPane(); - this.el.querySelector('.controlbox-panes').insertAdjacentElement('afterBegin', this.controlbox_pane.el); - }, + onConnected() { + if (this.model.get('connected')) { + this.render(); + this.insertRoster(); + } + }, - close(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } + insertRoster() { + if (_converse.authentication === _converse.ANONYMOUS) { + return; + } + /* Place the rosterview inside the "Contacts" panel. */ - if (_converse.sticky_controlbox) { - return; - } - if (_converse.connection.connected && !_converse.connection.disconnecting) { - this.model.save({ - 'closed': true - }); - } else { - this.model.trigger('hide'); - } + _converse.api.waitUntil('rosterViewInitialized').then(() => this.controlbox_pane.el.insertAdjacentElement('beforeEnd', _converse.rosterview.el)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, - _converse.emit('controlBoxClosed', this); + createBrandHeadingHTML() { + return templates_converse_brand_heading_html__WEBPACK_IMPORTED_MODULE_7___default()({ + 'sticky_controlbox': _converse.sticky_controlbox + }); + }, - return this; - }, + insertBrandHeading() { + const heading_el = this.el.querySelector('.brand-heading-container'); - ensureClosedState() { - if (this.model.get('closed')) { - this.hide(); - } else { - this.show(); - } - }, + if (_.isNull(heading_el)) { + const el = this.el.querySelector('.controlbox-head'); + el.insertAdjacentHTML('beforeend', this.createBrandHeadingHTML()); + } else { + heading_el.outerHTML = this.createBrandHeadingHTML(); + } + }, - hide(callback) { - if (_converse.sticky_controlbox) { - return; - } + renderLoginPanel() { + this.el.classList.add("logged-out"); - u.addClass('hidden', this.el); + if (_.isNil(this.loginpanel)) { + this.loginpanel = new _converse.LoginPanel({ + 'model': new _converse.LoginPanelModel() + }); + const panes = this.el.querySelector('.controlbox-panes'); + panes.innerHTML = ''; + panes.appendChild(this.loginpanel.render().el); + this.insertBrandHeading(); + } else { + this.loginpanel.render(); + } - _converse.emit('chatBoxClosed', this); + this.loginpanel.initPopovers(); + return this; + }, - if (!_converse.connection.connected) { - _converse.controlboxtoggle.render(); - } + renderControlBoxPane() { + /* Renders the "Contacts" panel of the controlbox. + * + * This will only be called after the user has already been + * logged in. + */ + if (this.loginpanel) { + this.loginpanel.remove(); + delete this.loginpanel; + } - _converse.controlboxtoggle.show(callback); + this.el.classList.remove("logged-out"); + this.controlbox_pane = new _converse.ControlBoxPane(); + this.el.querySelector('.controlbox-panes').insertAdjacentElement('afterBegin', this.controlbox_pane.el); + }, - return this; - }, + close(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } - onControlBoxToggleHidden() { - this.model.set('closed', false); - this.el.classList.remove('hidden'); - - _converse.emit('controlBoxOpened', this); - }, - - show() { - _converse.controlboxtoggle.hide(this.onControlBoxToggleHidden.bind(this)); - - return this; - }, - - showHelpMessages() { - /* Override showHelpMessages in ChatBoxView, for now do nothing. - * - * Parameters: - * (Array) msgs: Array of messages - */ + if (_converse.sticky_controlbox) { return; } - }); - _converse.LoginPanelModel = Backbone.Model.extend({ - defaults: { - // Passed-by-reference. Fine in this case because there's - // only one such model. - 'errors': [] - } - }); - _converse.LoginPanel = Backbone.VDOMView.extend({ - tagName: 'div', - id: "converse-login-panel", - className: 'controlbox-pane fade-in', - events: { - 'submit form#converse-login': 'authenticate', - 'change input': 'validate' - }, - - initialize(cfg) { - this.model.on('change', this.render, this); - this.listenTo(_converse.connfeedback, 'change', this.render); - this.render(); - }, - - toHTML() { - const connection_status = _converse.connfeedback.get('connection_status'); - - let feedback_class, pretty_status; - - if (_.includes(REPORTABLE_STATUSES, connection_status)) { - pretty_status = PRETTY_CONNECTION_STATUS[connection_status]; - feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status]; - } - - return tpl_login_panel(_.extend(this.model.toJSON(), { - '__': __, - '_converse': _converse, - 'ANONYMOUS': _converse.ANONYMOUS, - 'EXTERNAL': _converse.EXTERNAL, - 'LOGIN': _converse.LOGIN, - 'PREBIND': _converse.PREBIND, - 'auto_login': _converse.auto_login, - 'authentication': _converse.authentication, - 'connection_status': connection_status, - 'conn_feedback_class': feedback_class, - 'conn_feedback_subject': pretty_status, - 'conn_feedback_message': _converse.connfeedback.get('message'), - 'placeholder_username': (_converse.locked_domain || _converse.default_domain) && __('Username') || __('user@domain') - })); - }, - - initPopovers() { - _.forEach(this.el.querySelectorAll('[data-title]'), el => { - const popover = new bootstrap.Popover(el, { - 'trigger': _converse.view_mode === 'mobile' && 'click' || 'hover', - 'dismissible': _converse.view_mode === 'mobile' && true || false, - 'container': this.el.parentElement.parentElement.parentElement - }); + if (_converse.connection.connected && !_converse.connection.disconnecting) { + this.model.save({ + 'closed': true }); - }, - - validate() { - const form = this.el.querySelector('form'); - const jid_element = form.querySelector('input[name=jid]'); - - if (jid_element.value && !_converse.locked_domain && !_converse.default_domain && !u.isValidJID(jid_element.value)) { - jid_element.setCustomValidity(__('Please enter a valid XMPP address')); - return false; - } - - jid_element.setCustomValidity(''); - return true; - }, - - authenticate(ev) { - /* Authenticate the user based on a form submission event. - */ - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - if (_converse.authentication === _converse.ANONYMOUS) { - this.connect(_converse.jid, null); - return; - } - - if (!this.validate()) { - return; - } - - const form_data = new FormData(ev.target); - - _converse.config.save({ - 'trusted': form_data.get('trusted') && true || false, - 'storage': form_data.get('trusted') ? 'local' : 'session' - }); - - let jid = form_data.get('jid'); - - if (_converse.locked_domain) { - const last_part = '@' + _converse.locked_domain; - - if (jid.endsWith(last_part)) { - jid = jid.substr(0, jid.length - last_part.length); - } - - jid = Strophe.escapeNode(jid) + last_part; - } else if (_converse.default_domain && !_.includes(jid, '@')) { - jid = jid + '@' + _converse.default_domain; - } - - this.connect(jid, form_data.get('password')); - }, - - connect(jid, password) { - if (jid) { - const resource = Strophe.getResourceFromJid(jid); - - if (!resource) { - jid = jid.toLowerCase() + _converse.generateResource(); - } else { - jid = Strophe.getBareJidFromJid(jid).toLowerCase() + '/' + resource; - } - } - - if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) { - _converse.router.navigate('', { - 'replace': true - }); - } - - _converse.connection.reset(); - - _converse.connection.connect(jid, password, _converse.onConnectStatusChanged); + } else { + this.model.trigger('hide'); } - }); - _converse.ControlBoxPane = Backbone.NativeView.extend({ - tagName: 'div', - className: 'controlbox-pane', + _converse.emit('controlBoxClosed', this); - initialize() { - _converse.xmppstatusview = new _converse.XMPPStatusView({ - 'model': _converse.xmppstatus - }); - this.el.insertAdjacentElement('afterBegin', _converse.xmppstatusview.render().el); + return this; + }, + + ensureClosedState() { + if (this.model.get('closed')) { + this.hide(); + } else { + this.show(); + } + }, + + hide(callback) { + if (_converse.sticky_controlbox) { + return; } - }); - _converse.ControlBoxToggle = Backbone.NativeView.extend({ - tagName: 'a', - className: 'toggle-controlbox hidden', - id: 'toggle-controlbox', - events: { - 'click': 'onClick' - }, - attributes: { - 'href': "#" - }, + u.addClass('hidden', this.el); - initialize() { - _converse.chatboxviews.insertRowColumn(this.render().el); + _converse.emit('chatBoxClosed', this); - _converse.api.waitUntil('initialized').then(this.render.bind(this)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, + if (!_converse.connection.connected) { + _converse.controlboxtoggle.render(); + } - render() { - // We let the render method of ControlBoxView decide whether - // the ControlBox or the Toggle must be shown. This prevents - // artifacts (i.e. on page load the toggle is shown only to then - // seconds later be hidden in favor of the control box). - this.el.innerHTML = tpl_controlbox_toggle({ - 'label_toggle': _converse.connection.connected ? __('Chat Contacts') : __('Toggle chat') + _converse.controlboxtoggle.show(callback); + + return this; + }, + + onControlBoxToggleHidden() { + this.model.set('closed', false); + this.el.classList.remove('hidden'); + + _converse.emit('controlBoxOpened', this); + }, + + show() { + _converse.controlboxtoggle.hide(this.onControlBoxToggleHidden.bind(this)); + + return this; + }, + + showHelpMessages() { + /* Override showHelpMessages in ChatBoxView, for now do nothing. + * + * Parameters: + * (Array) msgs: Array of messages + */ + return; + } + + }); + _converse.LoginPanelModel = Backbone.Model.extend({ + defaults: { + // Passed-by-reference. Fine in this case because there's + // only one such model. + 'errors': [] + } + }); + _converse.LoginPanel = Backbone.VDOMView.extend({ + tagName: 'div', + id: "converse-login-panel", + className: 'controlbox-pane fade-in', + events: { + 'submit form#converse-login': 'authenticate', + 'change input': 'validate' + }, + + initialize(cfg) { + this.model.on('change', this.render, this); + this.listenTo(_converse.connfeedback, 'change', this.render); + this.render(); + }, + + toHTML() { + const connection_status = _converse.connfeedback.get('connection_status'); + + let feedback_class, pretty_status; + + if (_.includes(REPORTABLE_STATUSES, connection_status)) { + pretty_status = PRETTY_CONNECTION_STATUS[connection_status]; + feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status]; + } + + return templates_login_panel_html__WEBPACK_IMPORTED_MODULE_10___default()(_.extend(this.model.toJSON(), { + '__': __, + '_converse': _converse, + 'ANONYMOUS': _converse.ANONYMOUS, + 'EXTERNAL': _converse.EXTERNAL, + 'LOGIN': _converse.LOGIN, + 'PREBIND': _converse.PREBIND, + 'auto_login': _converse.auto_login, + 'authentication': _converse.authentication, + 'connection_status': connection_status, + 'conn_feedback_class': feedback_class, + 'conn_feedback_subject': pretty_status, + 'conn_feedback_message': _converse.connfeedback.get('message'), + 'placeholder_username': (_converse.locked_domain || _converse.default_domain) && __('Username') || __('user@domain') + })); + }, + + initPopovers() { + _.forEach(this.el.querySelectorAll('[data-title]'), el => { + const popover = new bootstrap__WEBPACK_IMPORTED_MODULE_4___default.a.Popover(el, { + 'trigger': _converse.view_mode === 'mobile' && 'click' || 'hover', + 'dismissible': _converse.view_mode === 'mobile' && true || false, + 'container': this.el.parentElement.parentElement.parentElement }); - return this; - }, + }); + }, - hide(callback) { - u.hideElement(this.el); - callback(); - }, + validate() { + const form = this.el.querySelector('form'); + const jid_element = form.querySelector('input[name=jid]'); - show(callback) { - u.fadeIn(this.el, callback); - }, + if (jid_element.value && !_converse.locked_domain && !_converse.default_domain && !u.isValidJID(jid_element.value)) { + jid_element.setCustomValidity(__('Please enter a valid XMPP address')); + return false; + } - showControlBox() { - let controlbox = _converse.chatboxes.get('controlbox'); + jid_element.setCustomValidity(''); + return true; + }, - if (!controlbox) { - controlbox = _converse.addControlBox(); + authenticate(ev) { + /* Authenticate the user based on a form submission event. + */ + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + if (_converse.authentication === _converse.ANONYMOUS) { + this.connect(_converse.jid, null); + return; + } + + if (!this.validate()) { + return; + } + + const form_data = new FormData(ev.target); + + _converse.config.save({ + 'trusted': form_data.get('trusted') && true || false, + 'storage': form_data.get('trusted') ? 'local' : 'session' + }); + + let jid = form_data.get('jid'); + + if (_converse.locked_domain) { + const last_part = '@' + _converse.locked_domain; + + if (jid.endsWith(last_part)) { + jid = jid.substr(0, jid.length - last_part.length); } + jid = Strophe.escapeNode(jid) + last_part; + } else if (_converse.default_domain && !_.includes(jid, '@')) { + jid = jid + '@' + _converse.default_domain; + } + + this.connect(jid, form_data.get('password')); + }, + + connect(jid, password) { + if (jid) { + const resource = Strophe.getResourceFromJid(jid); + + if (!resource) { + jid = jid.toLowerCase() + _converse.generateResource(); + } else { + jid = Strophe.getBareJidFromJid(jid).toLowerCase() + '/' + resource; + } + } + + if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) { + _converse.router.navigate('', { + 'replace': true + }); + } + + _converse.connection.reset(); + + _converse.connection.connect(jid, password, _converse.onConnectStatusChanged); + } + + }); + _converse.ControlBoxPane = Backbone.NativeView.extend({ + tagName: 'div', + className: 'controlbox-pane', + + initialize() { + _converse.xmppstatusview = new _converse.XMPPStatusView({ + 'model': _converse.xmppstatus + }); + this.el.insertAdjacentElement('afterBegin', _converse.xmppstatusview.render().el); + } + + }); + _converse.ControlBoxToggle = Backbone.NativeView.extend({ + tagName: 'a', + className: 'toggle-controlbox hidden', + id: 'toggle-controlbox', + events: { + 'click': 'onClick' + }, + attributes: { + 'href': "#" + }, + + initialize() { + _converse.chatboxviews.insertRowColumn(this.render().el); + + _converse.api.waitUntil('initialized').then(this.render.bind(this)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + render() { + // We let the render method of ControlBoxView decide whether + // the ControlBox or the Toggle must be shown. This prevents + // artifacts (i.e. on page load the toggle is shown only to then + // seconds later be hidden in favor of the control box). + this.el.innerHTML = templates_controlbox_toggle_html__WEBPACK_IMPORTED_MODULE_9___default()({ + 'label_toggle': _converse.connection.connected ? __('Chat Contacts') : __('Toggle chat') + }); + return this; + }, + + hide(callback) { + u.hideElement(this.el); + callback(); + }, + + show(callback) { + u.fadeIn(this.el, callback); + }, + + showControlBox() { + let controlbox = _converse.chatboxes.get('controlbox'); + + if (!controlbox) { + controlbox = _converse.addControlBox(); + } + + if (_converse.connection.connected) { + controlbox.save({ + closed: false + }); + } else { + controlbox.trigger('show'); + } + }, + + onClick(e) { + e.preventDefault(); + + if (u.isVisible(_converse.root.querySelector("#controlbox"))) { + const controlbox = _converse.chatboxes.get('controlbox'); + if (_converse.connection.connected) { controlbox.save({ - closed: false + closed: true }); } else { - controlbox.trigger('show'); + controlbox.trigger('hide'); } - }, + } else { + this.showControlBox(); + } + } - onClick(e) { - e.preventDefault(); + }); - if (u.isVisible(_converse.root.querySelector("#controlbox"))) { - const controlbox = _converse.chatboxes.get('controlbox'); + _converse.on('chatBoxViewsInitialized', () => { + const that = _converse.chatboxviews; - if (_converse.connection.connected) { - controlbox.save({ - closed: true - }); - } else { - controlbox.trigger('hide'); - } + _converse.chatboxes.on('add', item => { + if (item.get('type') === _converse.CONTROLBOX_TYPE) { + const view = that.get(item.get('id')); + + if (view) { + view.model = item; + view.initialize(); } else { - this.showControlBox(); - } - } - - }); - - _converse.on('chatBoxViewsInitialized', () => { - const that = _converse.chatboxviews; - - _converse.chatboxes.on('add', item => { - if (item.get('type') === _converse.CONTROLBOX_TYPE) { - const view = that.get(item.get('id')); - - if (view) { - view.model = item; - view.initialize(); - } else { - that.add(item.get('id'), new _converse.ControlBoxView({ - model: item - })); - } - } - }); - }); - - _converse.on('clearSession', () => { - if (_converse.config.get('trusted')) { - const chatboxes = _.get(_converse, 'chatboxes', null); - - if (!_.isNil(chatboxes)) { - const controlbox = chatboxes.get('controlbox'); - - if (controlbox && controlbox.collection && controlbox.collection.browserStorage) { - controlbox.save({ - 'connected': false - }); - } + that.add(item.get('id'), new _converse.ControlBoxView({ + model: item + })); } } }); + }); - Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxViewsInitialized')]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + _converse.on('clearSession', () => { + if (_converse.config.get('trusted')) { + const chatboxes = _.get(_converse, 'chatboxes', null); - _converse.on('chatBoxesFetched', () => { - const controlbox = _converse.chatboxes.get('controlbox') || _converse.addControlBox(); + if (!_.isNil(chatboxes)) { + const controlbox = chatboxes.get('controlbox'); - controlbox.save({ - connected: true - }); + if (controlbox && controlbox.collection && controlbox.collection.browserStorage) { + controlbox.save({ + 'connected': false + }); + } + } + } + }); + + Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxViewsInitialized')]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + + _converse.on('chatBoxesFetched', () => { + const controlbox = _converse.chatboxes.get('controlbox') || _converse.addControlBox(); + + controlbox.save({ + connected: true }); + }); - const disconnect = function disconnect() { - /* Upon disconnection, set connected to `false`, so that if - * we reconnect, "onConnected" will be called, - * to fetch the roster again and to send out a presence stanza. - */ - const view = _converse.chatboxviews.get('controlbox'); + const disconnect = function disconnect() { + /* Upon disconnection, set connected to `false`, so that if + * we reconnect, "onConnected" will be called, + * to fetch the roster again and to send out a presence stanza. + */ + const view = _converse.chatboxviews.get('controlbox'); - view.model.set({ - 'connected': false - }); - return view; - }; + view.model.set({ + 'connected': false + }); + return view; + }; - _converse.on('disconnected', () => disconnect().renderLoginPanel()); + _converse.on('disconnected', () => disconnect().renderLoginPanel()); - _converse.on('will-reconnect', disconnect); - } + _converse.on('will-reconnect', disconnect); + } - }); }); /***/ }), @@ -60726,10 +60810,17 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!************************************!*\ !*** ./src/converse-dragresize.js ***! \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_chatview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"); +/* harmony import */ var converse_controlbox__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_dragresize_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/dragresize.html */ "./src/templates/dragresize.html"); +/* harmony import */ var templates_dragresize_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_dragresize_html__WEBPACK_IMPORTED_MODULE_3__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand @@ -60737,409 +60828,404 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ // /*global define, window, document */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/dragresize.html */ "./src/templates/dragresize.html"), __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"), __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_dragresize) { - "use strict"; - const _ = converse.env._; - function renderDragResizeHandles(_converse, view) { - const flyout = view.el.querySelector('.box-flyout'); - const div = document.createElement('div'); - div.innerHTML = tpl_dragresize(); - flyout.insertBefore(div, flyout.firstChild); + + +const _ = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env._; + +function renderDragResizeHandles(_converse, view) { + const flyout = view.el.querySelector('.box-flyout'); + const div = document.createElement('div'); + div.innerHTML = templates_dragresize_html__WEBPACK_IMPORTED_MODULE_3___default()(); + flyout.insertBefore(div, flyout.firstChild); +} + +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-dragresize', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatview", "converse-headline", "converse-muc-views"], + + enabled(_converse) { + return _converse.view_mode == 'overlayed'; + }, + + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + registerGlobalEventHandlers() { + const that = this; + document.addEventListener('mousemove', function (ev) { + if (!that.resizing || !that.allow_dragresize) { + return true; + } + + ev.preventDefault(); + that.resizing.chatbox.resizeChatBox(ev); + }); + document.addEventListener('mouseup', function (ev) { + if (!that.resizing || !that.allow_dragresize) { + return true; + } + + ev.preventDefault(); + const height = that.applyDragResistance(that.resizing.chatbox.height, that.resizing.chatbox.model.get('default_height')); + const width = that.applyDragResistance(that.resizing.chatbox.width, that.resizing.chatbox.model.get('default_width')); + + if (that.connection.connected) { + that.resizing.chatbox.model.save({ + 'height': height + }); + that.resizing.chatbox.model.save({ + 'width': width + }); + } else { + that.resizing.chatbox.model.set({ + 'height': height + }); + that.resizing.chatbox.model.set({ + 'width': width + }); + } + + that.resizing = null; + }); + return this.__super__.registerGlobalEventHandlers.apply(this, arguments); + }, + + ChatBox: { + initialize() { + const _converse = this.__super__._converse; + + const result = this.__super__.initialize.apply(this, arguments), + height = this.get('height'), + width = this.get('width'), + save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this); + + save({ + 'height': _converse.applyDragResistance(height, this.get('default_height')), + 'width': _converse.applyDragResistance(width, this.get('default_width')) + }); + return result; + } + + }, + ChatBoxView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, + + initialize() { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + + this.__super__.initialize.apply(this, arguments); + }, + + render() { + const result = this.__super__.render.apply(this, arguments); + + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + }, + + setWidth() { + // If a custom width is applied (due to drag-resizing), + // then we need to set the width of the .chatbox element as well. + if (this.model.get('width')) { + this.el.style.width = this.model.get('width'); + } + }, + + _show() { + this.initDragResize().setDimensions(); + + this.__super__._show.apply(this, arguments); + }, + + initDragResize() { + /* Determine and store the default box size. + * We need this information for the drag-resizing feature. + */ + const _converse = this.__super__._converse, + flyout = this.el.querySelector('.box-flyout'), + style = window.getComputedStyle(flyout); + + if (_.isUndefined(this.model.get('height'))) { + const height = parseInt(style.height.replace(/px$/, ''), 10), + width = parseInt(style.width.replace(/px$/, ''), 10); + this.model.set('height', height); + this.model.set('default_height', height); + this.model.set('width', width); + this.model.set('default_width', width); + } + + const min_width = style['min-width']; + const min_height = style['min-height']; + this.model.set('min_width', min_width.endsWith('px') ? Number(min_width.replace(/px$/, '')) : 0); + this.model.set('min_height', min_height.endsWith('px') ? Number(min_height.replace(/px$/, '')) : 0); // Initialize last known mouse position + + this.prev_pageY = 0; + this.prev_pageX = 0; + + if (_converse.connection.connected) { + this.height = this.model.get('height'); + this.width = this.model.get('width'); + } + + return this; + }, + + setDimensions() { + // Make sure the chat box has the right height and width. + this.adjustToViewport(); + this.setChatBoxHeight(this.model.get('height')); + this.setChatBoxWidth(this.model.get('width')); + }, + + setChatBoxHeight(height) { + const _converse = this.__super__._converse; + + if (height) { + height = _converse.applyDragResistance(height, this.model.get('default_height')) + 'px'; + } else { + height = ""; + } + + const flyout_el = this.el.querySelector('.box-flyout'); + + if (!_.isNull(flyout_el)) { + flyout_el.style.height = height; + } + }, + + setChatBoxWidth(width) { + const _converse = this.__super__._converse; + + if (width) { + width = _converse.applyDragResistance(width, this.model.get('default_width')) + 'px'; + } else { + width = ""; + } + + this.el.style.width = width; + const flyout_el = this.el.querySelector('.box-flyout'); + + if (!_.isNull(flyout_el)) { + flyout_el.style.width = width; + } + }, + + adjustToViewport() { + /* Event handler called when viewport gets resized. We remove + * custom width/height from chat boxes. + */ + const viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + + if (viewport_width <= 480) { + this.model.set('height', undefined); + this.model.set('width', undefined); + } else if (viewport_width <= this.model.get('width')) { + this.model.set('width', undefined); + } else if (viewport_height <= this.model.get('height')) { + this.model.set('height', undefined); + } + }, + + onStartVerticalResize(ev) { + const _converse = this.__super__._converse; + + if (!_converse.allow_dragresize) { + return true; + } // Record element attributes for mouseMove(). + + + const flyout = this.el.querySelector('.box-flyout'), + style = window.getComputedStyle(flyout); + this.height = parseInt(style.height.replace(/px$/, ''), 10); + _converse.resizing = { + 'chatbox': this, + 'direction': 'top' + }; + this.prev_pageY = ev.pageY; + }, + + onStartHorizontalResize(ev) { + const _converse = this.__super__._converse; + + if (!_converse.allow_dragresize) { + return true; + } + + const flyout = this.el.querySelector('.box-flyout'), + style = window.getComputedStyle(flyout); + this.width = parseInt(style.width.replace(/px$/, ''), 10); + _converse.resizing = { + 'chatbox': this, + 'direction': 'left' + }; + this.prev_pageX = ev.pageX; + }, + + onStartDiagonalResize(ev) { + const _converse = this.__super__._converse; + this.onStartHorizontalResize(ev); + this.onStartVerticalResize(ev); + _converse.resizing.direction = 'topleft'; + }, + + resizeChatBox(ev) { + let diff; + const _converse = this.__super__._converse; + + if (_converse.resizing.direction.indexOf('top') === 0) { + diff = ev.pageY - this.prev_pageY; + + if (diff) { + this.height = this.height - diff > (this.model.get('min_height') || 0) ? this.height - diff : this.model.get('min_height'); + this.prev_pageY = ev.pageY; + this.setChatBoxHeight(this.height); + } + } + + if (_.includes(_converse.resizing.direction, 'left')) { + diff = this.prev_pageX - ev.pageX; + + if (diff) { + this.width = this.width + diff > (this.model.get('min_width') || 0) ? this.width + diff : this.model.get('min_width'); + this.prev_pageX = ev.pageX; + this.setChatBoxWidth(this.width); + } + } + } + + }, + HeadlinesBoxView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, + + initialize() { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + return this.__super__.initialize.apply(this, arguments); + }, + + render() { + const result = this.__super__.render.apply(this, arguments); + + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + } + + }, + ControlBoxView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, + + initialize() { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + + this.__super__.initialize.apply(this, arguments); + }, + + render() { + const result = this.__super__.render.apply(this, arguments); + + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + }, + + renderLoginPanel() { + const result = this.__super__.renderLoginPanel.apply(this, arguments); + + this.initDragResize().setDimensions(); + return result; + }, + + renderControlBoxPane() { + const result = this.__super__.renderControlBoxPane.apply(this, arguments); + + this.initDragResize().setDimensions(); + return result; + } + + }, + ChatRoomView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, + + initialize() { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + + this.__super__.initialize.apply(this, arguments); + }, + + render() { + const result = this.__super__.render.apply(this, arguments); + + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + } + + } + }, + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse; + + _converse.api.settings.update({ + allow_dragresize: true + }); + + _converse.applyDragResistance = function (value, default_value) { + /* This method applies some resistance around the + * default_value. If value is close enough to + * default_value, then default_value is returned instead. + */ + if (_.isUndefined(value)) { + return undefined; + } else if (_.isUndefined(default_value)) { + return value; + } + + const resistance = 10; + + if (value !== default_value && Math.abs(value - default_value) < resistance) { + return default_value; + } + + return value; + }; } - converse.plugins.add('converse-dragresize', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatview", "converse-headline", "converse-muc-views"], - - enabled(_converse) { - return _converse.view_mode == 'overlayed'; - }, - - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - registerGlobalEventHandlers() { - const that = this; - document.addEventListener('mousemove', function (ev) { - if (!that.resizing || !that.allow_dragresize) { - return true; - } - - ev.preventDefault(); - that.resizing.chatbox.resizeChatBox(ev); - }); - document.addEventListener('mouseup', function (ev) { - if (!that.resizing || !that.allow_dragresize) { - return true; - } - - ev.preventDefault(); - const height = that.applyDragResistance(that.resizing.chatbox.height, that.resizing.chatbox.model.get('default_height')); - const width = that.applyDragResistance(that.resizing.chatbox.width, that.resizing.chatbox.model.get('default_width')); - - if (that.connection.connected) { - that.resizing.chatbox.model.save({ - 'height': height - }); - that.resizing.chatbox.model.save({ - 'width': width - }); - } else { - that.resizing.chatbox.model.set({ - 'height': height - }); - that.resizing.chatbox.model.set({ - 'width': width - }); - } - - that.resizing = null; - }); - return this.__super__.registerGlobalEventHandlers.apply(this, arguments); - }, - - ChatBox: { - initialize() { - const _converse = this.__super__._converse; - - const result = this.__super__.initialize.apply(this, arguments), - height = this.get('height'), - width = this.get('width'), - save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this); - - save({ - 'height': _converse.applyDragResistance(height, this.get('default_height')), - 'width': _converse.applyDragResistance(width, this.get('default_width')) - }); - return result; - } - - }, - ChatBoxView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, - - initialize() { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - - this.__super__.initialize.apply(this, arguments); - }, - - render() { - const result = this.__super__.render.apply(this, arguments); - - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; - }, - - setWidth() { - // If a custom width is applied (due to drag-resizing), - // then we need to set the width of the .chatbox element as well. - if (this.model.get('width')) { - this.el.style.width = this.model.get('width'); - } - }, - - _show() { - this.initDragResize().setDimensions(); - - this.__super__._show.apply(this, arguments); - }, - - initDragResize() { - /* Determine and store the default box size. - * We need this information for the drag-resizing feature. - */ - const _converse = this.__super__._converse, - flyout = this.el.querySelector('.box-flyout'), - style = window.getComputedStyle(flyout); - - if (_.isUndefined(this.model.get('height'))) { - const height = parseInt(style.height.replace(/px$/, ''), 10), - width = parseInt(style.width.replace(/px$/, ''), 10); - this.model.set('height', height); - this.model.set('default_height', height); - this.model.set('width', width); - this.model.set('default_width', width); - } - - const min_width = style['min-width']; - const min_height = style['min-height']; - this.model.set('min_width', min_width.endsWith('px') ? Number(min_width.replace(/px$/, '')) : 0); - this.model.set('min_height', min_height.endsWith('px') ? Number(min_height.replace(/px$/, '')) : 0); // Initialize last known mouse position - - this.prev_pageY = 0; - this.prev_pageX = 0; - - if (_converse.connection.connected) { - this.height = this.model.get('height'); - this.width = this.model.get('width'); - } - - return this; - }, - - setDimensions() { - // Make sure the chat box has the right height and width. - this.adjustToViewport(); - this.setChatBoxHeight(this.model.get('height')); - this.setChatBoxWidth(this.model.get('width')); - }, - - setChatBoxHeight(height) { - const _converse = this.__super__._converse; - - if (height) { - height = _converse.applyDragResistance(height, this.model.get('default_height')) + 'px'; - } else { - height = ""; - } - - const flyout_el = this.el.querySelector('.box-flyout'); - - if (!_.isNull(flyout_el)) { - flyout_el.style.height = height; - } - }, - - setChatBoxWidth(width) { - const _converse = this.__super__._converse; - - if (width) { - width = _converse.applyDragResistance(width, this.model.get('default_width')) + 'px'; - } else { - width = ""; - } - - this.el.style.width = width; - const flyout_el = this.el.querySelector('.box-flyout'); - - if (!_.isNull(flyout_el)) { - flyout_el.style.width = width; - } - }, - - adjustToViewport() { - /* Event handler called when viewport gets resized. We remove - * custom width/height from chat boxes. - */ - const viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - - if (viewport_width <= 480) { - this.model.set('height', undefined); - this.model.set('width', undefined); - } else if (viewport_width <= this.model.get('width')) { - this.model.set('width', undefined); - } else if (viewport_height <= this.model.get('height')) { - this.model.set('height', undefined); - } - }, - - onStartVerticalResize(ev) { - const _converse = this.__super__._converse; - - if (!_converse.allow_dragresize) { - return true; - } // Record element attributes for mouseMove(). - - - const flyout = this.el.querySelector('.box-flyout'), - style = window.getComputedStyle(flyout); - this.height = parseInt(style.height.replace(/px$/, ''), 10); - _converse.resizing = { - 'chatbox': this, - 'direction': 'top' - }; - this.prev_pageY = ev.pageY; - }, - - onStartHorizontalResize(ev) { - const _converse = this.__super__._converse; - - if (!_converse.allow_dragresize) { - return true; - } - - const flyout = this.el.querySelector('.box-flyout'), - style = window.getComputedStyle(flyout); - this.width = parseInt(style.width.replace(/px$/, ''), 10); - _converse.resizing = { - 'chatbox': this, - 'direction': 'left' - }; - this.prev_pageX = ev.pageX; - }, - - onStartDiagonalResize(ev) { - const _converse = this.__super__._converse; - this.onStartHorizontalResize(ev); - this.onStartVerticalResize(ev); - _converse.resizing.direction = 'topleft'; - }, - - resizeChatBox(ev) { - let diff; - const _converse = this.__super__._converse; - - if (_converse.resizing.direction.indexOf('top') === 0) { - diff = ev.pageY - this.prev_pageY; - - if (diff) { - this.height = this.height - diff > (this.model.get('min_height') || 0) ? this.height - diff : this.model.get('min_height'); - this.prev_pageY = ev.pageY; - this.setChatBoxHeight(this.height); - } - } - - if (_.includes(_converse.resizing.direction, 'left')) { - diff = this.prev_pageX - ev.pageX; - - if (diff) { - this.width = this.width + diff > (this.model.get('min_width') || 0) ? this.width + diff : this.model.get('min_width'); - this.prev_pageX = ev.pageX; - this.setChatBoxWidth(this.width); - } - } - } - - }, - HeadlinesBoxView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, - - initialize() { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - return this.__super__.initialize.apply(this, arguments); - }, - - render() { - const result = this.__super__.render.apply(this, arguments); - - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; - } - - }, - ControlBoxView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, - - initialize() { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - - this.__super__.initialize.apply(this, arguments); - }, - - render() { - const result = this.__super__.render.apply(this, arguments); - - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; - }, - - renderLoginPanel() { - const result = this.__super__.renderLoginPanel.apply(this, arguments); - - this.initDragResize().setDimensions(); - return result; - }, - - renderControlBoxPane() { - const result = this.__super__.renderControlBoxPane.apply(this, arguments); - - this.initDragResize().setDimensions(); - return result; - } - - }, - ChatRoomView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, - - initialize() { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - - this.__super__.initialize.apply(this, arguments); - }, - - render() { - const result = this.__super__.render.apply(this, arguments); - - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; - } - - } - }, - - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse; - - _converse.api.settings.update({ - allow_dragresize: true - }); - - _converse.applyDragResistance = function (value, default_value) { - /* This method applies some resistance around the - * default_value. If value is close enough to - * default_value, then default_value is returned instead. - */ - if (_.isUndefined(value)) { - return undefined; - } else if (_.isUndefined(default_value)) { - return value; - } - - const resistance = 10; - - if (value !== default_value && Math.abs(value - default_value) < resistance) { - return default_value; - } - - return value; - }; - } - - }); }); /***/ }), @@ -61148,55 +61234,52 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**********************************!*\ !*** ./src/converse-embedded.js ***! \**********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_muc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - _ = _converse$env._; - converse.plugins.add('converse-embedded', { - enabled(_converse) { - return _converse.view_mode === 'embedded'; - }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - this._converse.api.settings.update({ - 'allow_logout': false, - // No point in logging out when we have auto_login as true. - 'allow_muc_invitations': false, - // Doesn't make sense to allow because only - // roster contacts can be invited - 'hide_muc_server': true - }); +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env, + Backbone = _converse$env.Backbone, + _ = _converse$env._; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-embedded', { + enabled(_converse) { + return _converse.view_mode === 'embedded'; + }, - const _converse = this._converse; + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + this._converse.api.settings.update({ + 'allow_logout': false, + // No point in logging out when we have auto_login as true. + 'allow_muc_invitations': false, + // Doesn't make sense to allow because only + // roster contacts can be invited + 'hide_muc_server': true + }); - if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) { - throw new Error("converse-embedded: auto_join_rooms must be an Array"); - } + const _converse = this._converse; - if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) { - throw new Error("converse-embedded: It doesn't make " + "sense to have the auto_join_rooms setting more then one, " + "since only one chat room can be open at any time."); - } + if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) { + throw new Error("converse-embedded: auto_join_rooms must be an Array"); } - }); + if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) { + throw new Error("converse-embedded: It doesn't make " + "sense to have the auto_join_rooms setting more then one, " + "since only one chat room can be open at any time."); + } + } + }); /***/ }), @@ -61205,66 +61288,70 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!************************************!*\ !*** ./src/converse-fullscreen.js ***! \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_muc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"); +/* harmony import */ var converse_chatview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"); +/* harmony import */ var converse_controlbox__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js"); +/* harmony import */ var converse_singleton__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! converse-singleton */ "./src/converse-singleton.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_inverse_brand_heading_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/inverse_brand_heading.html */ "./src/templates/inverse_brand_heading.html"); +/* harmony import */ var templates_inverse_brand_heading_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_inverse_brand_heading_html__WEBPACK_IMPORTED_MODULE_5__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) JC Brand // Licensed under the Mozilla Public License (MPLv2) // -/*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/inverse_brand_heading.html */ "./src/templates/inverse_brand_heading.html"), __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"), __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js"), __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"), __webpack_require__(/*! converse-singleton */ "./src/converse-singleton.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_brand_heading) { - "use strict"; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - _ = _converse$env._; - converse.plugins.add('converse-fullscreen', { - enabled(_converse) { - return _.includes(['fullscreen', 'embedded'], _converse.view_mode); - }, - overrides: { - // overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // new functions which don't exist yet can also be added. - ControlBoxView: { - createBrandHeadingHTML() { - return tpl_brand_heading(); - }, - insertBrandHeading() { - const _converse = this.__super__._converse; - const el = _converse.root.getElementById('converse-login-panel'); - el.parentNode.insertAdjacentHTML('afterbegin', this.createBrandHeadingHTML()); - } +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].env, + Strophe = _converse$env.Strophe, + _ = _converse$env._; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins.add('converse-fullscreen', { + enabled(_converse) { + return _.includes(['fullscreen', 'embedded'], _converse.view_mode); + }, + overrides: { + // overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // new functions which don't exist yet can also be added. + ControlBoxView: { + createBrandHeadingHTML() { + return templates_inverse_brand_heading_html__WEBPACK_IMPORTED_MODULE_5___default()(); + }, + + insertBrandHeading() { + const _converse = this.__super__._converse; + + const el = _converse.root.getElementById('converse-login-panel'); + + el.parentNode.insertAdjacentHTML('afterbegin', this.createBrandHeadingHTML()); } - }, - initialize() { - this._converse.api.settings.update({ - chatview_avatar_height: 50, - chatview_avatar_width: 50, - hide_open_bookmarks: true, - show_controlbox_by_default: true, - sticky_controlbox: true - }); } + }, + + initialize() { + this._converse.api.settings.update({ + chatview_avatar_height: 50, + chatview_avatar_width: 50, + hide_open_bookmarks: true, + show_controlbox_by_default: true, + sticky_controlbox: true + }); + } - }); }); /***/ }), @@ -61273,171 +61360,168 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**********************************!*\ !*** ./src/converse-headline.js ***! \**********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_chatview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_chatbox_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! templates/chatbox.html */ "./src/templates/chatbox.html"); +/* harmony import */ var templates_chatbox_html__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(templates_chatbox_html__WEBPACK_IMPORTED_MODULE_2__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) -// -/*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/chatbox.html */ "./src/templates/chatbox.html"), __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_chatbox) { - "use strict"; - const _converse$env = converse.env, - _ = _converse$env._, - utils = _converse$env.utils; - converse.plugins.add('converse-headline', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env, + _ = _converse$env._, + utils = _converse$env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-headline', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatview"], + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + ChatBoxes: { + model(attrs, options) { + const _converse = this.__super__._converse; + + if (attrs.type == _converse.HEADLINES_TYPE) { + return new _converse.HeadlinesBox(attrs, options); + } else { + return this.__super__.model.apply(this, arguments); + } + } + + } + }, + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. */ - dependencies: ["converse-chatview"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - ChatBoxes: { - model(attrs, options) { - const _converse = this.__super__._converse; + const _converse = this._converse, + __ = _converse.__; + _converse.HeadlinesBox = _converse.ChatBox.extend({ + defaults: { + 'type': _converse.HEADLINES_TYPE, + 'bookmarked': false, + 'chat_state': undefined, + 'num_unread': 0, + 'url': '' + } + }); + _converse.HeadlinesBoxView = _converse.ChatBoxView.extend({ + className: 'chatbox headlines', + events: { + 'click .close-chatbox-button': 'close', + 'click .toggle-chatbox-button': 'minimize', + 'keypress textarea.chat-textarea': 'keyPressed' + }, - if (attrs.type == _converse.HEADLINES_TYPE) { - return new _converse.HeadlinesBox(attrs, options); - } else { - return this.__super__.model.apply(this, arguments); - } + initialize() { + this.initDebounced(); + this.disable_mam = true; // Don't do MAM queries for this box + + this.model.messages.on('add', this.onMessageAdded, this); + this.model.on('show', this.show, this); + this.model.on('destroy', this.hide, this); + this.model.on('change:minimized', this.onMinimizedChanged, this); + this.render().insertHeading().fetchMessages().insertIntoDOM().hide(); + + _converse.emit('chatBoxOpened', this); + + _converse.emit('chatBoxInitialized', this); + }, + + render() { + this.el.setAttribute('id', this.model.get('box_id')); + this.el.innerHTML = templates_chatbox_html__WEBPACK_IMPORTED_MODULE_2___default()(_.extend(this.model.toJSON(), { + info_close: '', + label_personal_message: '', + show_send_button: false, + show_toolbar: false, + unread_msgs: '' + })); + this.content = this.el.querySelector('.chat-content'); + return this; + }, + + // Override to avoid the methods in converse-chatview.js + 'renderMessageForm': _.noop, + 'afterShown': _.noop + }); + + function onHeadlineMessage(message) { + /* Handler method for all incoming messages of type "headline". */ + const from_jid = message.getAttribute('from'); + + if (utils.isHeadlineMessage(_converse, message)) { + if (_.includes(from_jid, '@') && !_converse.api.contacts.get(from_jid) && !_converse.allow_non_roster_messaging) { + return; } - } - }, + if (_.isNull(message.querySelector('body'))) { + // Avoid creating a chat box if we have nothing to show + // inside it. + return; + } - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; - _converse.HeadlinesBox = _converse.ChatBox.extend({ - defaults: { + const chatbox = _converse.chatboxes.create({ + 'id': from_jid, + 'jid': from_jid, 'type': _converse.HEADLINES_TYPE, - 'bookmarked': false, - 'chat_state': undefined, - 'num_unread': 0, - 'url': '' - } - }); - _converse.HeadlinesBoxView = _converse.ChatBoxView.extend({ - className: 'chatbox headlines', - events: { - 'click .close-chatbox-button': 'close', - 'click .toggle-chatbox-button': 'minimize', - 'keypress textarea.chat-textarea': 'keyPressed' - }, - - initialize() { - this.initDebounced(); - this.disable_mam = true; // Don't do MAM queries for this box - - this.model.messages.on('add', this.onMessageAdded, this); - this.model.on('show', this.show, this); - this.model.on('destroy', this.hide, this); - this.model.on('change:minimized', this.onMinimizedChanged, this); - this.render().insertHeading().fetchMessages().insertIntoDOM().hide(); - - _converse.emit('chatBoxOpened', this); - - _converse.emit('chatBoxInitialized', this); - }, - - render() { - this.el.setAttribute('id', this.model.get('box_id')); - this.el.innerHTML = tpl_chatbox(_.extend(this.model.toJSON(), { - info_close: '', - label_personal_message: '', - show_send_button: false, - show_toolbar: false, - unread_msgs: '' - })); - this.content = this.el.querySelector('.chat-content'); - return this; - }, - - // Override to avoid the methods in converse-chatview.js - 'renderMessageForm': _.noop, - 'afterShown': _.noop - }); - - function onHeadlineMessage(message) { - /* Handler method for all incoming messages of type "headline". */ - const from_jid = message.getAttribute('from'); - - if (utils.isHeadlineMessage(_converse, message)) { - if (_.includes(from_jid, '@') && !_converse.api.contacts.get(from_jid) && !_converse.allow_non_roster_messaging) { - return; - } - - if (_.isNull(message.querySelector('body'))) { - // Avoid creating a chat box if we have nothing to show - // inside it. - return; - } - - const chatbox = _converse.chatboxes.create({ - 'id': from_jid, - 'jid': from_jid, - 'type': _converse.HEADLINES_TYPE, - 'from': from_jid - }); - - chatbox.createMessage(message, message); - - _converse.emit('message', { - 'chatbox': chatbox, - 'stanza': message - }); - } - - return true; - } - - function registerHeadlineHandler() { - _converse.connection.addHandler(onHeadlineMessage, null, 'message'); - } - - _converse.on('connected', registerHeadlineHandler); - - _converse.on('reconnected', registerHeadlineHandler); - - _converse.on('chatBoxViewsInitialized', () => { - const that = _converse.chatboxviews; - - _converse.chatboxes.on('add', item => { - if (!that.get(item.get('id')) && item.get('type') === _converse.HEADLINES_TYPE) { - that.add(item.get('id'), new _converse.HeadlinesBoxView({ - model: item - })); - } + 'from': from_jid }); - }); + + chatbox.createMessage(message, message); + + _converse.emit('message', { + 'chatbox': chatbox, + 'stanza': message + }); + } + + return true; } - }); + function registerHeadlineHandler() { + _converse.connection.addHandler(onHeadlineMessage, null, 'message'); + } + + _converse.on('connected', registerHeadlineHandler); + + _converse.on('reconnected', registerHeadlineHandler); + + _converse.on('chatBoxViewsInitialized', () => { + const that = _converse.chatboxviews; + + _converse.chatboxes.on('add', item => { + if (!that.get(item.get('id')) && item.get('type') === _converse.HEADLINES_TYPE) { + that.add(item.get('id'), new _converse.HeadlinesBoxView({ + model: item + })); + } + }); + }); + } + }); /***/ }), @@ -61446,271 +61530,290 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**************************************!*\ !*** ./src/converse-message-view.js ***! \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"); +/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(filesize__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _utils_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/html */ "./src/utils/html.js"); +/* harmony import */ var templates_csn_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/csn.html */ "./src/templates/csn.html"); +/* harmony import */ var templates_csn_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_csn_html__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/file_progress.html */ "./src/templates/file_progress.html"); +/* harmony import */ var templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"); +/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_info_html__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var templates_message_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/message.html */ "./src/templates/message.html"); +/* harmony import */ var templates_message_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_message_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/message_versions_modal.html */ "./src/templates/message_versions_modal.html"); +/* harmony import */ var templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var utils_emoji__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! utils/emoji */ "./src/headless/utils/emoji.js"); +/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"); +/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(xss__WEBPACK_IMPORTED_MODULE_9__); +// Converse.js // https://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ./utils/html */ "./src/utils/html.js"), __webpack_require__(/*! utils/emoji */ "./src/headless/utils/emoji.js"), __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"), __webpack_require__(/*! templates/csn.html */ "./src/templates/csn.html"), __webpack_require__(/*! templates/file_progress.html */ "./src/templates/file_progress.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/message.html */ "./src/templates/message.html"), __webpack_require__(/*! templates/message_versions_modal.html */ "./src/templates/message_versions_modal.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (html, u, converse, xss, filesize, tpl_csn, tpl_file_progress, tpl_info, tpl_message, tpl_message_versions_modal) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - _ = _converse$env._, - moment = _converse$env.moment; - converse.plugins.add('converse-message-view', { - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; - _converse.api.settings.update({ - 'show_images_inline': true - }); - _converse.MessageVersionsModal = _converse.BootstrapModal.extend({ - toHTML() { - return tpl_message_versions_modal(_.extend(this.model.toJSON(), { - '__': __ - })); + + + + + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + _ = _converse$env._, + moment = _converse$env.moment; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-message-view', { + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + + _converse.api.settings.update({ + 'show_images_inline': true + }); + + _converse.MessageVersionsModal = _converse.BootstrapModal.extend({ + toHTML() { + return templates_message_versions_modal_html__WEBPACK_IMPORTED_MODULE_7___default()(_.extend(this.model.toJSON(), { + '__': __ + })); + } + + }); + _converse.MessageView = _converse.ViewWithAvatar.extend({ + events: { + 'click .chat-msg__edit-modal': 'showMessageVersionsModal' + }, + + initialize() { + if (this.model.vcard) { + this.model.vcard.on('change', this.render, this); } - }); - _converse.MessageView = _converse.ViewWithAvatar.extend({ - events: { - 'click .chat-msg__edit-modal': 'showMessageVersionsModal' - }, + this.model.on('change', this.onChanged, this); + this.model.on('destroy', this.remove, this); + }, - initialize() { - if (this.model.vcard) { - this.model.vcard.on('change', this.render, this); - } + async render() { + const is_followup = utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].hasClass('chat-msg--followup', this.el); - this.model.on('change', this.onChanged, this); - this.model.on('destroy', this.remove, this); - }, - - async render() { - const is_followup = u.hasClass('chat-msg--followup', this.el); - - if (this.model.isOnlyChatStateNotification()) { - this.renderChatStateNotification(); - } else if (this.model.get('file') && !this.model.get('oob_url')) { - this.renderFileUploadProgresBar(); - } else if (this.model.get('type') === 'error') { - this.renderErrorMessage(); - } else { - await this.renderChatMessage(); - } - - if (is_followup) { - u.addClass('chat-msg--followup', this.el); - } - - return this.el; - }, - - async onChanged(item) { - // Jot down whether it was edited because the `changed` - // attr gets removed when this.render() gets called further - // down. - const edited = item.changed.edited; - - if (this.model.changed.progress) { - return this.renderFileUploadProgresBar(); - } - - if (_.filter(['correcting', 'message', 'type', 'upload'], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) { - await this.render(); - } - - if (edited) { - this.onMessageEdited(); - } - }, - - onMessageEdited() { - if (this.model.get('is_archived')) { - return; - } - - this.el.addEventListener('animationend', () => u.removeClass('onload', this.el)); - u.addClass('onload', this.el); - }, - - replaceElement(msg) { - if (!_.isNil(this.el.parentElement)) { - this.el.parentElement.replaceChild(msg, this.el); - } - - this.setElement(msg); - return this.el; - }, - - async renderChatMessage() { - const is_me_message = this.isMeCommand(), - moment_time = moment(this.model.get('time')), - role = this.model.vcard ? this.model.vcard.get('role') : null, - roles = role ? role.split(',') : []; - const msg = u.stringToElement(tpl_message(_.extend(this.model.toJSON(), { - '__': __, - 'is_me_message': is_me_message, - 'roles': roles, - 'pretty_time': moment_time.format(_converse.time_format), - 'time': moment_time.format(), - 'extra_classes': this.getExtraMessageClasses(), - 'label_show': __('Show more'), - 'username': this.model.getDisplayName() - }))); - const url = this.model.get('oob_url'); - - if (url) { - msg.querySelector('.chat-msg__media').innerHTML = _.flow(_.partial(u.renderFileURL, _converse), _.partial(u.renderMovieURL, _converse), _.partial(u.renderAudioURL, _converse), _.partial(u.renderImageURL, _converse))(url); - } - - let text = this.getMessageText(); - const msg_content = msg.querySelector('.chat-msg__text'); - - if (text && text !== url) { - if (is_me_message) { - text = text.replace(/^\/me/, ''); - } - - text = xss.filterXSS(text, { - 'whiteList': {} - }); - msg_content.innerHTML = _.flow(_.partial(u.geoUriToHttp, _, _converse.geouri_replacement), _.partial(u.addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), u.addHyperlinks, u.renderNewLines, _.partial(u.addEmoji, _converse, _))(text); - } - - const promises = []; - promises.push(u.renderImageURLs(_converse, msg_content)); - - if (this.model.get('type') !== 'headline') { - promises.push(this.renderAvatar(msg)); - } - - await Promise.all(promises); - this.replaceElement(msg); - this.model.collection.trigger('rendered', this); - }, - - renderErrorMessage() { - const moment_time = moment(this.model.get('time')), - msg = u.stringToElement(tpl_info(_.extend(this.model.toJSON(), { - 'extra_classes': 'chat-error', - 'isodate': moment_time.format() - }))); - return this.replaceElement(msg); - }, - - renderChatStateNotification() { - let text; - const from = this.model.get('from'), - name = this.model.getDisplayName(); - - if (this.model.get('chat_state') === _converse.COMPOSING) { - if (this.model.get('sender') === 'me') { - text = __('Typing from another device'); - } else { - text = __('%1$s is typing', name); - } - } else if (this.model.get('chat_state') === _converse.PAUSED) { - if (this.model.get('sender') === 'me') { - text = __('Stopped typing on the other device'); - } else { - text = __('%1$s has stopped typing', name); - } - } else if (this.model.get('chat_state') === _converse.GONE) { - text = __('%1$s has gone away', name); - } else { - return; - } - - const isodate = moment().format(); - this.replaceElement(u.stringToElement(tpl_csn({ - 'message': text, - 'from': from, - 'isodate': isodate - }))); - }, - - renderFileUploadProgresBar() { - const msg = u.stringToElement(tpl_file_progress(_.extend(this.model.toJSON(), { - 'filesize': filesize(this.model.get('file').size) - }))); - this.replaceElement(msg); - this.renderAvatar(); - }, - - showMessageVersionsModal(ev) { - ev.preventDefault(); - - if (_.isUndefined(this.model.message_versions_modal)) { - this.model.message_versions_modal = new _converse.MessageVersionsModal({ - 'model': this.model - }); - } - - this.model.message_versions_modal.show(ev); - }, - - getMessageText() { - if (this.model.get('is_encrypted')) { - return this.model.get('plaintext') || (_converse.debug ? __('Unencryptable OMEMO message') : null); - } - - return this.model.get('message'); - }, - - isMeCommand() { - const text = this.getMessageText(); - - if (!text) { - return false; - } - - const match = text.match(/^\/(.*?)(?: (.*))?$/); - return match && match[1] === 'me'; - }, - - processMessageText() { - var text = this.get('message'); - text = u.geoUriToHttp(text, _converse.geouri_replacement); - }, - - getExtraMessageClasses() { - let extra_classes = this.model.get('is_delayed') && 'delayed' || ''; - - if (this.model.get('type') === 'groupchat' && this.model.get('sender') === 'them') { - if (this.model.collection.chatbox.isUserMentioned(this.model)) { - // Add special class to mark groupchat messages - // in which we are mentioned. - extra_classes += ' mentioned'; - } - } - - if (this.model.get('correcting')) { - extra_classes += ' correcting'; - } - - return extra_classes; + if (this.model.isOnlyChatStateNotification()) { + this.renderChatStateNotification(); + } else if (this.model.get('file') && !this.model.get('oob_url')) { + this.renderFileUploadProgresBar(); + } else if (this.model.get('type') === 'error') { + this.renderErrorMessage(); + } else { + await this.renderChatMessage(); } - }); - } + if (is_followup) { + utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addClass('chat-msg--followup', this.el); + } + + return this.el; + }, + + async onChanged(item) { + // Jot down whether it was edited because the `changed` + // attr gets removed when this.render() gets called further + // down. + const edited = item.changed.edited; + + if (this.model.changed.progress) { + return this.renderFileUploadProgresBar(); + } + + if (_.filter(['correcting', 'message', 'type', 'upload'], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) { + await this.render(); + } + + if (edited) { + this.onMessageEdited(); + } + }, + + onMessageEdited() { + if (this.model.get('is_archived')) { + return; + } + + this.el.addEventListener('animationend', () => utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].removeClass('onload', this.el)); + utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addClass('onload', this.el); + }, + + replaceElement(msg) { + if (!_.isNil(this.el.parentElement)) { + this.el.parentElement.replaceChild(msg, this.el); + } + + this.setElement(msg); + return this.el; + }, + + async renderChatMessage() { + const is_me_message = this.isMeCommand(), + moment_time = moment(this.model.get('time')), + role = this.model.vcard ? this.model.vcard.get('role') : null, + roles = role ? role.split(',') : []; + const msg = utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_message_html__WEBPACK_IMPORTED_MODULE_6___default()(_.extend(this.model.toJSON(), { + '__': __, + 'is_me_message': is_me_message, + 'roles': roles, + 'pretty_time': moment_time.format(_converse.time_format), + 'time': moment_time.format(), + 'extra_classes': this.getExtraMessageClasses(), + 'label_show': __('Show more'), + 'username': this.model.getDisplayName() + }))); + const url = this.model.get('oob_url'); + + if (url) { + msg.querySelector('.chat-msg__media').innerHTML = _.flow(_.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderFileURL, _converse), _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderMovieURL, _converse), _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderAudioURL, _converse), _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderImageURL, _converse))(url); + } + + let text = this.getMessageText(); + const msg_content = msg.querySelector('.chat-msg__text'); + + if (text && text !== url) { + if (is_me_message) { + text = text.replace(/^\/me/, ''); + } + + text = xss__WEBPACK_IMPORTED_MODULE_9___default.a.filterXSS(text, { + 'whiteList': {} + }); + msg_content.innerHTML = _.flow(_.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].geoUriToHttp, _, _converse.geouri_replacement), _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addHyperlinks, utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderNewLines, _.partial(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].addEmoji, _converse, _))(text); + } + + const promises = []; + promises.push(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].renderImageURLs(_converse, msg_content)); + + if (this.model.get('type') !== 'headline') { + promises.push(this.renderAvatar(msg)); + } + + await Promise.all(promises); + this.replaceElement(msg); + this.model.collection.trigger('rendered', this); + }, + + renderErrorMessage() { + const moment_time = moment(this.model.get('time')), + msg = utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_info_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), { + 'extra_classes': 'chat-error', + 'isodate': moment_time.format() + }))); + return this.replaceElement(msg); + }, + + renderChatStateNotification() { + let text; + const from = this.model.get('from'), + name = this.model.getDisplayName(); + + if (this.model.get('chat_state') === _converse.COMPOSING) { + if (this.model.get('sender') === 'me') { + text = __('Typing from another device'); + } else { + text = __('%1$s is typing', name); + } + } else if (this.model.get('chat_state') === _converse.PAUSED) { + if (this.model.get('sender') === 'me') { + text = __('Stopped typing on the other device'); + } else { + text = __('%1$s has stopped typing', name); + } + } else if (this.model.get('chat_state') === _converse.GONE) { + text = __('%1$s has gone away', name); + } else { + return; + } + + const isodate = moment().format(); + this.replaceElement(utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_csn_html__WEBPACK_IMPORTED_MODULE_3___default()({ + 'message': text, + 'from': from, + 'isodate': isodate + }))); + }, + + renderFileUploadProgresBar() { + const msg = utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].stringToElement(templates_file_progress_html__WEBPACK_IMPORTED_MODULE_4___default()(_.extend(this.model.toJSON(), { + 'filesize': filesize__WEBPACK_IMPORTED_MODULE_1___default()(this.model.get('file').size) + }))); + this.replaceElement(msg); + this.renderAvatar(); + }, + + showMessageVersionsModal(ev) { + ev.preventDefault(); + + if (_.isUndefined(this.model.message_versions_modal)) { + this.model.message_versions_modal = new _converse.MessageVersionsModal({ + 'model': this.model + }); + } + + this.model.message_versions_modal.show(ev); + }, + + getMessageText() { + if (this.model.get('is_encrypted')) { + return this.model.get('plaintext') || (_converse.debug ? __('Unencryptable OMEMO message') : null); + } + + return this.model.get('message'); + }, + + isMeCommand() { + const text = this.getMessageText(); + + if (!text) { + return false; + } + + const match = text.match(/^\/(.*?)(?: (.*))?$/); + return match && match[1] === 'me'; + }, + + processMessageText() { + var text = this.get('message'); + text = utils_emoji__WEBPACK_IMPORTED_MODULE_8__["default"].geoUriToHttp(text, _converse.geouri_replacement); + }, + + getExtraMessageClasses() { + let extra_classes = this.model.get('is_delayed') && 'delayed' || ''; + + if (this.model.get('type') === 'groupchat' && this.model.get('sender') === 'them') { + if (this.model.collection.chatbox.isUserMentioned(this.model)) { + // Add special class to mark groupchat messages + // in which we are mentioned. + extra_classes += ' mentioned'; + } + } + + if (this.model.get('correcting')) { + extra_classes += ' correcting'; + } + + return extra_classes; + } + + }); + } - }); - return converse; }); /***/ }), @@ -61719,613 +61822,619 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**********************************!*\ !*** ./src/converse-minimize.js ***! \**********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_chatview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_chatbox_minimize_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! templates/chatbox_minimize.html */ "./src/templates/chatbox_minimize.html"); +/* harmony import */ var templates_chatbox_minimize_html__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(templates_chatbox_minimize_html__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var templates_chats_panel_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/chats_panel.html */ "./src/templates/chats_panel.html"); +/* harmony import */ var templates_chats_panel_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_chats_panel_html__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var templates_toggle_chats_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/toggle_chats.html */ "./src/templates/toggle_chats.html"); +/* harmony import */ var templates_toggle_chats_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_toggle_chats_html__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var templates_trimmed_chat_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/trimmed_chat.html */ "./src/templates/trimmed_chat.html"); +/* harmony import */ var templates_trimmed_chat_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_trimmed_chat_html__WEBPACK_IMPORTED_MODULE_5__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // -// Copyright (c) 2012-2017, Jan-Carel Brand +// Copyright (c) 2013-2018, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) -// -/*global define, window, document */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/chatbox_minimize.html */ "./src/templates/chatbox_minimize.html"), __webpack_require__(/*! templates/toggle_chats.html */ "./src/templates/toggle_chats.html"), __webpack_require__(/*! templates/trimmed_chat.html */ "./src/templates/trimmed_chat.html"), __webpack_require__(/*! templates/chats_panel.html */ "./src/templates/chats_panel.html"), __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_chatbox_minimize, tpl_toggle_chats, tpl_trimmed_chat, tpl_chats_panel) { - "use strict"; - const _converse$env = converse.env, - _ = _converse$env._, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - b64_sha1 = _converse$env.b64_sha1, - moment = _converse$env.moment; - const u = converse.env.utils; - converse.plugins.add('converse-minimize', { - /* Optional dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. They are called "optional" because they might not be - * available, in which case any overrides applicable to them will be - * ignored. - * - * It's possible however to make optional dependencies non-optional. - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatview", "converse-controlbox", "@converse/headless/converse-muc", "converse-muc-views", "converse-headline"], - enabled(_converse) { - return _converse.view_mode == 'overlayed'; + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env, + _ = _converse$env._, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + b64_sha1 = _converse$env.b64_sha1, + moment = _converse$env.moment; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-minimize', { + /* Optional dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. They are called "optional" because they might not be + * available, in which case any overrides applicable to them will be + * ignored. + * + * It's possible however to make optional dependencies non-optional. + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatview", "converse-controlbox", "converse-muc", "converse-muc-views", "converse-headline"], + + enabled(_converse) { + return _converse.view_mode == 'overlayed'; + }, + + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + ChatBox: { + initialize() { + this.__super__.initialize.apply(this, arguments); + + this.on('show', this.maximize, this); + + if (this.get('id') === 'controlbox') { + return; + } + + this.save({ + 'minimized': this.get('minimized') || false, + 'time_minimized': this.get('time_minimized') || moment() + }); + }, + + maximize() { + u.safeSave(this, { + 'minimized': false, + 'time_opened': moment().valueOf() + }); + }, + + minimize() { + u.safeSave(this, { + 'minimized': true, + 'time_minimized': moment().format() + }); + } + }, + ChatBoxView: { + events: { + 'click .toggle-chatbox-button': 'minimize' + }, - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - ChatBox: { - initialize() { - this.__super__.initialize.apply(this, arguments); + initialize() { + this.model.on('change:minimized', this.onMinimizedChanged, this); + return this.__super__.initialize.apply(this, arguments); + }, - this.on('show', this.maximize, this); + _show() { + const _converse = this.__super__._converse; - if (this.get('id') === 'controlbox') { - return; - } + if (!this.model.get('minimized')) { + this.__super__._show.apply(this, arguments); - this.save({ - 'minimized': this.get('minimized') || false, - 'time_minimized': this.get('time_minimized') || moment() + _converse.chatboxviews.trimChats(this); + } else { + this.minimize(); + } + }, + + isNewMessageHidden() { + return this.model.get('minimized') || this.__super__.isNewMessageHidden.apply(this, arguments); + }, + + shouldShowOnTextMessage() { + return !this.model.get('minimized') && this.__super__.shouldShowOnTextMessage.apply(this, arguments); + }, + + setChatBoxHeight(height) { + if (!this.model.get('minimized')) { + return this.__super__.setChatBoxHeight.apply(this, arguments); + } + }, + + setChatBoxWidth(width) { + if (!this.model.get('minimized')) { + return this.__super__.setChatBoxWidth.apply(this, arguments); + } + }, + + onMinimizedChanged(item) { + if (item.get('minimized')) { + this.minimize(); + } else { + this.maximize(); + } + }, + + maximize() { + // Restores a minimized chat box + const _converse = this.__super__._converse; + this.insertIntoDOM(); + + if (!this.model.isScrolledUp()) { + this.model.clearUnreadMsgCounter(); + } + + this.show(); + + this.__super__._converse.emit('chatBoxMaximized', this); + + return this; + }, + + minimize(ev) { + const _converse = this.__super__._converse; + + if (ev && ev.preventDefault) { + ev.preventDefault(); + } // save the scroll position to restore it on maximize + + + if (this.model.collection && this.model.collection.browserStorage) { + this.model.save({ + 'scroll': this.content.scrollTop }); - }, - - maximize() { - u.safeSave(this, { - 'minimized': false, - 'time_opened': moment().valueOf() - }); - }, - - minimize() { - u.safeSave(this, { - 'minimized': true, - 'time_minimized': moment().format() + } else { + this.model.set({ + 'scroll': this.content.scrollTop }); } + this.setChatState(_converse.INACTIVE).model.minimize(); + this.hide(); + + _converse.emit('chatBoxMinimized', this); + } + + }, + ChatBoxHeading: { + render() { + const _converse = this.__super__._converse, + __ = _converse.__; + + const result = this.__super__.render.apply(this, arguments); + + const new_html = templates_chatbox_minimize_html__WEBPACK_IMPORTED_MODULE_2___default()({ + info_minimize: __('Minimize this chat box') + }); + const el = this.el.querySelector('.toggle-chatbox-button'); + + if (el) { + el.outerHTML = new_html; + } else { + const button = this.el.querySelector('.close-chatbox-button'); + button.insertAdjacentHTML('afterEnd', new_html); + } + } + + }, + ChatRoomView: { + events: { + 'click .toggle-chatbox-button': 'minimize' }, - ChatBoxView: { - events: { - 'click .toggle-chatbox-button': 'minimize' - }, - initialize() { - this.model.on('change:minimized', this.onMinimizedChanged, this); - return this.__super__.initialize.apply(this, arguments); - }, - - _show() { - const _converse = this.__super__._converse; - - if (!this.model.get('minimized')) { - this.__super__._show.apply(this, arguments); - - _converse.chatboxviews.trimChats(this); - } else { - this.minimize(); - } - }, - - isNewMessageHidden() { - return this.model.get('minimized') || this.__super__.isNewMessageHidden.apply(this, arguments); - }, - - shouldShowOnTextMessage() { - return !this.model.get('minimized') && this.__super__.shouldShowOnTextMessage.apply(this, arguments); - }, - - setChatBoxHeight(height) { - if (!this.model.get('minimized')) { - return this.__super__.setChatBoxHeight.apply(this, arguments); - } - }, - - setChatBoxWidth(width) { - if (!this.model.get('minimized')) { - return this.__super__.setChatBoxWidth.apply(this, arguments); - } - }, - - onMinimizedChanged(item) { + initialize() { + this.model.on('change:minimized', function (item) { if (item.get('minimized')) { - this.minimize(); + this.hide(); } else { this.maximize(); } - }, + }, this); - maximize() { - // Restores a minimized chat box - const _converse = this.__super__._converse; - this.insertIntoDOM(); + const result = this.__super__.initialize.apply(this, arguments); - if (!this.model.isScrolledUp()) { - this.model.clearUnreadMsgCounter(); - } - - this.show(); - - this.__super__._converse.emit('chatBoxMaximized', this); - - return this; - }, - - minimize(ev) { - const _converse = this.__super__._converse; - - if (ev && ev.preventDefault) { - ev.preventDefault(); - } // save the scroll position to restore it on maximize - - - if (this.model.collection && this.model.collection.browserStorage) { - this.model.save({ - 'scroll': this.content.scrollTop - }); - } else { - this.model.set({ - 'scroll': this.content.scrollTop - }); - } - - this.setChatState(_converse.INACTIVE).model.minimize(); + if (this.model.get('minimized')) { this.hide(); - - _converse.emit('chatBoxMinimized', this); } + return result; }, - ChatBoxHeading: { - render() { - const _converse = this.__super__._converse, - __ = _converse.__; - const result = this.__super__.render.apply(this, arguments); + generateHeadingHTML() { + const _converse = this.__super__._converse, + __ = _converse.__; - const new_html = tpl_chatbox_minimize({ - info_minimize: __('Minimize this chat box') - }); - const el = this.el.querySelector('.toggle-chatbox-button'); + const html = this.__super__.generateHeadingHTML.apply(this, arguments); - if (el) { - el.outerHTML = new_html; - } else { - const button = this.el.querySelector('.close-chatbox-button'); - button.insertAdjacentHTML('afterEnd', new_html); - } + const div = document.createElement('div'); + div.innerHTML = html; + const button = div.querySelector('.close-chatbox-button'); + button.insertAdjacentHTML('afterend', templates_chatbox_minimize_html__WEBPACK_IMPORTED_MODULE_2___default()({ + 'info_minimize': __('Minimize this chat box') + })); + return div.innerHTML; + } + + }, + ChatBoxes: { + chatBoxMayBeShown(chatbox) { + return this.__super__.chatBoxMayBeShown.apply(this, arguments) && !chatbox.get('minimized'); + } + + }, + ChatBoxViews: { + getChatBoxWidth(view) { + if (!view.model.get('minimized') && u.isVisible(view.el)) { + return u.getOuterWidth(view.el, true); } + return 0; }, - ChatRoomView: { - events: { - 'click .toggle-chatbox-button': 'minimize' - }, - initialize() { - this.model.on('change:minimized', function (item) { - if (item.get('minimized')) { - this.hide(); - } else { - this.maximize(); - } - }, this); + getShownChats() { + return this.filter(view => // The controlbox can take a while to close, + // so we need to check its state. That's why we checked + // the 'closed' state. + !view.model.get('minimized') && !view.model.get('closed') && u.isVisible(view.el)); + }, - const result = this.__super__.initialize.apply(this, arguments); + trimChats(newchat) { + /* This method is called when a newly created chat box will + * be shown. + * + * It checks whether there is enough space on the page to show + * another chat box. Otherwise it minimizes the oldest chat box + * to create space. + */ + const _converse = this.__super__._converse, + shown_chats = this.getShownChats(), + body_width = u.getOuterWidth(document.querySelector('body'), true); - if (this.model.get('minimized')) { - this.hide(); - } - - return result; - }, - - generateHeadingHTML() { - const _converse = this.__super__._converse, - __ = _converse.__; - - const html = this.__super__.generateHeadingHTML.apply(this, arguments); - - const div = document.createElement('div'); - div.innerHTML = html; - const button = div.querySelector('.close-chatbox-button'); - button.insertAdjacentHTML('afterend', tpl_chatbox_minimize({ - 'info_minimize': __('Minimize this chat box') - })); - return div.innerHTML; + if (_converse.no_trimming || shown_chats.length <= 1) { + return; } - }, - ChatBoxes: { - chatBoxMayBeShown(chatbox) { - return this.__super__.chatBoxMayBeShown.apply(this, arguments) && !chatbox.get('minimized'); + if (this.getChatBoxWidth(shown_chats[0]) === body_width) { + // If the chats shown are the same width as the body, + // then we're in responsive mode and the chats are + // fullscreen. In this case we don't trim. + return; } - }, - ChatBoxViews: { - getChatBoxWidth(view) { - if (!view.model.get('minimized') && u.isVisible(view.el)) { - return u.getOuterWidth(view.el, true); - } + _converse.api.waitUntil('minimizedChatsInitialized').then(() => { + const minimized_el = _.get(_converse.minimized_chats, 'el'), + new_id = newchat ? newchat.model.get('id') : null; - return 0; - }, + if (minimized_el) { + const minimized_width = _.includes(this.model.pluck('minimized'), true) ? u.getOuterWidth(minimized_el, true) : 0; - getShownChats() { - return this.filter(view => // The controlbox can take a while to close, - // so we need to check its state. That's why we checked - // the 'closed' state. - !view.model.get('minimized') && !view.model.get('closed') && u.isVisible(view.el)); - }, + const boxes_width = _.reduce(this.xget(new_id), (memo, view) => memo + this.getChatBoxWidth(view), newchat ? u.getOuterWidth(newchat.el, true) : 0); - trimChats(newchat) { - /* This method is called when a newly created chat box will - * be shown. - * - * It checks whether there is enough space on the page to show - * another chat box. Otherwise it minimizes the oldest chat box - * to create space. - */ - const _converse = this.__super__._converse, - shown_chats = this.getShownChats(), - body_width = u.getOuterWidth(document.querySelector('body'), true); + if (minimized_width + boxes_width > body_width) { + const oldest_chat = this.getOldestMaximizedChat([new_id]); - if (_converse.no_trimming || shown_chats.length <= 1) { - return; - } + if (oldest_chat) { + // We hide the chat immediately, because waiting + // for the event to fire (and letting the + // ChatBoxView hide it then) causes race + // conditions. + const view = this.get(oldest_chat.get('id')); - if (this.getChatBoxWidth(shown_chats[0]) === body_width) { - // If the chats shown are the same width as the body, - // then we're in responsive mode and the chats are - // fullscreen. In this case we don't trim. - return; - } - - _converse.api.waitUntil('minimizedChatsInitialized').then(() => { - const minimized_el = _.get(_converse.minimized_chats, 'el'), - new_id = newchat ? newchat.model.get('id') : null; - - if (minimized_el) { - const minimized_width = _.includes(this.model.pluck('minimized'), true) ? u.getOuterWidth(minimized_el, true) : 0; - - const boxes_width = _.reduce(this.xget(new_id), (memo, view) => memo + this.getChatBoxWidth(view), newchat ? u.getOuterWidth(newchat.el, true) : 0); - - if (minimized_width + boxes_width > body_width) { - const oldest_chat = this.getOldestMaximizedChat([new_id]); - - if (oldest_chat) { - // We hide the chat immediately, because waiting - // for the event to fire (and letting the - // ChatBoxView hide it then) causes race - // conditions. - const view = this.get(oldest_chat.get('id')); - - if (view) { - view.hide(); - } - - oldest_chat.minimize(); + if (view) { + view.hide(); } + + oldest_chat.minimize(); } } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - getOldestMaximizedChat(exclude_ids) { - // Get oldest view (if its id is not excluded) - exclude_ids.push('controlbox'); - let i = 0; - let model = this.model.sort().at(i); - - while (_.includes(exclude_ids, model.get('id')) || model.get('minimized') === true) { - i++; - model = this.model.at(i); - - if (!model) { - return null; - } } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, - return model; + getOldestMaximizedChat(exclude_ids) { + // Get oldest view (if its id is not excluded) + exclude_ids.push('controlbox'); + let i = 0; + let model = this.model.sort().at(i); + + while (_.includes(exclude_ids, model.get('id')) || model.get('minimized') === true) { + i++; + model = this.model.at(i); + + if (!model) { + return null; + } } + return model; } - }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by Converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; // Add new HTML templates. + } + }, - _converse.templates.chatbox_minimize = tpl_chatbox_minimize; - _converse.templates.toggle_chats = tpl_toggle_chats; - _converse.templates.trimmed_chat = tpl_trimmed_chat; - _converse.templates.chats_panel = tpl_chats_panel; + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by Converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; // Add new HTML templates. - _converse.api.settings.update({ - no_trimming: false // Set to true for phantomjs tests (where browser apparently has no width) + _converse.templates.chatbox_minimize = templates_chatbox_minimize_html__WEBPACK_IMPORTED_MODULE_2___default.a; + _converse.templates.toggle_chats = templates_toggle_chats_html__WEBPACK_IMPORTED_MODULE_4___default.a; + _converse.templates.trimmed_chat = templates_trimmed_chat_html__WEBPACK_IMPORTED_MODULE_5___default.a; + _converse.templates.chats_panel = templates_chats_panel_html__WEBPACK_IMPORTED_MODULE_3___default.a; - }); + _converse.api.settings.update({ + no_trimming: false // Set to true for phantomjs tests (where browser apparently has no width) - _converse.api.promises.add('minimizedChatsInitialized'); + }); - _converse.MinimizedChatBoxView = Backbone.NativeView.extend({ - tagName: 'div', - className: 'chat-head row no-gutters', - events: { - 'click .close-chatbox-button': 'close', - 'click .restore-chat': 'restore' - }, + _converse.api.promises.add('minimizedChatsInitialized'); - initialize() { - this.model.on('change:num_unread', this.render, this); - }, + _converse.MinimizedChatBoxView = Backbone.NativeView.extend({ + tagName: 'div', + className: 'chat-head row no-gutters', + events: { + 'click .close-chatbox-button': 'close', + 'click .restore-chat': 'restore' + }, - render() { - const data = _.extend(this.model.toJSON(), { - 'tooltip': __('Click to restore this chat') - }); + initialize() { + this.model.on('change:num_unread', this.render, this); + }, - if (this.model.get('type') === 'chatroom') { - data.title = this.model.get('name'); - u.addClass('chat-head-chatroom', this.el); - } else { - data.title = this.model.get('fullname'); - u.addClass('chat-head-chatbox', this.el); - } - - this.el.innerHTML = tpl_trimmed_chat(data); - return this.el; - }, - - close(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - this.remove(); - - const view = _converse.chatboxviews.get(this.model.get('id')); - - if (view) { - // This will call model.destroy(), removing it from the - // collection and will also emit 'chatBoxClosed' - view.close(); - } else { - this.model.destroy(); - - _converse.emit('chatBoxClosed', this); - } - - return this; - }, - - restore: _.debounce(function (ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - this.model.off('change:num_unread', null, this); - this.remove(); - this.model.maximize(); - }, 200, { - 'leading': true - }) - }); - _converse.MinimizedChats = Backbone.Overview.extend({ - tagName: 'div', - id: "minimized-chats", - className: 'hidden', - events: { - "click #toggle-minimized-chats": "toggle" - }, - - initialize() { - this.render(); - this.initToggle(); - this.addMultipleChats(this.model.where({ - 'minimized': true - })); - this.model.on("add", this.onChanged, this); - this.model.on("destroy", this.removeChat, this); - this.model.on("change:minimized", this.onChanged, this); - this.model.on('change:num_unread', this.updateUnreadMessagesCounter, this); - }, - - render() { - if (!this.el.parentElement) { - this.el.innerHTML = tpl_chats_panel(); - - _converse.chatboxviews.insertRowColumn(this.el); - } - - if (this.keys().length === 0) { - this.el.classList.add('hidden'); - } else if (this.keys().length > 0 && !u.isVisible(this.el)) { - this.el.classList.remove('hidden'); - - _converse.chatboxviews.trimChats(); - } - - return this.el; - }, - - tearDown() { - this.model.off("add", this.onChanged); - this.model.off("destroy", this.removeChat); - this.model.off("change:minimized", this.onChanged); - this.model.off('change:num_unread', this.updateUnreadMessagesCounter); - return this; - }, - - initToggle() { - const storage = _converse.config.get('storage'), - id = b64_sha1(`converse.minchatstoggle${_converse.bare_jid}`); - - this.toggleview = new _converse.MinimizedChatsToggleView({ - 'model': new _converse.MinimizedChatsToggle({ - 'id': id - }) - }); - this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id); - this.toggleview.model.fetch(); - }, - - toggle(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - this.toggleview.model.save({ - 'collapsed': !this.toggleview.model.get('collapsed') - }); - u.slideToggleElement(this.el.querySelector('.minimized-chats-flyout'), 200); - }, - - onChanged(item) { - if (item.get('id') === 'controlbox') { - // The ControlBox has it's own minimize toggle - return; - } - - if (item.get('minimized')) { - this.addChat(item); - } else if (this.get(item.get('id'))) { - this.removeChat(item); - } - }, - - addChatView(item) { - const existing = this.get(item.get('id')); - - if (existing && existing.el.parentNode) { - return; - } - - const view = new _converse.MinimizedChatBoxView({ - model: item - }); - this.el.querySelector('.minimized-chats-flyout').insertAdjacentElement('beforeEnd', view.render()); - this.add(item.get('id'), view); - }, - - addMultipleChats(items) { - _.each(items, this.addChatView.bind(this)); - - this.toggleview.model.set({ - 'num_minimized': this.keys().length - }); - this.render(); - }, - - addChat(item) { - this.addChatView(item); - this.toggleview.model.set({ - 'num_minimized': this.keys().length - }); - this.render(); - }, - - removeChat(item) { - this.remove(item.get('id')); - this.toggleview.model.set({ - 'num_minimized': this.keys().length - }); - this.render(); - }, - - updateUnreadMessagesCounter() { - const ls = this.model.pluck('num_unread'); - let count = 0, - i; - - for (i = 0; i < ls.length; i++) { - count += ls[i]; - } - - this.toggleview.model.save({ - 'num_unread': count - }); - this.render(); - } - - }); - _converse.MinimizedChatsToggle = Backbone.Model.extend({ - defaults: { - 'collapsed': false, - 'num_minimized': 0, - 'num_unread': 0 - } - }); - _converse.MinimizedChatsToggleView = Backbone.NativeView.extend({ - el: '#toggle-minimized-chats', - - initialize() { - this.model.on('change:num_minimized', this.render, this); - this.model.on('change:num_unread', this.render, this); - this.flyout = this.el.parentElement.querySelector('.minimized-chats-flyout'); - }, - - render() { - this.el.innerHTML = tpl_toggle_chats(_.extend(this.model.toJSON(), { - 'Minimized': __('Minimized') - })); - - if (this.model.get('collapsed')) { - u.hideElement(this.flyout); - } else { - u.showElement(this.flyout); - } - - return this.el; - } - - }); - Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxViewsInitialized')]).then(() => { - _converse.minimized_chats = new _converse.MinimizedChats({ - model: _converse.chatboxes + render() { + const data = _.extend(this.model.toJSON(), { + 'tooltip': __('Click to restore this chat') }); - _converse.emit('minimizedChatsInitialized'); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - - _converse.on('registeredGlobalEventHandlers', function () { - window.addEventListener("resize", _.debounce(function (ev) { - if (_converse.connection.connected) { - _converse.chatboxviews.trimChats(); - } - }, 200)); - }); - - _converse.on('controlBoxOpened', function (chatbox) { - // Wrapped in anon method because at scan time, chatboxviews - // attr not set yet. - if (_converse.connection.connected) { - _converse.chatboxviews.trimChats(chatbox); + if (this.model.get('type') === 'chatroom') { + data.title = this.model.get('name'); + u.addClass('chat-head-chatroom', this.el); + } else { + data.title = this.model.get('fullname'); + u.addClass('chat-head-chatbox', this.el); } - }); - } - }); + this.el.innerHTML = templates_trimmed_chat_html__WEBPACK_IMPORTED_MODULE_5___default()(data); + return this.el; + }, + + close(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + this.remove(); + + const view = _converse.chatboxviews.get(this.model.get('id')); + + if (view) { + // This will call model.destroy(), removing it from the + // collection and will also emit 'chatBoxClosed' + view.close(); + } else { + this.model.destroy(); + + _converse.emit('chatBoxClosed', this); + } + + return this; + }, + + restore: _.debounce(function (ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + this.model.off('change:num_unread', null, this); + this.remove(); + this.model.maximize(); + }, 200, { + 'leading': true + }) + }); + _converse.MinimizedChats = Backbone.Overview.extend({ + tagName: 'div', + id: "minimized-chats", + className: 'hidden', + events: { + "click #toggle-minimized-chats": "toggle" + }, + + initialize() { + this.render(); + this.initToggle(); + this.addMultipleChats(this.model.where({ + 'minimized': true + })); + this.model.on("add", this.onChanged, this); + this.model.on("destroy", this.removeChat, this); + this.model.on("change:minimized", this.onChanged, this); + this.model.on('change:num_unread', this.updateUnreadMessagesCounter, this); + }, + + render() { + if (!this.el.parentElement) { + this.el.innerHTML = templates_chats_panel_html__WEBPACK_IMPORTED_MODULE_3___default()(); + + _converse.chatboxviews.insertRowColumn(this.el); + } + + if (this.keys().length === 0) { + this.el.classList.add('hidden'); + } else if (this.keys().length > 0 && !u.isVisible(this.el)) { + this.el.classList.remove('hidden'); + + _converse.chatboxviews.trimChats(); + } + + return this.el; + }, + + tearDown() { + this.model.off("add", this.onChanged); + this.model.off("destroy", this.removeChat); + this.model.off("change:minimized", this.onChanged); + this.model.off('change:num_unread', this.updateUnreadMessagesCounter); + return this; + }, + + initToggle() { + const storage = _converse.config.get('storage'), + id = b64_sha1(`converse.minchatstoggle${_converse.bare_jid}`); + + this.toggleview = new _converse.MinimizedChatsToggleView({ + 'model': new _converse.MinimizedChatsToggle({ + 'id': id + }) + }); + this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id); + this.toggleview.model.fetch(); + }, + + toggle(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + this.toggleview.model.save({ + 'collapsed': !this.toggleview.model.get('collapsed') + }); + u.slideToggleElement(this.el.querySelector('.minimized-chats-flyout'), 200); + }, + + onChanged(item) { + if (item.get('id') === 'controlbox') { + // The ControlBox has it's own minimize toggle + return; + } + + if (item.get('minimized')) { + this.addChat(item); + } else if (this.get(item.get('id'))) { + this.removeChat(item); + } + }, + + addChatView(item) { + const existing = this.get(item.get('id')); + + if (existing && existing.el.parentNode) { + return; + } + + const view = new _converse.MinimizedChatBoxView({ + model: item + }); + this.el.querySelector('.minimized-chats-flyout').insertAdjacentElement('beforeEnd', view.render()); + this.add(item.get('id'), view); + }, + + addMultipleChats(items) { + _.each(items, this.addChatView.bind(this)); + + this.toggleview.model.set({ + 'num_minimized': this.keys().length + }); + this.render(); + }, + + addChat(item) { + this.addChatView(item); + this.toggleview.model.set({ + 'num_minimized': this.keys().length + }); + this.render(); + }, + + removeChat(item) { + this.remove(item.get('id')); + this.toggleview.model.set({ + 'num_minimized': this.keys().length + }); + this.render(); + }, + + updateUnreadMessagesCounter() { + const ls = this.model.pluck('num_unread'); + let count = 0, + i; + + for (i = 0; i < ls.length; i++) { + count += ls[i]; + } + + this.toggleview.model.save({ + 'num_unread': count + }); + this.render(); + } + + }); + _converse.MinimizedChatsToggle = Backbone.Model.extend({ + defaults: { + 'collapsed': false, + 'num_minimized': 0, + 'num_unread': 0 + } + }); + _converse.MinimizedChatsToggleView = Backbone.NativeView.extend({ + el: '#toggle-minimized-chats', + + initialize() { + this.model.on('change:num_minimized', this.render, this); + this.model.on('change:num_unread', this.render, this); + this.flyout = this.el.parentElement.querySelector('.minimized-chats-flyout'); + }, + + render() { + this.el.innerHTML = templates_toggle_chats_html__WEBPACK_IMPORTED_MODULE_4___default()(_.extend(this.model.toJSON(), { + 'Minimized': __('Minimized') + })); + + if (this.model.get('collapsed')) { + u.hideElement(this.flyout); + } else { + u.showElement(this.flyout); + } + + return this.el; + } + + }); + Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxViewsInitialized')]).then(() => { + _converse.minimized_chats = new _converse.MinimizedChats({ + model: _converse.chatboxes + }); + + _converse.emit('minimizedChatsInitialized'); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + + _converse.on('registeredGlobalEventHandlers', function () { + window.addEventListener("resize", _.debounce(function (ev) { + if (_converse.connection.connected) { + _converse.chatboxviews.trimChats(); + } + }, 200)); + }); + + _converse.on('controlBoxOpened', function (chatbox) { + // Wrapped in anon method because at scan time, chatboxviews + // attr not set yet. + if (_converse.connection.connected) { + _converse.chatboxviews.trimChats(chatbox); + } + }); + } + }); /***/ }), @@ -62334,132 +62443,134 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!*******************************!*\ !*** ./src/converse-modal.js ***! \*******************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var backbone_vdomview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! backbone.vdomview */ "./node_modules/backbone.vdomview/backbone.vdomview.js"); +/* harmony import */ var backbone_vdomview__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(backbone_vdomview__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(bootstrap__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_alert_modal_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/alert_modal.html */ "./src/templates/alert_modal.html"); +/* harmony import */ var templates_alert_modal_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_alert_modal_html__WEBPACK_IMPORTED_MODULE_3__); +// Converse.js // http://conversejs.org // -// Copyright (c) 2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - if (true) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/alert_modal.html */ "./src/templates/alert_modal.html"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! backbone.vdomview */ "./node_modules/backbone.vdomview/backbone.vdomview.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env, + Strophe = _converse$env.Strophe, + Backbone = _converse$env.Backbone, + _ = _converse$env._; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-modal', { + initialize() { + const _converse = this._converse; + _converse.BootstrapModal = Backbone.VDOMView.extend({ + initialize() { + this.render().insertIntoDOM(); + this.modal = new bootstrap__WEBPACK_IMPORTED_MODULE_1___default.a.Modal(this.el, { + backdrop: 'static', + keyboard: true + }); + this.el.addEventListener('hide.bs.modal', event => { + if (!_.isNil(this.trigger_el)) { + this.trigger_el.classList.remove('selected'); + } + }, false); + }, + + insertIntoDOM() { + const container_el = _converse.chatboxviews.el.querySelector("#converse-modals"); + + container_el.insertAdjacentElement('beforeEnd', this.el); + }, + + show(ev) { + if (ev) { + ev.preventDefault(); + this.trigger_el = ev.target; + this.trigger_el.classList.add('selected'); + } + + this.modal.show(); + } + + }); + _converse.Alert = _converse.BootstrapModal.extend({ + initialize() { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + + this.model.on('change', this.render, this); + }, + + toHTML() { + return templates_alert_modal_html__WEBPACK_IMPORTED_MODULE_3___default()(this.model.toJSON()); + } + + }); + + _converse.api.listen.on('afterTearDown', () => { + if (!_converse.chatboxviews) { + return; + } + + const container = _converse.chatboxviews.el.querySelector("#converse-modals"); + + if (container) { + container.innerHTML = ''; + } + }); + /************************ BEGIN API ************************/ + // We extend the default converse.js API to add methods specific to MUC chat rooms. + + + let alert; + + _.extend(_converse.api, { + 'alert': { + 'show'(type, title, messages) { + if (_.isString(messages)) { + messages = [messages]; + } + + if (type === Strophe.LogLevel.ERROR) { + type = 'alert-danger'; + } else if (type === Strophe.LogLevel.INFO) { + type = 'alert-info'; + } else if (type === Strophe.LogLevel.WARN) { + type = 'alert-warning'; + } + + if (_.isUndefined(alert)) { + const model = new Backbone.Model({ + 'title': title, + 'messages': messages, + 'type': type + }); + alert = new _converse.Alert({ + 'model': model + }); + } else { + alert.model.set({ + 'title': title, + 'messages': messages, + 'type': type + }); + } + + alert.show(); + } + + } + }); } -})(this, function (converse, tpl_alert_modal, bootstrap) { - "use strict"; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - Backbone = _converse$env.Backbone, - _ = _converse$env._; - converse.plugins.add('converse-modal', { - initialize() { - const _converse = this._converse; - _converse.BootstrapModal = Backbone.VDOMView.extend({ - initialize() { - this.render().insertIntoDOM(); - this.modal = new bootstrap.Modal(this.el, { - backdrop: 'static', - keyboard: true - }); - this.el.addEventListener('hide.bs.modal', event => { - if (!_.isNil(this.trigger_el)) { - this.trigger_el.classList.remove('selected'); - } - }, false); - }, - - insertIntoDOM() { - const container_el = _converse.chatboxviews.el.querySelector("#converse-modals"); - - container_el.insertAdjacentElement('beforeEnd', this.el); - }, - - show(ev) { - if (ev) { - ev.preventDefault(); - this.trigger_el = ev.target; - this.trigger_el.classList.add('selected'); - } - - this.modal.show(); - } - - }); - _converse.Alert = _converse.BootstrapModal.extend({ - initialize() { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - - this.model.on('change', this.render, this); - }, - - toHTML() { - return tpl_alert_modal(this.model.toJSON()); - } - - }); - - _converse.api.listen.on('afterTearDown', () => { - if (!_converse.chatboxviews) { - return; - } - - const container = _converse.chatboxviews.el.querySelector("#converse-modals"); - - if (container) { - container.innerHTML = ''; - } - }); - /************************ BEGIN API ************************/ - // We extend the default converse.js API to add methods specific to MUC chat rooms. - - - let alert; - - _.extend(_converse.api, { - 'alert': { - 'show'(type, title, messages) { - if (_.isString(messages)) { - messages = [messages]; - } - - if (type === Strophe.LogLevel.ERROR) { - type = 'alert-danger'; - } else if (type === Strophe.LogLevel.INFO) { - type = 'alert-info'; - } else if (type === Strophe.LogLevel.WARN) { - type = 'alert-warning'; - } - - if (_.isUndefined(alert)) { - const model = new Backbone.Model({ - 'title': title, - 'messages': messages, - 'type': type - }); - alert = new _converse.Alert({ - 'model': model - }); - } else { - alert.model.set({ - 'title': title, - 'messages': messages, - 'type': type - }); - } - - alert.show(); - } - - } - }); - } - - }); }); /***/ }), @@ -62468,2243 +62579,2314 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!***********************************!*\ !*** ./src/converse-muc-views.js ***! \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_modal__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"); +/* harmony import */ var awesomplete__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! awesomplete */ "./node_modules/awesomplete-avoid-xss/awesomplete.js"); +/* harmony import */ var awesomplete__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(awesomplete__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(formdata_polyfill__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var utils_muc__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! utils/muc */ "./src/headless/utils/muc.js"); +/* harmony import */ var templates_add_chatroom_modal_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/add_chatroom_modal.html */ "./src/templates/add_chatroom_modal.html"); +/* harmony import */ var templates_add_chatroom_modal_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_add_chatroom_modal_html__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var templates_chatarea_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/chatarea.html */ "./src/templates/chatarea.html"); +/* harmony import */ var templates_chatarea_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_chatarea_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_chatroom_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/chatroom.html */ "./src/templates/chatroom.html"); +/* harmony import */ var templates_chatroom_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var templates_chatroom_destroyed_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/chatroom_destroyed.html */ "./src/templates/chatroom_destroyed.html"); +/* harmony import */ var templates_chatroom_destroyed_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_destroyed_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var templates_chatroom_details_modal_html__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! templates/chatroom_details_modal.html */ "./src/templates/chatroom_details_modal.html"); +/* harmony import */ var templates_chatroom_details_modal_html__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_details_modal_html__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var templates_chatroom_disconnect_html__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! templates/chatroom_disconnect.html */ "./src/templates/chatroom_disconnect.html"); +/* harmony import */ var templates_chatroom_disconnect_html__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_disconnect_html__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var templates_chatroom_features_html__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! templates/chatroom_features.html */ "./src/templates/chatroom_features.html"); +/* harmony import */ var templates_chatroom_features_html__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_features_html__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var templates_chatroom_form_html__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! templates/chatroom_form.html */ "./src/templates/chatroom_form.html"); +/* harmony import */ var templates_chatroom_form_html__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_form_html__WEBPACK_IMPORTED_MODULE_12__); +/* harmony import */ var templates_chatroom_head_html__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! templates/chatroom_head.html */ "./src/templates/chatroom_head.html"); +/* harmony import */ var templates_chatroom_head_html__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_head_html__WEBPACK_IMPORTED_MODULE_13__); +/* harmony import */ var templates_chatroom_invite_html__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! templates/chatroom_invite.html */ "./src/templates/chatroom_invite.html"); +/* harmony import */ var templates_chatroom_invite_html__WEBPACK_IMPORTED_MODULE_14___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_invite_html__WEBPACK_IMPORTED_MODULE_14__); +/* harmony import */ var templates_chatroom_nickname_form_html__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! templates/chatroom_nickname_form.html */ "./src/templates/chatroom_nickname_form.html"); +/* harmony import */ var templates_chatroom_nickname_form_html__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_nickname_form_html__WEBPACK_IMPORTED_MODULE_15__); +/* harmony import */ var templates_chatroom_password_form_html__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! templates/chatroom_password_form.html */ "./src/templates/chatroom_password_form.html"); +/* harmony import */ var templates_chatroom_password_form_html__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_password_form_html__WEBPACK_IMPORTED_MODULE_16__); +/* harmony import */ var templates_chatroom_sidebar_html__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! templates/chatroom_sidebar.html */ "./src/templates/chatroom_sidebar.html"); +/* harmony import */ var templates_chatroom_sidebar_html__WEBPACK_IMPORTED_MODULE_17___default = /*#__PURE__*/__webpack_require__.n(templates_chatroom_sidebar_html__WEBPACK_IMPORTED_MODULE_17__); +/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"); +/* harmony import */ var templates_info_html__WEBPACK_IMPORTED_MODULE_18___default = /*#__PURE__*/__webpack_require__.n(templates_info_html__WEBPACK_IMPORTED_MODULE_18__); +/* harmony import */ var templates_list_chatrooms_modal_html__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! templates/list_chatrooms_modal.html */ "./src/templates/list_chatrooms_modal.html"); +/* harmony import */ var templates_list_chatrooms_modal_html__WEBPACK_IMPORTED_MODULE_19___default = /*#__PURE__*/__webpack_require__.n(templates_list_chatrooms_modal_html__WEBPACK_IMPORTED_MODULE_19__); +/* harmony import */ var templates_occupant_html__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! templates/occupant.html */ "./src/templates/occupant.html"); +/* harmony import */ var templates_occupant_html__WEBPACK_IMPORTED_MODULE_20___default = /*#__PURE__*/__webpack_require__.n(templates_occupant_html__WEBPACK_IMPORTED_MODULE_20__); +/* harmony import */ var templates_room_description_html__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! templates/room_description.html */ "./src/templates/room_description.html"); +/* harmony import */ var templates_room_description_html__WEBPACK_IMPORTED_MODULE_21___default = /*#__PURE__*/__webpack_require__.n(templates_room_description_html__WEBPACK_IMPORTED_MODULE_21__); +/* harmony import */ var templates_room_item_html__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! templates/room_item.html */ "./src/templates/room_item.html"); +/* harmony import */ var templates_room_item_html__WEBPACK_IMPORTED_MODULE_22___default = /*#__PURE__*/__webpack_require__.n(templates_room_item_html__WEBPACK_IMPORTED_MODULE_22__); +/* harmony import */ var templates_room_panel_html__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! templates/room_panel.html */ "./src/templates/room_panel.html"); +/* harmony import */ var templates_room_panel_html__WEBPACK_IMPORTED_MODULE_23___default = /*#__PURE__*/__webpack_require__.n(templates_room_panel_html__WEBPACK_IMPORTED_MODULE_23__); +/* harmony import */ var templates_rooms_results_html__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! templates/rooms_results.html */ "./src/templates/rooms_results.html"); +/* harmony import */ var templates_rooms_results_html__WEBPACK_IMPORTED_MODULE_24___default = /*#__PURE__*/__webpack_require__.n(templates_rooms_results_html__WEBPACK_IMPORTED_MODULE_24__); +/* harmony import */ var templates_spinner_html__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"); +/* harmony import */ var templates_spinner_html__WEBPACK_IMPORTED_MODULE_25___default = /*#__PURE__*/__webpack_require__.n(templates_spinner_html__WEBPACK_IMPORTED_MODULE_25__); +/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"); +/* harmony import */ var xss__WEBPACK_IMPORTED_MODULE_26___default = /*#__PURE__*/__webpack_require__.n(xss__WEBPACK_IMPORTED_MODULE_26__); +// Converse.js // http://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"), __webpack_require__(/*! utils/muc */ "./src/headless/utils/muc.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! templates/add_chatroom_modal.html */ "./src/templates/add_chatroom_modal.html"), __webpack_require__(/*! templates/chatarea.html */ "./src/templates/chatarea.html"), __webpack_require__(/*! templates/chatroom.html */ "./src/templates/chatroom.html"), __webpack_require__(/*! templates/chatroom_details_modal.html */ "./src/templates/chatroom_details_modal.html"), __webpack_require__(/*! templates/chatroom_destroyed.html */ "./src/templates/chatroom_destroyed.html"), __webpack_require__(/*! templates/chatroom_disconnect.html */ "./src/templates/chatroom_disconnect.html"), __webpack_require__(/*! templates/chatroom_features.html */ "./src/templates/chatroom_features.html"), __webpack_require__(/*! templates/chatroom_form.html */ "./src/templates/chatroom_form.html"), __webpack_require__(/*! templates/chatroom_head.html */ "./src/templates/chatroom_head.html"), __webpack_require__(/*! templates/chatroom_invite.html */ "./src/templates/chatroom_invite.html"), __webpack_require__(/*! templates/chatroom_nickname_form.html */ "./src/templates/chatroom_nickname_form.html"), __webpack_require__(/*! templates/chatroom_password_form.html */ "./src/templates/chatroom_password_form.html"), __webpack_require__(/*! templates/chatroom_sidebar.html */ "./src/templates/chatroom_sidebar.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/list_chatrooms_modal.html */ "./src/templates/list_chatrooms_modal.html"), __webpack_require__(/*! templates/occupant.html */ "./src/templates/occupant.html"), __webpack_require__(/*! templates/room_description.html */ "./src/templates/room_description.html"), __webpack_require__(/*! templates/room_item.html */ "./src/templates/room_item.html"), __webpack_require__(/*! templates/room_panel.html */ "./src/templates/room_panel.html"), __webpack_require__(/*! templates/rooms_results.html */ "./src/templates/rooms_results.html"), __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"), __webpack_require__(/*! awesomplete */ "./node_modules/awesomplete-avoid-xss/awesomplete.js"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, _FormData, muc_utils, xss, tpl_add_chatroom_modal, tpl_chatarea, tpl_chatroom, tpl_chatroom_details_modal, tpl_chatroom_destroyed, tpl_chatroom_disconnect, tpl_chatroom_features, tpl_chatroom_form, tpl_chatroom_head, tpl_chatroom_invite, tpl_chatroom_nickname_form, tpl_chatroom_password_form, tpl_chatroom_sidebar, tpl_info, tpl_list_chatrooms_modal, tpl_occupant, tpl_room_description, tpl_room_item, tpl_room_panel, tpl_rooms_results, tpl_spinner, Awesomplete) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - b64_sha1 = _converse$env.b64_sha1, - moment = _converse$env.moment, - f = _converse$env.f, - sizzle = _converse$env.sizzle, - _ = _converse$env._, - $build = _converse$env.$build, - $iq = _converse$env.$iq, - $msg = _converse$env.$msg, - $pres = _converse$env.$pres; - const u = converse.env.utils; - const ROOM_FEATURES_MAP = { - 'passwordprotected': 'unsecured', - 'unsecured': 'passwordprotected', - 'hidden': 'publicroom', - 'publicroom': 'hidden', - 'membersonly': 'open', - 'open': 'membersonly', - 'persistent': 'temporary', - 'temporary': 'persistent', - 'nonanonymous': 'semianonymous', - 'semianonymous': 'nonanonymous', - 'moderated': 'unmoderated', - 'unmoderated': 'moderated' - }; - converse.plugins.add('converse-muc-views', { - /* Dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. They are "optional" because they might not be - * available, in which case any overrides applicable to them will be - * ignored. - * - * NB: These plugins need to have already been loaded via require.js. - * - * It's possible to make these dependencies "non-optional". - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. - */ - dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"], - overrides: { - ControlBoxView: { - renderRoomsPanel() { - const _converse = this.__super__._converse; - this.roomspanel = new _converse.RoomsPanel({ - 'model': new (_converse.RoomsPanelModel.extend({ - 'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), - // Required by sessionStorage - 'browserStorage': new Backbone.BrowserStorage[_converse.config.get('storage')](b64_sha1(`converse.roomspanel${_converse.bare_jid}`)) - }))() + + + + + + + + + + + + + + + + + + + + + + + + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + b64_sha1 = _converse$env.b64_sha1, + moment = _converse$env.moment, + f = _converse$env.f, + sizzle = _converse$env.sizzle, + _ = _converse$env._, + $build = _converse$env.$build, + $iq = _converse$env.$iq, + $msg = _converse$env.$msg, + $pres = _converse$env.$pres; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].env.utils; +const ROOM_FEATURES_MAP = { + 'passwordprotected': 'unsecured', + 'unsecured': 'passwordprotected', + 'hidden': 'publicroom', + 'publicroom': 'hidden', + 'membersonly': 'open', + 'open': 'membersonly', + 'persistent': 'temporary', + 'temporary': 'persistent', + 'nonanonymous': 'semianonymous', + 'semianonymous': 'nonanonymous', + 'moderated': 'unmoderated', + 'unmoderated': 'moderated' +}; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins.add('converse-muc-views', { + /* Dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. They are "optional" because they might not be + * available, in which case any overrides applicable to them will be + * ignored. + * + * NB: These plugins need to have already been loaded via require.js. + * + * It's possible to make these dependencies "non-optional". + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. + */ + dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"], + overrides: { + ControlBoxView: { + renderRoomsPanel() { + const _converse = this.__super__._converse; + this.roomspanel = new _converse.RoomsPanel({ + 'model': new (_converse.RoomsPanelModel.extend({ + 'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), + // Required by sessionStorage + 'browserStorage': new Backbone.BrowserStorage[_converse.config.get('storage')](b64_sha1(`converse.roomspanel${_converse.bare_jid}`)) + }))() + }); + this.roomspanel.model.fetch(); + this.el.querySelector('.controlbox-pane').insertAdjacentElement('beforeEnd', this.roomspanel.render().el); + + if (!this.roomspanel.model.get('nick')) { + this.roomspanel.model.save({ + nick: _converse.xmppstatus.vcard.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid) }); - this.roomspanel.model.fetch(); - this.el.querySelector('.controlbox-pane').insertAdjacentElement('beforeEnd', this.roomspanel.render().el); - - if (!this.roomspanel.model.get('nick')) { - this.roomspanel.model.save({ - nick: _converse.xmppstatus.vcard.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid) - }); - } - - _converse.emit('roomsPanelRendered'); - }, - - renderControlBoxPane() { - const _converse = this.__super__._converse; - - this.__super__.renderControlBoxPane.apply(this, arguments); - - if (_converse.allow_muc) { - this.renderRoomsPanel(); - } } + _converse.emit('roomsPanelRendered'); + }, + + renderControlBoxPane() { + const _converse = this.__super__._converse; + + this.__super__.renderControlBoxPane.apply(this, arguments); + + if (_converse.allow_muc) { + this.renderRoomsPanel(); + } } - }, - initialize() { - const _converse = this._converse, - __ = _converse.__; + } + }, - _converse.api.promises.add(['roomsPanelRendered']); // Configuration values for this plugin - // ==================================== - // Refer to docs/source/configuration.rst for explanations of these - // configuration settings. + initialize() { + const _converse = this._converse, + __ = _converse.__; + + _converse.api.promises.add(['roomsPanelRendered']); // Configuration values for this plugin + // ==================================== + // Refer to docs/source/configuration.rst for explanations of these + // configuration settings. - _converse.api.settings.update({ - 'auto_list_rooms': false, - 'hide_muc_server': false, - // TODO: no longer implemented... - 'muc_disable_moderator_commands': false, - 'visible_toolbar_buttons': { - 'toggle_occupants': true - } - }); + _converse.api.settings.update({ + 'auto_list_rooms': false, + 'hide_muc_server': false, + // TODO: no longer implemented... + 'muc_disable_moderator_commands': false, + 'visible_toolbar_buttons': { + 'toggle_occupants': true + } + }); - function ___(str) { - /* This is part of a hack to get gettext to scan strings to be - * translated. Strings we cannot send to the function above because - * they require variable interpolation and we don't yet have the - * variables at scan time. + function ___(str) { + /* This is part of a hack to get gettext to scan strings to be + * translated. Strings we cannot send to the function above because + * they require variable interpolation and we don't yet have the + * variables at scan time. + * + * See actionInfoMessages further below. + */ + return str; + } + /* http://xmpp.org/extensions/xep-0045.html + * ---------------------------------------- + * 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID + * 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat + * 102 message Configuration change Inform occupants that groupchat now shows unavailable members + * 103 message Configuration change Inform occupants that groupchat now does not show unavailable members + * 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred + * 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants + * 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled + * 171 message Configuration change Inform occupants that groupchat logging is now disabled + * 172 message Configuration change Inform occupants that the groupchat is now non-anonymous + * 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous + * 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous + * 201 presence Entering a groupchat Inform user that a new groupchat has been created + * 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick + * 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat + * 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname + * 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat + * 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change + * 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member + * 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown + */ + + + _converse.muc = { + info_messages: { + 100: __('This groupchat is not anonymous'), + 102: __('This groupchat now shows unavailable members'), + 103: __('This groupchat does not show unavailable members'), + 104: __('The groupchat configuration has changed'), + 170: __('groupchat logging is now enabled'), + 171: __('groupchat logging is now disabled'), + 172: __('This groupchat is now no longer anonymous'), + 173: __('This groupchat is now semi-anonymous'), + 174: __('This groupchat is now fully-anonymous'), + 201: __('A new groupchat has been created') + }, + disconnect_messages: { + 301: __('You have been banned from this groupchat'), + 307: __('You have been kicked from this groupchat'), + 321: __("You have been removed from this groupchat because of an affiliation change"), + 322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"), + 332: __("You have been removed from this groupchat because the service hosting it is being shut down") + }, + action_info_messages: { + /* XXX: Note the triple underscore function and not double + * underscore. * - * See actionInfoMessages further below. + * This is a hack. We can't pass the strings to __ because we + * don't yet know what the variable to interpolate is. + * + * Triple underscore will just return the string again, but we + * can then at least tell gettext to scan for it so that these + * strings are picked up by the translation machinery. */ - return str; + 301: ___("%1$s has been banned"), + 303: ___("%1$s's nickname has changed"), + 307: ___("%1$s has been kicked out"), + 321: ___("%1$s has been removed because of an affiliation change"), + 322: ___("%1$s has been removed for not being a member") + }, + new_nickname_messages: { + 210: ___('Your nickname has been automatically set to %1$s'), + 303: ___('Your nickname has been changed to %1$s') } - /* http://xmpp.org/extensions/xep-0045.html - * ---------------------------------------- - * 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID - * 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat - * 102 message Configuration change Inform occupants that groupchat now shows unavailable members - * 103 message Configuration change Inform occupants that groupchat now does not show unavailable members - * 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred - * 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants - * 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled - * 171 message Configuration change Inform occupants that groupchat logging is now disabled - * 172 message Configuration change Inform occupants that the groupchat is now non-anonymous - * 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous - * 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous - * 201 presence Entering a groupchat Inform user that a new groupchat has been created - * 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick - * 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat - * 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname - * 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat - * 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change - * 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member - * 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown + }; + + function insertRoomInfo(el, stanza) { + /* Insert groupchat info (based on returned #disco IQ stanza) + * + * Parameters: + * (HTMLElement) el: The HTML DOM element that should + * contain the info. + * (XMLElement) stanza: The IQ stanza containing the groupchat + * info. */ + // All MUC features found here: http://xmpp.org/registrar/disco-features.html + el.querySelector('span.spinner').remove(); + el.querySelector('a.room-info').classList.add('selected'); + el.insertAdjacentHTML('beforeEnd', templates_room_description_html__WEBPACK_IMPORTED_MODULE_21___default()({ + 'jid': stanza.getAttribute('from'), + 'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'), + 'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'), + 'hidden': sizzle('feature[var="muc_hidden"]', stanza).length, + 'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length, + 'moderated': sizzle('feature[var="muc_moderated"]', stanza).length, + 'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length, + 'open': sizzle('feature[var="muc_open"]', stanza).length, + 'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length, + 'persistent': sizzle('feature[var="muc_persistent"]', stanza).length, + 'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length, + 'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length, + 'temporary': sizzle('feature[var="muc_temporary"]', stanza).length, + 'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length, + 'label_desc': __('Description:'), + 'label_jid': __('Groupchat Address (JID):'), + 'label_occ': __('Participants:'), + 'label_features': __('Features:'), + 'label_requires_auth': __('Requires authentication'), + 'label_hidden': __('Hidden'), + 'label_requires_invite': __('Requires an invitation'), + 'label_moderated': __('Moderated'), + 'label_non_anon': __('Non-anonymous'), + 'label_open_room': __('Open'), + 'label_permanent_room': __('Permanent'), + 'label_public': __('Public'), + 'label_semi_anon': __('Semi-anonymous'), + 'label_temp_room': __('Temporary'), + 'label_unmoderated': __('Unmoderated') + })); + } + function toggleRoomInfo(ev) { + /* Show/hide extra information about a groupchat in a listing. */ + const parent_el = u.ancestor(ev.target, '.room-item'), + div_el = parent_el.querySelector('div.room-info'); - _converse.muc = { - info_messages: { - 100: __('This groupchat is not anonymous'), - 102: __('This groupchat now shows unavailable members'), - 103: __('This groupchat does not show unavailable members'), - 104: __('The groupchat configuration has changed'), - 170: __('groupchat logging is now enabled'), - 171: __('groupchat logging is now disabled'), - 172: __('This groupchat is now no longer anonymous'), - 173: __('This groupchat is now semi-anonymous'), - 174: __('This groupchat is now fully-anonymous'), - 201: __('A new groupchat has been created') - }, - disconnect_messages: { - 301: __('You have been banned from this groupchat'), - 307: __('You have been kicked from this groupchat'), - 321: __("You have been removed from this groupchat because of an affiliation change"), - 322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"), - 332: __("You have been removed from this groupchat because the service hosting it is being shut down") - }, - action_info_messages: { - /* XXX: Note the triple underscore function and not double - * underscore. - * - * This is a hack. We can't pass the strings to __ because we - * don't yet know what the variable to interpolate is. - * - * Triple underscore will just return the string again, but we - * can then at least tell gettext to scan for it so that these - * strings are picked up by the translation machinery. - */ - 301: ___("%1$s has been banned"), - 303: ___("%1$s's nickname has changed"), - 307: ___("%1$s has been kicked out"), - 321: ___("%1$s has been removed because of an affiliation change"), - 322: ___("%1$s has been removed for not being a member") - }, - new_nickname_messages: { - 210: ___('Your nickname has been automatically set to %1$s'), - 303: ___('Your nickname has been changed to %1$s') + if (div_el) { + u.slideIn(div_el).then(u.removeElement); + parent_el.querySelector('a.room-info').classList.remove('selected'); + } else { + parent_el.insertAdjacentHTML('beforeend', templates_spinner_html__WEBPACK_IMPORTED_MODULE_25___default()()); + + _converse.api.disco.info(ev.target.getAttribute('data-room-jid'), null).then(stanza => insertRoomInfo(parent_el, stanza)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + } + } + + _converse.ListChatRoomsModal = _converse.BootstrapModal.extend({ + events: { + 'submit form': 'showRooms', + 'click a.room-info': 'toggleRoomInfo', + 'change input[name=nick]': 'setNick', + 'change input[name=server]': 'setDomain', + 'click .open-room': 'openRoom' + }, + + initialize() { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + + this.model.on('change:muc_domain', this.onDomainChange, this); + }, + + toHTML() { + return templates_list_chatrooms_modal_html__WEBPACK_IMPORTED_MODULE_19___default()(_.extend(this.model.toJSON(), { + 'heading_list_chatrooms': __('Query for Groupchats'), + 'label_server_address': __('Server address'), + 'label_query': __('Show groupchats'), + 'server_placeholder': __('conference.example.org') + })); + }, + + afterRender() { + this.el.addEventListener('shown.bs.modal', () => { + this.el.querySelector('input[name="server"]').focus(); + }, false); + }, + + openRoom(ev) { + ev.preventDefault(); + const jid = ev.target.getAttribute('data-room-jid'); + const name = ev.target.getAttribute('data-room-name'); + this.modal.hide(); + + _converse.api.rooms.open(jid, { + 'name': name + }); + }, + + toggleRoomInfo(ev) { + ev.preventDefault(); + toggleRoomInfo(ev); + }, + + onDomainChange(model) { + if (_converse.auto_list_rooms) { + this.updateRoomsList(); } - }; + }, - function insertRoomInfo(el, stanza) { - /* Insert groupchat info (based on returned #disco IQ stanza) - * - * Parameters: - * (HTMLElement) el: The HTML DOM element that should - * contain the info. - * (XMLElement) stanza: The IQ stanza containing the groupchat - * info. + roomStanzaItemToHTMLElement(groupchat) { + const name = Strophe.unescapeNode(groupchat.getAttribute('name') || groupchat.getAttribute('jid')); + const div = document.createElement('div'); + div.innerHTML = templates_room_item_html__WEBPACK_IMPORTED_MODULE_22___default()({ + 'name': Strophe.xmlunescape(name), + 'jid': groupchat.getAttribute('jid'), + 'open_title': __('Click to open this groupchat'), + 'info_title': __('Show more information on this groupchat') + }); + return div.firstElementChild; + }, + + removeSpinner() { + _.each(this.el.querySelectorAll('span.spinner'), el => el.parentNode.removeChild(el)); + }, + + informNoRoomsFound() { + const chatrooms_el = this.el.querySelector('.available-chatrooms'); + chatrooms_el.innerHTML = templates_rooms_results_html__WEBPACK_IMPORTED_MODULE_24___default()({ + 'feedback_text': __('No groupchats found') + }); + const input_el = this.el.querySelector('input[name="server"]'); + input_el.classList.remove('hidden'); + this.removeSpinner(); + }, + + onRoomsFound(iq) { + /* Handle the IQ stanza returned from the server, containing + * all its public groupchats. */ - // All MUC features found here: http://xmpp.org/registrar/disco-features.html - el.querySelector('span.spinner').remove(); - el.querySelector('a.room-info').classList.add('selected'); - el.insertAdjacentHTML('beforeEnd', tpl_room_description({ - 'jid': stanza.getAttribute('from'), - 'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'), - 'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'), - 'hidden': sizzle('feature[var="muc_hidden"]', stanza).length, - 'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length, - 'moderated': sizzle('feature[var="muc_moderated"]', stanza).length, - 'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length, - 'open': sizzle('feature[var="muc_open"]', stanza).length, - 'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length, - 'persistent': sizzle('feature[var="muc_persistent"]', stanza).length, - 'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length, - 'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length, - 'temporary': sizzle('feature[var="muc_temporary"]', stanza).length, - 'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length, - 'label_desc': __('Description:'), - 'label_jid': __('Groupchat Address (JID):'), - 'label_occ': __('Participants:'), - 'label_features': __('Features:'), - 'label_requires_auth': __('Requires authentication'), - 'label_hidden': __('Hidden'), - 'label_requires_invite': __('Requires an invitation'), - 'label_moderated': __('Moderated'), - 'label_non_anon': __('Non-anonymous'), - 'label_open_room': __('Open'), - 'label_permanent_room': __('Permanent'), - 'label_public': __('Public'), - 'label_semi_anon': __('Semi-anonymous'), - 'label_temp_room': __('Temporary'), - 'label_unmoderated': __('Unmoderated') + const available_chatrooms = this.el.querySelector('.available-chatrooms'); + this.rooms = iq.querySelectorAll('query item'); + + if (this.rooms.length) { + // For translators: %1$s is a variable and will be + // replaced with the XMPP server name + available_chatrooms.innerHTML = templates_rooms_results_html__WEBPACK_IMPORTED_MODULE_24___default()({ + 'feedback_text': __('Groupchats found:') + }); + const fragment = document.createDocumentFragment(); + + const children = _.reject(_.map(this.rooms, this.roomStanzaItemToHTMLElement), _.isNil); + + _.each(children, child => fragment.appendChild(child)); + + available_chatrooms.appendChild(fragment); + this.removeSpinner(); + } else { + this.informNoRoomsFound(); + } + + return true; + }, + + updateRoomsList() { + /* Send an IQ stanza to the server asking for all groupchats + */ + _converse.connection.sendIQ($iq({ + 'to': this.model.get('muc_domain'), + 'from': _converse.connection.jid, + 'type': "get" + }).c("query", { + xmlns: Strophe.NS.DISCO_ITEMS + }), this.onRoomsFound.bind(this), this.informNoRoomsFound.bind(this), 5000); + }, + + showRooms(ev) { + ev.preventDefault(); + const data = new FormData(ev.target); + this.model.save('muc_domain', Strophe.getDomainFromJid(data.get('server'))); + this.updateRoomsList(); + }, + + setDomain(ev) { + this.model.save('muc_domain', Strophe.getDomainFromJid(ev.target.value)); + }, + + setNick(ev) { + this.model.save({ + nick: ev.target.value + }); + } + + }); + _converse.AddChatRoomModal = _converse.BootstrapModal.extend({ + events: { + 'submit form.add-chatroom': 'openChatRoom' + }, + + toHTML() { + return templates_add_chatroom_modal_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), { + 'heading_new_chatroom': __('Enter a new Groupchat'), + 'label_room_address': __('Groupchat address'), + 'label_nickname': __('Optional nickname'), + 'chatroom_placeholder': __('name@conference.example.org'), + 'label_join': __('Join') + })); + }, + + afterRender() { + this.el.addEventListener('shown.bs.modal', () => { + this.el.querySelector('input[name="chatroom"]').focus(); + }, false); + }, + + parseRoomDataFromEvent(form) { + const data = new FormData(form); + const jid = data.get('chatroom'); + this.model.save('muc_domain', Strophe.getDomainFromJid(jid)); + return { + 'jid': jid, + 'nick': data.get('nickname') + }; + }, + + openChatRoom(ev) { + ev.preventDefault(); + const data = this.parseRoomDataFromEvent(ev.target); + + if (data.nick === "") { + // Make sure defaults apply if no nick is provided. + data.nick = undefined; + } + + _converse.api.rooms.open(data.jid, data); + + this.modal.hide(); + ev.target.reset(); + } + + }); + _converse.RoomDetailsModal = _converse.BootstrapModal.extend({ + initialize() { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + + this.model.on('change', this.render, this); + this.model.occupants.on('add', this.render, this); + this.model.occupants.on('change', this.render, this); + }, + + toHTML() { + return templates_chatroom_details_modal_html__WEBPACK_IMPORTED_MODULE_9___default()(_.extend(this.model.toJSON(), { + '_': _, + '__': __, + 'topic': u.addHyperlinks(xss__WEBPACK_IMPORTED_MODULE_26___default.a.filterXSS(_.get(this.model.get('subject'), 'text'), { + 'whiteList': {} + })), + 'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()), + 'num_occupants': this.model.occupants.length })); } - function toggleRoomInfo(ev) { - /* Show/hide extra information about a groupchat in a listing. */ - const parent_el = u.ancestor(ev.target, '.room-item'), - div_el = parent_el.querySelector('div.room-info'); + }); + _converse.ChatRoomView = _converse.ChatBoxView.extend({ + /* Backbone.NativeView which renders a groupchat, based upon the view + * for normal one-on-one chat boxes. + */ + length: 300, + tagName: 'div', + className: 'chatbox chatroom hidden', + is_chatroom: true, + events: { + 'change input.fileupload': 'onFileSelection', + 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', + 'click .chatbox-navback': 'showControlBox', + 'click .close-chatbox-button': 'close', + 'click .configure-chatroom-button': 'getAndRenderConfigurationForm', + 'click .hide-occupants': 'hideOccupants', + 'click .new-msgs-indicator': 'viewUnreadMessages', + 'click .occupant-nick': 'onOccupantClicked', + 'click .send-button': 'onFormSubmitted', + 'click .show-room-details-modal': 'showRoomDetailsModal', + 'click .toggle-call': 'toggleCall', + 'click .toggle-occupants': 'toggleOccupants', + 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', + 'click .toggle-smiley': 'toggleEmojiMenu', + 'click .upload-file': 'toggleFileUpload', + 'keydown .chat-textarea': 'keyPressed', + 'keyup .chat-textarea': 'keyUp', + 'input .chat-textarea': 'inputChanged' + }, - if (div_el) { - u.slideIn(div_el).then(u.removeElement); - parent_el.querySelector('a.room-info').classList.remove('selected'); - } else { - parent_el.insertAdjacentHTML('beforeend', tpl_spinner()); + initialize() { + this.initDebounced(); + this.model.messages.on('add', this.onMessageAdded, this); + this.model.messages.on('rendered', this.scrollDown, this); + this.model.on('change:affiliation', this.renderHeading, this); + this.model.on('change:connection_status', this.afterConnected, this); + this.model.on('change:jid', this.renderHeading, this); + this.model.on('change:name', this.renderHeading, this); + this.model.on('change:subject', this.renderHeading, this); + this.model.on('change:subject', this.setChatRoomSubject, this); + this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this); + this.model.on('destroy', this.hide, this); + this.model.on('show', this.show, this); + this.model.occupants.on('add', this.onOccupantAdded, this); + this.model.occupants.on('remove', this.onOccupantRemoved, this); + this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this); + this.model.occupants.on('change:role', this.informOfOccupantsRoleChange, this); + this.model.occupants.on('change:affiliation', this.informOfOccupantsAffiliationChange, this); + this.createEmojiPicker(); + this.createOccupantsView(); + this.render().insertIntoDOM(); + this.registerHandlers(); + this.enterRoom(); + }, - _converse.api.disco.info(ev.target.getAttribute('data-room-jid'), null).then(stanza => insertRoomInfo(parent_el, stanza)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - } - } - - _converse.ListChatRoomsModal = _converse.BootstrapModal.extend({ - events: { - 'submit form': 'showRooms', - 'click a.room-info': 'toggleRoomInfo', - 'change input[name=nick]': 'setNick', - 'change input[name=server]': 'setDomain', - 'click .open-room': 'openRoom' - }, - - initialize() { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - - this.model.on('change:muc_domain', this.onDomainChange, this); - }, - - toHTML() { - return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), { - 'heading_list_chatrooms': __('Query for Groupchats'), - 'label_server_address': __('Server address'), - 'label_query': __('Show groupchats'), - 'server_placeholder': __('conference.example.org') - })); - }, - - afterRender() { - this.el.addEventListener('shown.bs.modal', () => { - this.el.querySelector('input[name="server"]').focus(); - }, false); - }, - - openRoom(ev) { + enterRoom(ev) { + if (ev) { ev.preventDefault(); - const jid = ev.target.getAttribute('data-room-jid'); - const name = ev.target.getAttribute('data-room-name'); - this.modal.hide(); - - _converse.api.rooms.open(jid, { - 'name': name - }); - }, - - toggleRoomInfo(ev) { - ev.preventDefault(); - toggleRoomInfo(ev); - }, - - onDomainChange(model) { - if (_converse.auto_list_rooms) { - this.updateRoomsList(); - } - }, - - roomStanzaItemToHTMLElement(groupchat) { - const name = Strophe.unescapeNode(groupchat.getAttribute('name') || groupchat.getAttribute('jid')); - const div = document.createElement('div'); - div.innerHTML = tpl_room_item({ - 'name': Strophe.xmlunescape(name), - 'jid': groupchat.getAttribute('jid'), - 'open_title': __('Click to open this groupchat'), - 'info_title': __('Show more information on this groupchat') - }); - return div.firstElementChild; - }, - - removeSpinner() { - _.each(this.el.querySelectorAll('span.spinner'), el => el.parentNode.removeChild(el)); - }, - - informNoRoomsFound() { - const chatrooms_el = this.el.querySelector('.available-chatrooms'); - chatrooms_el.innerHTML = tpl_rooms_results({ - 'feedback_text': __('No groupchats found') - }); - const input_el = this.el.querySelector('input[name="server"]'); - input_el.classList.remove('hidden'); - this.removeSpinner(); - }, - - onRoomsFound(iq) { - /* Handle the IQ stanza returned from the server, containing - * all its public groupchats. - */ - const available_chatrooms = this.el.querySelector('.available-chatrooms'); - this.rooms = iq.querySelectorAll('query item'); - - if (this.rooms.length) { - // For translators: %1$s is a variable and will be - // replaced with the XMPP server name - available_chatrooms.innerHTML = tpl_rooms_results({ - 'feedback_text': __('Groupchats found:') - }); - const fragment = document.createDocumentFragment(); - - const children = _.reject(_.map(this.rooms, this.roomStanzaItemToHTMLElement), _.isNil); - - _.each(children, child => fragment.appendChild(child)); - - available_chatrooms.appendChild(fragment); - this.removeSpinner(); - } else { - this.informNoRoomsFound(); - } - - return true; - }, - - updateRoomsList() { - /* Send an IQ stanza to the server asking for all groupchats - */ - _converse.connection.sendIQ($iq({ - 'to': this.model.get('muc_domain'), - 'from': _converse.connection.jid, - 'type': "get" - }).c("query", { - xmlns: Strophe.NS.DISCO_ITEMS - }), this.onRoomsFound.bind(this), this.informNoRoomsFound.bind(this), 5000); - }, - - showRooms(ev) { - ev.preventDefault(); - const data = new FormData(ev.target); - this.model.save('muc_domain', Strophe.getDomainFromJid(data.get('server'))); - this.updateRoomsList(); - }, - - setDomain(ev) { - this.model.save('muc_domain', Strophe.getDomainFromJid(ev.target.value)); - }, - - setNick(ev) { - this.model.save({ - nick: ev.target.value - }); } - }); - _converse.AddChatRoomModal = _converse.BootstrapModal.extend({ - events: { - 'submit form.add-chatroom': 'openChatRoom' - }, + if (this.model.get('connection_status') !== _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.ENTERED) { + const handler = () => { + if (!u.isPersistableModel(this.model)) { + // Happens during tests, nothing to do if this + // is a hanging chatbox (i.e. not in the collection anymore). + return; + } - toHTML() { - return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), { - 'heading_new_chatroom': __('Enter a new Groupchat'), - 'label_room_address': __('Groupchat address'), - 'label_nickname': __('Optional nickname'), - 'chatroom_placeholder': __('name@conference.example.org'), - 'label_join': __('Join') - })); - }, - - afterRender() { - this.el.addEventListener('shown.bs.modal', () => { - this.el.querySelector('input[name="chatroom"]').focus(); - }, false); - }, - - parseRoomDataFromEvent(form) { - const data = new FormData(form); - const jid = data.get('chatroom'); - this.model.save('muc_domain', Strophe.getDomainFromJid(jid)); - return { - 'jid': jid, - 'nick': data.get('nickname') - }; - }, - - openChatRoom(ev) { - ev.preventDefault(); - const data = this.parseRoomDataFromEvent(ev.target); - - if (data.nick === "") { - // Make sure defaults apply if no nick is provided. - data.nick = undefined; - } - - _converse.api.rooms.open(data.jid, data); - - this.modal.hide(); - ev.target.reset(); - } - - }); - _converse.RoomDetailsModal = _converse.BootstrapModal.extend({ - initialize() { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - - this.model.on('change', this.render, this); - this.model.occupants.on('add', this.render, this); - this.model.occupants.on('change', this.render, this); - }, - - toHTML() { - return tpl_chatroom_details_modal(_.extend(this.model.toJSON(), { - '_': _, - '__': __, - 'topic': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), { - 'whiteList': {} - })), - 'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()), - 'num_occupants': this.model.occupants.length - })); - } - - }); - _converse.ChatRoomView = _converse.ChatBoxView.extend({ - /* Backbone.NativeView which renders a groupchat, based upon the view - * for normal one-on-one chat boxes. - */ - length: 300, - tagName: 'div', - className: 'chatbox chatroom hidden', - is_chatroom: true, - events: { - 'change input.fileupload': 'onFileSelection', - 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', - 'click .chatbox-navback': 'showControlBox', - 'click .close-chatbox-button': 'close', - 'click .configure-chatroom-button': 'getAndRenderConfigurationForm', - 'click .hide-occupants': 'hideOccupants', - 'click .new-msgs-indicator': 'viewUnreadMessages', - 'click .occupant-nick': 'onOccupantClicked', - 'click .send-button': 'onFormSubmitted', - 'click .show-room-details-modal': 'showRoomDetailsModal', - 'click .toggle-call': 'toggleCall', - 'click .toggle-occupants': 'toggleOccupants', - 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', - 'click .toggle-smiley': 'toggleEmojiMenu', - 'click .upload-file': 'toggleFileUpload', - 'keydown .chat-textarea': 'keyPressed', - 'keyup .chat-textarea': 'keyUp', - 'input .chat-textarea': 'inputChanged' - }, - - initialize() { - this.initDebounced(); - this.model.messages.on('add', this.onMessageAdded, this); - this.model.messages.on('rendered', this.scrollDown, this); - this.model.on('change:affiliation', this.renderHeading, this); - this.model.on('change:connection_status', this.afterConnected, this); - this.model.on('change:jid', this.renderHeading, this); - this.model.on('change:name', this.renderHeading, this); - this.model.on('change:subject', this.renderHeading, this); - this.model.on('change:subject', this.setChatRoomSubject, this); - this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this); - this.model.on('destroy', this.hide, this); - this.model.on('show', this.show, this); - this.model.occupants.on('add', this.onOccupantAdded, this); - this.model.occupants.on('remove', this.onOccupantRemoved, this); - this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this); - this.model.occupants.on('change:role', this.informOfOccupantsRoleChange, this); - this.model.occupants.on('change:affiliation', this.informOfOccupantsAffiliationChange, this); - this.createEmojiPicker(); - this.createOccupantsView(); - this.render().insertIntoDOM(); - this.registerHandlers(); - this.enterRoom(); - }, - - enterRoom(ev) { - if (ev) { - ev.preventDefault(); - } - - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - const handler = () => { - if (!u.isPersistableModel(this.model)) { - // Happens during tests, nothing to do if this - // is a hanging chatbox (i.e. not in the collection anymore). - return; - } - - this.populateAndJoin(); - - _converse.emit('chatRoomOpened', this); - }; - - this.model.getRoomFeatures().then(handler, handler); - } else { - this.fetchMessages(); + this.populateAndJoin(); _converse.emit('chatRoomOpened', this); - } - }, + }; - render() { - this.el.setAttribute('id', this.model.get('box_id')); - this.el.innerHTML = tpl_chatroom(); - this.renderHeading(); - this.renderChatArea(); - this.renderMessageForm(); - this.initAutoComplete(); - - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - this.showSpinner(); - } - - return this; - }, - - renderHeading() { - /* Render the heading UI of the groupchat. */ - this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML(); - }, - - renderChatArea() { - /* Render the UI container in which groupchat messages will appear. - */ - if (_.isNull(this.el.querySelector('.chat-area'))) { - const container_el = this.el.querySelector('.chatroom-body'); - container_el.insertAdjacentHTML('beforeend', tpl_chatarea({ - 'show_send_button': _converse.show_send_button - })); - container_el.insertAdjacentElement('beforeend', this.occupantsview.el); - this.content = this.el.querySelector('.chat-content'); - this.toggleOccupants(null, true); - } - - return this; - }, - - initAutoComplete() { - this.auto_complete = new _converse.AutoComplete(this.el, { - 'auto_first': true, - 'auto_evaluate': false, - 'min_chars': 1, - 'match_current_word': true, - 'match_on_tab': true, - 'list': () => this.model.occupants.map(o => ({ - 'label': o.getDisplayName(), - 'value': `@${o.getDisplayName()}` - })), - 'filter': _converse.FILTER_STARTSWITH, - 'trigger_on_at': true - }); - this.auto_complete.on('suggestion-box-selectcomplete', () => this.auto_completing = false); - }, - - keyPressed(ev) { - if (this.auto_complete.keyPressed(ev)) { - return; - } - - return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments); - }, - - keyUp(ev) { - this.auto_complete.evaluate(ev); - }, - - showRoomDetailsModal(ev) { - ev.preventDefault(); - - if (_.isUndefined(this.model.room_details_modal)) { - this.model.room_details_modal = new _converse.RoomDetailsModal({ - 'model': this.model - }); - } - - this.model.room_details_modal.show(ev); - }, - - showChatStateNotification(message) { - if (message.get('sender') === 'me') { - return; - } - - return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments); - }, - - createOccupantsView() { - /* Create the ChatRoomOccupantsView Backbone.NativeView - */ - this.model.occupants.chatroomview = this; - this.occupantsview = new _converse.ChatRoomOccupantsView({ - 'model': this.model.occupants - }); - return this; - }, - - informOfOccupantsAffiliationChange(occupant, changed) { - const previous_affiliation = occupant._previousAttributes.affiliation, - current_affiliation = occupant.get('affiliation'); - - if (previous_affiliation === 'admin') { - this.showChatEvent(__("%1$s is no longer an admin of this groupchat", occupant.get('nick'))); - } else if (previous_affiliation === 'owner') { - this.showChatEvent(__("%1$s is no longer an owner of this groupchat", occupant.get('nick'))); - } else if (previous_affiliation === 'outcast') { - this.showChatEvent(__("%1$s is no longer banned from this groupchat", occupant.get('nick'))); - } - - if (current_affiliation === 'none' && previous_affiliation === 'member') { - this.showChatEvent(__("%1$s is no longer a permanent member of this groupchat", occupant.get('nick'))); - } - - if (current_affiliation === 'member') { - this.showChatEvent(__("%1$s is now a permanent member of this groupchat", occupant.get('nick'))); - } else if (current_affiliation === 'outcast') { - this.showChatEvent(__("%1$s has been banned from this groupchat", occupant.get('nick'))); - } else if (current_affiliation === 'admin' || current_affiliation == 'owner') { - this.showChatEvent(__(`%1$s is now an ${current_affiliation} of this groupchat`, occupant.get('nick'))); - } - }, - - informOfOccupantsRoleChange(occupant, changed) { - const previous_role = occupant._previousAttributes.role; - - if (previous_role === 'moderator') { - this.showChatEvent(__("%1$s is no longer a moderator", occupant.get('nick'))); - } - - if (previous_role === 'visitor') { - this.showChatEvent(__("%1$s has been given a voice again", occupant.get('nick'))); - } - - if (occupant.get('role') === 'visitor') { - this.showChatEvent(__("%1$s has been muted", occupant.get('nick'))); - } - - if (occupant.get('role') === 'moderator') { - this.showChatEvent(__("%1$s is now a moderator", occupant.get('nick'))); - } - }, - - generateHeadingHTML() { - /* Returns the heading HTML to be rendered. - */ - return tpl_chatroom_head(_.extend(this.model.toJSON(), { - 'Strophe': Strophe, - 'info_close': __('Close and leave this groupchat'), - 'info_configure': __('Configure this groupchat'), - 'info_details': __('Show more details about this groupchat'), - 'description': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), { - 'whiteList': {} - })) - })); - }, - - afterShown() { - /* Override from converse-chatview, specifically to avoid - * the 'active' chat state from being sent out prematurely. - * - * This is instead done in `afterConnected` below. - */ - if (u.isPersistableModel(this.model)) { - this.model.clearUnreadMsgCounter(); - this.model.save(); - } - - this.occupantsview.setOccupantsHeight(); - this.scrollDown(); - this.renderEmojiPicker(); - }, - - show() { - if (u.isVisible(this.el)) { - this.focus(); - return; - } // Override from converse-chatview in order to not use - // "fadeIn", which causes flashing. - - - u.showElement(this.el); - this.afterShown(); - }, - - afterConnected() { - if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) { - this.hideSpinner(); - this.setChatState(_converse.ACTIVE); - this.scrollDown(); - this.focus(); - } - }, - - getToolbarOptions() { - return _.extend(_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), { - 'label_hide_occupants': __('Hide the list of participants'), - 'show_occupants_toggle': this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants - }); - }, - - close(ev) { - /* Close this chat box, which implies leaving the groupchat as - * well. - */ - this.hide(); - - if (Backbone.history.getFragment() === "converse/room?jid=" + this.model.get('jid')) { - _converse.router.navigate(''); - } - - this.model.leave(); - - _converse.ChatBoxView.prototype.close.apply(this, arguments); - }, - - setOccupantsVisibility() { - const icon_el = this.el.querySelector('.toggle-occupants'); - - if (this.model.get('hidden_occupants')) { - u.removeClass('fa-angle-double-right', icon_el); - u.addClass('fa-angle-double-left', icon_el); - u.addClass('full', this.el.querySelector('.chat-area')); - u.hideElement(this.el.querySelector('.occupants')); - } else { - u.addClass('fa-angle-double-right', icon_el); - u.removeClass('fa-angle-double-left', icon_el); - u.removeClass('full', this.el.querySelector('.chat-area')); - u.removeClass('hidden', this.el.querySelector('.occupants')); - } - - this.occupantsview.setOccupantsHeight(); - }, - - hideOccupants(ev, preserve_state) { - /* Show or hide the right sidebar containing the chat - * occupants (and the invite widget). - */ - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - - this.model.save({ - 'hidden_occupants': true - }); - this.setOccupantsVisibility(); - this.scrollDown(); - }, - - toggleOccupants(ev, preserve_state) { - /* Show or hide the right sidebar containing the chat - * occupants (and the invite widget). - */ - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - - if (!preserve_state) { - this.model.set({ - 'hidden_occupants': !this.model.get('hidden_occupants') - }); - } - - this.setOccupantsVisibility(); - this.scrollDown(); - }, - - onOccupantClicked(ev) { - /* When an occupant is clicked, insert their nickname into - * the chat textarea input. - */ - this.insertIntoTextArea(ev.target.textContent); - }, - - handleChatStateNotification(message) { - /* Override the method on the ChatBoxView base class to - * ignore notifications in groupchats. - * - * As laid out in the business rules in XEP-0085 - * http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat - */ - if (message.get('fullname') === this.model.get('nick')) { - // Don't know about other servers, but OpenFire sends - // back to you your own chat state notifications. - // We ignore them here... - return; - } - - if (message.get('chat_state') !== _converse.GONE) { - _converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments); - } - }, - - modifyRole(groupchat, nick, role, reason, onSuccess, onError) { - const item = $build("item", { - nick, - role - }); - const iq = $iq({ - to: groupchat, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_ADMIN - }).cnode(item.node); - - if (reason !== null) { - iq.c("reason", reason); - } - - return _converse.connection.sendIQ(iq, onSuccess, onError); - }, - - verifyRoles(roles) { - const me = this.model.occupants.findWhere({ - 'jid': _converse.bare_jid - }); - - if (!_.includes(roles, me.get('role'))) { - this.showErrorMessage(__(`Forbidden: you do not have the necessary role in order to do that.`)); - return false; - } - - return true; - }, - - verifyAffiliations(affiliations) { - const me = this.model.occupants.findWhere({ - 'jid': _converse.bare_jid - }); - - if (!_.includes(affiliations, me.get('affiliation'))) { - this.showErrorMessage(__(`Forbidden: you do not have the necessary affiliation in order to do that.`)); - return false; - } - - return true; - }, - - validateRoleChangeCommand(command, args) { - /* Check that a command to change a groupchat user's role or - * affiliation has anough arguments. - */ - if (args.length < 1 || args.length > 2) { - this.showErrorMessage(__('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', command)); - return false; - } - - if (!this.model.occupants.findWhere({ - 'nick': args[0] - }) && !this.model.occupants.findWhere({ - 'jid': args[0] - })) { - this.showErrorMessage(__('Error: couldn\'t find a groupchat participant "%1$s"', args[0])); - return false; - } - - return true; - }, - - onCommandError(err) { - _converse.log(err, Strophe.LogLevel.FATAL); - - this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details.")); - }, - - parseMessageForCommands(text) { - if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) { - return true; - } - - if (_converse.muc_disable_moderator_commands) { - return false; - } - - const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''], - args = match[2] && match[2].splitOnce(' ').filter(s => s) || [], - command = match[1].toLowerCase(); - - switch (command) { - case 'admin': - if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.model.setAffiliation('admin', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; - - case 'ban': - if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.model.setAffiliation('outcast', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; - - case 'deop': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); - break; - - case 'help': - this.showHelpMessages([`/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, `/clear: ${__('Remove messages')}`, `/deop: ${__('Change user role to participant')}`, `/help: ${__('Show this menu')}`, `/kick: ${__('Kick user from groupchat')}`, `/me: ${__('Write in 3rd person')}`, `/member: ${__('Grant membership to a user')}`, `/mute: ${__("Remove user's ability to post messages")}`, `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, `/register: ${__("Register a nickname for this room")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}`]); - break; - - case 'kick': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.modifyRole(this.model.get('jid'), args[0], 'none', args[1], undefined, this.onCommandError.bind(this)); - break; - - case 'mute': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.modifyRole(this.model.get('jid'), args[0], 'visitor', args[1], undefined, this.onCommandError.bind(this)); - break; - - case 'member': - { - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - const occupant = this.model.occupants.findWhere({ - 'nick': args[0] - }) || this.model.occupants.findWhere({ - 'jid': args[0] - }), - attrs = { - 'jid': occupant.get('jid'), - 'reason': args[1] - }; - - if (_converse.auto_register_muc_nickname) { - attrs['nick'] = occupant.get('nick'); - } - - this.model.setAffiliation('member', [attrs]).then(() => this.model.occupants.fetchMembers()).catch(err => this.onCommandError(err)); - break; - } - - case 'nick': - if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { - break; - } - - _converse.connection.send($pres({ - from: _converse.connection.jid, - to: this.model.getRoomJIDAndNick(match[2]), - id: _converse.connection.getUniqueId() - }).tree()); - - break; - - case 'owner': - if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.model.setAffiliation('owner', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; - - case 'op': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.modifyRole(this.model.get('jid'), args[0], 'moderator', args[1], undefined, this.onCommandError.bind(this)); - break; - - case 'register': - if (args.length > 1) { - this.showErrorMessage(__(`Error: invalid number of arguments`)); - } else { - this.model.registerNickname().then(err_msg => { - if (err_msg) this.showErrorMessage(err_msg); - }); - } - - break; - - case 'revoke': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.model.setAffiliation('none', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; - - case 'topic': - case 'subject': - // TODO: should be done via API call to _converse.api.rooms - _converse.connection.send($msg({ - to: this.model.get('jid'), - from: _converse.connection.jid, - type: "groupchat" - }).c("subject", { - xmlns: "jabber:client" - }).t(match[2] || "").tree()); - - break; - - case 'voice': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - - this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); - break; - - default: - return false; - } - - return true; - }, - - registerHandlers() { - /* Register presence and message handlers for this chat - * groupchat - */ - // XXX: Ideally this can be refactored out so that we don't - // need to do stanza processing inside the views in this - // module. See the comment in "onPresence" for more info. - this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this)); // XXX instead of having a method showStatusMessages, we could instead - // create message models in converse-muc.js and then give them views in this module. - - this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this)); - }, - - onPresence(pres) { - /* Handles all MUC presence stanzas. - * - * Parameters: - * (XMLElement) pres: The stanza - */ - // XXX: Current thinking is that excessive stanza - // processing inside a view is a "code smell". - // Instead stanza processing should happen inside the - // models/collections. - if (pres.getAttribute('type') === 'error') { - this.showErrorMessageFromPresence(pres); - } else { - // Instead of doing it this way, we could perhaps rather - // create StatusMessage objects inside the messages - // Collection and then simply render those. Then stanza - // processing is done on the model and rendering in the - // view(s). - this.showStatusMessages(pres); - } - }, - - populateAndJoin() { - this.model.occupants.fetchMembers(); - this.join(); + this.model.getRoomFeatures().then(handler, handler); + } else { this.fetchMessages(); - }, - join(nick, password) { - /* Join the groupchat. - * - * Parameters: - * (String) nick: The user's nickname - * (String) password: Optional password, if required by - * the groupchat. - */ - if (!nick && !this.model.get('nick')) { - this.checkForReservedNick(); - return this; - } + _converse.emit('chatRoomOpened', this); + } + }, - this.model.join(nick, password); - return this; - }, + render() { + this.el.setAttribute('id', this.model.get('box_id')); + this.el.innerHTML = templates_chatroom_html__WEBPACK_IMPORTED_MODULE_7___default()(); + this.renderHeading(); + this.renderChatArea(); + this.renderMessageForm(); + this.initAutoComplete(); - renderConfigurationForm(stanza) { - /* Renders a form given an IQ stanza containing the current - * groupchat configuration. - * - * Returns a promise which resolves once the user has - * either submitted the form, or canceled it. - * - * Parameters: - * (XMLElement) stanza: The IQ stanza containing the groupchat - * config. - */ - const container_el = this.el.querySelector('.chatroom-body'); - - _.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement); - - _.each(container_el.children, u.hideElement); - - container_el.insertAdjacentHTML('beforeend', tpl_chatroom_form()); - - const form_el = container_el.querySelector('form.chatroom-form'), - fieldset_el = form_el.querySelector('fieldset'), - fields = stanza.querySelectorAll('field'), - title = _.get(stanza.querySelector('title'), 'textContent'), - instructions = _.get(stanza.querySelector('instructions'), 'textContent'); - - u.removeElement(fieldset_el.querySelector('span.spinner')); - fieldset_el.insertAdjacentHTML('beforeend', `${title}`); - - if (instructions && instructions !== title) { - fieldset_el.insertAdjacentHTML('beforeend', `

${instructions}

`); - } - - _.each(fields, function (field) { - fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza)); - }); // Render save/cancel buttons - - - const last_fieldset_el = document.createElement('fieldset'); - last_fieldset_el.insertAdjacentHTML('beforeend', ``); - last_fieldset_el.insertAdjacentHTML('beforeend', ``); - form_el.insertAdjacentElement('beforeend', last_fieldset_el); - last_fieldset_el.querySelector('input[type=button]').addEventListener('click', ev => { - ev.preventDefault(); - this.closeForm(); - }); - form_el.addEventListener('submit', ev => { - ev.preventDefault(); - this.model.saveConfiguration(ev.target).then(() => this.model.refreshRoomFeatures()); - this.closeForm(); - }, false); - }, - - closeForm() { - /* Remove the configuration form without submitting and - * return to the chat view. - */ - u.removeElement(this.el.querySelector('.chatroom-form-container')); - this.renderAfterTransition(); - }, - - getAndRenderConfigurationForm(ev) { - /* Start the process of configuring a groupchat, either by - * rendering a configuration form, or by auto-configuring - * based on the "roomconfig" data stored on the - * Backbone.Model. - * - * Stores the new configuration on the Backbone.Model once - * completed. - * - * Paremeters: - * (Event) ev: DOM event that might be passed in if this - * method is called due to a user action. In this - * case, auto-configure won't happen, regardless of - * the settings. - */ + if (this.model.get('connection_status') !== _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.ENTERED) { this.showSpinner(); - this.model.fetchRoomConfiguration().then(this.renderConfigurationForm.bind(this)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - }, + } - submitNickname(ev) { - /* Get the nickname value from the form and then join the - * groupchat with it. - */ - ev.preventDefault(); - const nick_el = ev.target.nick; - const nick = nick_el.value; + return this; + }, - if (!nick) { - nick_el.classList.add('error'); - return; - } else { - nick_el.classList.remove('error'); - } + renderHeading() { + /* Render the heading UI of the groupchat. */ + this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML(); + }, - this.el.querySelector('.chatroom-form-container').outerHTML = tpl_spinner(); - this.join(nick); - }, - - checkForReservedNick() { - /* User service-discovery to ask the XMPP server whether - * this user has a reserved nickname for this groupchat. - * If so, we'll use that, otherwise we render the nickname form. - */ - this.showSpinner(); - this.model.checkForReservedNick().then(this.onReservedNickFound.bind(this)).catch(this.onReservedNickNotFound.bind(this)); - }, - - onReservedNickFound(iq) { - if (this.model.get('nick')) { - this.join(); - } else { - this.onReservedNickNotFound(); - } - }, - - onReservedNickNotFound(message) { - const nick = this.model.getDefaultNick(); - - if (nick) { - this.join(nick); - } else { - this.renderNicknameForm(message); - } - }, - - onNicknameClash(presence) { - /* When the nickname is already taken, we either render a - * form for the user to choose a new nickname, or we - * try to make the nickname unique by adding an integer to - * it. So john will become john-2, and then john-3 and so on. - * - * Which option is take depends on the value of - * muc_nickname_from_jid. - */ - if (_converse.muc_nickname_from_jid) { - const nick = presence.getAttribute('from').split('/')[1]; - - if (nick === this.model.getDefaultNick()) { - this.join(nick + '-2'); - } else { - const del = nick.lastIndexOf("-"); - const num = nick.substring(del + 1, nick.length); - this.join(nick.substring(0, del + 1) + String(Number(num) + 1)); - } - } else { - this.renderNicknameForm(__("The nickname you chose is reserved or " + "currently in use, please choose a different one.")); - } - }, - - hideChatRoomContents() { + renderChatArea() { + /* Render the UI container in which groupchat messages will appear. + */ + if (_.isNull(this.el.querySelector('.chat-area'))) { const container_el = this.el.querySelector('.chatroom-body'); - - if (!_.isNull(container_el)) { - _.each(container_el.children, child => { - child.classList.add('hidden'); - }); - } - }, - - renderNicknameForm(message) { - /* Render a form which allows the user to choose their - * nickname. - */ - this.hideChatRoomContents(); - - _.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement); - - if (!_.isString(message)) { - message = ''; - } - - const container_el = this.el.querySelector('.chatroom-body'); - container_el.insertAdjacentHTML('beforeend', tpl_chatroom_nickname_form({ - heading: __('Please choose your nickname'), - label_nickname: __('Nickname'), - label_join: __('Enter groupchat'), - validation_message: message + container_el.insertAdjacentHTML('beforeend', templates_chatarea_html__WEBPACK_IMPORTED_MODULE_6___default()({ + 'show_send_button': _converse.show_send_button })); - this.model.save('connection_status', converse.ROOMSTATUS.NICKNAME_REQUIRED); - const form_el = this.el.querySelector('.chatroom-form'); - form_el.addEventListener('submit', this.submitNickname.bind(this), false); - }, + container_el.insertAdjacentElement('beforeend', this.occupantsview.el); + this.content = this.el.querySelector('.chat-content'); + this.toggleOccupants(null, true); + } - submitPassword(ev) { - ev.preventDefault(); - const password = this.el.querySelector('.chatroom-form input[type=password]').value; - this.showSpinner(); - this.join(this.model.get('nick'), password); - }, + return this; + }, - renderPasswordForm() { - const container_el = this.el.querySelector('.chatroom-body'); - - _.each(container_el.children, u.hideElement); - - _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - - _.each(this.el.querySelectorAll('.chatroom-form-container'), u.removeElement); - - container_el.insertAdjacentHTML('beforeend', tpl_chatroom_password_form({ - 'heading': __('This groupchat requires a password'), - 'label_password': __('Password: '), - 'label_submit': __('Submit') - })); - this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED); - this.el.querySelector('.chatroom-form').addEventListener('submit', ev => this.submitPassword(ev), false); - }, - - showDestroyedMessage(error) { - u.hideElement(this.el.querySelector('.chat-area')); - u.hideElement(this.el.querySelector('.occupants')); - - _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - - const container = this.el.querySelector('.disconnect-container'); - - const moved_jid = _.get(sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), 'textContent').replace(/^xmpp:/, '').replace(/\?join$/, ''); - - const reason = _.get(sizzle('text[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), 'textContent'); - - container.innerHTML = tpl_chatroom_destroyed({ - '_': _, - '__': __, - 'jid': moved_jid, - 'reason': reason ? `"${reason}"` : null - }); - const switch_el = container.querySelector('a.switch-chat'); - - if (switch_el) { - switch_el.addEventListener('click', ev => { - ev.preventDefault(); - this.model.save('jid', moved_jid); - container.innerHTML = ''; - this.showSpinner(); - this.enterRoom(); - }); - } - - u.showElement(container); - }, - - showDisconnectMessages(msgs) { - if (_.isString(msgs)) { - msgs = [msgs]; - } - - u.hideElement(this.el.querySelector('.chat-area')); - u.hideElement(this.el.querySelector('.occupants')); - - _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - - const container = this.el.querySelector('.disconnect-container'); - container.innerHTML = tpl_chatroom_disconnect({ - '_': _, - 'disconnect_messages': msgs - }); - u.showElement(container); - }, - - getMessageFromStatus(stat, stanza, is_self) { - /* Parameters: - * (XMLElement) stat: A element. - * (Boolean) is_self: Whether the element refers to the - * current user. - * (XMLElement) stanza: The original stanza received. - */ - const code = stat.getAttribute('code'); - - if (code === '110' || code === '100' && !is_self) { - return; - } - - if (code in _converse.muc.info_messages) { - return _converse.muc.info_messages[code]; - } - - let nick; - - if (!is_self) { - if (code in _converse.muc.action_info_messages) { - nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); - return __(_converse.muc.action_info_messages[code], nick); - } - } else if (code in _converse.muc.new_nickname_messages) { - if (is_self && code === "210") { - nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); - } else if (is_self && code === "303") { - nick = stanza.querySelector('x item').getAttribute('nick'); - } - - return __(_converse.muc.new_nickname_messages[code], nick); - } + initAutoComplete() { + this.auto_complete = new _converse.AutoComplete(this.el, { + 'auto_first': true, + 'auto_evaluate': false, + 'min_chars': 1, + 'match_current_word': true, + 'match_on_tab': true, + 'list': () => this.model.occupants.map(o => ({ + 'label': o.getDisplayName(), + 'value': `@${o.getDisplayName()}` + })), + 'filter': _converse.FILTER_STARTSWITH, + 'trigger_on_at': true + }); + this.auto_complete.on('suggestion-box-selectcomplete', () => this.auto_completing = false); + }, + keyPressed(ev) { + if (this.auto_complete.keyPressed(ev)) { return; - }, + } - getNotificationWithMessage(message) { - let el = this.content.lastElementChild; + return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments); + }, - while (!_.isNil(el)) { - const data = _.get(el, 'dataset', {}); + keyUp(ev) { + this.auto_complete.evaluate(ev); + }, - if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { - return; + showRoomDetailsModal(ev) { + ev.preventDefault(); + + if (_.isUndefined(this.model.room_details_modal)) { + this.model.room_details_modal = new _converse.RoomDetailsModal({ + 'model': this.model + }); + } + + this.model.room_details_modal.show(ev); + }, + + showChatStateNotification(message) { + if (message.get('sender') === 'me') { + return; + } + + return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments); + }, + + createOccupantsView() { + /* Create the ChatRoomOccupantsView Backbone.NativeView + */ + this.model.occupants.chatroomview = this; + this.occupantsview = new _converse.ChatRoomOccupantsView({ + 'model': this.model.occupants + }); + return this; + }, + + informOfOccupantsAffiliationChange(occupant, changed) { + const previous_affiliation = occupant._previousAttributes.affiliation, + current_affiliation = occupant.get('affiliation'); + + if (previous_affiliation === 'admin') { + this.showChatEvent(__("%1$s is no longer an admin of this groupchat", occupant.get('nick'))); + } else if (previous_affiliation === 'owner') { + this.showChatEvent(__("%1$s is no longer an owner of this groupchat", occupant.get('nick'))); + } else if (previous_affiliation === 'outcast') { + this.showChatEvent(__("%1$s is no longer banned from this groupchat", occupant.get('nick'))); + } + + if (current_affiliation === 'none' && previous_affiliation === 'member') { + this.showChatEvent(__("%1$s is no longer a permanent member of this groupchat", occupant.get('nick'))); + } + + if (current_affiliation === 'member') { + this.showChatEvent(__("%1$s is now a permanent member of this groupchat", occupant.get('nick'))); + } else if (current_affiliation === 'outcast') { + this.showChatEvent(__("%1$s has been banned from this groupchat", occupant.get('nick'))); + } else if (current_affiliation === 'admin' || current_affiliation == 'owner') { + this.showChatEvent(__(`%1$s is now an ${current_affiliation} of this groupchat`, occupant.get('nick'))); + } + }, + + informOfOccupantsRoleChange(occupant, changed) { + const previous_role = occupant._previousAttributes.role; + + if (previous_role === 'moderator') { + this.showChatEvent(__("%1$s is no longer a moderator", occupant.get('nick'))); + } + + if (previous_role === 'visitor') { + this.showChatEvent(__("%1$s has been given a voice again", occupant.get('nick'))); + } + + if (occupant.get('role') === 'visitor') { + this.showChatEvent(__("%1$s has been muted", occupant.get('nick'))); + } + + if (occupant.get('role') === 'moderator') { + this.showChatEvent(__("%1$s is now a moderator", occupant.get('nick'))); + } + }, + + generateHeadingHTML() { + /* Returns the heading HTML to be rendered. + */ + return templates_chatroom_head_html__WEBPACK_IMPORTED_MODULE_13___default()(_.extend(this.model.toJSON(), { + 'Strophe': Strophe, + 'info_close': __('Close and leave this groupchat'), + 'info_configure': __('Configure this groupchat'), + 'info_details': __('Show more details about this groupchat'), + 'description': u.addHyperlinks(xss__WEBPACK_IMPORTED_MODULE_26___default.a.filterXSS(_.get(this.model.get('subject'), 'text'), { + 'whiteList': {} + })) + })); + }, + + afterShown() { + /* Override from converse-chatview, specifically to avoid + * the 'active' chat state from being sent out prematurely. + * + * This is instead done in `afterConnected` below. + */ + if (u.isPersistableModel(this.model)) { + this.model.clearUnreadMsgCounter(); + this.model.save(); + } + + this.occupantsview.setOccupantsHeight(); + this.scrollDown(); + this.renderEmojiPicker(); + }, + + show() { + if (u.isVisible(this.el)) { + this.focus(); + return; + } // Override from converse-chatview in order to not use + // "fadeIn", which causes flashing. + + + u.showElement(this.el); + this.afterShown(); + }, + + afterConnected() { + if (this.model.get('connection_status') === _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.ENTERED) { + this.hideSpinner(); + this.setChatState(_converse.ACTIVE); + this.scrollDown(); + this.focus(); + } + }, + + getToolbarOptions() { + return _.extend(_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), { + 'label_hide_occupants': __('Hide the list of participants'), + 'show_occupants_toggle': this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants + }); + }, + + close(ev) { + /* Close this chat box, which implies leaving the groupchat as + * well. + */ + this.hide(); + + if (Backbone.history.getFragment() === "converse/room?jid=" + this.model.get('jid')) { + _converse.router.navigate(''); + } + + this.model.leave(); + + _converse.ChatBoxView.prototype.close.apply(this, arguments); + }, + + setOccupantsVisibility() { + const icon_el = this.el.querySelector('.toggle-occupants'); + + if (this.model.get('hidden_occupants')) { + u.removeClass('fa-angle-double-right', icon_el); + u.addClass('fa-angle-double-left', icon_el); + u.addClass('full', this.el.querySelector('.chat-area')); + u.hideElement(this.el.querySelector('.occupants')); + } else { + u.addClass('fa-angle-double-right', icon_el); + u.removeClass('fa-angle-double-left', icon_el); + u.removeClass('full', this.el.querySelector('.chat-area')); + u.removeClass('hidden', this.el.querySelector('.occupants')); + } + + this.occupantsview.setOccupantsHeight(); + }, + + hideOccupants(ev, preserve_state) { + /* Show or hide the right sidebar containing the chat + * occupants (and the invite widget). + */ + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + this.model.save({ + 'hidden_occupants': true + }); + this.setOccupantsVisibility(); + this.scrollDown(); + }, + + toggleOccupants(ev, preserve_state) { + /* Show or hide the right sidebar containing the chat + * occupants (and the invite widget). + */ + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + if (!preserve_state) { + this.model.set({ + 'hidden_occupants': !this.model.get('hidden_occupants') + }); + } + + this.setOccupantsVisibility(); + this.scrollDown(); + }, + + onOccupantClicked(ev) { + /* When an occupant is clicked, insert their nickname into + * the chat textarea input. + */ + this.insertIntoTextArea(ev.target.textContent); + }, + + handleChatStateNotification(message) { + /* Override the method on the ChatBoxView base class to + * ignore notifications in groupchats. + * + * As laid out in the business rules in XEP-0085 + * http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat + */ + if (message.get('fullname') === this.model.get('nick')) { + // Don't know about other servers, but OpenFire sends + // back to you your own chat state notifications. + // We ignore them here... + return; + } + + if (message.get('chat_state') !== _converse.GONE) { + _converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments); + } + }, + + modifyRole(groupchat, nick, role, reason, onSuccess, onError) { + const item = $build("item", { + nick, + role + }); + const iq = $iq({ + to: groupchat, + type: "set" + }).c("query", { + xmlns: Strophe.NS.MUC_ADMIN + }).cnode(item.node); + + if (reason !== null) { + iq.c("reason", reason); + } + + return _converse.connection.sendIQ(iq, onSuccess, onError); + }, + + verifyRoles(roles) { + const me = this.model.occupants.findWhere({ + 'jid': _converse.bare_jid + }); + + if (!_.includes(roles, me.get('role'))) { + this.showErrorMessage(__(`Forbidden: you do not have the necessary role in order to do that.`)); + return false; + } + + return true; + }, + + verifyAffiliations(affiliations) { + const me = this.model.occupants.findWhere({ + 'jid': _converse.bare_jid + }); + + if (!_.includes(affiliations, me.get('affiliation'))) { + this.showErrorMessage(__(`Forbidden: you do not have the necessary affiliation in order to do that.`)); + return false; + } + + return true; + }, + + validateRoleChangeCommand(command, args) { + /* Check that a command to change a groupchat user's role or + * affiliation has anough arguments. + */ + if (args.length < 1 || args.length > 2) { + this.showErrorMessage(__('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', command)); + return false; + } + + if (!this.model.occupants.findWhere({ + 'nick': args[0] + }) && !this.model.occupants.findWhere({ + 'jid': args[0] + })) { + this.showErrorMessage(__('Error: couldn\'t find a groupchat participant "%1$s"', args[0])); + return false; + } + + return true; + }, + + onCommandError(err) { + _converse.log(err, Strophe.LogLevel.FATAL); + + this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details.")); + }, + + parseMessageForCommands(text) { + if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) { + return true; + } + + if (_converse.muc_disable_moderator_commands) { + return false; + } + + const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''], + args = match[2] && match[2].splitOnce(' ').filter(s => s) || [], + command = match[1].toLowerCase(); + + switch (command) { + case 'admin': + if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { + break; } - if (el.textContent === message) { - return el; + this.model.setAffiliation('admin', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); + break; + + case 'ban': + if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) { + break; } - el = el.previousElementSibling; + this.model.setAffiliation('outcast', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); + break; + + case 'deop': + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); + break; + + case 'help': + this.showHelpMessages([`/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, `/clear: ${__('Remove messages')}`, `/deop: ${__('Change user role to participant')}`, `/help: ${__('Show this menu')}`, `/kick: ${__('Kick user from groupchat')}`, `/me: ${__('Write in 3rd person')}`, `/member: ${__('Grant membership to a user')}`, `/mute: ${__("Remove user's ability to post messages")}`, `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, `/register: ${__("Register a nickname for this room")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}`]); + break; + + case 'kick': + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'none', args[1], undefined, this.onCommandError.bind(this)); + break; + + case 'mute': + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'visitor', args[1], undefined, this.onCommandError.bind(this)); + break; + + case 'member': + { + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + const occupant = this.model.occupants.findWhere({ + 'nick': args[0] + }) || this.model.occupants.findWhere({ + 'jid': args[0] + }), + attrs = { + 'jid': occupant.get('jid'), + 'reason': args[1] + }; + + if (_converse.auto_register_muc_nickname) { + attrs['nick'] = occupant.get('nick'); + } + + this.model.setAffiliation('member', [attrs]).then(() => this.model.occupants.fetchMembers()).catch(err => this.onCommandError(err)); + break; + } + + case 'nick': + if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { + break; + } + + _converse.connection.send($pres({ + from: _converse.connection.jid, + to: this.model.getRoomJIDAndNick(match[2]), + id: _converse.connection.getUniqueId() + }).tree()); + + break; + + case 'owner': + if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.model.setAffiliation('owner', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); + break; + + case 'op': + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'moderator', args[1], undefined, this.onCommandError.bind(this)); + break; + + case 'register': + if (args.length > 1) { + this.showErrorMessage(__(`Error: invalid number of arguments`)); + } else { + this.model.registerNickname().then(err_msg => { + if (err_msg) this.showErrorMessage(err_msg); + }); + } + + break; + + case 'revoke': + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.model.setAffiliation('none', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); + break; + + case 'topic': + case 'subject': + // TODO: should be done via API call to _converse.api.rooms + _converse.connection.send($msg({ + to: this.model.get('jid'), + from: _converse.connection.jid, + type: "groupchat" + }).c("subject", { + xmlns: "jabber:client" + }).t(match[2] || "").tree()); + + break; + + case 'voice': + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); + break; + + default: + return false; + } + + return true; + }, + + registerHandlers() { + /* Register presence and message handlers for this chat + * groupchat + */ + // XXX: Ideally this can be refactored out so that we don't + // need to do stanza processing inside the views in this + // module. See the comment in "onPresence" for more info. + this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this)); // XXX instead of having a method showStatusMessages, we could instead + // create message models in converse-muc.js and then give them views in this module. + + this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this)); + }, + + onPresence(pres) { + /* Handles all MUC presence stanzas. + * + * Parameters: + * (XMLElement) pres: The stanza + */ + // XXX: Current thinking is that excessive stanza + // processing inside a view is a "code smell". + // Instead stanza processing should happen inside the + // models/collections. + if (pres.getAttribute('type') === 'error') { + this.showErrorMessageFromPresence(pres); + } else { + // Instead of doing it this way, we could perhaps rather + // create StatusMessage objects inside the messages + // Collection and then simply render those. Then stanza + // processing is done on the model and rendering in the + // view(s). + this.showStatusMessages(pres); + } + }, + + populateAndJoin() { + this.model.occupants.fetchMembers(); + this.join(); + this.fetchMessages(); + }, + + join(nick, password) { + /* Join the groupchat. + * + * Parameters: + * (String) nick: The user's nickname + * (String) password: Optional password, if required by + * the groupchat. + */ + if (!nick && !this.model.get('nick')) { + this.checkForReservedNick(); + return this; + } + + this.model.join(nick, password); + return this; + }, + + renderConfigurationForm(stanza) { + /* Renders a form given an IQ stanza containing the current + * groupchat configuration. + * + * Returns a promise which resolves once the user has + * either submitted the form, or canceled it. + * + * Parameters: + * (XMLElement) stanza: The IQ stanza containing the groupchat + * config. + */ + const container_el = this.el.querySelector('.chatroom-body'); + + _.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement); + + _.each(container_el.children, u.hideElement); + + container_el.insertAdjacentHTML('beforeend', templates_chatroom_form_html__WEBPACK_IMPORTED_MODULE_12___default()()); + + const form_el = container_el.querySelector('form.chatroom-form'), + fieldset_el = form_el.querySelector('fieldset'), + fields = stanza.querySelectorAll('field'), + title = _.get(stanza.querySelector('title'), 'textContent'), + instructions = _.get(stanza.querySelector('instructions'), 'textContent'); + + u.removeElement(fieldset_el.querySelector('span.spinner')); + fieldset_el.insertAdjacentHTML('beforeend', `${title}`); + + if (instructions && instructions !== title) { + fieldset_el.insertAdjacentHTML('beforeend', `

${instructions}

`); + } + + _.each(fields, function (field) { + fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza)); + }); // Render save/cancel buttons + + + const last_fieldset_el = document.createElement('fieldset'); + last_fieldset_el.insertAdjacentHTML('beforeend', ``); + last_fieldset_el.insertAdjacentHTML('beforeend', ``); + form_el.insertAdjacentElement('beforeend', last_fieldset_el); + last_fieldset_el.querySelector('input[type=button]').addEventListener('click', ev => { + ev.preventDefault(); + this.closeForm(); + }); + form_el.addEventListener('submit', ev => { + ev.preventDefault(); + this.model.saveConfiguration(ev.target).then(() => this.model.refreshRoomFeatures()); + this.closeForm(); + }, false); + }, + + closeForm() { + /* Remove the configuration form without submitting and + * return to the chat view. + */ + u.removeElement(this.el.querySelector('.chatroom-form-container')); + this.renderAfterTransition(); + }, + + getAndRenderConfigurationForm(ev) { + /* Start the process of configuring a groupchat, either by + * rendering a configuration form, or by auto-configuring + * based on the "roomconfig" data stored on the + * Backbone.Model. + * + * Stores the new configuration on the Backbone.Model once + * completed. + * + * Paremeters: + * (Event) ev: DOM event that might be passed in if this + * method is called due to a user action. In this + * case, auto-configure won't happen, regardless of + * the settings. + */ + this.showSpinner(); + this.model.fetchRoomConfiguration().then(this.renderConfigurationForm.bind(this)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }, + + submitNickname(ev) { + /* Get the nickname value from the form and then join the + * groupchat with it. + */ + ev.preventDefault(); + const nick_el = ev.target.nick; + const nick = nick_el.value; + + if (!nick) { + nick_el.classList.add('error'); + return; + } else { + nick_el.classList.remove('error'); + } + + this.el.querySelector('.chatroom-form-container').outerHTML = templates_spinner_html__WEBPACK_IMPORTED_MODULE_25___default()(); + this.join(nick); + }, + + checkForReservedNick() { + /* User service-discovery to ask the XMPP server whether + * this user has a reserved nickname for this groupchat. + * If so, we'll use that, otherwise we render the nickname form. + */ + this.showSpinner(); + this.model.checkForReservedNick().then(this.onReservedNickFound.bind(this)).catch(this.onReservedNickNotFound.bind(this)); + }, + + onReservedNickFound(iq) { + if (this.model.get('nick')) { + this.join(); + } else { + this.onReservedNickNotFound(); + } + }, + + onReservedNickNotFound(message) { + const nick = this.model.getDefaultNick(); + + if (nick) { + this.join(nick); + } else { + this.renderNicknameForm(message); + } + }, + + onNicknameClash(presence) { + /* When the nickname is already taken, we either render a + * form for the user to choose a new nickname, or we + * try to make the nickname unique by adding an integer to + * it. So john will become john-2, and then john-3 and so on. + * + * Which option is take depends on the value of + * muc_nickname_from_jid. + */ + if (_converse.muc_nickname_from_jid) { + const nick = presence.getAttribute('from').split('/')[1]; + + if (nick === this.model.getDefaultNick()) { + this.join(nick + '-2'); + } else { + const del = nick.lastIndexOf("-"); + const num = nick.substring(del + 1, nick.length); + this.join(nick.substring(0, del + 1) + String(Number(num) + 1)); } - }, + } else { + this.renderNicknameForm(__("The nickname you chose is reserved or " + "currently in use, please choose a different one.")); + } + }, - parseXUserElement(x, stanza, is_self) { - /* Parse the passed-in - * element and construct a map containing relevant - * information. - */ - // 1. Get notification messages based on the elements. - const statuses = x.querySelectorAll('status'); + hideChatRoomContents() { + const container_el = this.el.querySelector('.chatroom-body'); - const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self); + if (!_.isNull(container_el)) { + _.each(container_el.children, child => { + child.classList.add('hidden'); + }); + } + }, - const notification = {}; + renderNicknameForm(message) { + /* Render a form which allows the user to choose their + * nickname. + */ + this.hideChatRoomContents(); - const messages = _.reject(_.reject(_.map(statuses, mapper), _.isUndefined), message => this.getNotificationWithMessage(message)); + _.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement); - if (messages.length) { - notification.messages = messages; - } // 2. Get disconnection messages based on the elements + if (!_.isString(message)) { + message = ''; + } + const container_el = this.el.querySelector('.chatroom-body'); + container_el.insertAdjacentHTML('beforeend', templates_chatroom_nickname_form_html__WEBPACK_IMPORTED_MODULE_15___default()({ + heading: __('Please choose your nickname'), + label_nickname: __('Nickname'), + label_join: __('Enter groupchat'), + validation_message: message + })); + this.model.save('connection_status', _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.NICKNAME_REQUIRED); + const form_el = this.el.querySelector('.chatroom-form'); + form_el.addEventListener('submit', this.submitNickname.bind(this), false); + }, - const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code'); + submitPassword(ev) { + ev.preventDefault(); + const password = this.el.querySelector('.chatroom-form input[type=password]').value; + this.showSpinner(); + this.join(this.model.get('nick'), password); + }, - const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages)); + renderPasswordForm() { + const container_el = this.el.querySelector('.chatroom-body'); - const disconnected = is_self && disconnection_codes.length > 0; + _.each(container_el.children, u.hideElement); - if (disconnected) { - notification.disconnected = true; - notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]]; - } // 3. Find the reason and actor from the element + _.each(this.el.querySelectorAll('.spinner'), u.removeElement); + _.each(this.el.querySelectorAll('.chatroom-form-container'), u.removeElement); - const item = x.querySelector('item'); // By using querySelector above, we assume here there is - // one per - // element. This appears to be a safe assumption, since - // each element pertains to a single user. + container_el.insertAdjacentHTML('beforeend', templates_chatroom_password_form_html__WEBPACK_IMPORTED_MODULE_16___default()({ + 'heading': __('This groupchat requires a password'), + 'label_password': __('Password: '), + 'label_submit': __('Submit') + })); + this.model.save('connection_status', _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.PASSWORD_REQUIRED); + this.el.querySelector('.chatroom-form').addEventListener('submit', ev => this.submitPassword(ev), false); + }, - if (!_.isNull(item)) { - const reason = item.querySelector('reason'); + showDestroyedMessage(error) { + u.hideElement(this.el.querySelector('.chat-area')); + u.hideElement(this.el.querySelector('.occupants')); - if (reason) { - notification.reason = reason ? reason.textContent : undefined; - } + _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - const actor = item.querySelector('actor'); + const container = this.el.querySelector('.disconnect-container'); - if (actor) { - notification.actor = actor ? actor.getAttribute('nick') : undefined; - } + const moved_jid = _.get(sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), 'textContent').replace(/^xmpp:/, '').replace(/\?join$/, ''); + + const reason = _.get(sizzle('text[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), 'textContent'); + + container.innerHTML = templates_chatroom_destroyed_html__WEBPACK_IMPORTED_MODULE_8___default()({ + '_': _, + '__': __, + 'jid': moved_jid, + 'reason': reason ? `"${reason}"` : null + }); + const switch_el = container.querySelector('a.switch-chat'); + + if (switch_el) { + switch_el.addEventListener('click', ev => { + ev.preventDefault(); + this.model.save('jid', moved_jid); + container.innerHTML = ''; + this.showSpinner(); + this.enterRoom(); + }); + } + + u.showElement(container); + }, + + showDisconnectMessages(msgs) { + if (_.isString(msgs)) { + msgs = [msgs]; + } + + u.hideElement(this.el.querySelector('.chat-area')); + u.hideElement(this.el.querySelector('.occupants')); + + _.each(this.el.querySelectorAll('.spinner'), u.removeElement); + + const container = this.el.querySelector('.disconnect-container'); + container.innerHTML = templates_chatroom_disconnect_html__WEBPACK_IMPORTED_MODULE_10___default()({ + '_': _, + 'disconnect_messages': msgs + }); + u.showElement(container); + }, + + getMessageFromStatus(stat, stanza, is_self) { + /* Parameters: + * (XMLElement) stat: A element. + * (Boolean) is_self: Whether the element refers to the + * current user. + * (XMLElement) stanza: The original stanza received. + */ + const code = stat.getAttribute('code'); + + if (code === '110' || code === '100' && !is_self) { + return; + } + + if (code in _converse.muc.info_messages) { + return _converse.muc.info_messages[code]; + } + + let nick; + + if (!is_self) { + if (code in _converse.muc.action_info_messages) { + nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); + return __(_converse.muc.action_info_messages[code], nick); + } + } else if (code in _converse.muc.new_nickname_messages) { + if (is_self && code === "210") { + nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); + } else if (is_self && code === "303") { + nick = stanza.querySelector('x item').getAttribute('nick'); } - return notification; - }, + return __(_converse.muc.new_nickname_messages[code], nick); + } - showNotificationsforUser(notification) { - /* Given the notification object generated by - * parseXUserElement, display any relevant messages and - * information to the user. - */ - if (notification.disconnected) { - const messages = []; - messages.push(notification.disconnection_message); + return; + }, - if (notification.actor) { - messages.push(__('This action was done by %1$s.', notification.actor)); - } + getNotificationWithMessage(message) { + let el = this.content.lastElementChild; - if (notification.reason) { - messages.push(__('The reason given is: "%1$s".', notification.reason)); - } + while (!_.isNil(el)) { + const data = _.get(el, 'dataset', {}); - this.showDisconnectMessages(messages); - this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); + if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { return; } - _.each(notification.messages, message => { - this.content.insertAdjacentHTML('beforeend', tpl_info({ - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - })); - }); + if (el.textContent === message) { + return el; + } + + el = el.previousElementSibling; + } + }, + + parseXUserElement(x, stanza, is_self) { + /* Parse the passed-in + * element and construct a map containing relevant + * information. + */ + // 1. Get notification messages based on the elements. + const statuses = x.querySelectorAll('status'); + + const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self); + + const notification = {}; + + const messages = _.reject(_.reject(_.map(statuses, mapper), _.isUndefined), message => this.getNotificationWithMessage(message)); + + if (messages.length) { + notification.messages = messages; + } // 2. Get disconnection messages based on the elements + + + const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code'); + + const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages)); + + const disconnected = is_self && disconnection_codes.length > 0; + + if (disconnected) { + notification.disconnected = true; + notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]]; + } // 3. Find the reason and actor from the element + + + const item = x.querySelector('item'); // By using querySelector above, we assume here there is + // one per + // element. This appears to be a safe assumption, since + // each element pertains to a single user. + + if (!_.isNull(item)) { + const reason = item.querySelector('reason'); + + if (reason) { + notification.reason = reason ? reason.textContent : undefined; + } + + const actor = item.querySelector('actor'); + + if (actor) { + notification.actor = actor ? actor.getAttribute('nick') : undefined; + } + } + + return notification; + }, + + showNotificationsforUser(notification) { + /* Given the notification object generated by + * parseXUserElement, display any relevant messages and + * information to the user. + */ + if (notification.disconnected) { + const messages = []; + messages.push(notification.disconnection_message); + + if (notification.actor) { + messages.push(__('This action was done by %1$s.', notification.actor)); + } if (notification.reason) { - this.showChatEvent(__('The reason given is: "%1$s".', notification.reason)); + messages.push(__('The reason given is: "%1$s".', notification.reason)); } - if (_.get(notification.messages, 'length')) { - this.scrollDown(); - } - }, + this.showDisconnectMessages(messages); + this.model.save('connection_status', _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.DISCONNECTED); + return; + } - onOccupantAdded(occupant) { - if (occupant.get('show') === 'online') { - this.showJoinNotification(occupant); - } - }, - - onOccupantRemoved(occupant) { - if (occupant.get('show') === 'online') { - this.showLeaveNotification(occupant); - } - }, - - showJoinOrLeaveNotification(occupant) { - if (_.includes(occupant.get('states'), '303')) { - return; - } - - if (occupant.get('show') === 'offline') { - this.showLeaveNotification(occupant); - } else if (occupant.get('show') === 'online') { - this.showJoinNotification(occupant); - } - }, - - getPreviousJoinOrLeaveNotification(el, nick) { - /* Working backwards, get the first join/leave notification - * from the same user, on the same day and BEFORE any chat - * messages were received. - */ - while (!_.isNil(el)) { - const data = _.get(el, 'dataset', {}); - - if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { - return; - } - - if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { - el = el.previousElementSibling; - continue; - } - - if (data.join === nick || data.leave === nick || data.leavejoin === nick || data.joinleave === nick) { - return el; - } - - el = el.previousElementSibling; - } - }, - - showJoinNotification(occupant) { - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - return; - } - - const nick = occupant.get('nick'), - stat = occupant.get('status'), - prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), - data = _.get(prev_info_el, 'dataset', {}); - - if (data.leave === nick) { - let message; - - if (_.isNil(stat)) { - message = __('%1$s has left and re-entered the groupchat', nick); - } else { - message = __('%1$s has left and re-entered the groupchat. "%2$s"', nick, stat); - } - - const data = { - 'data_name': 'leavejoin', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }; - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - const el = this.content.lastElementChild; - setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); - } else { - let message; - - if (_.isNil(stat)) { - message = __('%1$s has entered the groupchat', nick); - } else { - message = __('%1$s has entered the groupchat. "%2$s"', nick, stat); - } - - const data = { - 'data_name': 'join', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }; - - if (prev_info_el) { - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - } else { - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - this.insertDayIndicator(this.content.lastElementChild); - } - } - - this.scrollDown(); - }, - - showLeaveNotification(occupant) { - if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) { - return; - } - - const nick = occupant.get('nick'), - stat = occupant.get('status'), - prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), - dataset = _.get(prev_info_el, 'dataset', {}); - - if (dataset.join === nick) { - let message; - - if (_.isNil(stat)) { - message = __('%1$s has entered and left the groupchat', nick); - } else { - message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); - } - - const data = { - 'data_name': 'joinleave', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }; - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - const el = this.content.lastElementChild; - setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); - } else { - let message; - - if (_.isNil(stat)) { - message = __('%1$s has left the groupchat', nick); - } else { - message = __('%1$s has left the groupchat. "%2$s"', nick, stat); - } - - const data = { - 'message': message, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'data_name': 'leave', - 'data_value': nick - }; - - if (prev_info_el) { - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - } else { - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - this.insertDayIndicator(this.content.lastElementChild); - } - } - - this.scrollDown(); - }, - - showStatusMessages(stanza) { - /* Check for status codes and communicate their purpose to the user. - * See: http://xmpp.org/registrar/mucstatus.html - * - * Parameters: - * (XMLElement) stanza: The message or presence stanza - * containing the status codes. - */ - const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza); - const is_self = stanza.querySelectorAll("status[code='110']").length; - - const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self); - - const notifications = _.reject(_.map(elements, iteratee), _.isEmpty); - - _.each(notifications, this.showNotificationsforUser.bind(this)); - }, - - showErrorMessageFromPresence(presence) { - // We didn't enter the groupchat, so we must remove it from the MUC add-on - const error = presence.querySelector('error'); - - if (error.getAttribute('type') === 'auth') { - if (!_.isNull(error.querySelector('not-authorized'))) { - this.renderPasswordForm(); - } else if (!_.isNull(error.querySelector('registration-required'))) { - this.showDisconnectMessages(__('You are not on the member list of this groupchat.')); - } else if (!_.isNull(error.querySelector('forbidden'))) { - this.showDisconnectMessages(__('You have been banned from this groupchat.')); - } - } else if (error.getAttribute('type') === 'modify') { - if (!_.isNull(error.querySelector('jid-malformed'))) { - this.showDisconnectMessages(__('No nickname was specified.')); - } - } else if (error.getAttribute('type') === 'cancel') { - if (!_.isNull(error.querySelector('not-allowed'))) { - this.showDisconnectMessages(__('You are not allowed to create new groupchats.')); - } else if (!_.isNull(error.querySelector('not-acceptable'))) { - this.showDisconnectMessages(__("Your nickname doesn't conform to this groupchat's policies.")); - } else if (sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).length) { - this.showDestroyedMessage(error); - } else if (!_.isNull(error.querySelector('conflict'))) { - this.onNicknameClash(presence); - } else if (!_.isNull(error.querySelector('item-not-found'))) { - this.showDisconnectMessages(__("This groupchat does not (yet) exist.")); - } else if (!_.isNull(error.querySelector('service-unavailable'))) { - this.showDisconnectMessages(__("This groupchat has reached its maximum number of participants.")); - } else if (!_.isNull(error.querySelector('remote-server-not-found'))) { - const messages = [__("Remote server not found")]; - - const reason = _.get(error.querySelector('text'), 'textContent'); - - if (reason) { - messages.push(__('The explanation given is: "%1$s".', reason)); - } - - this.showDisconnectMessages(messages); - } - } - }, - - renderAfterTransition() { - /* Rerender the groupchat after some kind of transition. For - * example after the spinner has been removed or after a - * form has been submitted and removed. - */ - if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) { - this.renderNicknameForm(); - } else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) { - this.renderPasswordForm(); - } else { - this.el.querySelector('.chat-area').classList.remove('hidden'); - this.setOccupantsVisibility(); - this.scrollDown(); - } - }, - - showSpinner() { - u.removeElement(this.el.querySelector('.spinner')); - const container_el = this.el.querySelector('.chatroom-body'); - const children = Array.prototype.slice.call(container_el.children, 0); - container_el.insertAdjacentHTML('afterbegin', tpl_spinner()); - - _.each(children, u.hideElement); - }, - - hideSpinner() { - /* Check if the spinner is being shown and if so, hide it. - * Also make sure then that the chat area and occupants - * list are both visible. - */ - const spinner = this.el.querySelector('.spinner'); - - if (!_.isNull(spinner)) { - u.removeElement(spinner); - this.renderAfterTransition(); - } - - return this; - }, - - setChatRoomSubject() { - // For translators: the %1$s and %2$s parts will get - // replaced by the user and topic text respectively - // Example: Topic set by JC Brand to: Hello World! - const subject = this.model.get('subject'), - message = subject.text ? __('Topic set by %1$s', subject.author) : __('Topic cleared by %1$s', subject.author), - date = moment().format(); - this.content.insertAdjacentHTML('beforeend', tpl_info({ - 'isodate': date, + _.each(notification.messages, message => { + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()({ + 'isodate': moment().format(), 'extra_classes': 'chat-event', 'message': message })); + }); - if (subject.text) { - this.content.insertAdjacentHTML('beforeend', tpl_info({ - 'isodate': date, - 'extra_classes': 'chat-topic', - 'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), { - 'whiteList': {} - })), - 'render_message': true - })); - } + if (notification.reason) { + this.showChatEvent(__('The reason given is: "%1$s".', notification.reason)); + } + if (_.get(notification.messages, 'length')) { this.scrollDown(); } + }, - }); - _converse.RoomsPanel = Backbone.NativeView.extend({ - /* Backbone.NativeView which renders MUC section of the control box. - */ - tagName: 'div', - className: 'controlbox-section', - id: 'chatrooms', - events: { - 'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal', - 'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal' - }, - - render() { - this.el.innerHTML = tpl_room_panel({ - 'heading_chatrooms': __('Groupchats'), - 'title_new_room': __('Add a new groupchat'), - 'title_list_rooms': __('Query for groupchats') - }); - return this; - }, - - showAddRoomModal(ev) { - if (_.isUndefined(this.add_room_modal)) { - this.add_room_modal = new _converse.AddChatRoomModal({ - 'model': this.model - }); - } - - this.add_room_modal.show(ev); - }, - - showListRoomsModal(ev) { - if (_.isUndefined(this.list_rooms_modal)) { - this.list_rooms_modal = new _converse.ListChatRoomsModal({ - 'model': this.model - }); - } - - this.list_rooms_modal.show(ev); + onOccupantAdded(occupant) { + if (occupant.get('show') === 'online') { + this.showJoinNotification(occupant); } + }, - }); - _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({ - tagName: 'li', - - initialize() { - this.model.on('change', this.render, this); - }, - - toHTML() { - const show = this.model.get('show'); - return tpl_occupant(_.extend({ - '_': _, - // XXX Normally this should already be included, - // but with the current webpack build, - // we only get a subset of the _ methods. - 'jid': '', - 'show': show, - 'hint_show': _converse.PRETTY_CHAT_STATUS[show], - 'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')), - 'desc_moderator': __('This user is a moderator.'), - 'desc_participant': __('This user can send messages in this groupchat.'), - 'desc_visitor': __('This user can NOT send messages in this groupchat.'), - 'label_moderator': __('Moderator'), - 'label_visitor': __('Visitor'), - 'label_owner': __('Owner'), - 'label_member': __('Member'), - 'label_admin': __('Admin') - }, this.model.toJSON())); - }, - - destroy() { - this.el.parentElement.removeChild(this.el); + onOccupantRemoved(occupant) { + if (occupant.get('show') === 'online') { + this.showLeaveNotification(occupant); } + }, - }); - _converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({ - tagName: 'div', - className: 'occupants col-md-3 col-4', - listItems: 'model', - sortEvent: 'change:role', - listSelector: '.occupant-list', - ItemView: _converse.ChatRoomOccupantView, - - initialize() { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - this.chatroomview = this.model.chatroomview; - this.chatroomview.model.on('change:open', this.renderInviteWidget, this); - this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this); - this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this); - this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this); - this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this); - this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this); - this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this); - this.chatroomview.model.on('change:open', this.onFeatureChanged, this); - this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this); - this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this); - this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this); - this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this); - this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this); - this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this); - this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this); - this.render(); - this.model.fetch({ - 'add': true, - 'silent': true, - 'success': this.sortAndPositionAllItems.bind(this) - }); - }, - - render() { - this.el.innerHTML = tpl_chatroom_sidebar(_.extend(this.chatroomview.model.toJSON(), { - 'allow_muc_invitations': _converse.allow_muc_invitations, - 'label_occupants': __('Participants') - })); - - if (_converse.allow_muc_invitations) { - _converse.api.waitUntil('rosterContactsFetched').then(this.renderInviteWidget.bind(this)); - } - - return this.renderRoomFeatures(); - }, - - renderInviteWidget() { - const form = this.el.querySelector('form.room-invite'); - - if (this.shouldInviteWidgetBeShown()) { - if (_.isNull(form)) { - const heading = this.el.querySelector('.occupants-heading'); - heading.insertAdjacentHTML('afterend', tpl_chatroom_invite({ - 'error_message': null, - 'label_invitation': __('Invite') - })); - this.initInviteWidget(); - } - } else if (!_.isNull(form)) { - form.remove(); - } - - return this; - }, - - renderRoomFeatures() { - const picks = _.pick(this.chatroomview.model.attributes, converse.ROOM_FEATURES), - iteratee = (a, v) => a || v, - el = this.el.querySelector('.chatroom-features'); - - el.innerHTML = tpl_chatroom_features(_.extend(this.chatroomview.model.toJSON(), { - '__': __, - 'has_features': _.reduce(_.values(picks), iteratee) - })); - this.setOccupantsHeight(); - return this; - }, - - onFeatureChanged(model) { - /* When a feature has been changed, it's logical opposite - * must be set to the opposite value. - * - * So for example, if "temporary" was set to "false", then - * "persistent" will be set to "true" in this method. - * - * Additionally a debounced render method is called to make - * sure the features widget gets updated. - */ - if (_.isUndefined(this.debouncedRenderRoomFeatures)) { - this.debouncedRenderRoomFeatures = _.debounce(this.renderRoomFeatures, 100, { - 'leading': false - }); - } - - const changed_features = {}; - - _.each(_.keys(model.changed), function (k) { - if (!_.isNil(ROOM_FEATURES_MAP[k])) { - changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k]; - } - }); - - this.chatroomview.model.save(changed_features, { - 'silent': true - }); - this.debouncedRenderRoomFeatures(); - }, - - setOccupantsHeight() { - const el = this.el.querySelector('.chatroom-features'); - this.el.querySelector('.occupant-list').style.cssText = `height: calc(100% - ${el.offsetHeight}px - 5em);`; - }, - - promptForInvite(suggestion) { - const reason = prompt(__('You are about to invite %1$s to the groupchat "%2$s". ' + 'You may optionally include a message, explaining the reason for the invitation.', suggestion.text.label, this.model.get('id'))); - - if (reason !== null) { - this.chatroomview.model.directInvite(suggestion.text.value, reason); - } - - const form = suggestion.target.form, - error = form.querySelector('.pure-form-message.error'); - - if (!_.isNull(error)) { - error.parentNode.removeChild(error); - } - - suggestion.target.value = ''; - }, - - inviteFormSubmitted(evt) { - evt.preventDefault(); - const el = evt.target.querySelector('input.invited-contact'), - jid = el.value; - - if (!jid || _.compact(jid.split('@')).length < 2) { - evt.target.outerHTML = tpl_chatroom_invite({ - 'error_message': __('Please enter a valid XMPP username'), - 'label_invitation': __('Invite') - }); - this.initInviteWidget(); - return; - } - - this.promptForInvite({ - 'target': el, - 'text': { - 'label': jid, - 'value': jid - } - }); - }, - - shouldInviteWidgetBeShown() { - return _converse.allow_muc_invitations && (this.chatroomview.model.get('open') || this.chatroomview.model.get('affiliation') === "owner"); - }, - - initInviteWidget() { - const form = this.el.querySelector('form.room-invite'); - - if (_.isNull(form)) { - return; - } - - form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false); - const el = this.el.querySelector('input.invited-contact'); - - const list = _converse.roster.map(function (item) { - const label = item.get('fullname') || item.get('jid'); - return { - 'label': label, - 'value': item.get('jid') - }; - }); - - const awesomplete = new Awesomplete(el, { - 'minChars': 1, - 'list': list - }); - el.addEventListener('awesomplete-selectcomplete', this.promptForInvite.bind(this)); - } - - }); - - function setMUCDomain(domain, controlboxview) { - _converse.muc_domain = domain; - controlboxview.roomspanel.model.save('muc_domain', Strophe.getDomainFromJid(domain)); - } - - function setMUCDomainFromDisco(controlboxview) { - /* Check whether service discovery for the user's domain - * returned MUC information and use that to automatically - * set the MUC domain in the "Add groupchat" modal. - */ - function featureAdded(feature) { - if (!feature) { - return; - } - - if (feature.get('var') === Strophe.NS.MUC) { - feature.entity.getIdentity('conference', 'text').then(identity => { - if (identity) { - setMUCDomain(feature.get('from'), controlboxview); - } - }); - } - } - - _converse.api.waitUntil('discoInitialized').then(() => { - _converse.api.listen.on('serviceDiscovered', featureAdded); // Features could have been added before the controlbox was - // initialized. We're only interested in MUC - - - _converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({ - 'var': Strophe.NS.MUC - }))); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - } - - function fetchAndSetMUCDomain(controlboxview) { - if (controlboxview.model.get('connected')) { - if (!controlboxview.roomspanel.model.get('muc_domain')) { - if (_.isUndefined(_converse.muc_domain)) { - setMUCDomainFromDisco(controlboxview); - } else { - setMUCDomain(_converse.muc_domain, controlboxview); - } - } - } - } - /************************ BEGIN Event Handlers ************************/ - - - _converse.on('chatBoxViewsInitialized', () => { - function openChatRoomFromURIClicked(ev) { - ev.preventDefault(); - - _converse.api.rooms.open(ev.target.href); - } - - _converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked); - - const that = _converse.chatboxviews; - - _converse.chatboxes.on('add', item => { - if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) { - return that.add(item.get('id'), new _converse.ChatRoomView({ - 'model': item - })); - } - }); - }); - - _converse.on('controlboxInitialized', view => { - if (!_converse.allow_muc) { + showJoinOrLeaveNotification(occupant) { + if (_.includes(occupant.get('states'), '303')) { return; } - fetchAndSetMUCDomain(view); - view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view)); - }); + if (occupant.get('show') === 'offline') { + this.showLeaveNotification(occupant); + } else if (occupant.get('show') === 'online') { + this.showJoinNotification(occupant); + } + }, - function reconnectToChatRooms() { - /* Upon a reconnection event from converse, join again - * all the open groupchats. + getPreviousJoinOrLeaveNotification(el, nick) { + /* Working backwards, get the first join/leave notification + * from the same user, on the same day and BEFORE any chat + * messages were received. */ - _converse.chatboxviews.each(function (view) { - if (view.model.get('type') === _converse.CHATROOMS_TYPE) { - view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); - view.model.registerHandlers(); - view.populateAndJoin(); + while (!_.isNil(el)) { + const data = _.get(el, 'dataset', {}); + + if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { + return; } - }); + + if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { + el = el.previousElementSibling; + continue; + } + + if (data.join === nick || data.leave === nick || data.leavejoin === nick || data.joinleave === nick) { + return el; + } + + el = el.previousElementSibling; + } + }, + + showJoinNotification(occupant) { + if (this.model.get('connection_status') !== _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.ENTERED) { + return; + } + + const nick = occupant.get('nick'), + stat = occupant.get('status'), + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + data = _.get(prev_info_el, 'dataset', {}); + + if (data.leave === nick) { + let message; + + if (_.isNil(stat)) { + message = __('%1$s has left and re-entered the groupchat', nick); + } else { + message = __('%1$s has left and re-entered the groupchat. "%2$s"', nick, stat); + } + + const data = { + 'data_name': 'leavejoin', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()(data)); + const el = this.content.lastElementChild; + setTimeout(() => u.addClass('fade-out', el), 5000); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); + } else { + let message; + + if (_.isNil(stat)) { + message = __('%1$s has entered the groupchat', nick); + } else { + message = __('%1$s has entered the groupchat. "%2$s"', nick, stat); + } + + const data = { + 'data_name': 'join', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()(data)); + } else { + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()(data)); + this.insertDayIndicator(this.content.lastElementChild); + } + } + + this.scrollDown(); + }, + + showLeaveNotification(occupant) { + if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) { + return; + } + + const nick = occupant.get('nick'), + stat = occupant.get('status'), + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + dataset = _.get(prev_info_el, 'dataset', {}); + + if (dataset.join === nick) { + let message; + + if (_.isNil(stat)) { + message = __('%1$s has entered and left the groupchat', nick); + } else { + message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); + } + + const data = { + 'data_name': 'joinleave', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()(data)); + const el = this.content.lastElementChild; + setTimeout(() => u.addClass('fade-out', el), 5000); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); + } else { + let message; + + if (_.isNil(stat)) { + message = __('%1$s has left the groupchat', nick); + } else { + message = __('%1$s has left the groupchat. "%2$s"', nick, stat); + } + + const data = { + 'message': message, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'data_name': 'leave', + 'data_value': nick + }; + + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()(data)); + } else { + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()(data)); + this.insertDayIndicator(this.content.lastElementChild); + } + } + + this.scrollDown(); + }, + + showStatusMessages(stanza) { + /* Check for status codes and communicate their purpose to the user. + * See: http://xmpp.org/registrar/mucstatus.html + * + * Parameters: + * (XMLElement) stanza: The message or presence stanza + * containing the status codes. + */ + const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza); + const is_self = stanza.querySelectorAll("status[code='110']").length; + + const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self); + + const notifications = _.reject(_.map(elements, iteratee), _.isEmpty); + + _.each(notifications, this.showNotificationsforUser.bind(this)); + }, + + showErrorMessageFromPresence(presence) { + // We didn't enter the groupchat, so we must remove it from the MUC add-on + const error = presence.querySelector('error'); + + if (error.getAttribute('type') === 'auth') { + if (!_.isNull(error.querySelector('not-authorized'))) { + this.renderPasswordForm(); + } else if (!_.isNull(error.querySelector('registration-required'))) { + this.showDisconnectMessages(__('You are not on the member list of this groupchat.')); + } else if (!_.isNull(error.querySelector('forbidden'))) { + this.showDisconnectMessages(__('You have been banned from this groupchat.')); + } + } else if (error.getAttribute('type') === 'modify') { + if (!_.isNull(error.querySelector('jid-malformed'))) { + this.showDisconnectMessages(__('No nickname was specified.')); + } + } else if (error.getAttribute('type') === 'cancel') { + if (!_.isNull(error.querySelector('not-allowed'))) { + this.showDisconnectMessages(__('You are not allowed to create new groupchats.')); + } else if (!_.isNull(error.querySelector('not-acceptable'))) { + this.showDisconnectMessages(__("Your nickname doesn't conform to this groupchat's policies.")); + } else if (sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).length) { + this.showDestroyedMessage(error); + } else if (!_.isNull(error.querySelector('conflict'))) { + this.onNicknameClash(presence); + } else if (!_.isNull(error.querySelector('item-not-found'))) { + this.showDisconnectMessages(__("This groupchat does not (yet) exist.")); + } else if (!_.isNull(error.querySelector('service-unavailable'))) { + this.showDisconnectMessages(__("This groupchat has reached its maximum number of participants.")); + } else if (!_.isNull(error.querySelector('remote-server-not-found'))) { + const messages = [__("Remote server not found")]; + + const reason = _.get(error.querySelector('text'), 'textContent'); + + if (reason) { + messages.push(__('The explanation given is: "%1$s".', reason)); + } + + this.showDisconnectMessages(messages); + } + } + }, + + renderAfterTransition() { + /* Rerender the groupchat after some kind of transition. For + * example after the spinner has been removed or after a + * form has been submitted and removed. + */ + if (this.model.get('connection_status') == _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.NICKNAME_REQUIRED) { + this.renderNicknameForm(); + } else if (this.model.get('connection_status') == _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.PASSWORD_REQUIRED) { + this.renderPasswordForm(); + } else { + this.el.querySelector('.chat-area').classList.remove('hidden'); + this.setOccupantsVisibility(); + this.scrollDown(); + } + }, + + showSpinner() { + u.removeElement(this.el.querySelector('.spinner')); + const container_el = this.el.querySelector('.chatroom-body'); + const children = Array.prototype.slice.call(container_el.children, 0); + container_el.insertAdjacentHTML('afterbegin', templates_spinner_html__WEBPACK_IMPORTED_MODULE_25___default()()); + + _.each(children, u.hideElement); + }, + + hideSpinner() { + /* Check if the spinner is being shown and if so, hide it. + * Also make sure then that the chat area and occupants + * list are both visible. + */ + const spinner = this.el.querySelector('.spinner'); + + if (!_.isNull(spinner)) { + u.removeElement(spinner); + this.renderAfterTransition(); + } + + return this; + }, + + setChatRoomSubject() { + // For translators: the %1$s and %2$s parts will get + // replaced by the user and topic text respectively + // Example: Topic set by JC Brand to: Hello World! + const subject = this.model.get('subject'), + message = subject.text ? __('Topic set by %1$s', subject.author) : __('Topic cleared by %1$s', subject.author), + date = moment().format(); + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()({ + 'isodate': date, + 'extra_classes': 'chat-event', + 'message': message + })); + + if (subject.text) { + this.content.insertAdjacentHTML('beforeend', templates_info_html__WEBPACK_IMPORTED_MODULE_18___default()({ + 'isodate': date, + 'extra_classes': 'chat-topic', + 'message': u.addHyperlinks(xss__WEBPACK_IMPORTED_MODULE_26___default.a.filterXSS(_.get(this.model.get('subject'), 'text'), { + 'whiteList': {} + })), + 'render_message': true + })); + } + + this.scrollDown(); } - _converse.on('reconnected', reconnectToChatRooms); - /************************ END Event Handlers ************************/ + }); + _converse.RoomsPanel = Backbone.NativeView.extend({ + /* Backbone.NativeView which renders MUC section of the control box. + */ + tagName: 'div', + className: 'controlbox-section', + id: 'chatrooms', + events: { + 'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal', + 'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal' + }, - /************************ BEGIN API ************************/ + render() { + this.el.innerHTML = templates_room_panel_html__WEBPACK_IMPORTED_MODULE_23___default()({ + 'heading_chatrooms': __('Groupchats'), + 'title_new_room': __('Add a new groupchat'), + 'title_list_rooms': __('Query for groupchats') + }); + return this; + }, + showAddRoomModal(ev) { + if (_.isUndefined(this.add_room_modal)) { + this.add_room_modal = new _converse.AddChatRoomModal({ + 'model': this.model + }); + } - _.extend(_converse.api, { - /** - * The "roomviews" namespace groups methods relevant to chatroom - * (aka groupchats) views. - * - * @namespace _converse.api.roomviews - * @memberOf _converse.api - */ - 'roomviews': { - /** - * Lets you close open chatrooms. - * - * You can call this method without any arguments to close - * all open chatrooms, or you can specify a single JID or - * an array of JIDs. - * - * @method _converse.api.roomviews.close - * @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s) - */ - 'close'(jids) { - if (_.isUndefined(jids)) { - _converse.chatboxviews.each(function (view) { - if (view.is_chatroom && view.model) { - view.close(); - } - }); - } else if (_.isString(jids)) { - const view = _converse.chatboxviews.get(jids); + this.add_room_modal.show(ev); + }, - if (view) { - view.close(); - } - } else { - _.each(jids, function (jid) { - const view = _converse.chatboxviews.get(jid); + showListRoomsModal(ev) { + if (_.isUndefined(this.list_rooms_modal)) { + this.list_rooms_modal = new _converse.ListChatRoomsModal({ + 'model': this.model + }); + } - if (view) { - view.close(); - } - }); - } + this.list_rooms_modal.show(ev); + } + + }); + _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({ + tagName: 'li', + + initialize() { + this.model.on('change', this.render, this); + }, + + toHTML() { + const show = this.model.get('show'); + return templates_occupant_html__WEBPACK_IMPORTED_MODULE_20___default()(_.extend({ + '_': _, + // XXX Normally this should already be included, + // but with the current webpack build, + // we only get a subset of the _ methods. + 'jid': '', + 'show': show, + 'hint_show': _converse.PRETTY_CHAT_STATUS[show], + 'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')), + 'desc_moderator': __('This user is a moderator.'), + 'desc_participant': __('This user can send messages in this groupchat.'), + 'desc_visitor': __('This user can NOT send messages in this groupchat.'), + 'label_moderator': __('Moderator'), + 'label_visitor': __('Visitor'), + 'label_owner': __('Owner'), + 'label_member': __('Member'), + 'label_admin': __('Admin') + }, this.model.toJSON())); + }, + + destroy() { + this.el.parentElement.removeChild(this.el); + } + + }); + _converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({ + tagName: 'div', + className: 'occupants col-md-3 col-4', + listItems: 'model', + sortEvent: 'change:role', + listSelector: '.occupant-list', + ItemView: _converse.ChatRoomOccupantView, + + initialize() { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + this.chatroomview = this.model.chatroomview; + this.chatroomview.model.on('change:open', this.renderInviteWidget, this); + this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this); + this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this); + this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this); + this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this); + this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this); + this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this); + this.chatroomview.model.on('change:open', this.onFeatureChanged, this); + this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this); + this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this); + this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this); + this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this); + this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this); + this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this); + this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this); + this.render(); + this.model.fetch({ + 'add': true, + 'silent': true, + 'success': this.sortAndPositionAllItems.bind(this) + }); + }, + + render() { + this.el.innerHTML = templates_chatroom_sidebar_html__WEBPACK_IMPORTED_MODULE_17___default()(_.extend(this.chatroomview.model.toJSON(), { + 'allow_muc_invitations': _converse.allow_muc_invitations, + 'label_occupants': __('Participants') + })); + + if (_converse.allow_muc_invitations) { + _converse.api.waitUntil('rosterContactsFetched').then(this.renderInviteWidget.bind(this)); + } + + return this.renderRoomFeatures(); + }, + + renderInviteWidget() { + const form = this.el.querySelector('form.room-invite'); + + if (this.shouldInviteWidgetBeShown()) { + if (_.isNull(form)) { + const heading = this.el.querySelector('.occupants-heading'); + heading.insertAdjacentHTML('afterend', templates_chatroom_invite_html__WEBPACK_IMPORTED_MODULE_14___default()({ + 'error_message': null, + 'label_invitation': __('Invite') + })); + this.initInviteWidget(); } + } else if (!_.isNull(form)) { + form.remove(); + } + return this; + }, + + renderRoomFeatures() { + const picks = _.pick(this.chatroomview.model.attributes, _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOM_FEATURES), + iteratee = (a, v) => a || v, + el = this.el.querySelector('.chatroom-features'); + + el.innerHTML = templates_chatroom_features_html__WEBPACK_IMPORTED_MODULE_11___default()(_.extend(this.chatroomview.model.toJSON(), { + '__': __, + 'has_features': _.reduce(_.values(picks), iteratee) + })); + this.setOccupantsHeight(); + return this; + }, + + onFeatureChanged(model) { + /* When a feature has been changed, it's logical opposite + * must be set to the opposite value. + * + * So for example, if "temporary" was set to "false", then + * "persistent" will be set to "true" in this method. + * + * Additionally a debounced render method is called to make + * sure the features widget gets updated. + */ + if (_.isUndefined(this.debouncedRenderRoomFeatures)) { + this.debouncedRenderRoomFeatures = _.debounce(this.renderRoomFeatures, 100, { + 'leading': false + }); + } + + const changed_features = {}; + + _.each(_.keys(model.changed), function (k) { + if (!_.isNil(ROOM_FEATURES_MAP[k])) { + changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k]; + } + }); + + this.chatroomview.model.save(changed_features, { + 'silent': true + }); + this.debouncedRenderRoomFeatures(); + }, + + setOccupantsHeight() { + const el = this.el.querySelector('.chatroom-features'); + this.el.querySelector('.occupant-list').style.cssText = `height: calc(100% - ${el.offsetHeight}px - 5em);`; + }, + + promptForInvite(suggestion) { + const reason = prompt(__('You are about to invite %1$s to the groupchat "%2$s". ' + 'You may optionally include a message, explaining the reason for the invitation.', suggestion.text.label, this.model.get('id'))); + + if (reason !== null) { + this.chatroomview.model.directInvite(suggestion.text.value, reason); + } + + const form = suggestion.target.form, + error = form.querySelector('.pure-form-message.error'); + + if (!_.isNull(error)) { + error.parentNode.removeChild(error); + } + + suggestion.target.value = ''; + }, + + inviteFormSubmitted(evt) { + evt.preventDefault(); + const el = evt.target.querySelector('input.invited-contact'), + jid = el.value; + + if (!jid || _.compact(jid.split('@')).length < 2) { + evt.target.outerHTML = templates_chatroom_invite_html__WEBPACK_IMPORTED_MODULE_14___default()({ + 'error_message': __('Please enter a valid XMPP username'), + 'label_invitation': __('Invite') + }); + this.initInviteWidget(); + return; + } + + this.promptForInvite({ + 'target': el, + 'text': { + 'label': jid, + 'value': jid + } + }); + }, + + shouldInviteWidgetBeShown() { + return _converse.allow_muc_invitations && (this.chatroomview.model.get('open') || this.chatroomview.model.get('affiliation') === "owner"); + }, + + initInviteWidget() { + const form = this.el.querySelector('form.room-invite'); + + if (_.isNull(form)) { + return; + } + + form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false); + const el = this.el.querySelector('input.invited-contact'); + + const list = _converse.roster.map(function (item) { + const label = item.get('fullname') || item.get('jid'); + return { + 'label': label, + 'value': item.get('jid') + }; + }); + + const awesomplete = new awesomplete__WEBPACK_IMPORTED_MODULE_1___default.a(el, { + 'minChars': 1, + 'list': list + }); + el.addEventListener('awesomplete-selectcomplete', this.promptForInvite.bind(this)); + } + + }); + + function setMUCDomain(domain, controlboxview) { + _converse.muc_domain = domain; + controlboxview.roomspanel.model.save('muc_domain', Strophe.getDomainFromJid(domain)); + } + + function setMUCDomainFromDisco(controlboxview) { + /* Check whether service discovery for the user's domain + * returned MUC information and use that to automatically + * set the MUC domain in the "Add groupchat" modal. + */ + function featureAdded(feature) { + if (!feature) { + return; + } + + if (feature.get('var') === Strophe.NS.MUC) { + feature.entity.getIdentity('conference', 'text').then(identity => { + if (identity) { + setMUCDomain(feature.get('from'), controlboxview); + } + }); + } + } + + _converse.api.waitUntil('discoInitialized').then(() => { + _converse.api.listen.on('serviceDiscovered', featureAdded); // Features could have been added before the controlbox was + // initialized. We're only interested in MUC + + + _converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({ + 'var': Strophe.NS.MUC + }))); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + } + + function fetchAndSetMUCDomain(controlboxview) { + if (controlboxview.model.get('connected')) { + if (!controlboxview.roomspanel.model.get('muc_domain')) { + if (_.isUndefined(_converse.muc_domain)) { + setMUCDomainFromDisco(controlboxview); + } else { + setMUCDomain(_converse.muc_domain, controlboxview); + } + } + } + } + /************************ BEGIN Event Handlers ************************/ + + + _converse.on('chatBoxViewsInitialized', () => { + function openChatRoomFromURIClicked(ev) { + ev.preventDefault(); + + _converse.api.rooms.open(ev.target.href); + } + + _converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked); + + const that = _converse.chatboxviews; + + _converse.chatboxes.on('add', item => { + if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) { + return that.add(item.get('id'), new _converse.ChatRoomView({ + 'model': item + })); + } + }); + }); + + _converse.on('controlboxInitialized', view => { + if (!_converse.allow_muc) { + return; + } + + fetchAndSetMUCDomain(view); + view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view)); + }); + + function reconnectToChatRooms() { + /* Upon a reconnection event from converse, join again + * all the open groupchats. + */ + _converse.chatboxviews.each(function (view) { + if (view.model.get('type') === _converse.CHATROOMS_TYPE) { + view.model.save('connection_status', _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].ROOMSTATUS.DISCONNECTED); + view.model.registerHandlers(); + view.populateAndJoin(); } }); } - }); + _converse.on('reconnected', reconnectToChatRooms); + /************************ END Event Handlers ************************/ + + /************************ BEGIN API ************************/ + + + _.extend(_converse.api, { + /** + * The "roomviews" namespace groups methods relevant to chatroom + * (aka groupchats) views. + * + * @namespace _converse.api.roomviews + * @memberOf _converse.api + */ + 'roomviews': { + /** + * Lets you close open chatrooms. + * + * You can call this method without any arguments to close + * all open chatrooms, or you can specify a single JID or + * an array of JIDs. + * + * @method _converse.api.roomviews.close + * @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s) + */ + 'close'(jids) { + if (_.isUndefined(jids)) { + _converse.chatboxviews.each(function (view) { + if (view.is_chatroom && view.model) { + view.close(); + } + }); + } else if (_.isString(jids)) { + const view = _converse.chatboxviews.get(jids); + + if (view) { + view.close(); + } + } else { + _.each(jids, function (jid) { + const view = _converse.chatboxviews.get(jid); + + if (view) { + view.close(); + } + }); + } + } + + } + }); + } + }); /***/ }), @@ -64713,10 +64895,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**************************************!*\ !*** ./src/converse-notification.js ***! \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2013-2018, JC Brand @@ -64724,306 +64909,298 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ // /*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - "use strict"; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - _ = _converse$env._, - sizzle = _converse$env.sizzle, - u = converse.env.utils; - converse.plugins.add('converse-notification', { - dependencies: ["converse-chatboxes"], +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Strophe = _converse$env.Strophe, + _ = _converse$env._, + sizzle = _converse$env.sizzle, + u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-notification', { + dependencies: ["converse-chatboxes"], - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse; + const __ = _converse.__; + _converse.supports_html5_notification = "Notification" in window; + + _converse.api.settings.update({ + notify_all_room_messages: false, + show_desktop_notifications: true, + show_chatstate_notifications: false, + chatstate_notification_blacklist: [], + // ^ a list of JIDs to ignore concerning chat state notifications + play_sounds: true, + sounds_path: '/sounds/', + notification_icon: '/logo/conversejs-filled.svg' + }); + + _converse.isOnlyChatStateNotification = msg => // See XEP-0085 Chat State Notification + _.isNull(msg.querySelector('body')) && (_.isNull(msg.querySelector(_converse.ACTIVE)) || _.isNull(msg.querySelector(_converse.COMPOSING)) || _.isNull(msg.querySelector(_converse.INACTIVE)) || _.isNull(msg.querySelector(_converse.PAUSED)) || _.isNull(msg.querySelector(_converse.GONE))); + + _converse.shouldNotifyOfGroupMessage = function (message) { + /* Is this a group message worthy of notification? */ - const _converse = this._converse; - const __ = _converse.__; - _converse.supports_html5_notification = "Notification" in window; + let notify_all = _converse.notify_all_room_messages; + const jid = message.getAttribute('from'), + resource = Strophe.getResourceFromJid(jid), + room_jid = Strophe.getBareJidFromJid(jid), + sender = resource && Strophe.unescapeNode(resource) || ''; - _converse.api.settings.update({ - notify_all_room_messages: false, - show_desktop_notifications: true, - show_chatstate_notifications: false, - chatstate_notification_blacklist: [], - // ^ a list of JIDs to ignore concerning chat state notifications - play_sounds: true, - sounds_path: '/sounds/', - notification_icon: '/logo/conversejs-filled.svg' - }); + if (sender === '' || message.querySelectorAll('delay').length > 0) { + return false; + } - _converse.isOnlyChatStateNotification = msg => // See XEP-0085 Chat State Notification - _.isNull(msg.querySelector('body')) && (_.isNull(msg.querySelector(_converse.ACTIVE)) || _.isNull(msg.querySelector(_converse.COMPOSING)) || _.isNull(msg.querySelector(_converse.INACTIVE)) || _.isNull(msg.querySelector(_converse.PAUSED)) || _.isNull(msg.querySelector(_converse.GONE))); + const room = _converse.chatboxes.get(room_jid); - _converse.shouldNotifyOfGroupMessage = function (message) { - /* Is this a group message worthy of notification? - */ - let notify_all = _converse.notify_all_room_messages; - const jid = message.getAttribute('from'), - resource = Strophe.getResourceFromJid(jid), - room_jid = Strophe.getBareJidFromJid(jid), - sender = resource && Strophe.unescapeNode(resource) || ''; + const body = message.querySelector('body'); - if (sender === '' || message.querySelectorAll('delay').length > 0) { - return false; - } + if (_.isNull(body)) { + return false; + } - const room = _converse.chatboxes.get(room_jid); + const mentioned = new RegExp(`\\b${room.get('nick')}\\b`).test(body.textContent); + notify_all = notify_all === true || _.isArray(notify_all) && _.includes(notify_all, room_jid); - const body = message.querySelector('body'); + if (sender === room.get('nick') || !notify_all && !mentioned) { + return false; + } - if (_.isNull(body)) { - return false; - } + return true; + }; - const mentioned = new RegExp(`\\b${room.get('nick')}\\b`).test(body.textContent); - notify_all = notify_all === true || _.isArray(notify_all) && _.includes(notify_all, room_jid); + _converse.isMessageToHiddenChat = function (message) { + if (_.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode)) { + const jid = Strophe.getBareJidFromJid(message.getAttribute('from')), + view = _converse.chatboxviews.get(jid); - if (sender === room.get('nick') || !notify_all && !mentioned) { - return false; + if (!_.isNil(view)) { + return view.model.get('hidden') || _converse.windowState === 'hidden' || !u.isVisible(view.el); } return true; - }; + } - _converse.isMessageToHiddenChat = function (message) { - if (_.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode)) { - const jid = Strophe.getBareJidFromJid(message.getAttribute('from')), - view = _converse.chatboxviews.get(jid); + return _converse.windowState === 'hidden'; + }; - if (!_.isNil(view)) { - return view.model.get('hidden') || _converse.windowState === 'hidden' || !u.isVisible(view.el); - } + _converse.shouldNotifyOfMessage = function (message) { + const forwarded = message.querySelector('forwarded'); - return true; - } + if (!_.isNull(forwarded)) { + return false; + } else if (message.getAttribute('type') === 'groupchat') { + return _converse.shouldNotifyOfGroupMessage(message); + } else if (u.isHeadlineMessage(_converse, message)) { + // We want to show notifications for headline messages. + return _converse.isMessageToHiddenChat(message); + } - return _converse.windowState === 'hidden'; - }; + const is_me = Strophe.getBareJidFromJid(message.getAttribute('from')) === _converse.bare_jid; - _converse.shouldNotifyOfMessage = function (message) { - const forwarded = message.querySelector('forwarded'); + return !_converse.isOnlyChatStateNotification(message) && !is_me && _converse.isMessageToHiddenChat(message); + }; - if (!_.isNull(forwarded)) { - return false; - } else if (message.getAttribute('type') === 'groupchat') { - return _converse.shouldNotifyOfGroupMessage(message); - } else if (u.isHeadlineMessage(_converse, message)) { - // We want to show notifications for headline messages. - return _converse.isMessageToHiddenChat(message); - } + _converse.playSoundNotification = function () { + /* Plays a sound to notify that a new message was recieved. + */ + // XXX Eventually this can be refactored to use Notification's sound + // feature, but no browser currently supports it. + // https://developer.mozilla.org/en-US/docs/Web/API/notification/sound + let audio; - const is_me = Strophe.getBareJidFromJid(message.getAttribute('from')) === _converse.bare_jid; + if (_converse.play_sounds && !_.isUndefined(window.Audio)) { + audio = new Audio(_converse.sounds_path + "msg_received.ogg"); - return !_converse.isOnlyChatStateNotification(message) && !is_me && _converse.isMessageToHiddenChat(message); - }; - - _converse.playSoundNotification = function () { - /* Plays a sound to notify that a new message was recieved. - */ - // XXX Eventually this can be refactored to use Notification's sound - // feature, but no browser currently supports it. - // https://developer.mozilla.org/en-US/docs/Web/API/notification/sound - let audio; - - if (_converse.play_sounds && !_.isUndefined(window.Audio)) { - audio = new Audio(_converse.sounds_path + "msg_received.ogg"); - - if (audio.canPlayType('audio/ogg')) { - audio.play(); - } else { - audio = new Audio(_converse.sounds_path + "msg_received.mp3"); - - if (audio.canPlayType('audio/mp3')) { - audio.play(); - } - } - } - }; - - _converse.areDesktopNotificationsEnabled = function () { - return _converse.supports_html5_notification && _converse.show_desktop_notifications && Notification.permission === "granted"; - }; - - _converse.showMessageNotification = function (message) { - /* Shows an HTML5 Notification to indicate that a new chat - * message was received. - */ - let title, roster_item; - const full_from_jid = message.getAttribute('from'), - from_jid = Strophe.getBareJidFromJid(full_from_jid); - - if (message.getAttribute('type') === 'headline') { - if (!_.includes(from_jid, '@') || _converse.allow_non_roster_messaging) { - title = __("Notification from %1$s", from_jid); - } else { - return; - } - } else if (!_.includes(from_jid, '@')) { - // workaround for Prosody which doesn't give type "headline" - title = __("Notification from %1$s", from_jid); - } else if (message.getAttribute('type') === 'groupchat') { - title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid)); + if (audio.canPlayType('audio/ogg')) { + audio.play(); } else { - if (_.isUndefined(_converse.roster)) { - _converse.log("Could not send notification, because roster is undefined", Strophe.LogLevel.ERROR); + audio = new Audio(_converse.sounds_path + "msg_received.mp3"); + if (audio.canPlayType('audio/mp3')) { + audio.play(); + } + } + } + }; + + _converse.areDesktopNotificationsEnabled = function () { + return _converse.supports_html5_notification && _converse.show_desktop_notifications && Notification.permission === "granted"; + }; + + _converse.showMessageNotification = function (message) { + /* Shows an HTML5 Notification to indicate that a new chat + * message was received. + */ + let title, roster_item; + const full_from_jid = message.getAttribute('from'), + from_jid = Strophe.getBareJidFromJid(full_from_jid); + + if (message.getAttribute('type') === 'headline') { + if (!_.includes(from_jid, '@') || _converse.allow_non_roster_messaging) { + title = __("Notification from %1$s", from_jid); + } else { + return; + } + } else if (!_.includes(from_jid, '@')) { + // workaround for Prosody which doesn't give type "headline" + title = __("Notification from %1$s", from_jid); + } else if (message.getAttribute('type') === 'groupchat') { + title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid)); + } else { + if (_.isUndefined(_converse.roster)) { + _converse.log("Could not send notification, because roster is undefined", Strophe.LogLevel.ERROR); + + return; + } + + roster_item = _converse.roster.get(from_jid); + + if (!_.isUndefined(roster_item)) { + title = __("%1$s says", roster_item.getDisplayName()); + } else { + if (_converse.allow_non_roster_messaging) { + title = __("%1$s says", from_jid); + } else { return; } - - roster_item = _converse.roster.get(from_jid); - - if (!_.isUndefined(roster_item)) { - title = __("%1$s says", roster_item.getDisplayName()); - } else { - if (_converse.allow_non_roster_messaging) { - title = __("%1$s says", from_jid); - } else { - return; - } - } - } // TODO: we should suppress notifications if we cannot decrypt - // the message... - - - const body = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, message).length ? __('OMEMO Message received') : _.get(message.querySelector('body'), 'textContent'); - - if (!body) { - return; } + } // TODO: we should suppress notifications if we cannot decrypt + // the message... - const n = new Notification(title, { - 'body': body, - 'lang': _converse.locale, - 'icon': _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - }; - _converse.showChatStateNotification = function (contact) { - /* Creates an HTML5 Notification to inform of a change in a - * contact's chat state. - */ - if (_.includes(_converse.chatstate_notification_blacklist, contact.jid)) { - // Don't notify if the user is being ignored. - return; - } + const body = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, message).length ? __('OMEMO Message received') : _.get(message.querySelector('body'), 'textContent'); - const chat_state = contact.chat_status; - let message = null; + if (!body) { + return; + } - if (chat_state === 'offline') { - message = __('has gone offline'); - } else if (chat_state === 'away') { - message = __('has gone away'); - } else if (chat_state === 'dnd') { - message = __('is busy'); - } else if (chat_state === 'online') { - message = __('has come online'); - } - - if (message === null) { - return; - } - - const n = new Notification(contact.getDisplayName(), { - body: message, - lang: _converse.locale, - icon: _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - }; - - _converse.showContactRequestNotification = function (contact) { - const n = new Notification(contact.getDisplayName(), { - body: __('wants to be your contact'), - lang: _converse.locale, - icon: _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - }; - - _converse.showFeedbackNotification = function (data) { - if (data.klass === 'error' || data.klass === 'warn') { - const n = new Notification(data.subject, { - body: data.message, - lang: _converse.locale, - icon: _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - } - }; - - _converse.handleChatStateNotification = function (contact) { - /* Event handler for on('contactPresenceChanged'). - * Will show an HTML5 notification to indicate that the chat - * status has changed. - */ - if (_converse.areDesktopNotificationsEnabled() && _converse.show_chatstate_notifications) { - _converse.showChatStateNotification(contact); - } - }; - - _converse.handleMessageNotification = function (data) { - /* Event handler for the on('message') event. Will call methods - * to play sounds and show HTML5 notifications. - */ - const message = data.stanza; - - if (!_converse.shouldNotifyOfMessage(message)) { - return false; - } - - _converse.playSoundNotification(); - - if (_converse.areDesktopNotificationsEnabled()) { - _converse.showMessageNotification(message); - } - }; - - _converse.handleContactRequestNotification = function (contact) { - if (_converse.areDesktopNotificationsEnabled(true)) { - _converse.showContactRequestNotification(contact); - } - }; - - _converse.handleFeedback = function (data) { - if (_converse.areDesktopNotificationsEnabled(true)) { - _converse.showFeedbackNotification(data); - } - }; - - _converse.requestPermission = function () { - if (_converse.supports_html5_notification && !_.includes(['denied', 'granted'], Notification.permission)) { - // Ask user to enable HTML5 notifications - Notification.requestPermission(); - } - }; - - _converse.on('pluginsInitialized', function () { - // We only register event handlers after all plugins are - // registered, because other plugins might override some of our - // handlers. - _converse.on('contactRequest', _converse.handleContactRequestNotification); - - _converse.on('contactPresenceChanged', _converse.handleChatStateNotification); - - _converse.on('message', _converse.handleMessageNotification); - - _converse.on('feedback', _converse.handleFeedback); - - _converse.on('connected', _converse.requestPermission); + const n = new Notification(title, { + 'body': body, + 'lang': _converse.locale, + 'icon': _converse.notification_icon }); - } + setTimeout(n.close.bind(n), 5000); + }; + + _converse.showChatStateNotification = function (contact) { + /* Creates an HTML5 Notification to inform of a change in a + * contact's chat state. + */ + if (_.includes(_converse.chatstate_notification_blacklist, contact.jid)) { + // Don't notify if the user is being ignored. + return; + } + + const chat_state = contact.chat_status; + let message = null; + + if (chat_state === 'offline') { + message = __('has gone offline'); + } else if (chat_state === 'away') { + message = __('has gone away'); + } else if (chat_state === 'dnd') { + message = __('is busy'); + } else if (chat_state === 'online') { + message = __('has come online'); + } + + if (message === null) { + return; + } + + const n = new Notification(contact.getDisplayName(), { + body: message, + lang: _converse.locale, + icon: _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + }; + + _converse.showContactRequestNotification = function (contact) { + const n = new Notification(contact.getDisplayName(), { + body: __('wants to be your contact'), + lang: _converse.locale, + icon: _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + }; + + _converse.showFeedbackNotification = function (data) { + if (data.klass === 'error' || data.klass === 'warn') { + const n = new Notification(data.subject, { + body: data.message, + lang: _converse.locale, + icon: _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + } + }; + + _converse.handleChatStateNotification = function (contact) { + /* Event handler for on('contactPresenceChanged'). + * Will show an HTML5 notification to indicate that the chat + * status has changed. + */ + if (_converse.areDesktopNotificationsEnabled() && _converse.show_chatstate_notifications) { + _converse.showChatStateNotification(contact); + } + }; + + _converse.handleMessageNotification = function (data) { + /* Event handler for the on('message') event. Will call methods + * to play sounds and show HTML5 notifications. + */ + const message = data.stanza; + + if (!_converse.shouldNotifyOfMessage(message)) { + return false; + } + + _converse.playSoundNotification(); + + if (_converse.areDesktopNotificationsEnabled()) { + _converse.showMessageNotification(message); + } + }; + + _converse.handleContactRequestNotification = function (contact) { + if (_converse.areDesktopNotificationsEnabled(true)) { + _converse.showContactRequestNotification(contact); + } + }; + + _converse.handleFeedback = function (data) { + if (_converse.areDesktopNotificationsEnabled(true)) { + _converse.showFeedbackNotification(data); + } + }; + + _converse.requestPermission = function () { + if (_converse.supports_html5_notification && !_.includes(['denied', 'granted'], Notification.permission)) { + // Ask user to enable HTML5 notifications + Notification.requestPermission(); + } + }; + + _converse.on('pluginsInitialized', function () { + // We only register event handlers after all plugins are + // registered, because other plugins might override some of our + // handlers. + _converse.on('contactRequest', _converse.handleContactRequestNotification); + + _converse.on('contactPresenceChanged', _converse.handleChatStateNotification); + + _converse.on('message', _converse.handleMessageNotification); + + _converse.on('feedback', _converse.handleFeedback); + + _converse.on('connected', _converse.requestPermission); + }); + } - }); }); /***/ }), @@ -65032,1165 +65209,1165 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!*******************************!*\ !*** ./src/converse-omemo.js ***! \*******************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_toolbar_omemo_html__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! templates/toolbar_omemo.html */ "./src/templates/toolbar_omemo.html"); +/* harmony import */ var templates_toolbar_omemo_html__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(templates_toolbar_omemo_html__WEBPACK_IMPORTED_MODULE_1__); +// Converse.js // http://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) /* global libsignal, ArrayBuffer, parseInt, crypto */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/toolbar_omemo.html */ "./src/templates/toolbar_omemo.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_toolbar_omemo) { - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - moment = _converse$env.moment, - sizzle = _converse$env.sizzle, - $iq = _converse$env.$iq, - $msg = _converse$env.$msg, - _ = _converse$env._, - f = _converse$env.f, - b64_sha1 = _converse$env.b64_sha1; - const u = converse.env.utils; - Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO + ".devicelist"); - Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO + ".verification"); - Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO + ".whitelisted"); - Strophe.addNamespace('OMEMO_BUNDLES', Strophe.NS.OMEMO + ".bundles"); - const UNDECIDED = 0; - const TRUSTED = 1; - const UNTRUSTED = -1; - const TAG_LENGTH = 128; - const KEY_ALGO = { - 'name': "AES-GCM", - 'length': 128 - }; - function parseBundle(bundle_el) { - /* Given an XML element representing a user's OMEMO bundle, parse it - * and return a map. - */ - const signed_prekey_public_el = bundle_el.querySelector('signedPreKeyPublic'), - signed_prekey_signature_el = bundle_el.querySelector('signedPreKeySignature'), - identity_key_el = bundle_el.querySelector('identityKey'); - const prekeys = _.map(sizzle(`prekeys > preKeyPublic`, bundle_el), el => { - return { - 'id': parseInt(el.getAttribute('preKeyId'), 10), - 'key': el.textContent - }; - }); +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + moment = _converse$env.moment, + sizzle = _converse$env.sizzle, + $iq = _converse$env.$iq, + $msg = _converse$env.$msg, + _ = _converse$env._, + f = _converse$env.f, + b64_sha1 = _converse$env.b64_sha1; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO + ".devicelist"); +Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO + ".verification"); +Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO + ".whitelisted"); +Strophe.addNamespace('OMEMO_BUNDLES', Strophe.NS.OMEMO + ".bundles"); +const UNDECIDED = 0; +const TRUSTED = 1; +const UNTRUSTED = -1; +const TAG_LENGTH = 128; +const KEY_ALGO = { + 'name': "AES-GCM", + 'length': 128 +}; +function parseBundle(bundle_el) { + /* Given an XML element representing a user's OMEMO bundle, parse it + * and return a map. + */ + const signed_prekey_public_el = bundle_el.querySelector('signedPreKeyPublic'), + signed_prekey_signature_el = bundle_el.querySelector('signedPreKeySignature'), + identity_key_el = bundle_el.querySelector('identityKey'); + + const prekeys = _.map(sizzle(`prekeys > preKeyPublic`, bundle_el), el => { return { - 'identity_key': bundle_el.querySelector('identityKey').textContent.trim(), - 'signed_prekey': { - 'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10), - 'public_key': signed_prekey_public_el.textContent, - 'signature': signed_prekey_signature_el.textContent - }, - 'prekeys': prekeys + 'id': parseInt(el.getAttribute('preKeyId'), 10), + 'key': el.textContent }; - } + }); - converse.plugins.add('converse-omemo', { - enabled(_converse) { - return !_.isNil(window.libsignal) && !f.includes('converse-omemo', _converse.blacklisted_plugins); + return { + 'identity_key': bundle_el.querySelector('identityKey').textContent.trim(), + 'signed_prekey': { + 'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10), + 'public_key': signed_prekey_public_el.textContent, + 'signature': signed_prekey_signature_el.textContent }, + 'prekeys': prekeys + }; +} - dependencies: ["converse-chatview"], - overrides: { - ProfileModal: { - events: { - 'change input.select-all': 'selectAll', - 'submit .fingerprint-removal': 'removeSelectedFingerprints' - }, - - initialize() { - const _converse = this.__super__._converse; - this.debouncedRender = _.debounce(this.render, 50); - this.devicelist = _converse.devicelists.get(_converse.bare_jid); - this.devicelist.devices.on('change:bundle', this.debouncedRender, this); - this.devicelist.devices.on('reset', this.debouncedRender, this); - this.devicelist.devices.on('remove', this.debouncedRender, this); - this.devicelist.devices.on('add', this.debouncedRender, this); - return this.__super__.initialize.apply(this, arguments); - }, - - beforeRender() { - const _converse = this.__super__._converse, - device_id = _converse.omemo_store.get('device_id'); - - this.current_device = this.devicelist.devices.get(device_id); - this.other_devices = this.devicelist.devices.filter(d => d.get('id') !== device_id); - - if (this.__super__.beforeRender) { - return this.__super__.beforeRender.apply(this, arguments); - } - }, - - selectAll(ev) { - let sibling = u.ancestor(ev.target, 'li'); - - while (sibling) { - sibling.querySelector('input[type="checkbox"]').checked = ev.target.checked; - sibling = sibling.nextElementSibling; - } - }, - - removeSelectedFingerprints(ev) { - ev.preventDefault(); - ev.stopPropagation(); - ev.target.querySelector('.select-all').checked = false; - - const checkboxes = ev.target.querySelectorAll('.fingerprint-removal-item input[type="checkbox"]:checked'), - device_ids = _.map(checkboxes, 'value'); - - this.devicelist.removeOwnDevices(device_ids).then(this.modal.hide).catch(err => { - const _converse = this.__super__._converse, - __ = _converse.__; - - _converse.log(err, Strophe.LogLevel.ERROR); - - _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__('Sorry, an error occurred while trying to remove the devices.')]); - }); - } +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-omemo', { + enabled(_converse) { + return !_.isNil(window.libsignal) && !f.includes('converse-omemo', _converse.blacklisted_plugins); + }, + dependencies: ["converse-chatview"], + overrides: { + ProfileModal: { + events: { + 'change input.select-all': 'selectAll', + 'submit .fingerprint-removal': 'removeSelectedFingerprints' }, - UserDetailsModal: { - events: { - 'click .fingerprint-trust .btn input': 'toggleDeviceTrust' - }, - - initialize() { - const _converse = this.__super__._converse; - const jid = this.model.get('jid'); - this.devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ - 'jid': jid - }); - this.devicelist.devices.on('change:bundle', this.render, this); - this.devicelist.devices.on('change:trusted', this.render, this); - this.devicelist.devices.on('remove', this.render, this); - this.devicelist.devices.on('add', this.render, this); - this.devicelist.devices.on('reset', this.render, this); - return this.__super__.initialize.apply(this, arguments); - }, - - toggleDeviceTrust(ev) { - const radio = ev.target; - const device = this.devicelist.devices.get(radio.getAttribute('name')); - device.save('trusted', parseInt(radio.value, 10)); - } + initialize() { + const _converse = this.__super__._converse; + this.debouncedRender = _.debounce(this.render, 50); + this.devicelist = _converse.devicelists.get(_converse.bare_jid); + this.devicelist.devices.on('change:bundle', this.debouncedRender, this); + this.devicelist.devices.on('reset', this.debouncedRender, this); + this.devicelist.devices.on('remove', this.debouncedRender, this); + this.devicelist.devices.on('add', this.debouncedRender, this); + return this.__super__.initialize.apply(this, arguments); }, - ChatBox: { - getBundlesAndBuildSessions() { - const _converse = this.__super__._converse; - let devices; - return _converse.getDevicesForContact(this.get('jid')).then(their_devices => { - const device_id = _converse.omemo_store.get('device_id'), - devicelist = _converse.devicelists.get(_converse.bare_jid), - own_devices = devicelist.devices.filter(device => device.get('id') !== device_id); - devices = _.concat(own_devices, their_devices.models); - return Promise.all(devices.map(device => device.getBundle())); - }).then(() => this.buildSessions(devices)); - }, + beforeRender() { + const _converse = this.__super__._converse, + device_id = _converse.omemo_store.get('device_id'); - buildSession(device) { + this.current_device = this.devicelist.devices.get(device_id); + this.other_devices = this.devicelist.devices.filter(d => d.get('id') !== device_id); + + if (this.__super__.beforeRender) { + return this.__super__.beforeRender.apply(this, arguments); + } + }, + + selectAll(ev) { + let sibling = u.ancestor(ev.target, 'li'); + + while (sibling) { + sibling.querySelector('input[type="checkbox"]').checked = ev.target.checked; + sibling = sibling.nextElementSibling; + } + }, + + removeSelectedFingerprints(ev) { + ev.preventDefault(); + ev.stopPropagation(); + ev.target.querySelector('.select-all').checked = false; + + const checkboxes = ev.target.querySelectorAll('.fingerprint-removal-item input[type="checkbox"]:checked'), + device_ids = _.map(checkboxes, 'value'); + + this.devicelist.removeOwnDevices(device_ids).then(this.modal.hide).catch(err => { const _converse = this.__super__._converse, - address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id')), - sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address), - prekey = device.getRandomPreKey(); - return device.getBundle().then(bundle => { - return sessionBuilder.processPreKey({ - 'registrationId': parseInt(device.get('id'), 10), - 'identityKey': u.base64ToArrayBuffer(bundle.identity_key), - 'signedPreKey': { - 'keyId': bundle.signed_prekey.id, - // - 'publicKey': u.base64ToArrayBuffer(bundle.signed_prekey.public_key), - 'signature': u.base64ToArrayBuffer(bundle.signed_prekey.signature) - }, - 'preKey': { - 'keyId': prekey.id, - // - 'publicKey': u.base64ToArrayBuffer(prekey.key) - } - }); - }); - }, + __ = _converse.__; - getSession(device) { - const _converse = this.__super__._converse, - address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id')); - return _converse.omemo_store.loadSession(address.toString()).then(session => { - if (session) { - return Promise.resolve(); - } else { - return this.buildSession(device); + _converse.log(err, Strophe.LogLevel.ERROR); + + _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__('Sorry, an error occurred while trying to remove the devices.')]); + }); + } + + }, + UserDetailsModal: { + events: { + 'click .fingerprint-trust .btn input': 'toggleDeviceTrust' + }, + + initialize() { + const _converse = this.__super__._converse; + const jid = this.model.get('jid'); + this.devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ + 'jid': jid + }); + this.devicelist.devices.on('change:bundle', this.render, this); + this.devicelist.devices.on('change:trusted', this.render, this); + this.devicelist.devices.on('remove', this.render, this); + this.devicelist.devices.on('add', this.render, this); + this.devicelist.devices.on('reset', this.render, this); + return this.__super__.initialize.apply(this, arguments); + }, + + toggleDeviceTrust(ev) { + const radio = ev.target; + const device = this.devicelist.devices.get(radio.getAttribute('name')); + device.save('trusted', parseInt(radio.value, 10)); + } + + }, + ChatBox: { + getBundlesAndBuildSessions() { + const _converse = this.__super__._converse; + let devices; + return _converse.getDevicesForContact(this.get('jid')).then(their_devices => { + const device_id = _converse.omemo_store.get('device_id'), + devicelist = _converse.devicelists.get(_converse.bare_jid), + own_devices = devicelist.devices.filter(device => device.get('id') !== device_id); + + devices = _.concat(own_devices, their_devices.models); + return Promise.all(devices.map(device => device.getBundle())); + }).then(() => this.buildSessions(devices)); + }, + + buildSession(device) { + const _converse = this.__super__._converse, + address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id')), + sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address), + prekey = device.getRandomPreKey(); + return device.getBundle().then(bundle => { + return sessionBuilder.processPreKey({ + 'registrationId': parseInt(device.get('id'), 10), + 'identityKey': u.base64ToArrayBuffer(bundle.identity_key), + 'signedPreKey': { + 'keyId': bundle.signed_prekey.id, + // + 'publicKey': u.base64ToArrayBuffer(bundle.signed_prekey.public_key), + 'signature': u.base64ToArrayBuffer(bundle.signed_prekey.signature) + }, + 'preKey': { + 'keyId': prekey.id, + // + 'publicKey': u.base64ToArrayBuffer(prekey.key) } }); - }, + }); + }, - async encryptMessage(plaintext) { - // The client MUST use fresh, randomly generated key/IV pairs - // with AES-128 in Galois/Counter Mode (GCM). - // For GCM a 12 byte IV is strongly suggested as other IV lengths - // will require additional calculations. In principle any IV size - // can be used as long as the IV doesn't ever repeat. NIST however - // suggests that only an IV size of 12 bytes needs to be supported - // by implementations. - // - // https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode - const iv = crypto.getRandomValues(new window.Uint8Array(12)), - key = await crypto.subtle.generateKey(KEY_ALGO, true, ["encrypt", "decrypt"]), - algo = { - 'name': 'AES-GCM', - 'iv': iv, - 'tagLength': TAG_LENGTH - }, - encrypted = await crypto.subtle.encrypt(algo, key, u.stringToArrayBuffer(plaintext)), - length = encrypted.byteLength - (128 + 7 >> 3), - ciphertext = encrypted.slice(0, length), - tag = encrypted.slice(length), - exported_key = await crypto.subtle.exportKey("raw", key); - return Promise.resolve({ - 'key': exported_key, - 'tag': tag, - 'key_and_tag': u.appendArrayBuffer(exported_key, tag), - 'payload': u.arrayBufferToBase64(ciphertext), - 'iv': u.arrayBufferToBase64(iv) - }); - }, - - async decryptMessage(obj) { - const key_obj = await crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt', 'decrypt']), - cipher = u.appendArrayBuffer(u.base64ToArrayBuffer(obj.payload), obj.tag), - algo = { - 'name': "AES-GCM", - 'iv': u.base64ToArrayBuffer(obj.iv), - 'tagLength': TAG_LENGTH - }; - return u.arrayBufferToString((await crypto.subtle.decrypt(algo, key_obj, cipher))); - }, - - reportDecryptionError(e) { - const _converse = this.__super__._converse; - - if (_converse.debug) { - const __ = _converse.__; - this.messages.create({ - 'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`, - 'type': 'error' - }); - } - - _converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR); - }, - - decrypt(attrs) { - const _converse = this.__super__._converse, - session_cipher = this.getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10)); // https://xmpp.org/extensions/xep-0384.html#usecases-receiving - - if (attrs.encrypted.prekey === 'true') { - let plaintext; - return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => { - if (attrs.encrypted.payload) { - const key = key_and_tag.slice(0, 16), - tag = key_and_tag.slice(16); - return this.decryptMessage(_.extend(attrs.encrypted, { - 'key': key, - 'tag': tag - })); - } - - return Promise.resolve(); - }).then(pt => { - plaintext = pt; - return _converse.omemo_store.generateMissingPreKeys(); - }).then(() => _converse.omemo_store.publishBundle()).then(() => { - if (plaintext) { - return _.extend(attrs, { - 'plaintext': plaintext - }); - } else { - return _.extend(attrs, { - 'is_only_key': true - }); - } - }).catch(e => { - this.reportDecryptionError(e); - return attrs; - }); + getSession(device) { + const _converse = this.__super__._converse, + address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id')); + return _converse.omemo_store.loadSession(address.toString()).then(session => { + if (session) { + return Promise.resolve(); } else { - return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => { + return this.buildSession(device); + } + }); + }, + + async encryptMessage(plaintext) { + // The client MUST use fresh, randomly generated key/IV pairs + // with AES-128 in Galois/Counter Mode (GCM). + // For GCM a 12 byte IV is strongly suggested as other IV lengths + // will require additional calculations. In principle any IV size + // can be used as long as the IV doesn't ever repeat. NIST however + // suggests that only an IV size of 12 bytes needs to be supported + // by implementations. + // + // https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode + const iv = crypto.getRandomValues(new window.Uint8Array(12)), + key = await crypto.subtle.generateKey(KEY_ALGO, true, ["encrypt", "decrypt"]), + algo = { + 'name': 'AES-GCM', + 'iv': iv, + 'tagLength': TAG_LENGTH + }, + encrypted = await crypto.subtle.encrypt(algo, key, u.stringToArrayBuffer(plaintext)), + length = encrypted.byteLength - (128 + 7 >> 3), + ciphertext = encrypted.slice(0, length), + tag = encrypted.slice(length), + exported_key = await crypto.subtle.exportKey("raw", key); + return Promise.resolve({ + 'key': exported_key, + 'tag': tag, + 'key_and_tag': u.appendArrayBuffer(exported_key, tag), + 'payload': u.arrayBufferToBase64(ciphertext), + 'iv': u.arrayBufferToBase64(iv) + }); + }, + + async decryptMessage(obj) { + const key_obj = await crypto.subtle.importKey('raw', obj.key, KEY_ALGO, true, ['encrypt', 'decrypt']), + cipher = u.appendArrayBuffer(u.base64ToArrayBuffer(obj.payload), obj.tag), + algo = { + 'name': "AES-GCM", + 'iv': u.base64ToArrayBuffer(obj.iv), + 'tagLength': TAG_LENGTH + }; + return u.arrayBufferToString((await crypto.subtle.decrypt(algo, key_obj, cipher))); + }, + + reportDecryptionError(e) { + const _converse = this.__super__._converse; + + if (_converse.debug) { + const __ = _converse.__; + this.messages.create({ + 'message': __("Sorry, could not decrypt a received OMEMO message due to an error.") + ` ${e.name} ${e.message}`, + 'type': 'error' + }); + } + + _converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR); + }, + + decrypt(attrs) { + const _converse = this.__super__._converse, + session_cipher = this.getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10)); // https://xmpp.org/extensions/xep-0384.html#usecases-receiving + + if (attrs.encrypted.prekey === 'true') { + let plaintext; + return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => { + if (attrs.encrypted.payload) { const key = key_and_tag.slice(0, 16), tag = key_and_tag.slice(16); return this.decryptMessage(_.extend(attrs.encrypted, { 'key': key, 'tag': tag })); - }).then(plaintext => _.extend(attrs, { - 'plaintext': plaintext - })).catch(e => { - this.reportDecryptionError(e); - return attrs; - }); - } - }, - - getEncryptionAttributesfromStanza(stanza, original_stanza, attrs) { - const _converse = this.__super__._converse, - encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(), - header = encrypted.querySelector('header'), - key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop(); - - if (key) { - attrs['is_encrypted'] = true; - attrs['encrypted'] = { - 'device_id': header.getAttribute('sid'), - 'iv': header.querySelector('iv').textContent, - 'key': key.textContent, - 'payload': _.get(encrypted.querySelector('payload'), 'textContent', null), - 'prekey': key.getAttribute('prekey') - }; - return this.decrypt(attrs); - } else { - return Promise.resolve(attrs); - } - }, - - getMessageAttributesFromStanza(stanza, original_stanza) { - const _converse = this.__super__._converse, - encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(), - attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments); - - if (!encrypted || !_converse.config.get('trusted')) { - return attrs; - } else { - return this.getEncryptionAttributesfromStanza(stanza, original_stanza, attrs); - } - }, - - buildSessions(devices) { - return Promise.all(devices.map(device => this.getSession(device))).then(() => devices); - }, - - getSessionCipher(jid, id) { - const _converse = this.__super__._converse, - address = new libsignal.SignalProtocolAddress(jid, id); - this.session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address); - return this.session_cipher; - }, - - encryptKey(plaintext, device) { - return this.getSessionCipher(device.get('jid'), device.get('id')).encrypt(plaintext).then(payload => ({ - 'payload': payload, - 'device': device - })); - }, - - addKeysToMessageStanza(stanza, dicts, iv) { - for (var i in dicts) { - if (Object.prototype.hasOwnProperty.call(dicts, i)) { - const payload = dicts[i].payload, - device = dicts[i].device, - prekey = 3 == parseInt(payload.type, 10); - stanza.c('key', { - 'rid': device.get('id') - }).t(btoa(payload.body)); - - if (prekey) { - stanza.attrs({ - 'prekey': prekey - }); - } - - stanza.up(); - - if (i == dicts.length - 1) { - stanza.c('iv').t(iv).up().up(); - } } - } - - return Promise.resolve(stanza); - }, - - createOMEMOMessageStanza(message, devices) { - const _converse = this.__super__._converse, - __ = _converse.__; - - const body = __("This is an OMEMO encrypted message which your client doesn’t seem to support. " + "Find more information on https://conversations.im/omemo"); - - if (!message.get('message')) { - throw new Error("No message body to encrypt!"); - } - - const stanza = $msg({ - 'from': _converse.connection.jid, - 'to': this.get('jid'), - 'type': this.get('message_type'), - 'id': message.get('msgid') - }).c('body').t(body).up() // An encrypted header is added to the message for - // each device that is supposed to receive it. - // These headers simply contain the key that the - // payload message is encrypted with, - // and they are separately encrypted using the - // session corresponding to the counterpart device. - .c('encrypted', { - 'xmlns': Strophe.NS.OMEMO - }).c('header', { - 'sid': _converse.omemo_store.get('device_id') - }); - return this.encryptMessage(message.get('message')).then(obj => { - // The 16 bytes key and the GCM authentication tag (The tag - // SHOULD have at least 128 bit) are concatenated and for each - // intended recipient device, i.e. both own devices as well as - // devices associated with the contact, the result of this - // concatenation is encrypted using the corresponding - // long-standing SignalProtocol session. - const promises = devices.filter(device => device.get('trusted') != UNTRUSTED).map(device => this.encryptKey(obj.key_and_tag, device)); - return Promise.all(promises).then(dicts => this.addKeysToMessageStanza(stanza, dicts, obj.iv)).then(stanza => { - stanza.c('payload').t(obj.payload).up().up(); - stanza.c('store', { - 'xmlns': Strophe.NS.HINTS - }); - return stanza; - }); - }); - }, - - sendMessage(attrs) { - const _converse = this.__super__._converse, - __ = _converse.__; - - if (this.get('omemo_active') && attrs.message) { - attrs['is_encrypted'] = true; - attrs['plaintext'] = attrs.message; - const message = this.messages.create(attrs); - this.getBundlesAndBuildSessions().then(devices => this.createOMEMOMessageStanza(message, devices)).then(stanza => this.sendMessageStanza(stanza)).catch(e => { - this.messages.create({ - 'message': __("Sorry, could not send the message due to an error.") + ` ${e.message}`, - 'type': 'error' - }); - - _converse.log(e, Strophe.LogLevel.ERROR); - }); - } else { - return this.__super__.sendMessage.apply(this, arguments); - } - } - - }, - ChatBoxView: { - events: { - 'click .toggle-omemo': 'toggleOMEMO' - }, - - showMessage(message) { - // We don't show a message if it's only keying material - if (!message.get('is_only_key')) { - return this.__super__.showMessage.apply(this, arguments); - } - }, - - async renderOMEMOToolbarButton() { - const _converse = this.__super__._converse, - __ = _converse.__; - const support = await _converse.contactHasOMEMOSupport(this.model.get('jid')); - - if (support) { - const icon = this.el.querySelector('.toggle-omemo'), - html = tpl_toolbar_omemo(_.extend(this.model.toJSON(), { - '__': __ - })); - - if (icon) { - icon.outerHTML = html; - } else { - this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', html); - } - } - }, - - toggleOMEMO(ev) { - ev.preventDefault(); - this.model.save({ - 'omemo_active': !this.model.get('omemo_active') - }); - this.renderOMEMOToolbarButton(); - } - - } - }, - - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by Converse.js's plugin machinery. - */ - const _converse = this._converse; - - _converse.api.promises.add(['OMEMOInitialized']); - - _converse.NUM_PREKEYS = 100; // Set here so that tests can override - - function generateFingerprint(device) { - if (_.get(device.get('bundle'), 'fingerprint')) { - return; - } - - return device.getBundle().then(bundle => { - bundle['fingerprint'] = u.arrayBufferToHex(u.base64ToArrayBuffer(bundle['identity_key'])); - device.save('bundle', bundle); - device.trigger('change:bundle'); // Doesn't get triggered automatically due to pass-by-reference - }); - } - - _converse.generateFingerprints = function (jid) { - return _converse.getDevicesForContact(jid).then(devices => Promise.all(devices.map(d => generateFingerprint(d)))); - }; - - _converse.getDeviceForContact = function (jid, device_id) { - return _converse.getDevicesForContact(jid).then(devices => devices.get(device_id)); - }; - - _converse.getDevicesForContact = function (jid) { - let devicelist; - return _converse.api.waitUntil('OMEMOInitialized').then(() => { - devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ - 'jid': jid - }); - return devicelist.fetchDevices(); - }).then(() => devicelist.devices); - }; - - _converse.contactHasOMEMOSupport = function (jid) { - /* Checks whether the contact advertises any OMEMO-compatible devices. */ - return new Promise((resolve, reject) => { - _converse.getDevicesForContact(jid).then(devices => resolve(devices.length > 0)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - }); - }; - - function generateDeviceID() { - /* Generates a device ID, making sure that it's unique */ - const existing_ids = _converse.devicelists.get(_converse.bare_jid).devices.pluck('id'); - - let device_id = libsignal.KeyHelper.generateRegistrationId(); - let i = 0; - - while (_.includes(existing_ids, device_id)) { - device_id = libsignal.KeyHelper.generateRegistrationId(); - i++; - - if (i == 10) { - throw new Error("Unable to generate a unique device ID"); - } - } - - return device_id.toString(); - } - - _converse.OMEMOStore = Backbone.Model.extend({ - Direction: { - SENDING: 1, - RECEIVING: 2 - }, - - getIdentityKeyPair() { - const keypair = this.get('identity_keypair'); - return Promise.resolve({ - 'privKey': u.base64ToArrayBuffer(keypair.privKey), - 'pubKey': u.base64ToArrayBuffer(keypair.pubKey) - }); - }, - - getLocalRegistrationId() { - return Promise.resolve(parseInt(this.get('device_id'), 10)); - }, - - isTrustedIdentity(identifier, identity_key, direction) { - if (_.isNil(identifier)) { - throw new Error("Can't check identity key for invalid key"); - } - - if (!(identity_key instanceof ArrayBuffer)) { - throw new Error("Expected identity_key to be an ArrayBuffer"); - } - - const trusted = this.get('identity_key' + identifier); - - if (trusted === undefined) { - return Promise.resolve(true); - } - - return Promise.resolve(u.arrayBufferToBase64(identity_key) === trusted); - }, - - loadIdentityKey(identifier) { - if (_.isNil(identifier)) { - throw new Error("Can't load identity_key for invalid identifier"); - } - - return Promise.resolve(u.base64ToArrayBuffer(this.get('identity_key' + identifier))); - }, - - saveIdentity(identifier, identity_key) { - if (_.isNil(identifier)) { - throw new Error("Can't save identity_key for invalid identifier"); - } - - const address = new libsignal.SignalProtocolAddress.fromString(identifier), - existing = this.get('identity_key' + address.getName()); - const b64_idkey = u.arrayBufferToBase64(identity_key); - this.save('identity_key' + address.getName(), b64_idkey); - - if (existing && b64_idkey !== existing) { - return Promise.resolve(true); - } else { - return Promise.resolve(false); - } - }, - - getPreKeys() { - return this.get('prekeys') || {}; - }, - - loadPreKey(key_id) { - const res = this.getPreKeys()[key_id]; - - if (res) { - return Promise.resolve({ - 'privKey': u.base64ToArrayBuffer(res.privKey), - 'pubKey': u.base64ToArrayBuffer(res.pubKey) - }); - } - - return Promise.resolve(); - }, - - storePreKey(key_id, key_pair) { - const prekey = {}; - prekey[key_id] = { - 'pubKey': u.arrayBufferToBase64(key_pair.pubKey), - 'privKey': u.arrayBufferToBase64(key_pair.privKey) - }; - this.save('prekeys', _.extend(this.getPreKeys(), prekey)); - return Promise.resolve(); - }, - - removePreKey(key_id) { - this.save('prekeys', _.omit(this.getPreKeys(), key_id)); - return Promise.resolve(); - }, - - loadSignedPreKey(keyId) { - const res = this.get('signed_prekey'); - - if (res) { - return Promise.resolve({ - 'privKey': u.base64ToArrayBuffer(res.privKey), - 'pubKey': u.base64ToArrayBuffer(res.pubKey) - }); - } - - return Promise.resolve(); - }, - - storeSignedPreKey(spk) { - if (typeof spk !== "object") { - // XXX: We've changed the signature of this method from the - // example given in InMemorySignalProtocolStore. - // Should be fine because the libsignal code doesn't - // actually call this method. - throw new Error("storeSignedPreKey: expected an object"); - } - - this.save('signed_prekey', { - 'id': spk.keyId, - 'privKey': u.arrayBufferToBase64(spk.keyPair.privKey), - 'pubKey': u.arrayBufferToBase64(spk.keyPair.pubKey), - // XXX: The InMemorySignalProtocolStore does not pass - // in or store the signature, but we need it when we - // publish out bundle and this method isn't called from - // within libsignal code, so we modify it to also store - // the signature. - 'signature': u.arrayBufferToBase64(spk.signature) - }); - return Promise.resolve(); - }, - - removeSignedPreKey(key_id) { - if (this.get('signed_prekey')['id'] === key_id) { - this.unset('signed_prekey'); - this.save(); - } - - return Promise.resolve(); - }, - - loadSession(identifier) { - return Promise.resolve(this.get('session' + identifier)); - }, - - storeSession(identifier, record) { - return Promise.resolve(this.save('session' + identifier, record)); - }, - - removeSession(identifier) { - return Promise.resolve(this.unset('session' + identifier)); - }, - - removeAllSessions(identifier) { - const keys = _.filter(_.keys(this.attributes), key => { - if (key.startsWith('session' + identifier)) { - return key; - } - }); - - const attrs = {}; - - _.forEach(keys, key => { - attrs[key] = undefined; - }); - - this.save(attrs); - return Promise.resolve(); - }, - - publishBundle() { - const signed_prekey = this.get('signed_prekey'); - const stanza = $iq({ - 'from': _converse.bare_jid, - 'type': 'set' - }).c('pubsub', { - 'xmlns': Strophe.NS.PUBSUB - }).c('publish', { - 'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('device_id')}` - }).c('item').c('bundle', { - 'xmlns': Strophe.NS.OMEMO - }).c('signedPreKeyPublic', { - 'signedPreKeyId': signed_prekey.id - }).t(signed_prekey.pubKey).up().c('signedPreKeySignature').t(signed_prekey.signature).up().c('identityKey').t(this.get('identity_keypair').pubKey).up().c('prekeys'); - - _.forEach(this.get('prekeys'), (prekey, id) => stanza.c('preKeyPublic', { - 'preKeyId': id - }).t(prekey.pubKey).up()); - - return _converse.api.sendIQ(stanza); - }, - - generateMissingPreKeys() { - const current_keys = this.getPreKeys(), - missing_keys = _.difference(_.invokeMap(_.range(0, _converse.NUM_PREKEYS), Number.prototype.toString), _.keys(current_keys)); - - if (missing_keys.length < 1) { - _converse.log("No missing prekeys to generate for our own device", Strophe.LogLevel.WARN); return Promise.resolve(); - } - - return Promise.all(_.map(missing_keys, id => libsignal.KeyHelper.generatePreKey(parseInt(id, 10)))).then(keys => { - _.forEach(keys, k => this.storePreKey(k.keyId, k.keyPair)); - - const marshalled_keys = _.map(this.getPreKeys(), k => ({ - 'id': k.keyId, - 'key': u.arrayBufferToBase64(k.pubKey) - })), - devicelist = _converse.devicelists.get(_converse.bare_jid), - device = devicelist.devices.get(this.get('device_id')); - - return device.getBundle().then(bundle => device.save('bundle', _.extend(bundle, { - 'prekeys': marshalled_keys - }))); + }).then(pt => { + plaintext = pt; + return _converse.omemo_store.generateMissingPreKeys(); + }).then(() => _converse.omemo_store.publishBundle()).then(() => { + if (plaintext) { + return _.extend(attrs, { + 'plaintext': plaintext + }); + } else { + return _.extend(attrs, { + 'is_only_key': true + }); + } + }).catch(e => { + this.reportDecryptionError(e); + return attrs; }); - }, - - async generateBundle() { - /* The first thing that needs to happen if a client wants to - * start using OMEMO is they need to generate an IdentityKey - * and a Device ID. The IdentityKey is a Curve25519 [6] - * public/private Key pair. The Device ID is a randomly - * generated integer between 1 and 2^31 - 1. - */ - const identity_keypair = await libsignal.KeyHelper.generateIdentityKeyPair(); - const bundle = {}, - identity_key = u.arrayBufferToBase64(identity_keypair.pubKey), - device_id = generateDeviceID(); - bundle['identity_key'] = identity_key; - bundle['device_id'] = device_id; - this.save({ - 'device_id': device_id, - 'identity_keypair': { - 'privKey': u.arrayBufferToBase64(identity_keypair.privKey), - 'pubKey': identity_key - }, - 'identity_key': identity_key + } else { + return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => { + const key = key_and_tag.slice(0, 16), + tag = key_and_tag.slice(16); + return this.decryptMessage(_.extend(attrs.encrypted, { + 'key': key, + 'tag': tag + })); + }).then(plaintext => _.extend(attrs, { + 'plaintext': plaintext + })).catch(e => { + this.reportDecryptionError(e); + return attrs; }); - const signed_prekey = await libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 0); + } + }, - _converse.omemo_store.storeSignedPreKey(signed_prekey); + getEncryptionAttributesfromStanza(stanza, original_stanza, attrs) { + const _converse = this.__super__._converse, + encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(), + header = encrypted.querySelector('header'), + key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop(); - bundle['signed_prekey'] = { - 'id': signed_prekey.keyId, - 'public_key': u.arrayBufferToBase64(signed_prekey.keyPair.privKey), - 'signature': u.arrayBufferToBase64(signed_prekey.signature) + if (key) { + attrs['is_encrypted'] = true; + attrs['encrypted'] = { + 'device_id': header.getAttribute('sid'), + 'iv': header.querySelector('iv').textContent, + 'key': key.textContent, + 'payload': _.get(encrypted.querySelector('payload'), 'textContent', null), + 'prekey': key.getAttribute('prekey') }; - const keys = await Promise.all(_.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id))); + return this.decrypt(attrs); + } else { + return Promise.resolve(attrs); + } + }, - _.forEach(keys, k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair)); + getMessageAttributesFromStanza(stanza, original_stanza) { + const _converse = this.__super__._converse, + encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(), + attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments); - const devicelist = _converse.devicelists.get(_converse.bare_jid), - device = devicelist.devices.create({ - 'id': bundle.device_id, - 'jid': _converse.bare_jid - }), - marshalled_keys = _.map(keys, k => ({ - 'id': k.keyId, - 'key': u.arrayBufferToBase64(k.keyPair.pubKey) + if (!encrypted || !_converse.config.get('trusted')) { + return attrs; + } else { + return this.getEncryptionAttributesfromStanza(stanza, original_stanza, attrs); + } + }, + + buildSessions(devices) { + return Promise.all(devices.map(device => this.getSession(device))).then(() => devices); + }, + + getSessionCipher(jid, id) { + const _converse = this.__super__._converse, + address = new libsignal.SignalProtocolAddress(jid, id); + this.session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address); + return this.session_cipher; + }, + + encryptKey(plaintext, device) { + return this.getSessionCipher(device.get('jid'), device.get('id')).encrypt(plaintext).then(payload => ({ + 'payload': payload, + 'device': device + })); + }, + + addKeysToMessageStanza(stanza, dicts, iv) { + for (var i in dicts) { + if (Object.prototype.hasOwnProperty.call(dicts, i)) { + const payload = dicts[i].payload, + device = dicts[i].device, + prekey = 3 == parseInt(payload.type, 10); + stanza.c('key', { + 'rid': device.get('id') + }).t(btoa(payload.body)); + + if (prekey) { + stanza.attrs({ + 'prekey': prekey + }); + } + + stanza.up(); + + if (i == dicts.length - 1) { + stanza.c('iv').t(iv).up().up(); + } + } + } + + return Promise.resolve(stanza); + }, + + createOMEMOMessageStanza(message, devices) { + const _converse = this.__super__._converse, + __ = _converse.__; + + const body = __("This is an OMEMO encrypted message which your client doesn’t seem to support. " + "Find more information on https://conversations.im/omemo"); + + if (!message.get('message')) { + throw new Error("No message body to encrypt!"); + } + + const stanza = $msg({ + 'from': _converse.connection.jid, + 'to': this.get('jid'), + 'type': this.get('message_type'), + 'id': message.get('msgid') + }).c('body').t(body).up() // An encrypted header is added to the message for + // each device that is supposed to receive it. + // These headers simply contain the key that the + // payload message is encrypted with, + // and they are separately encrypted using the + // session corresponding to the counterpart device. + .c('encrypted', { + 'xmlns': Strophe.NS.OMEMO + }).c('header', { + 'sid': _converse.omemo_store.get('device_id') + }); + return this.encryptMessage(message.get('message')).then(obj => { + // The 16 bytes key and the GCM authentication tag (The tag + // SHOULD have at least 128 bit) are concatenated and for each + // intended recipient device, i.e. both own devices as well as + // devices associated with the contact, the result of this + // concatenation is encrypted using the corresponding + // long-standing SignalProtocol session. + const promises = devices.filter(device => device.get('trusted') != UNTRUSTED).map(device => this.encryptKey(obj.key_and_tag, device)); + return Promise.all(promises).then(dicts => this.addKeysToMessageStanza(stanza, dicts, obj.iv)).then(stanza => { + stanza.c('payload').t(obj.payload).up().up(); + stanza.c('store', { + 'xmlns': Strophe.NS.HINTS + }); + return stanza; + }); + }); + }, + + sendMessage(attrs) { + const _converse = this.__super__._converse, + __ = _converse.__; + + if (this.get('omemo_active') && attrs.message) { + attrs['is_encrypted'] = true; + attrs['plaintext'] = attrs.message; + const message = this.messages.create(attrs); + this.getBundlesAndBuildSessions().then(devices => this.createOMEMOMessageStanza(message, devices)).then(stanza => this.sendMessageStanza(stanza)).catch(e => { + this.messages.create({ + 'message': __("Sorry, could not send the message due to an error.") + ` ${e.message}`, + 'type': 'error' + }); + + _converse.log(e, Strophe.LogLevel.ERROR); + }); + } else { + return this.__super__.sendMessage.apply(this, arguments); + } + } + + }, + ChatBoxView: { + events: { + 'click .toggle-omemo': 'toggleOMEMO' + }, + + showMessage(message) { + // We don't show a message if it's only keying material + if (!message.get('is_only_key')) { + return this.__super__.showMessage.apply(this, arguments); + } + }, + + async renderOMEMOToolbarButton() { + const _converse = this.__super__._converse, + __ = _converse.__; + const support = await _converse.contactHasOMEMOSupport(this.model.get('jid')); + + if (support) { + const icon = this.el.querySelector('.toggle-omemo'), + html = templates_toolbar_omemo_html__WEBPACK_IMPORTED_MODULE_1___default()(_.extend(this.model.toJSON(), { + '__': __ })); - bundle['prekeys'] = marshalled_keys; - device.save('bundle', bundle); - }, - - fetchSession() { - if (_.isUndefined(this._setup_promise)) { - this._setup_promise = new Promise((resolve, reject) => { - this.fetch({ - 'success': () => { - if (!_converse.omemo_store.get('device_id')) { - this.generateBundle().then(resolve).catch(resolve); - } else { - resolve(); - } - }, - 'error': () => { - this.generateBundle().then(resolve).catch(resolve); - } - }); - }); - } - - return this._setup_promise; - } - - }); - _converse.Device = Backbone.Model.extend({ - defaults: { - 'trusted': UNDECIDED - }, - - getRandomPreKey() { - // XXX: assumes that the bundle has already been fetched - const bundle = this.get('bundle'); - return bundle.prekeys[u.getRandomInt(bundle.prekeys.length)]; - }, - - fetchBundleFromServer() { - const stanza = $iq({ - 'type': 'get', - 'from': _converse.bare_jid, - 'to': this.get('jid') - }).c('pubsub', { - 'xmlns': Strophe.NS.PUBSUB - }).c('items', { - 'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}` - }); - return _converse.api.sendIQ(stanza).then(iq => { - const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(), - bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(), - bundle = parseBundle(bundle_el); - this.save('bundle', bundle); - return bundle; - }).catch(iq => { - _converse.log(iq.outerHTML, Strophe.LogLevel.ERROR); - }); - }, - - getBundle() { - /* Fetch and save the bundle information associated with - * this device, if the information is not at hand already. - */ - if (this.get('bundle')) { - return Promise.resolve(this.get('bundle'), this); + if (icon) { + icon.outerHTML = html; } else { - return this.fetchBundleFromServer(); + this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', html); } } + }, - }); - _converse.Devices = Backbone.Collection.extend({ - model: _converse.Device - }); - _converse.DeviceList = Backbone.Model.extend({ - idAttribute: 'jid', - - initialize() { - this.devices = new _converse.Devices(); - const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`; - this.devices.browserStorage = new Backbone.BrowserStorage.session(id); - this.fetchDevices(); - }, - - fetchDevices() { - if (_.isUndefined(this._devices_promise)) { - this._devices_promise = new Promise((resolve, reject) => { - this.devices.fetch({ - 'success': collection => { - if (collection.length === 0) { - this.fetchDevicesFromServer().then(ids => this.publishCurrentDevice(ids)).finally(resolve); - } else { - resolve(); - } - } - }); - }); - } - - return this._devices_promise; - }, - - async publishCurrentDevice(device_ids) { - if (this.get('jid') !== _converse.bare_jid) { - // We only publish for ourselves. - return; - } - - await restoreOMEMOSession(); - - let device_id = _converse.omemo_store.get('device_id'); - - if (!this.devices.findWhere({ - 'id': device_id - })) { - // Generate a new bundle if we cannot find our device - await _converse.omemo_store.generateBundle(); - device_id = _converse.omemo_store.get('device_id'); - } - - if (!_.includes(device_ids, device_id)) { - return this.publishDevices(); - } - }, - - fetchDevicesFromServer() { - const stanza = $iq({ - 'type': 'get', - 'from': _converse.bare_jid, - 'to': this.get('jid') - }).c('pubsub', { - 'xmlns': Strophe.NS.PUBSUB - }).c('items', { - 'node': Strophe.NS.OMEMO_DEVICELIST - }); - return _converse.api.sendIQ(stanza).then(iq => { - const device_ids = _.map(sizzle(`list[xmlns="${Strophe.NS.OMEMO}"] device`, iq), dev => dev.getAttribute('id')); - - _.forEach(device_ids, id => this.devices.create({ - 'id': id, - 'jid': this.get('jid') - })); - - return device_ids; - }); - }, - - publishDevices() { - const stanza = $iq({ - 'from': _converse.bare_jid, - 'type': 'set' - }).c('pubsub', { - 'xmlns': Strophe.NS.PUBSUB - }).c('publish', { - 'node': Strophe.NS.OMEMO_DEVICELIST - }).c('item').c('list', { - 'xmlns': Strophe.NS.OMEMO - }); - this.devices.each(device => stanza.c('device', { - 'id': device.get('id') - }).up()); - return _converse.api.sendIQ(stanza); - }, - - removeOwnDevices(device_ids) { - if (this.get('jid') !== _converse.bare_jid) { - throw new Error("Cannot remove devices from someone else's device list"); - } - - _.forEach(device_ids, device_id => this.devices.get(device_id).destroy()); - - return this.publishDevices(); - } - - }); - _converse.DeviceLists = Backbone.Collection.extend({ - model: _converse.DeviceList - }); - - function fetchDeviceLists() { - return new Promise((resolve, reject) => _converse.devicelists.fetch({ - 'success': resolve - })); - } - - function fetchOwnDevices() { - return fetchDeviceLists().then(() => { - let own_devicelist = _converse.devicelists.get(_converse.bare_jid); - - if (_.isNil(own_devicelist)) { - own_devicelist = _converse.devicelists.create({ - 'jid': _converse.bare_jid - }); - } - - return own_devicelist.fetchDevices(); + toggleOMEMO(ev) { + ev.preventDefault(); + this.model.save({ + 'omemo_active': !this.model.get('omemo_active') }); + this.renderOMEMOToolbarButton(); } - function updateBundleFromStanza(stanza) { - const items_el = sizzle(`items`, stanza).pop(); + } + }, - if (!items_el || !items_el.getAttribute('node').startsWith(Strophe.NS.OMEMO_BUNDLES)) { - return; - } + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by Converse.js's plugin machinery. + */ + const _converse = this._converse; - const device_id = items_el.getAttribute('node').split(':')[1], - jid = stanza.getAttribute('from'), - bundle_el = sizzle(`item > bundle`, items_el).pop(), - devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ - 'jid': jid - }), - device = devicelist.devices.get(device_id) || devicelist.devices.create({ - 'id': device_id, - 'jid': jid - }); + _converse.api.promises.add(['OMEMOInitialized']); - device.save({ - 'bundle': parseBundle(bundle_el) - }); + _converse.NUM_PREKEYS = 100; // Set here so that tests can override + + function generateFingerprint(device) { + if (_.get(device.get('bundle'), 'fingerprint')) { + return; } - function updateDevicesFromStanza(stanza) { - const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_DEVICELIST}"]`, stanza).pop(); - - if (!items_el) { - return; - } - - const device_ids = _.map(sizzle(`item list[xmlns="${Strophe.NS.OMEMO}"] device`, items_el), device => device.getAttribute('id')); - - const jid = stanza.getAttribute('from'), - devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ - 'jid': jid - }), - devices = devicelist.devices, - removed_ids = _.difference(devices.pluck('id'), device_ids); - - _.forEach(removed_ids, id => { - if (jid === _converse.bare_jid && id === _converse.omemo_store.get('device_id')) { - // We don't remove the current device - return; - } - - devices.get(id).destroy(); - }); - - _.forEach(device_ids, device_id => { - if (!devices.get(device_id)) { - devices.create({ - 'id': device_id, - 'jid': jid - }); - } - }); - - if (Strophe.getBareJidFromJid(jid) === _converse.bare_jid) { - // Make sure our own device is on the list (i.e. if it was - // removed, add it again. - _converse.devicelists.get(_converse.bare_jid).publishCurrentDevice(device_ids); - } - } - - function registerPEPPushHandler() { - // Add a handler for devices pushed from other connected clients - _converse.connection.addHandler(message => { - try { - if (sizzle(`event[xmlns="${Strophe.NS.PUBSUB}#event"]`, message).length) { - updateDevicesFromStanza(message); - updateBundleFromStanza(message); - } - } catch (e) { - _converse.log(e.message, Strophe.LogLevel.ERROR); - } - - return true; - }, null, 'message', 'headline'); - } - - function restoreOMEMOSession() { - if (_.isUndefined(_converse.omemo_store)) { - const storage = _converse.config.get('storage'), - id = `converse.omemosession-${_converse.bare_jid}`; - - _converse.omemo_store = new _converse.OMEMOStore({ - 'id': id - }); - _converse.omemo_store.browserStorage = new Backbone.BrowserStorage[storage](id); - } - - return _converse.omemo_store.fetchSession(); - } - - function initOMEMO() { - if (!_converse.config.get('trusted')) { - return; - } - - _converse.devicelists = new _converse.DeviceLists(); - - const storage = _converse.config.get('storage'), - id = `converse.devicelists-${_converse.bare_jid}`; - - _converse.devicelists.browserStorage = new Backbone.BrowserStorage[storage](id); - fetchOwnDevices().then(() => restoreOMEMOSession()).then(() => _converse.omemo_store.publishBundle()).then(() => _converse.emit('OMEMOInitialized')).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - } - - _converse.api.listen.on('afterTearDown', () => { - if (_converse.devicelists) { - _converse.devicelists.reset(); - } - - delete _converse.omemo_store; - }); - - _converse.api.listen.on('connected', registerPEPPushHandler); - - _converse.api.listen.on('renderToolbar', view => view.renderOMEMOToolbarButton()); - - _converse.api.listen.on('statusInitialized', initOMEMO); - - _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(`${Strophe.NS.OMEMO_DEVICELIST}+notify`)); - - _converse.api.listen.on('userDetailsModalInitialized', contact => { - const jid = contact.get('jid'); - - _converse.generateFingerprints(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - }); - - _converse.api.listen.on('profileModalInitialized', contact => { - _converse.generateFingerprints(_converse.bare_jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + return device.getBundle().then(bundle => { + bundle['fingerprint'] = u.arrayBufferToHex(u.base64ToArrayBuffer(bundle['identity_key'])); + device.save('bundle', bundle); + device.trigger('change:bundle'); // Doesn't get triggered automatically due to pass-by-reference }); } - }); + _converse.generateFingerprints = function (jid) { + return _converse.getDevicesForContact(jid).then(devices => Promise.all(devices.map(d => generateFingerprint(d)))); + }; + + _converse.getDeviceForContact = function (jid, device_id) { + return _converse.getDevicesForContact(jid).then(devices => devices.get(device_id)); + }; + + _converse.getDevicesForContact = function (jid) { + let devicelist; + return _converse.api.waitUntil('OMEMOInitialized').then(() => { + devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ + 'jid': jid + }); + return devicelist.fetchDevices(); + }).then(() => devicelist.devices); + }; + + _converse.contactHasOMEMOSupport = function (jid) { + /* Checks whether the contact advertises any OMEMO-compatible devices. */ + return new Promise((resolve, reject) => { + _converse.getDevicesForContact(jid).then(devices => resolve(devices.length > 0)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }); + }; + + function generateDeviceID() { + /* Generates a device ID, making sure that it's unique */ + const existing_ids = _converse.devicelists.get(_converse.bare_jid).devices.pluck('id'); + + let device_id = libsignal.KeyHelper.generateRegistrationId(); + let i = 0; + + while (_.includes(existing_ids, device_id)) { + device_id = libsignal.KeyHelper.generateRegistrationId(); + i++; + + if (i == 10) { + throw new Error("Unable to generate a unique device ID"); + } + } + + return device_id.toString(); + } + + _converse.OMEMOStore = Backbone.Model.extend({ + Direction: { + SENDING: 1, + RECEIVING: 2 + }, + + getIdentityKeyPair() { + const keypair = this.get('identity_keypair'); + return Promise.resolve({ + 'privKey': u.base64ToArrayBuffer(keypair.privKey), + 'pubKey': u.base64ToArrayBuffer(keypair.pubKey) + }); + }, + + getLocalRegistrationId() { + return Promise.resolve(parseInt(this.get('device_id'), 10)); + }, + + isTrustedIdentity(identifier, identity_key, direction) { + if (_.isNil(identifier)) { + throw new Error("Can't check identity key for invalid key"); + } + + if (!(identity_key instanceof ArrayBuffer)) { + throw new Error("Expected identity_key to be an ArrayBuffer"); + } + + const trusted = this.get('identity_key' + identifier); + + if (trusted === undefined) { + return Promise.resolve(true); + } + + return Promise.resolve(u.arrayBufferToBase64(identity_key) === trusted); + }, + + loadIdentityKey(identifier) { + if (_.isNil(identifier)) { + throw new Error("Can't load identity_key for invalid identifier"); + } + + return Promise.resolve(u.base64ToArrayBuffer(this.get('identity_key' + identifier))); + }, + + saveIdentity(identifier, identity_key) { + if (_.isNil(identifier)) { + throw new Error("Can't save identity_key for invalid identifier"); + } + + const address = new libsignal.SignalProtocolAddress.fromString(identifier), + existing = this.get('identity_key' + address.getName()); + const b64_idkey = u.arrayBufferToBase64(identity_key); + this.save('identity_key' + address.getName(), b64_idkey); + + if (existing && b64_idkey !== existing) { + return Promise.resolve(true); + } else { + return Promise.resolve(false); + } + }, + + getPreKeys() { + return this.get('prekeys') || {}; + }, + + loadPreKey(key_id) { + const res = this.getPreKeys()[key_id]; + + if (res) { + return Promise.resolve({ + 'privKey': u.base64ToArrayBuffer(res.privKey), + 'pubKey': u.base64ToArrayBuffer(res.pubKey) + }); + } + + return Promise.resolve(); + }, + + storePreKey(key_id, key_pair) { + const prekey = {}; + prekey[key_id] = { + 'pubKey': u.arrayBufferToBase64(key_pair.pubKey), + 'privKey': u.arrayBufferToBase64(key_pair.privKey) + }; + this.save('prekeys', _.extend(this.getPreKeys(), prekey)); + return Promise.resolve(); + }, + + removePreKey(key_id) { + this.save('prekeys', _.omit(this.getPreKeys(), key_id)); + return Promise.resolve(); + }, + + loadSignedPreKey(keyId) { + const res = this.get('signed_prekey'); + + if (res) { + return Promise.resolve({ + 'privKey': u.base64ToArrayBuffer(res.privKey), + 'pubKey': u.base64ToArrayBuffer(res.pubKey) + }); + } + + return Promise.resolve(); + }, + + storeSignedPreKey(spk) { + if (typeof spk !== "object") { + // XXX: We've changed the signature of this method from the + // example given in InMemorySignalProtocolStore. + // Should be fine because the libsignal code doesn't + // actually call this method. + throw new Error("storeSignedPreKey: expected an object"); + } + + this.save('signed_prekey', { + 'id': spk.keyId, + 'privKey': u.arrayBufferToBase64(spk.keyPair.privKey), + 'pubKey': u.arrayBufferToBase64(spk.keyPair.pubKey), + // XXX: The InMemorySignalProtocolStore does not pass + // in or store the signature, but we need it when we + // publish out bundle and this method isn't called from + // within libsignal code, so we modify it to also store + // the signature. + 'signature': u.arrayBufferToBase64(spk.signature) + }); + return Promise.resolve(); + }, + + removeSignedPreKey(key_id) { + if (this.get('signed_prekey')['id'] === key_id) { + this.unset('signed_prekey'); + this.save(); + } + + return Promise.resolve(); + }, + + loadSession(identifier) { + return Promise.resolve(this.get('session' + identifier)); + }, + + storeSession(identifier, record) { + return Promise.resolve(this.save('session' + identifier, record)); + }, + + removeSession(identifier) { + return Promise.resolve(this.unset('session' + identifier)); + }, + + removeAllSessions(identifier) { + const keys = _.filter(_.keys(this.attributes), key => { + if (key.startsWith('session' + identifier)) { + return key; + } + }); + + const attrs = {}; + + _.forEach(keys, key => { + attrs[key] = undefined; + }); + + this.save(attrs); + return Promise.resolve(); + }, + + publishBundle() { + const signed_prekey = this.get('signed_prekey'); + const stanza = $iq({ + 'from': _converse.bare_jid, + 'type': 'set' + }).c('pubsub', { + 'xmlns': Strophe.NS.PUBSUB + }).c('publish', { + 'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('device_id')}` + }).c('item').c('bundle', { + 'xmlns': Strophe.NS.OMEMO + }).c('signedPreKeyPublic', { + 'signedPreKeyId': signed_prekey.id + }).t(signed_prekey.pubKey).up().c('signedPreKeySignature').t(signed_prekey.signature).up().c('identityKey').t(this.get('identity_keypair').pubKey).up().c('prekeys'); + + _.forEach(this.get('prekeys'), (prekey, id) => stanza.c('preKeyPublic', { + 'preKeyId': id + }).t(prekey.pubKey).up()); + + return _converse.api.sendIQ(stanza); + }, + + generateMissingPreKeys() { + const current_keys = this.getPreKeys(), + missing_keys = _.difference(_.invokeMap(_.range(0, _converse.NUM_PREKEYS), Number.prototype.toString), _.keys(current_keys)); + + if (missing_keys.length < 1) { + _converse.log("No missing prekeys to generate for our own device", Strophe.LogLevel.WARN); + + return Promise.resolve(); + } + + return Promise.all(_.map(missing_keys, id => libsignal.KeyHelper.generatePreKey(parseInt(id, 10)))).then(keys => { + _.forEach(keys, k => this.storePreKey(k.keyId, k.keyPair)); + + const marshalled_keys = _.map(this.getPreKeys(), k => ({ + 'id': k.keyId, + 'key': u.arrayBufferToBase64(k.pubKey) + })), + devicelist = _converse.devicelists.get(_converse.bare_jid), + device = devicelist.devices.get(this.get('device_id')); + + return device.getBundle().then(bundle => device.save('bundle', _.extend(bundle, { + 'prekeys': marshalled_keys + }))); + }); + }, + + async generateBundle() { + /* The first thing that needs to happen if a client wants to + * start using OMEMO is they need to generate an IdentityKey + * and a Device ID. The IdentityKey is a Curve25519 [6] + * public/private Key pair. The Device ID is a randomly + * generated integer between 1 and 2^31 - 1. + */ + const identity_keypair = await libsignal.KeyHelper.generateIdentityKeyPair(); + const bundle = {}, + identity_key = u.arrayBufferToBase64(identity_keypair.pubKey), + device_id = generateDeviceID(); + bundle['identity_key'] = identity_key; + bundle['device_id'] = device_id; + this.save({ + 'device_id': device_id, + 'identity_keypair': { + 'privKey': u.arrayBufferToBase64(identity_keypair.privKey), + 'pubKey': identity_key + }, + 'identity_key': identity_key + }); + const signed_prekey = await libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 0); + + _converse.omemo_store.storeSignedPreKey(signed_prekey); + + bundle['signed_prekey'] = { + 'id': signed_prekey.keyId, + 'public_key': u.arrayBufferToBase64(signed_prekey.keyPair.privKey), + 'signature': u.arrayBufferToBase64(signed_prekey.signature) + }; + const keys = await Promise.all(_.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id))); + + _.forEach(keys, k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair)); + + const devicelist = _converse.devicelists.get(_converse.bare_jid), + device = devicelist.devices.create({ + 'id': bundle.device_id, + 'jid': _converse.bare_jid + }), + marshalled_keys = _.map(keys, k => ({ + 'id': k.keyId, + 'key': u.arrayBufferToBase64(k.keyPair.pubKey) + })); + + bundle['prekeys'] = marshalled_keys; + device.save('bundle', bundle); + }, + + fetchSession() { + if (_.isUndefined(this._setup_promise)) { + this._setup_promise = new Promise((resolve, reject) => { + this.fetch({ + 'success': () => { + if (!_converse.omemo_store.get('device_id')) { + this.generateBundle().then(resolve).catch(resolve); + } else { + resolve(); + } + }, + 'error': () => { + this.generateBundle().then(resolve).catch(resolve); + } + }); + }); + } + + return this._setup_promise; + } + + }); + _converse.Device = Backbone.Model.extend({ + defaults: { + 'trusted': UNDECIDED + }, + + getRandomPreKey() { + // XXX: assumes that the bundle has already been fetched + const bundle = this.get('bundle'); + return bundle.prekeys[u.getRandomInt(bundle.prekeys.length)]; + }, + + fetchBundleFromServer() { + const stanza = $iq({ + 'type': 'get', + 'from': _converse.bare_jid, + 'to': this.get('jid') + }).c('pubsub', { + 'xmlns': Strophe.NS.PUBSUB + }).c('items', { + 'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}` + }); + return _converse.api.sendIQ(stanza).then(iq => { + const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(), + bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(), + bundle = parseBundle(bundle_el); + this.save('bundle', bundle); + return bundle; + }).catch(iq => { + _converse.log(iq.outerHTML, Strophe.LogLevel.ERROR); + }); + }, + + getBundle() { + /* Fetch and save the bundle information associated with + * this device, if the information is not at hand already. + */ + if (this.get('bundle')) { + return Promise.resolve(this.get('bundle'), this); + } else { + return this.fetchBundleFromServer(); + } + } + + }); + _converse.Devices = Backbone.Collection.extend({ + model: _converse.Device + }); + _converse.DeviceList = Backbone.Model.extend({ + idAttribute: 'jid', + + initialize() { + this.devices = new _converse.Devices(); + const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`; + this.devices.browserStorage = new Backbone.BrowserStorage.session(id); + this.fetchDevices(); + }, + + fetchDevices() { + if (_.isUndefined(this._devices_promise)) { + this._devices_promise = new Promise((resolve, reject) => { + this.devices.fetch({ + 'success': collection => { + if (collection.length === 0) { + this.fetchDevicesFromServer().then(ids => this.publishCurrentDevice(ids)).finally(resolve); + } else { + resolve(); + } + } + }); + }); + } + + return this._devices_promise; + }, + + async publishCurrentDevice(device_ids) { + if (this.get('jid') !== _converse.bare_jid) { + // We only publish for ourselves. + return; + } + + await restoreOMEMOSession(); + + let device_id = _converse.omemo_store.get('device_id'); + + if (!this.devices.findWhere({ + 'id': device_id + })) { + // Generate a new bundle if we cannot find our device + await _converse.omemo_store.generateBundle(); + device_id = _converse.omemo_store.get('device_id'); + } + + if (!_.includes(device_ids, device_id)) { + return this.publishDevices(); + } + }, + + fetchDevicesFromServer() { + const stanza = $iq({ + 'type': 'get', + 'from': _converse.bare_jid, + 'to': this.get('jid') + }).c('pubsub', { + 'xmlns': Strophe.NS.PUBSUB + }).c('items', { + 'node': Strophe.NS.OMEMO_DEVICELIST + }); + return _converse.api.sendIQ(stanza).then(iq => { + const device_ids = _.map(sizzle(`list[xmlns="${Strophe.NS.OMEMO}"] device`, iq), dev => dev.getAttribute('id')); + + _.forEach(device_ids, id => this.devices.create({ + 'id': id, + 'jid': this.get('jid') + })); + + return device_ids; + }); + }, + + publishDevices() { + const stanza = $iq({ + 'from': _converse.bare_jid, + 'type': 'set' + }).c('pubsub', { + 'xmlns': Strophe.NS.PUBSUB + }).c('publish', { + 'node': Strophe.NS.OMEMO_DEVICELIST + }).c('item').c('list', { + 'xmlns': Strophe.NS.OMEMO + }); + this.devices.each(device => stanza.c('device', { + 'id': device.get('id') + }).up()); + return _converse.api.sendIQ(stanza); + }, + + removeOwnDevices(device_ids) { + if (this.get('jid') !== _converse.bare_jid) { + throw new Error("Cannot remove devices from someone else's device list"); + } + + _.forEach(device_ids, device_id => this.devices.get(device_id).destroy()); + + return this.publishDevices(); + } + + }); + _converse.DeviceLists = Backbone.Collection.extend({ + model: _converse.DeviceList + }); + + function fetchDeviceLists() { + return new Promise((resolve, reject) => _converse.devicelists.fetch({ + 'success': resolve + })); + } + + function fetchOwnDevices() { + return fetchDeviceLists().then(() => { + let own_devicelist = _converse.devicelists.get(_converse.bare_jid); + + if (_.isNil(own_devicelist)) { + own_devicelist = _converse.devicelists.create({ + 'jid': _converse.bare_jid + }); + } + + return own_devicelist.fetchDevices(); + }); + } + + function updateBundleFromStanza(stanza) { + const items_el = sizzle(`items`, stanza).pop(); + + if (!items_el || !items_el.getAttribute('node').startsWith(Strophe.NS.OMEMO_BUNDLES)) { + return; + } + + const device_id = items_el.getAttribute('node').split(':')[1], + jid = stanza.getAttribute('from'), + bundle_el = sizzle(`item > bundle`, items_el).pop(), + devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ + 'jid': jid + }), + device = devicelist.devices.get(device_id) || devicelist.devices.create({ + 'id': device_id, + 'jid': jid + }); + + device.save({ + 'bundle': parseBundle(bundle_el) + }); + } + + function updateDevicesFromStanza(stanza) { + const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_DEVICELIST}"]`, stanza).pop(); + + if (!items_el) { + return; + } + + const device_ids = _.map(sizzle(`item list[xmlns="${Strophe.NS.OMEMO}"] device`, items_el), device => device.getAttribute('id')); + + const jid = stanza.getAttribute('from'), + devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ + 'jid': jid + }), + devices = devicelist.devices, + removed_ids = _.difference(devices.pluck('id'), device_ids); + + _.forEach(removed_ids, id => { + if (jid === _converse.bare_jid && id === _converse.omemo_store.get('device_id')) { + // We don't remove the current device + return; + } + + devices.get(id).destroy(); + }); + + _.forEach(device_ids, device_id => { + if (!devices.get(device_id)) { + devices.create({ + 'id': device_id, + 'jid': jid + }); + } + }); + + if (Strophe.getBareJidFromJid(jid) === _converse.bare_jid) { + // Make sure our own device is on the list (i.e. if it was + // removed, add it again. + _converse.devicelists.get(_converse.bare_jid).publishCurrentDevice(device_ids); + } + } + + function registerPEPPushHandler() { + // Add a handler for devices pushed from other connected clients + _converse.connection.addHandler(message => { + try { + if (sizzle(`event[xmlns="${Strophe.NS.PUBSUB}#event"]`, message).length) { + updateDevicesFromStanza(message); + updateBundleFromStanza(message); + } + } catch (e) { + _converse.log(e.message, Strophe.LogLevel.ERROR); + } + + return true; + }, null, 'message', 'headline'); + } + + function restoreOMEMOSession() { + if (_.isUndefined(_converse.omemo_store)) { + const storage = _converse.config.get('storage'), + id = `converse.omemosession-${_converse.bare_jid}`; + + _converse.omemo_store = new _converse.OMEMOStore({ + 'id': id + }); + _converse.omemo_store.browserStorage = new Backbone.BrowserStorage[storage](id); + } + + return _converse.omemo_store.fetchSession(); + } + + function initOMEMO() { + if (!_converse.config.get('trusted')) { + return; + } + + _converse.devicelists = new _converse.DeviceLists(); + + const storage = _converse.config.get('storage'), + id = `converse.devicelists-${_converse.bare_jid}`; + + _converse.devicelists.browserStorage = new Backbone.BrowserStorage[storage](id); + fetchOwnDevices().then(() => restoreOMEMOSession()).then(() => _converse.omemo_store.publishBundle()).then(() => _converse.emit('OMEMOInitialized')).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + } + + _converse.api.listen.on('afterTearDown', () => { + if (_converse.devicelists) { + _converse.devicelists.reset(); + } + + delete _converse.omemo_store; + }); + + _converse.api.listen.on('connected', registerPEPPushHandler); + + _converse.api.listen.on('renderToolbar', view => view.renderOMEMOToolbarButton()); + + _converse.api.listen.on('statusInitialized', initOMEMO); + + _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(`${Strophe.NS.OMEMO_DEVICELIST}+notify`)); + + _converse.api.listen.on('userDetailsModalInitialized', contact => { + const jid = contact.get('jid'); + + _converse.generateFingerprints(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }); + + _converse.api.listen.on('profileModalInitialized', contact => { + _converse.generateFingerprints(_converse.bare_jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }); + } + }); /***/ }), @@ -66199,10 +66376,29 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!*********************************!*\ !*** ./src/converse-profile.js ***! \*********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_vcard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-vcard */ "./src/headless/converse-vcard.js"); +/* harmony import */ var converse_modal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(formdata_polyfill__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"); +/* harmony import */ var bootstrap__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(bootstrap__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_alert_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/alert.html */ "./src/templates/alert.html"); +/* harmony import */ var templates_alert_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_alert_html__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var templates_chat_status_modal_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/chat_status_modal.html */ "./src/templates/chat_status_modal.html"); +/* harmony import */ var templates_chat_status_modal_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_chat_status_modal_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_profile_modal_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/profile_modal.html */ "./src/templates/profile_modal.html"); +/* harmony import */ var templates_profile_modal_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_profile_modal_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var templates_profile_view_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/profile_view.html */ "./src/templates/profile_view.html"); +/* harmony import */ var templates_profile_view_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_profile_view_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var templates_status_option_html__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! templates/status_option.html */ "./src/templates/status_option.html"); +/* harmony import */ var templates_status_option_html__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(templates_status_option_html__WEBPACK_IMPORTED_MODULE_9__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2013-2017, Jan-Carel Brand @@ -66210,261 +66406,262 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ // /*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! bootstrap */ "./node_modules/bootstrap.native/dist/bootstrap-native-v4.js"), __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"), __webpack_require__(/*! templates/alert.html */ "./src/templates/alert.html"), __webpack_require__(/*! templates/chat_status_modal.html */ "./src/templates/chat_status_modal.html"), __webpack_require__(/*! templates/profile_modal.html */ "./src/templates/profile_modal.html"), __webpack_require__(/*! templates/profile_view.html */ "./src/templates/profile_view.html"), __webpack_require__(/*! templates/status_option.html */ "./src/templates/status_option.html"), __webpack_require__(/*! @converse/headless/converse-vcard */ "./src/headless/converse-vcard.js"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, bootstrap, _FormData, tpl_alert, tpl_chat_status_modal, tpl_profile_modal, tpl_profile_view, tpl_status_option) { - "use strict"; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - utils = _converse$env.utils, - _ = _converse$env._, - moment = _converse$env.moment; - const u = converse.env.utils; - converse.plugins.add('converse-profile', { - dependencies: ["converse-modal", "converse-vcard", "converse-chatboxviews"], - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; - _converse.ProfileModal = _converse.BootstrapModal.extend({ - events: { - 'click .change-avatar': "openFileSelection", - 'change input[type="file"': "updateFilePreview", - 'submit .profile-form': 'onFormSubmitted' - }, - initialize() { - this.model.on('change', this.render, this); - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - _converse.emit('profileModalInitialized', this.model); - }, - toHTML() { - return tpl_profile_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { - '_': _, - '__': __, - '_converse': _converse, - 'alt_avatar': __('Your avatar image'), - 'heading_profile': __('Your Profile'), - 'label_close': __('Close'), - 'label_email': __('Email'), - 'label_fullname': __('Full Name'), - 'label_jid': __('XMPP Address (JID)'), - 'label_nickname': __('Nickname'), - 'label_role': __('Role'), - 'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'), - 'label_url': __('URL'), - 'utils': u, - 'view': this - })); - }, - afterRender() { - this.tabs = _.map(this.el.querySelectorAll('.nav-item'), tab => new bootstrap.Tab(tab)); - }, - openFileSelection(ev) { - ev.preventDefault(); - this.el.querySelector('input[type="file"]').click(); - }, - updateFilePreview(ev) { - const file = ev.target.files[0], - reader = new FileReader(); - reader.onloadend = () => { - this.el.querySelector('.avatar').setAttribute('src', reader.result); - }; +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].env, + Strophe = _converse$env.Strophe, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + utils = _converse$env.utils, + _ = _converse$env._, + moment = _converse$env.moment; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins.add('converse-profile', { + dependencies: ["converse-modal", "converse-vcard", "converse-chatboxviews"], - reader.readAsDataURL(file); - }, + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + _converse.ProfileModal = _converse.BootstrapModal.extend({ + events: { + 'click .change-avatar': "openFileSelection", + 'change input[type="file"': "updateFilePreview", + 'submit .profile-form': 'onFormSubmitted' + }, - setVCard(data) { - _converse.api.vcard.set(_converse.bare_jid, data).then(() => _converse.api.vcard.update(this.model.vcard, true)).catch(err => { - _converse.log(err, Strophe.LogLevel.FATAL); + initialize() { + this.model.on('change', this.render, this); - _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, an error happened while trying to save your profile data."), __("You can check your browser's developer console for any error output.")]); + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + + _converse.emit('profileModalInitialized', this.model); + }, + + toHTML() { + return templates_profile_modal_html__WEBPACK_IMPORTED_MODULE_7___default()(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { + '_': _, + '__': __, + '_converse': _converse, + 'alt_avatar': __('Your avatar image'), + 'heading_profile': __('Your Profile'), + 'label_close': __('Close'), + 'label_email': __('Email'), + 'label_fullname': __('Full Name'), + 'label_jid': __('XMPP Address (JID)'), + 'label_nickname': __('Nickname'), + 'label_role': __('Role'), + 'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'), + 'label_url': __('URL'), + 'utils': u, + 'view': this + })); + }, + + afterRender() { + this.tabs = _.map(this.el.querySelectorAll('.nav-item'), tab => new bootstrap__WEBPACK_IMPORTED_MODULE_3___default.a.Tab(tab)); + }, + + openFileSelection(ev) { + ev.preventDefault(); + this.el.querySelector('input[type="file"]').click(); + }, + + updateFilePreview(ev) { + const file = ev.target.files[0], + reader = new FileReader(); + + reader.onloadend = () => { + this.el.querySelector('.avatar').setAttribute('src', reader.result); + }; + + reader.readAsDataURL(file); + }, + + setVCard(data) { + _converse.api.vcard.set(_converse.bare_jid, data).then(() => _converse.api.vcard.update(this.model.vcard, true)).catch(err => { + _converse.log(err, Strophe.LogLevel.FATAL); + + _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, an error happened while trying to save your profile data."), __("You can check your browser's developer console for any error output.")]); + }); + + this.modal.hide(); + }, + + onFormSubmitted(ev) { + ev.preventDefault(); + const reader = new FileReader(), + form_data = new FormData(ev.target), + image_file = form_data.get('image'); + const data = { + 'fn': form_data.get('fn'), + 'nickname': form_data.get('nickname'), + 'role': form_data.get('role'), + 'email': form_data.get('email'), + 'url': form_data.get('url') + }; + + if (!image_file.size) { + _.extend(data, { + 'image': this.model.vcard.get('image'), + 'image_type': this.model.vcard.get('image_type') }); - this.modal.hide(); - }, - - onFormSubmitted(ev) { - ev.preventDefault(); - const reader = new FileReader(), - form_data = new FormData(ev.target), - image_file = form_data.get('image'); - const data = { - 'fn': form_data.get('fn'), - 'nickname': form_data.get('nickname'), - 'role': form_data.get('role'), - 'email': form_data.get('email'), - 'url': form_data.get('url') - }; - - if (!image_file.size) { + this.setVCard(data); + } else { + reader.onloadend = () => { _.extend(data, { - 'image': this.model.vcard.get('image'), - 'image_type': this.model.vcard.get('image_type') + 'image': btoa(reader.result), + 'image_type': image_file.type }); this.setVCard(data); - } else { - reader.onloadend = () => { - _.extend(data, { - 'image': btoa(reader.result), - 'image_type': image_file.type - }); + }; - this.setVCard(data); - }; + reader.readAsBinaryString(image_file); + } + } - reader.readAsBinaryString(image_file); - } + }); + _converse.ChatStatusModal = _converse.BootstrapModal.extend({ + events: { + "submit form#set-xmpp-status": "onFormSubmitted", + "click .clear-input": "clearStatusMessage" + }, + + toHTML() { + return templates_chat_status_modal_html__WEBPACK_IMPORTED_MODULE_6___default()(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { + 'label_away': __('Away'), + 'label_close': __('Close'), + 'label_busy': __('Busy'), + 'label_cancel': __('Cancel'), + 'label_custom_status': __('Custom status'), + 'label_offline': __('Offline'), + 'label_online': __('Online'), + 'label_save': __('Save'), + 'label_xa': __('Away for long'), + 'modal_title': __('Change chat status'), + 'placeholder_status_message': __('Personal status message') + })); + }, + + afterRender() { + this.el.addEventListener('shown.bs.modal', () => { + this.el.querySelector('input[name="status_message"]').focus(); + }, false); + }, + + clearStatusMessage(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + u.hideElement(this.el.querySelector('.clear-input')); } - }); - _converse.ChatStatusModal = _converse.BootstrapModal.extend({ - events: { - "submit form#set-xmpp-status": "onFormSubmitted", - "click .clear-input": "clearStatusMessage" - }, + const roster_filter = this.el.querySelector('input[name="status_message"]'); + roster_filter.value = ''; + }, - toHTML() { - return tpl_chat_status_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { - 'label_away': __('Away'), - 'label_close': __('Close'), - 'label_busy': __('Busy'), - 'label_cancel': __('Cancel'), - 'label_custom_status': __('Custom status'), - 'label_offline': __('Offline'), - 'label_online': __('Online'), - 'label_save': __('Save'), - 'label_xa': __('Away for long'), - 'modal_title': __('Change chat status'), - 'placeholder_status_message': __('Personal status message') - })); - }, + onFormSubmitted(ev) { + ev.preventDefault(); + const data = new FormData(ev.target); + this.model.save({ + 'status_message': data.get('status_message'), + 'status': data.get('chat_status') + }); + this.modal.hide(); + } - afterRender() { - this.el.addEventListener('shown.bs.modal', () => { - this.el.querySelector('input[name="status_message"]').focus(); - }, false); - }, + }); + _converse.XMPPStatusView = _converse.VDOMViewWithAvatar.extend({ + tagName: "div", + events: { + "click a.show-profile": "showProfileModal", + "click a.change-status": "showStatusChangeModal", + "click .logout": "logOut" + }, - clearStatusMessage(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - u.hideElement(this.el.querySelector('.clear-input')); - } + initialize() { + this.model.on("change", this.render, this); + this.model.vcard.on("change", this.render, this); + }, - const roster_filter = this.el.querySelector('input[name="status_message"]'); - roster_filter.value = ''; - }, + toHTML() { + const chat_status = this.model.get('status') || 'offline'; + return templates_profile_view_html__WEBPACK_IMPORTED_MODULE_8___default()(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { + '__': __, + 'fullname': this.model.vcard.get('fullname') || _converse.bare_jid, + 'status_message': this.model.get('status_message') || __("I am %1$s", this.getPrettyStatus(chat_status)), + 'chat_status': chat_status, + '_converse': _converse, + 'title_change_settings': __('Change settings'), + 'title_change_status': __('Click to change your chat status'), + 'title_log_out': __('Log out'), + 'title_your_profile': __('Your profile') + })); + }, - onFormSubmitted(ev) { - ev.preventDefault(); - const data = new FormData(ev.target); - this.model.save({ - 'status_message': data.get('status_message'), - 'status': data.get('chat_status') + afterRender() { + this.renderAvatar(); + }, + + showProfileModal(ev) { + if (_.isUndefined(this.profile_modal)) { + this.profile_modal = new _converse.ProfileModal({ + model: this.model }); - this.modal.hide(); } - }); - _converse.XMPPStatusView = _converse.VDOMViewWithAvatar.extend({ - tagName: "div", - events: { - "click a.show-profile": "showProfileModal", - "click a.change-status": "showStatusChangeModal", - "click .logout": "logOut" - }, + this.profile_modal.show(ev); + }, - initialize() { - this.model.on("change", this.render, this); - this.model.vcard.on("change", this.render, this); - }, - - toHTML() { - const chat_status = this.model.get('status') || 'offline'; - return tpl_profile_view(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), { - '__': __, - 'fullname': this.model.vcard.get('fullname') || _converse.bare_jid, - 'status_message': this.model.get('status_message') || __("I am %1$s", this.getPrettyStatus(chat_status)), - 'chat_status': chat_status, - '_converse': _converse, - 'title_change_settings': __('Change settings'), - 'title_change_status': __('Click to change your chat status'), - 'title_log_out': __('Log out'), - 'title_your_profile': __('Your profile') - })); - }, - - afterRender() { - this.renderAvatar(); - }, - - showProfileModal(ev) { - if (_.isUndefined(this.profile_modal)) { - this.profile_modal = new _converse.ProfileModal({ - model: this.model - }); - } - - this.profile_modal.show(ev); - }, - - showStatusChangeModal(ev) { - if (_.isUndefined(this.status_modal)) { - this.status_modal = new _converse.ChatStatusModal({ - model: this.model - }); - } - - this.status_modal.show(ev); - }, - - logOut(ev) { - ev.preventDefault(); - const result = confirm(__("Are you sure you want to log out?")); - - if (result === true) { - _converse.logOut(); - } - }, - - getPrettyStatus(stat) { - if (stat === 'chat') { - return __('online'); - } else if (stat === 'dnd') { - return __('busy'); - } else if (stat === 'xa') { - return __('away for long'); - } else if (stat === 'away') { - return __('away'); - } else if (stat === 'offline') { - return __('offline'); - } else { - return __(stat) || __('online'); - } + showStatusChangeModal(ev) { + if (_.isUndefined(this.status_modal)) { + this.status_modal = new _converse.ChatStatusModal({ + model: this.model + }); } - }); - } + this.status_modal.show(ev); + }, + + logOut(ev) { + ev.preventDefault(); + const result = confirm(__("Are you sure you want to log out?")); + + if (result === true) { + _converse.logOut(); + } + }, + + getPrettyStatus(stat) { + if (stat === 'chat') { + return __('online'); + } else if (stat === 'dnd') { + return __('busy'); + } else if (stat === 'xa') { + return __('away for long'); + } else if (stat === 'away') { + return __('away'); + } else if (stat === 'offline') { + return __('offline'); + } else { + return __(stat) || __('online'); + } + } + + }); + } - }); }); /***/ }), @@ -66473,10 +66670,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!******************************!*\ !*** ./src/converse-push.js ***! \******************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js // https://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers @@ -66485,159 +66685,151 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /* This is a Converse.js plugin which add support for registering * an "App Server" as defined in XEP-0357 */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - "use strict"; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - $iq = _converse$env.$iq, - _ = _converse$env._; - Strophe.addNamespace('PUSH', 'urn:xmpp:push:0'); - converse.plugins.add('converse-push', { - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq, + _ = _converse$env._; +Strophe.addNamespace('PUSH', 'urn:xmpp:push:0'); +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-push', { + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; - _converse.api.settings.update({ - 'push_app_servers': [], - 'enable_muc_push': false + _converse.api.settings.update({ + 'push_app_servers': [], + 'enable_muc_push': false + }); + + async function disablePushAppServer(domain, push_app_server) { + if (!push_app_server.jid) { + return; + } + + const result = await _converse.api.disco.supports(Strophe.NS.PUSH, domain || _converse.bare_jid); + + if (!result.length) { + return _converse.log(`Not disabling push app server "${push_app_server.jid}", no disco support from your server.`, Strophe.LogLevel.WARN); + } + + const stanza = $iq({ + 'type': 'set' }); - async function disablePushAppServer(domain, push_app_server) { - if (!push_app_server.jid) { - return; - } - - const result = await _converse.api.disco.supports(Strophe.NS.PUSH, domain || _converse.bare_jid); - - if (!result.length) { - return _converse.log(`Not disabling push app server "${push_app_server.jid}", no disco support from your server.`, Strophe.LogLevel.WARN); - } - - const stanza = $iq({ - 'type': 'set' - }); - - if (domain !== _converse.bare_jid) { - stanza.attrs({ - 'to': domain - }); - } - - stanza.c('disable', { - 'xmlns': Strophe.NS.PUSH, - 'jid': push_app_server.jid - }); - - if (push_app_server.node) { - stanza.attrs({ - 'node': push_app_server.node - }); - } - - _converse.api.sendIQ(stanza).catch(e => { - _converse.log(`Could not disable push app server for ${push_app_server.jid}`, Strophe.LogLevel.ERROR); - - _converse.log(e, Strophe.LogLevel.ERROR); + if (domain !== _converse.bare_jid) { + stanza.attrs({ + 'to': domain }); } - async function enablePushAppServer(domain, push_app_server) { - if (!push_app_server.jid || !push_app_server.node) { - return; - } + stanza.c('disable', { + 'xmlns': Strophe.NS.PUSH, + 'jid': push_app_server.jid + }); - const identity = await _converse.api.disco.getIdentity('pubsub', 'push', push_app_server.jid); - - if (!identity) { - return _converse.log(`Not enabling push the service "${push_app_server.jid}", it doesn't have the right disco identtiy.`, Strophe.LogLevel.WARN); - } - - const result = await Promise.all([_converse.api.disco.supports(Strophe.NS.PUSH, push_app_server.jid), _converse.api.disco.supports(Strophe.NS.PUSH, domain)]); - - if (!result[0].length && !result[1].length) { - return _converse.log(`Not enabling push app server "${push_app_server.jid}", no disco support from your server.`, Strophe.LogLevel.WARN); - } - - const stanza = $iq({ - 'type': 'set' - }); - - if (domain !== _converse.bare_jid) { - stanza.attrs({ - 'to': domain - }); - } - - stanza.c('enable', { - 'xmlns': Strophe.NS.PUSH, - 'jid': push_app_server.jid, + if (push_app_server.node) { + stanza.attrs({ 'node': push_app_server.node }); - - if (push_app_server.secret) { - stanza.c('x', { - 'xmlns': Strophe.NS.XFORM, - 'type': 'submit' - }).c('field', { - 'var': 'FORM_TYPE' - }).c('value').t(`${Strophe.NS.PUBSUB}#publish-options`).up().up().c('field', { - 'var': 'secret' - }).c('value').t(push_app_server.secret); - } - - return _converse.api.sendIQ(stanza); } - async function enablePush(domain) { - domain = domain || _converse.bare_jid; - const push_enabled = _converse.session.get('push_enabled') || []; + _converse.api.sendIQ(stanza).catch(e => { + _converse.log(`Could not disable push app server for ${push_app_server.jid}`, Strophe.LogLevel.ERROR); - if (_.includes(push_enabled, domain)) { - return; - } + _converse.log(e, Strophe.LogLevel.ERROR); + }); + } - const enabled_services = _.reject(_converse.push_app_servers, 'disable'); - - try { - await Promise.all(_.map(enabled_services, _.partial(enablePushAppServer, domain))); - } catch (e) { - _converse.log('Could not enable push App Server', Strophe.LogLevel.ERROR); - - if (e) _converse.log(e, Strophe.LogLevel.ERROR); - } finally { - push_enabled.push(domain); - } - - const disabled_services = _.filter(_converse.push_app_servers, 'disable'); - - _.each(disabled_services, _.partial(disablePushAppServer, domain)); - - _converse.session.save('push_enabled', push_enabled); + async function enablePushAppServer(domain, push_app_server) { + if (!push_app_server.jid || !push_app_server.node) { + return; } - _converse.api.listen.on('statusInitialized', () => enablePush()); + const identity = await _converse.api.disco.getIdentity('pubsub', 'push', push_app_server.jid); - function onChatBoxAdded(model) { - if (model.get('type') == _converse.CHATROOMS_TYPE) { - enablePush(Strophe.getDomainFromJid(model.get('jid'))); - } + if (!identity) { + return _converse.log(`Not enabling push the service "${push_app_server.jid}", it doesn't have the right disco identtiy.`, Strophe.LogLevel.WARN); } - if (_converse.enable_muc_push) { - _converse.api.listen.on('chatBoxesInitialized', () => _converse.chatboxes.on('add', onChatBoxAdded)); + const result = await Promise.all([_converse.api.disco.supports(Strophe.NS.PUSH, push_app_server.jid), _converse.api.disco.supports(Strophe.NS.PUSH, domain)]); + + if (!result[0].length && !result[1].length) { + return _converse.log(`Not enabling push app server "${push_app_server.jid}", no disco support from your server.`, Strophe.LogLevel.WARN); + } + + const stanza = $iq({ + 'type': 'set' + }); + + if (domain !== _converse.bare_jid) { + stanza.attrs({ + 'to': domain + }); + } + + stanza.c('enable', { + 'xmlns': Strophe.NS.PUSH, + 'jid': push_app_server.jid, + 'node': push_app_server.node + }); + + if (push_app_server.secret) { + stanza.c('x', { + 'xmlns': Strophe.NS.XFORM, + 'type': 'submit' + }).c('field', { + 'var': 'FORM_TYPE' + }).c('value').t(`${Strophe.NS.PUBSUB}#publish-options`).up().up().c('field', { + 'var': 'secret' + }).c('value').t(push_app_server.secret); + } + + return _converse.api.sendIQ(stanza); + } + + async function enablePush(domain) { + domain = domain || _converse.bare_jid; + const push_enabled = _converse.session.get('push_enabled') || []; + + if (_.includes(push_enabled, domain)) { + return; + } + + const enabled_services = _.reject(_converse.push_app_servers, 'disable'); + + try { + await Promise.all(_.map(enabled_services, _.partial(enablePushAppServer, domain))); + } catch (e) { + _converse.log('Could not enable push App Server', Strophe.LogLevel.ERROR); + + if (e) _converse.log(e, Strophe.LogLevel.ERROR); + } finally { + push_enabled.push(domain); + } + + const disabled_services = _.filter(_converse.push_app_servers, 'disable'); + + _.each(disabled_services, _.partial(disablePushAppServer, domain)); + + _converse.session.save('push_enabled', push_enabled); + } + + _converse.api.listen.on('statusInitialized', () => enablePush()); + + function onChatBoxAdded(model) { + if (model.get('type') == _converse.CHATROOMS_TYPE) { + enablePush(Strophe.getDomainFromJid(model.get('jid'))); } } - }); + if (_converse.enable_muc_push) { + _converse.api.listen.on('chatBoxesInitialized', () => _converse.chatboxes.on('add', onChatBoxAdded)); + } + } + }); /***/ }), @@ -66646,10 +66838,29 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**********************************!*\ !*** ./src/converse-register.js ***! \**********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_controlbox__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_form_input_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! templates/form_input.html */ "./src/templates/form_input.html"); +/* harmony import */ var templates_form_input_html__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(templates_form_input_html__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var templates_form_username_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/form_username.html */ "./src/templates/form_username.html"); +/* harmony import */ var templates_form_username_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_form_username_html__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var templates_register_link_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! templates/register_link.html */ "./src/templates/register_link.html"); +/* harmony import */ var templates_register_link_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(templates_register_link_html__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var templates_register_panel_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/register_panel.html */ "./src/templates/register_panel.html"); +/* harmony import */ var templates_register_panel_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_register_panel_html__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var templates_registration_form_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/registration_form.html */ "./src/templates/registration_form.html"); +/* harmony import */ var templates_registration_form_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_registration_form_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_registration_request_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/registration_request.html */ "./src/templates/registration_request.html"); +/* harmony import */ var templates_registration_request_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_registration_request_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var templates_spinner_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"); +/* harmony import */ var templates_spinner_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_spinner_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var utils_form__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! utils/form */ "./src/headless/utils/form.js"); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand @@ -66661,734 +66872,736 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /* This is a Converse.js plugin which add support for in-band registration * as specified in XEP-0077. */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! utils/form */ "./src/headless/utils/form.js"), __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! templates/form_username.html */ "./src/templates/form_username.html"), __webpack_require__(/*! templates/register_link.html */ "./src/templates/register_link.html"), __webpack_require__(/*! templates/register_panel.html */ "./src/templates/register_panel.html"), __webpack_require__(/*! templates/registration_form.html */ "./src/templates/registration_form.html"), __webpack_require__(/*! templates/registration_request.html */ "./src/templates/registration_request.html"), __webpack_require__(/*! templates/form_input.html */ "./src/templates/form_input.html"), __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"), __webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (utils, converse, tpl_form_username, tpl_register_link, tpl_register_panel, tpl_registration_form, tpl_registration_request, tpl_form_input, tpl_spinner) { - "use strict"; // Strophe methods for building stanzas - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - Backbone = _converse$env.Backbone, - sizzle = _converse$env.sizzle, - $iq = _converse$env.$iq, - _ = _converse$env._; // Add Strophe Namespaces - Strophe.addNamespace('REGISTER', 'jabber:iq:register'); // Add Strophe Statuses - let i = 0; - _.each(_.keys(Strophe.Status), function (key) { - i = Math.max(i, Strophe.Status[key]); - }); - Strophe.Status.REGIFAIL = i + 1; - Strophe.Status.REGISTERED = i + 2; - Strophe.Status.CONFLICT = i + 3; - Strophe.Status.NOTACCEPTABLE = i + 5; - converse.plugins.add('converse-register', { - 'overrides': { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - LoginPanel: { - insertRegisterLink() { - const _converse = this.__super__._converse; - if (_.isUndefined(this.registerlinkview)) { - this.registerlinkview = new _converse.RegisterLinkView({ - 'model': this.model - }); - this.registerlinkview.render(); - this.el.querySelector('.buttons').insertAdjacentElement('afterend', this.registerlinkview.el); - } + + + // Strophe methods for building stanzas + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env, + Strophe = _converse$env.Strophe, + Backbone = _converse$env.Backbone, + sizzle = _converse$env.sizzle, + $iq = _converse$env.$iq, + _ = _converse$env._; // Add Strophe Namespaces + +Strophe.addNamespace('REGISTER', 'jabber:iq:register'); // Add Strophe Statuses + +let i = 0; + +_.each(_.keys(Strophe.Status), function (key) { + i = Math.max(i, Strophe.Status[key]); +}); + +Strophe.Status.REGIFAIL = i + 1; +Strophe.Status.REGISTERED = i + 2; +Strophe.Status.CONFLICT = i + 3; +Strophe.Status.NOTACCEPTABLE = i + 5; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-register', { + 'overrides': { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + LoginPanel: { + insertRegisterLink() { + const _converse = this.__super__._converse; + + if (_.isUndefined(this.registerlinkview)) { + this.registerlinkview = new _converse.RegisterLinkView({ + 'model': this.model + }); this.registerlinkview.render(); - }, - - render(cfg) { - const _converse = this.__super__._converse; - - this.__super__.render.apply(this, arguments); - - if (_converse.allow_registration && !_converse.auto_login) { - this.insertRegisterLink(); - } - - return this; + this.el.querySelector('.buttons').insertAdjacentElement('afterend', this.registerlinkview.el); } + this.registerlinkview.render(); }, - ControlBoxView: { - initialize() { - this.__super__.initialize.apply(this, arguments); - this.model.on('change:active-form', this.showLoginOrRegisterForm.bind(this)); - }, + render(cfg) { + const _converse = this.__super__._converse; - showLoginOrRegisterForm() { - const _converse = this.__super__._converse; + this.__super__.render.apply(this, arguments); - if (_.isNil(this.registerpanel)) { - return; - } - - if (this.model.get('active-form') == "register") { - this.loginpanel.el.classList.add('hidden'); - this.registerpanel.el.classList.remove('hidden'); - } else { - this.loginpanel.el.classList.remove('hidden'); - this.registerpanel.el.classList.add('hidden'); - } - }, - - renderRegistrationPanel() { - const _converse = this.__super__._converse; - - if (_converse.allow_registration) { - this.registerpanel = new _converse.RegisterPanel({ - 'model': this.model - }); - this.registerpanel.render(); - this.registerpanel.el.classList.add('hidden'); - this.el.querySelector('#converse-login-panel').insertAdjacentElement('afterend', this.registerpanel.el); - this.showLoginOrRegisterForm(); - } - - return this; - }, - - renderLoginPanel() { - /* Also render a registration panel, when rendering the - * login panel. - */ - this.__super__.renderLoginPanel.apply(this, arguments); - - this.renderRegistrationPanel(); - return this; + if (_converse.allow_registration && !_converse.auto_login) { + this.insertRegisterLink(); } + return this; } + }, + ControlBoxView: { + initialize() { + this.__super__.initialize.apply(this, arguments); - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; - _converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL'; - _converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED'; - _converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT'; - _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE'; + this.model.on('change:active-form', this.showLoginOrRegisterForm.bind(this)); + }, - _converse.api.settings.update({ - 'allow_registration': true, - 'domain_placeholder': __(" e.g. conversejs.org"), - // Placeholder text shown in the domain input on the registration form - 'providers_link': 'https://compliance.conversations.im/', - // Link to XMPP providers shown on registration page - 'registration_domain': '' - }); + showLoginOrRegisterForm() { + const _converse = this.__super__._converse; - function setActiveForm(value) { - _converse.api.waitUntil('controlboxInitialized').then(() => { - const controlbox = _converse.chatboxes.get('controlbox'); - - controlbox.set({ - 'active-form': value - }); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - - _converse.router.route('converse/login', _.partial(setActiveForm, 'login')); - - _converse.router.route('converse/register', _.partial(setActiveForm, 'register')); - - _converse.RegisterLinkView = Backbone.VDOMView.extend({ - toHTML() { - return tpl_register_link(_.extend(this.model.toJSON(), { - '__': _converse.__, - '_converse': _converse, - 'connection_status': _converse.connfeedback.get('connection_status') - })); + if (_.isNil(this.registerpanel)) { + return; } - }); - _converse.RegisterPanel = Backbone.NativeView.extend({ - tagName: 'div', - id: "converse-register-panel", - className: 'controlbox-pane fade-in', - events: { - 'submit form#converse-register': 'onFormSubmission', - 'click .button-cancel': 'renderProviderChoiceForm' - }, + if (this.model.get('active-form') == "register") { + this.loginpanel.el.classList.add('hidden'); + this.registerpanel.el.classList.remove('hidden'); + } else { + this.loginpanel.el.classList.remove('hidden'); + this.registerpanel.el.classList.add('hidden'); + } + }, - initialize(cfg) { - this.reset(); - this.registerHooks(); - }, + renderRegistrationPanel() { + const _converse = this.__super__._converse; - render() { - this.model.set('registration_form_rendered', false); - this.el.innerHTML = tpl_register_panel({ - '__': __, - 'default_domain': _converse.registration_domain, - 'label_register': __('Fetch registration form'), - 'help_providers': __('Tip: A list of public XMPP providers is available'), - 'help_providers_link': __('here'), - 'href_providers': _converse.providers_link, - 'domain_placeholder': _converse.domain_placeholder + if (_converse.allow_registration) { + this.registerpanel = new _converse.RegisterPanel({ + 'model': this.model }); + this.registerpanel.render(); + this.registerpanel.el.classList.add('hidden'); + this.el.querySelector('#converse-login-panel').insertAdjacentElement('afterend', this.registerpanel.el); + this.showLoginOrRegisterForm(); + } - if (_converse.registration_domain) { + return this; + }, + + renderLoginPanel() { + /* Also render a registration panel, when rendering the + * login panel. + */ + this.__super__.renderLoginPanel.apply(this, arguments); + + this.renderRegistrationPanel(); + return this; + } + + } + }, + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + _converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL'; + _converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED'; + _converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT'; + _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE'; + + _converse.api.settings.update({ + 'allow_registration': true, + 'domain_placeholder': __(" e.g. conversejs.org"), + // Placeholder text shown in the domain input on the registration form + 'providers_link': 'https://compliance.conversations.im/', + // Link to XMPP providers shown on registration page + 'registration_domain': '' + }); + + function setActiveForm(value) { + _converse.api.waitUntil('controlboxInitialized').then(() => { + const controlbox = _converse.chatboxes.get('controlbox'); + + controlbox.set({ + 'active-form': value + }); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + + _converse.router.route('converse/login', _.partial(setActiveForm, 'login')); + + _converse.router.route('converse/register', _.partial(setActiveForm, 'register')); + + _converse.RegisterLinkView = Backbone.VDOMView.extend({ + toHTML() { + return templates_register_link_html__WEBPACK_IMPORTED_MODULE_4___default()(_.extend(this.model.toJSON(), { + '__': _converse.__, + '_converse': _converse, + 'connection_status': _converse.connfeedback.get('connection_status') + })); + } + + }); + _converse.RegisterPanel = Backbone.NativeView.extend({ + tagName: 'div', + id: "converse-register-panel", + className: 'controlbox-pane fade-in', + events: { + 'submit form#converse-register': 'onFormSubmission', + 'click .button-cancel': 'renderProviderChoiceForm' + }, + + initialize(cfg) { + this.reset(); + this.registerHooks(); + }, + + render() { + this.model.set('registration_form_rendered', false); + this.el.innerHTML = templates_register_panel_html__WEBPACK_IMPORTED_MODULE_5___default()({ + '__': __, + 'default_domain': _converse.registration_domain, + 'label_register': __('Fetch registration form'), + 'help_providers': __('Tip: A list of public XMPP providers is available'), + 'help_providers_link': __('here'), + 'href_providers': _converse.providers_link, + 'domain_placeholder': _converse.domain_placeholder + }); + + if (_converse.registration_domain) { + this.fetchRegistrationForm(_converse.registration_domain); + } + + return this; + }, + + registerHooks() { + /* Hook into Strophe's _connect_cb, so that we can send an IQ + * requesting the registration fields. + */ + const conn = _converse.connection; + + const connect_cb = conn._connect_cb.bind(conn); + + conn._connect_cb = (req, callback, raw) => { + if (!this._registering) { + connect_cb(req, callback, raw); + } else { + if (this.getRegistrationFields(req, callback, raw)) { + this._registering = false; + } + } + }; + }, + + getRegistrationFields(req, _callback, raw) { + /* Send an IQ stanza to the XMPP server asking for the + * registration fields. + * Parameters: + * (Strophe.Request) req - The current request + * (Function) callback + */ + const conn = _converse.connection; + conn.connected = true; + + const body = conn._proto._reqToData(req); + + if (!body) { + return; + } + + if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) { + this.showValidationError(__("Sorry, we're unable to connect to your chosen provider.")); + return false; + } + + const register = body.getElementsByTagName("register"); + const mechanisms = body.getElementsByTagName("mechanism"); + + if (register.length === 0 && mechanisms.length === 0) { + conn._proto._no_auth_received(_callback); + + return false; + } + + if (register.length === 0) { + conn._changeConnectStatus(Strophe.Status.REGIFAIL); + + this.showValidationError(__("Sorry, the given provider does not support in " + "band account registration. Please try with a " + "different provider.")); + return true; + } // Send an IQ stanza to get all required data fields + + + conn._addSysHandler(this.onRegistrationFields.bind(this), null, "iq", null, null); + + const stanza = $iq({ + type: "get" + }).c("query", { + xmlns: Strophe.NS.REGISTER + }).tree(); + stanza.setAttribute("id", conn.getUniqueId("sendIQ")); + conn.send(stanza); + conn.connected = false; + return true; + }, + + onRegistrationFields(stanza) { + /* Handler for Registration Fields Request. + * + * Parameters: + * (XMLElement) elem - The query stanza. + */ + if (stanza.getAttribute("type") === "error") { + _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, __('Something went wrong while establishing a connection with "%1$s". ' + 'Are you sure it exists?', this.domain)); + + return false; + } + + if (stanza.getElementsByTagName("query").length !== 1) { + _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown"); + + return false; + } + + this.setFields(stanza); + + if (!this.model.get('registration_form_rendered')) { + this.renderRegistrationForm(stanza); + } + + return false; + }, + + reset(settings) { + const defaults = { + fields: {}, + urls: [], + title: "", + instructions: "", + registered: false, + _registering: false, + domain: null, + form_type: null + }; + + _.extend(this, defaults); + + if (settings) { + _.extend(this, _.pick(settings, _.keys(defaults))); + } + }, + + onFormSubmission(ev) { + /* Event handler when the #converse-register form is + * submitted. + * + * Depending on the available input fields, we delegate to + * other methods. + */ + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + if (_.isNull(ev.target.querySelector('input[name=domain]'))) { + this.submitRegistrationForm(ev.target); + } else { + this.onProviderChosen(ev.target); + } + }, + + onProviderChosen(form) { + /* Callback method that gets called when the user has chosen an + * XMPP provider. + * + * Parameters: + * (HTMLElement) form - The form that was submitted + */ + const domain_input = form.querySelector('input[name=domain]'), + domain = _.get(domain_input, 'value'); + + if (!domain) { + // TODO: add validation message + domain_input.classList.add('error'); + return; + } + + form.querySelector('input[type=submit]').classList.add('hidden'); + this.fetchRegistrationForm(domain.trim()); + }, + + fetchRegistrationForm(domain_name) { + /* This is called with a domain name based on which, it fetches a + * registration form from the requested domain. + * + * Parameters: + * (String) domain_name - XMPP server domain + */ + if (!this.model.get('registration_form_rendered')) { + this.renderRegistrationRequest(); + } + + this.reset({ + 'domain': Strophe.getDomainFromJid(domain_name), + '_registering': true + }); + + _converse.connection.connect(this.domain, "", this.onConnectStatusChanged.bind(this)); + + return false; + }, + + renderRegistrationRequest() { + /* Clear the form and inform the user that the registration + * form is being fetched. + */ + this.clearRegistrationForm().insertAdjacentHTML('beforeend', templates_registration_request_html__WEBPACK_IMPORTED_MODULE_7___default()({ + '__': _converse.__, + 'cancel': _converse.registration_domain + })); + }, + + giveFeedback(message, klass) { + let feedback = this.el.querySelector('.reg-feedback'); + + if (!_.isNull(feedback)) { + feedback.parentNode.removeChild(feedback); + } + + const form = this.el.querySelector('form'); + form.insertAdjacentHTML('afterbegin', ''); + feedback = form.querySelector('.reg-feedback'); + feedback.textContent = message; + + if (klass) { + feedback.classList.add(klass); + } + }, + + clearRegistrationForm() { + const form = this.el.querySelector('form'); + form.innerHTML = ''; + this.model.set('registration_form_rendered', false); + return form; + }, + + showSpinner() { + const form = this.el.querySelector('form'); + form.innerHTML = templates_spinner_html__WEBPACK_IMPORTED_MODULE_8___default()(); + this.model.set('registration_form_rendered', false); + return this; + }, + + onConnectStatusChanged(status_code) { + /* Callback function called by Strophe whenever the + * connection status changes. + * + * Passed to Strophe specifically during a registration + * attempt. + * + * Parameters: + * (Integer) status_code - The Stroph.Status status code + */ + _converse.log('converse-register: onConnectStatusChanged'); + + if (_.includes([Strophe.Status.DISCONNECTED, Strophe.Status.CONNFAIL, Strophe.Status.REGIFAIL, Strophe.Status.NOTACCEPTABLE, Strophe.Status.CONFLICT], status_code)) { + _converse.log(`Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}`, Strophe.LogLevel.ERROR); + + this.abortRegistration(); + } else if (status_code === Strophe.Status.REGISTERED) { + _converse.log("Registered successfully."); + + _converse.connection.reset(); + + this.showSpinner(); + + if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) { + _converse.router.navigate('', { + 'replace': true + }); + } + + if (this.fields.password && this.fields.username) { + // automatically log the user in + _converse.connection.connect(this.fields.username.toLowerCase() + '@' + this.domain.toLowerCase(), this.fields.password, _converse.onConnectStatusChanged); + + this.giveFeedback(__('Now logging you in'), 'info'); + } else { + _converse.chatboxviews.get('controlbox').renderLoginPanel(); + + _converse.giveFeedback(__('Registered successfully')); + } + + this.reset(); + } + }, + + renderLegacyRegistrationForm(form) { + _.each(_.keys(this.fields), key => { + if (key === "username") { + form.insertAdjacentHTML('beforeend', templates_form_username_html__WEBPACK_IMPORTED_MODULE_3___default()({ + 'domain': ` @${this.domain}`, + 'name': key, + 'type': "text", + 'label': key, + 'value': '', + 'required': true + })); + } else { + form.insertAdjacentHTML('beforeend', templates_form_input_html__WEBPACK_IMPORTED_MODULE_2___default()({ + 'label': key, + 'name': key, + 'placeholder': key, + 'required': true, + 'type': key === 'password' || key === 'email' ? key : "text", + 'value': '' + })); + } + }); // Show urls + + + _.each(this.urls, url => { + form.insertAdjacentHTML('afterend', '' + url + ''); + }); + }, + + renderRegistrationForm(stanza) { + /* Renders the registration form based on the XForm fields + * received from the XMPP server. + * + * Parameters: + * (XMLElement) stanza - The IQ stanza received from the XMPP server. + */ + const form = this.el.querySelector('form'); + form.innerHTML = templates_registration_form_html__WEBPACK_IMPORTED_MODULE_6___default()({ + '__': _converse.__, + 'domain': this.domain, + 'title': this.title, + 'instructions': this.instructions, + 'registration_domain': _converse.registration_domain + }); + const buttons = form.querySelector('fieldset.buttons'); + + if (this.form_type === 'xform') { + _.each(stanza.querySelectorAll('field'), field => { + buttons.insertAdjacentHTML('beforebegin', utils_form__WEBPACK_IMPORTED_MODULE_9__["default"].xForm2webForm(field, stanza, this.domain)); + }); + } else { + this.renderLegacyRegistrationForm(form); + } + + if (!this.fields) { + form.querySelector('.button-primary').classList.add('hidden'); + } + + form.classList.remove('hidden'); + this.model.set('registration_form_rendered', true); + }, + + showValidationError(message) { + const form = this.el.querySelector('form'); + let flash = form.querySelector('.form-errors'); + + if (_.isNull(flash)) { + flash = ''; + const instructions = form.querySelector('p.instructions'); + + if (_.isNull(instructions)) { + form.insertAdjacentHTML('afterbegin', flash); + } else { + instructions.insertAdjacentHTML('afterend', flash); + } + + flash = form.querySelector('.form-errors'); + } else { + flash.innerHTML = ''; + } + + flash.insertAdjacentHTML('beforeend', '

' + message + '

'); + flash.classList.remove('hidden'); + }, + + reportErrors(stanza) { + /* Report back to the user any error messages received from the + * XMPP server after attempted registration. + * + * Parameters: + * (XMLElement) stanza - The IQ stanza received from the + * XMPP server. + */ + const errors = stanza.querySelectorAll('error'); + + _.each(errors, error => { + this.showValidationError(error.textContent); + }); + + if (!errors.length) { + const message = __('The provider rejected your registration attempt. ' + 'Please check the values you entered for correctness.'); + + this.showValidationError(message); + } + }, + + renderProviderChoiceForm(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + _converse.connection._proto._abortAllRequests(); + + _converse.connection.reset(); + + this.render(); + }, + + abortRegistration() { + _converse.connection._proto._abortAllRequests(); + + _converse.connection.reset(); + + if (this.model.get('registration_form_rendered')) { + if (_converse.registration_domain && this.model.get('registration_form_rendered')) { this.fetchRegistrationForm(_converse.registration_domain); } + } else { + this.render(); + } + }, - return this; - }, + submitRegistrationForm(form) { + /* Handler, when the user submits the registration form. + * Provides form error feedback or starts the registration + * process. + * + * Parameters: + * (HTMLElement) form - The HTML form that was submitted + */ + const has_empty_inputs = _.reduce(this.el.querySelectorAll('input.required'), function (result, input) { + if (input.value === '') { + input.classList.add('error'); + return result + 1; + } - registerHooks() { - /* Hook into Strophe's _connect_cb, so that we can send an IQ - * requesting the registration fields. - */ - const conn = _converse.connection; + return result; + }, 0); - const connect_cb = conn._connect_cb.bind(conn); + if (has_empty_inputs) { + return; + } - conn._connect_cb = (req, callback, raw) => { - if (!this._registering) { - connect_cb(req, callback, raw); - } else { - if (this.getRegistrationFields(req, callback, raw)) { - this._registering = false; - } + const inputs = sizzle(':input:not([type=button]):not([type=submit])', form), + iq = $iq({ + 'type': 'set', + 'id': _converse.connection.getUniqueId() + }).c("query", { + xmlns: Strophe.NS.REGISTER + }); + + if (this.form_type === 'xform') { + iq.c("x", { + xmlns: Strophe.NS.XFORM, + type: 'submit' + }); + + _.each(inputs, input => { + iq.cnode(utils_form__WEBPACK_IMPORTED_MODULE_9__["default"].webForm2xForm(input)).up(); + }); + } else { + _.each(inputs, input => { + iq.c(input.getAttribute('name'), {}, input.value); + }); + } + + _converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, "iq", null, null); + + _converse.connection.send(iq); + + this.setFields(iq.tree()); + }, + + setFields(stanza) { + /* Stores the values that will be sent to the XMPP server + * during attempted registration. + * + * Parameters: + * (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server. + */ + const query = stanza.querySelector('query'); + const xform = sizzle(`x[xmlns="${Strophe.NS.XFORM}"]`, query); + + if (xform.length > 0) { + this._setFieldsFromXForm(xform.pop()); + } else { + this._setFieldsFromLegacy(query); + } + }, + + _setFieldsFromLegacy(query) { + _.each(query.children, field => { + if (field.tagName.toLowerCase() === 'instructions') { + this.instructions = Strophe.getText(field); + return; + } else if (field.tagName.toLowerCase() === 'x') { + if (field.getAttribute('xmlns') === 'jabber:x:oob') { + this.urls.concat(_.map(field.querySelectorAll('url'), 'textContent')); } - }; - }, - getRegistrationFields(req, _callback, raw) { - /* Send an IQ stanza to the XMPP server asking for the - * registration fields. - * Parameters: - * (Strophe.Request) req - The current request - * (Function) callback - */ - const conn = _converse.connection; - conn.connected = true; - - const body = conn._proto._reqToData(req); - - if (!body) { return; } - if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) { - this.showValidationError(__("Sorry, we're unable to connect to your chosen provider.")); - return false; + this.fields[field.tagName.toLowerCase()] = Strophe.getText(field); + }); + + this.form_type = 'legacy'; + }, + + _setFieldsFromXForm(xform) { + this.title = _.get(xform.querySelector('title'), 'textContent'); + this.instructions = _.get(xform.querySelector('instructions'), 'textContent'); + + _.each(xform.querySelectorAll('field'), field => { + const _var = field.getAttribute('var'); + + if (_var) { + this.fields[_var.toLowerCase()] = _.get(field.querySelector('value'), 'textContent', ''); + } else { + // TODO: other option seems to be type="fixed" + _converse.log("Found field we couldn't parse", Strophe.LogLevel.WARN); } + }); - const register = body.getElementsByTagName("register"); - const mechanisms = body.getElementsByTagName("mechanism"); + this.form_type = 'xform'; + }, - if (register.length === 0 && mechanisms.length === 0) { - conn._proto._no_auth_received(_callback); + _onRegisterIQ(stanza) { + /* Callback method that gets called when a return IQ stanza + * is received from the XMPP server, after attempting to + * register a new user. + * + * Parameters: + * (XMLElement) stanza - The IQ stanza. + */ + if (stanza.getAttribute("type") === "error") { + _converse.log("Registration failed.", Strophe.LogLevel.ERROR); - return false; - } + this.reportErrors(stanza); + let error = stanza.getElementsByTagName("error"); - if (register.length === 0) { - conn._changeConnectStatus(Strophe.Status.REGIFAIL); - - this.showValidationError(__("Sorry, the given provider does not support in " + "band account registration. Please try with a " + "different provider.")); - return true; - } // Send an IQ stanza to get all required data fields - - - conn._addSysHandler(this.onRegistrationFields.bind(this), null, "iq", null, null); - - const stanza = $iq({ - type: "get" - }).c("query", { - xmlns: Strophe.NS.REGISTER - }).tree(); - stanza.setAttribute("id", conn.getUniqueId("sendIQ")); - conn.send(stanza); - conn.connected = false; - return true; - }, - - onRegistrationFields(stanza) { - /* Handler for Registration Fields Request. - * - * Parameters: - * (XMLElement) elem - The query stanza. - */ - if (stanza.getAttribute("type") === "error") { - _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, __('Something went wrong while establishing a connection with "%1$s". ' + 'Are you sure it exists?', this.domain)); - - return false; - } - - if (stanza.getElementsByTagName("query").length !== 1) { + if (error.length !== 1) { _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown"); return false; } - this.setFields(stanza); + error = error[0].firstChild.tagName.toLowerCase(); - if (!this.model.get('registration_form_rendered')) { - this.renderRegistrationForm(stanza); - } - - return false; - }, - - reset(settings) { - const defaults = { - fields: {}, - urls: [], - title: "", - instructions: "", - registered: false, - _registering: false, - domain: null, - form_type: null - }; - - _.extend(this, defaults); - - if (settings) { - _.extend(this, _.pick(settings, _.keys(defaults))); - } - }, - - onFormSubmission(ev) { - /* Event handler when the #converse-register form is - * submitted. - * - * Depending on the available input fields, we delegate to - * other methods. - */ - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - if (_.isNull(ev.target.querySelector('input[name=domain]'))) { - this.submitRegistrationForm(ev.target); + if (error === 'conflict') { + _converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error); + } else if (error === 'not-acceptable') { + _converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error); } else { - this.onProviderChosen(ev.target); + _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error); } - }, - - onProviderChosen(form) { - /* Callback method that gets called when the user has chosen an - * XMPP provider. - * - * Parameters: - * (HTMLElement) form - The form that was submitted - */ - const domain_input = form.querySelector('input[name=domain]'), - domain = _.get(domain_input, 'value'); - - if (!domain) { - // TODO: add validation message - domain_input.classList.add('error'); - return; - } - - form.querySelector('input[type=submit]').classList.add('hidden'); - this.fetchRegistrationForm(domain.trim()); - }, - - fetchRegistrationForm(domain_name) { - /* This is called with a domain name based on which, it fetches a - * registration form from the requested domain. - * - * Parameters: - * (String) domain_name - XMPP server domain - */ - if (!this.model.get('registration_form_rendered')) { - this.renderRegistrationRequest(); - } - - this.reset({ - 'domain': Strophe.getDomainFromJid(domain_name), - '_registering': true - }); - - _converse.connection.connect(this.domain, "", this.onConnectStatusChanged.bind(this)); - - return false; - }, - - renderRegistrationRequest() { - /* Clear the form and inform the user that the registration - * form is being fetched. - */ - this.clearRegistrationForm().insertAdjacentHTML('beforeend', tpl_registration_request({ - '__': _converse.__, - 'cancel': _converse.registration_domain - })); - }, - - giveFeedback(message, klass) { - let feedback = this.el.querySelector('.reg-feedback'); - - if (!_.isNull(feedback)) { - feedback.parentNode.removeChild(feedback); - } - - const form = this.el.querySelector('form'); - form.insertAdjacentHTML('afterbegin', ''); - feedback = form.querySelector('.reg-feedback'); - feedback.textContent = message; - - if (klass) { - feedback.classList.add(klass); - } - }, - - clearRegistrationForm() { - const form = this.el.querySelector('form'); - form.innerHTML = ''; - this.model.set('registration_form_rendered', false); - return form; - }, - - showSpinner() { - const form = this.el.querySelector('form'); - form.innerHTML = tpl_spinner(); - this.model.set('registration_form_rendered', false); - return this; - }, - - onConnectStatusChanged(status_code) { - /* Callback function called by Strophe whenever the - * connection status changes. - * - * Passed to Strophe specifically during a registration - * attempt. - * - * Parameters: - * (Integer) status_code - The Stroph.Status status code - */ - _converse.log('converse-register: onConnectStatusChanged'); - - if (_.includes([Strophe.Status.DISCONNECTED, Strophe.Status.CONNFAIL, Strophe.Status.REGIFAIL, Strophe.Status.NOTACCEPTABLE, Strophe.Status.CONFLICT], status_code)) { - _converse.log(`Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}`, Strophe.LogLevel.ERROR); - - this.abortRegistration(); - } else if (status_code === Strophe.Status.REGISTERED) { - _converse.log("Registered successfully."); - - _converse.connection.reset(); - - this.showSpinner(); - - if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) { - _converse.router.navigate('', { - 'replace': true - }); - } - - if (this.fields.password && this.fields.username) { - // automatically log the user in - _converse.connection.connect(this.fields.username.toLowerCase() + '@' + this.domain.toLowerCase(), this.fields.password, _converse.onConnectStatusChanged); - - this.giveFeedback(__('Now logging you in'), 'info'); - } else { - _converse.chatboxviews.get('controlbox').renderLoginPanel(); - - _converse.giveFeedback(__('Registered successfully')); - } - - this.reset(); - } - }, - - renderLegacyRegistrationForm(form) { - _.each(_.keys(this.fields), key => { - if (key === "username") { - form.insertAdjacentHTML('beforeend', tpl_form_username({ - 'domain': ` @${this.domain}`, - 'name': key, - 'type': "text", - 'label': key, - 'value': '', - 'required': true - })); - } else { - form.insertAdjacentHTML('beforeend', tpl_form_input({ - 'label': key, - 'name': key, - 'placeholder': key, - 'required': true, - 'type': key === 'password' || key === 'email' ? key : "text", - 'value': '' - })); - } - }); // Show urls - - - _.each(this.urls, url => { - form.insertAdjacentHTML('afterend', '' + url + ''); - }); - }, - - renderRegistrationForm(stanza) { - /* Renders the registration form based on the XForm fields - * received from the XMPP server. - * - * Parameters: - * (XMLElement) stanza - The IQ stanza received from the XMPP server. - */ - const form = this.el.querySelector('form'); - form.innerHTML = tpl_registration_form({ - '__': _converse.__, - 'domain': this.domain, - 'title': this.title, - 'instructions': this.instructions, - 'registration_domain': _converse.registration_domain - }); - const buttons = form.querySelector('fieldset.buttons'); - - if (this.form_type === 'xform') { - _.each(stanza.querySelectorAll('field'), field => { - buttons.insertAdjacentHTML('beforebegin', utils.xForm2webForm(field, stanza, this.domain)); - }); - } else { - this.renderLegacyRegistrationForm(form); - } - - if (!this.fields) { - form.querySelector('.button-primary').classList.add('hidden'); - } - - form.classList.remove('hidden'); - this.model.set('registration_form_rendered', true); - }, - - showValidationError(message) { - const form = this.el.querySelector('form'); - let flash = form.querySelector('.form-errors'); - - if (_.isNull(flash)) { - flash = ''; - const instructions = form.querySelector('p.instructions'); - - if (_.isNull(instructions)) { - form.insertAdjacentHTML('afterbegin', flash); - } else { - instructions.insertAdjacentHTML('afterend', flash); - } - - flash = form.querySelector('.form-errors'); - } else { - flash.innerHTML = ''; - } - - flash.insertAdjacentHTML('beforeend', '

' + message + '

'); - flash.classList.remove('hidden'); - }, - - reportErrors(stanza) { - /* Report back to the user any error messages received from the - * XMPP server after attempted registration. - * - * Parameters: - * (XMLElement) stanza - The IQ stanza received from the - * XMPP server. - */ - const errors = stanza.querySelectorAll('error'); - - _.each(errors, error => { - this.showValidationError(error.textContent); - }); - - if (!errors.length) { - const message = __('The provider rejected your registration attempt. ' + 'Please check the values you entered for correctness.'); - - this.showValidationError(message); - } - }, - - renderProviderChoiceForm(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - _converse.connection._proto._abortAllRequests(); - - _converse.connection.reset(); - - this.render(); - }, - - abortRegistration() { - _converse.connection._proto._abortAllRequests(); - - _converse.connection.reset(); - - if (this.model.get('registration_form_rendered')) { - if (_converse.registration_domain && this.model.get('registration_form_rendered')) { - this.fetchRegistrationForm(_converse.registration_domain); - } - } else { - this.render(); - } - }, - - submitRegistrationForm(form) { - /* Handler, when the user submits the registration form. - * Provides form error feedback or starts the registration - * process. - * - * Parameters: - * (HTMLElement) form - The HTML form that was submitted - */ - const has_empty_inputs = _.reduce(this.el.querySelectorAll('input.required'), function (result, input) { - if (input.value === '') { - input.classList.add('error'); - return result + 1; - } - - return result; - }, 0); - - if (has_empty_inputs) { - return; - } - - const inputs = sizzle(':input:not([type=button]):not([type=submit])', form), - iq = $iq({ - 'type': 'set', - 'id': _converse.connection.getUniqueId() - }).c("query", { - xmlns: Strophe.NS.REGISTER - }); - - if (this.form_type === 'xform') { - iq.c("x", { - xmlns: Strophe.NS.XFORM, - type: 'submit' - }); - - _.each(inputs, input => { - iq.cnode(utils.webForm2xForm(input)).up(); - }); - } else { - _.each(inputs, input => { - iq.c(input.getAttribute('name'), {}, input.value); - }); - } - - _converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, "iq", null, null); - - _converse.connection.send(iq); - - this.setFields(iq.tree()); - }, - - setFields(stanza) { - /* Stores the values that will be sent to the XMPP server - * during attempted registration. - * - * Parameters: - * (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server. - */ - const query = stanza.querySelector('query'); - const xform = sizzle(`x[xmlns="${Strophe.NS.XFORM}"]`, query); - - if (xform.length > 0) { - this._setFieldsFromXForm(xform.pop()); - } else { - this._setFieldsFromLegacy(query); - } - }, - - _setFieldsFromLegacy(query) { - _.each(query.children, field => { - if (field.tagName.toLowerCase() === 'instructions') { - this.instructions = Strophe.getText(field); - return; - } else if (field.tagName.toLowerCase() === 'x') { - if (field.getAttribute('xmlns') === 'jabber:x:oob') { - this.urls.concat(_.map(field.querySelectorAll('url'), 'textContent')); - } - - return; - } - - this.fields[field.tagName.toLowerCase()] = Strophe.getText(field); - }); - - this.form_type = 'legacy'; - }, - - _setFieldsFromXForm(xform) { - this.title = _.get(xform.querySelector('title'), 'textContent'); - this.instructions = _.get(xform.querySelector('instructions'), 'textContent'); - - _.each(xform.querySelectorAll('field'), field => { - const _var = field.getAttribute('var'); - - if (_var) { - this.fields[_var.toLowerCase()] = _.get(field.querySelector('value'), 'textContent', ''); - } else { - // TODO: other option seems to be type="fixed" - _converse.log("Found field we couldn't parse", Strophe.LogLevel.WARN); - } - }); - - this.form_type = 'xform'; - }, - - _onRegisterIQ(stanza) { - /* Callback method that gets called when a return IQ stanza - * is received from the XMPP server, after attempting to - * register a new user. - * - * Parameters: - * (XMLElement) stanza - The IQ stanza. - */ - if (stanza.getAttribute("type") === "error") { - _converse.log("Registration failed.", Strophe.LogLevel.ERROR); - - this.reportErrors(stanza); - let error = stanza.getElementsByTagName("error"); - - if (error.length !== 1) { - _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown"); - - return false; - } - - error = error[0].firstChild.tagName.toLowerCase(); - - if (error === 'conflict') { - _converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error); - } else if (error === 'not-acceptable') { - _converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error); - } else { - _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error); - } - } else { - _converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null); - } - - return false; + } else { + _converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null); } - }); - } + return false; + } + + }); + } - }); }); /***/ }), @@ -67397,342 +67610,344 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!***********************************!*\ !*** ./src/converse-roomslist.js ***! \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var _converse_headless_converse_muc__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"); +/* harmony import */ var templates_rooms_list_html__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! templates/rooms_list.html */ "./src/templates/rooms_list.html"); +/* harmony import */ var templates_rooms_list_html__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(templates_rooms_list_html__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var templates_rooms_list_item_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! templates/rooms_list_item.html */ "./src/templates/rooms_list_item.html"); +/* harmony import */ var templates_rooms_list_item_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(templates_rooms_list_item_html__WEBPACK_IMPORTED_MODULE_3__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // -// Copyright (c) 2012-2017, Jan-Carel Brand +// Copyright (c) 2013-2018, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) -// - -/*global define */ /* This is a non-core Converse.js plugin which shows a list of currently open * rooms in the "Rooms Panel" of the ControlBox. */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! @converse/headless/converse-muc */ "./src/headless/converse-muc.js"), __webpack_require__(/*! templates/rooms_list.html */ "./src/templates/rooms_list.html"), __webpack_require__(/*! templates/rooms_list_item.html */ "./src/templates/rooms_list_item.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, muc, tpl_rooms_list, tpl_rooms_list_item) { - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - b64_sha1 = _converse$env.b64_sha1, - sizzle = _converse$env.sizzle, - _ = _converse$env._; - const u = converse.env.utils; - converse.plugins.add('converse-roomslist', { - /* Optional dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. They are called "optional" because they might not be - * available, in which case any overrides applicable to them will be - * ignored. - * - * It's possible however to make optional dependencies non-optional. - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. - * - * NB: These plugins need to have already been loaded via require.js. + + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + b64_sha1 = _converse$env.b64_sha1, + sizzle = _converse$env.sizzle, + _ = _converse$env._; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-roomslist', { + /* Optional dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. They are called "optional" because they might not be + * available, in which case any overrides applicable to them will be + * ignored. + * + * It's possible however to make optional dependencies non-optional. + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-singleton", "converse-controlbox", "converse-muc", "converse-bookmarks"], + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. */ - dependencies: ["converse-singleton", "converse-controlbox", "@converse/headless/converse-muc", "converse-bookmarks"], - - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; - _converse.OpenRooms = Backbone.Collection.extend({ - comparator(room) { - if (room.get('bookmarked')) { - const bookmark = _.head(_converse.bookmarksview.model.where({ - 'jid': room.get('jid') - })); - - return bookmark.get('name'); - } else { - return room.get('name'); - } - }, - - initialize() { - _converse.chatboxes.on('add', this.onChatBoxAdded, this); - - _converse.chatboxes.on('change:hidden', this.onChatBoxChanged, this); - - _converse.chatboxes.on('change:bookmarked', this.onChatBoxChanged, this); - - _converse.chatboxes.on('change:name', this.onChatBoxChanged, this); - - _converse.chatboxes.on('change:num_unread', this.onChatBoxChanged, this); - - _converse.chatboxes.on('change:num_unread_general', this.onChatBoxChanged, this); - - _converse.chatboxes.on('remove', this.onChatBoxRemoved, this); - - this.reset(_.map(_converse.chatboxes.where({ - 'type': 'chatroom' - }), 'attributes')); - }, - - onChatBoxAdded(item) { - if (item.get('type') === 'chatroom') { - this.create(item.attributes); - } - }, - - onChatBoxChanged(item) { - if (item.get('type') === 'chatroom') { - const room = this.get(item.get('jid')); - - if (!_.isNil(room)) { - room.set(item.attributes); - } - } - }, - - onChatBoxRemoved(item) { - if (item.get('type') === 'chatroom') { - const room = this.get(item.get('jid')); - this.remove(room); - } - } - - }); - _converse.RoomsList = Backbone.Model.extend({ - defaults: { - "toggle-state": _converse.OPENED - } - }); - _converse.RoomsListElementView = Backbone.VDOMView.extend({ - events: { - 'click .room-info': 'showRoomDetailsModal' - }, - - initialize() { - this.model.on('destroy', this.remove, this); - this.model.on('remove', this.remove, this); - this.model.on('change:bookmarked', this.render, this); - this.model.on('change:hidden', this.render, this); - this.model.on('change:name', this.render, this); - this.model.on('change:num_unread', this.render, this); - this.model.on('change:num_unread_general', this.render, this); - }, - - toHTML() { - return tpl_rooms_list_item(_.extend(this.model.toJSON(), { - // XXX: By the time this renders, the _converse.bookmarks - // collection should already exist if bookmarks are - // supported by the XMPP server. So we can use it - // as a check for support (other ways of checking are async). - 'allow_bookmarks': _converse.allow_bookmarks && _converse.bookmarks, - 'currently_open': _converse.isSingleton() && !this.model.get('hidden'), - 'info_leave_room': __('Leave this groupchat'), - 'info_remove_bookmark': __('Unbookmark this groupchat'), - 'info_add_bookmark': __('Bookmark this groupchat'), - 'info_title': __('Show more information on this groupchat'), - 'name': this.getRoomsListElementName(), - 'open_title': __('Click to open this groupchat') + const _converse = this._converse, + __ = _converse.__; + _converse.OpenRooms = Backbone.Collection.extend({ + comparator(room) { + if (room.get('bookmarked')) { + const bookmark = _.head(_converse.bookmarksview.model.where({ + 'jid': room.get('jid') })); - }, - showRoomDetailsModal(ev) { - const room = _converse.chatboxes.get(this.model.get('jid')); + return bookmark.get('name'); + } else { + return room.get('name'); + } + }, - ev.preventDefault(); + initialize() { + _converse.chatboxes.on('add', this.onChatBoxAdded, this); - if (_.isUndefined(room.room_details_modal)) { - room.room_details_modal = new _converse.RoomDetailsModal({ - 'model': room - }); - } + _converse.chatboxes.on('change:hidden', this.onChatBoxChanged, this); - room.room_details_modal.show(ev); - }, + _converse.chatboxes.on('change:bookmarked', this.onChatBoxChanged, this); - getRoomsListElementName() { - if (this.model.get('bookmarked') && _converse.bookmarksview) { - const bookmark = _.head(_converse.bookmarksview.model.where({ - 'jid': this.model.get('jid') - })); + _converse.chatboxes.on('change:name', this.onChatBoxChanged, this); - return bookmark.get('name'); - } else { - return this.model.get('name'); + _converse.chatboxes.on('change:num_unread', this.onChatBoxChanged, this); + + _converse.chatboxes.on('change:num_unread_general', this.onChatBoxChanged, this); + + _converse.chatboxes.on('remove', this.onChatBoxRemoved, this); + + this.reset(_.map(_converse.chatboxes.where({ + 'type': 'chatroom' + }), 'attributes')); + }, + + onChatBoxAdded(item) { + if (item.get('type') === 'chatroom') { + this.create(item.attributes); + } + }, + + onChatBoxChanged(item) { + if (item.get('type') === 'chatroom') { + const room = this.get(item.get('jid')); + + if (!_.isNil(room)) { + room.set(item.attributes); } } + }, - }); - _converse.RoomsListView = Backbone.OrderedListView.extend({ - tagName: 'div', - className: 'open-rooms-list list-container rooms-list-container', - events: { - 'click .add-bookmark': 'addBookmark', - 'click .close-room': 'closeRoom', - 'click .list-toggle': 'toggleRoomsList', - 'click .remove-bookmark': 'removeBookmark', - 'click .open-room': 'openRoom' - }, - listSelector: '.rooms-list', - ItemView: _converse.RoomsListElementView, - subviewIndex: 'jid', - - initialize() { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - this.model.on('add', this.showOrHide, this); - this.model.on('remove', this.showOrHide, this); - - const storage = _converse.config.get('storage'), - id = b64_sha1(`converse.roomslist${_converse.bare_jid}`); - - this.list_model = new _converse.RoomsList({ - 'id': id - }); - this.list_model.browserStorage = new Backbone.BrowserStorage[storage](id); - this.list_model.fetch(); - this.render(); - this.sortAndPositionAllItems(); - }, - - render() { - this.el.innerHTML = tpl_rooms_list({ - 'toggle_state': this.list_model.get('toggle-state'), - 'desc_rooms': __('Click to toggle the list of open groupchats'), - 'label_rooms': __('Open Groupchats'), - '_converse': _converse - }); - - if (this.list_model.get('toggle-state') !== _converse.OPENED) { - this.el.querySelector('.open-rooms-list').classList.add('collapsed'); - } - - this.showOrHide(); - this.insertIntoControlBox(); - return this; - }, - - insertIntoControlBox() { - const controlboxview = _converse.chatboxviews.get('controlbox'); - - if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) { - const el = controlboxview.el.querySelector('.open-rooms-list'); - - if (!_.isNull(el)) { - el.parentNode.replaceChild(this.el, el); - } - } - }, - - hide() { - u.hideElement(this.el); - }, - - show() { - u.showElement(this.el); - }, - - openRoom(ev) { - ev.preventDefault(); - const name = ev.target.textContent; - const jid = ev.target.getAttribute('data-room-jid'); - const data = { - 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid - }; - - _converse.api.rooms.open(jid, data); - }, - - closeRoom(ev) { - ev.preventDefault(); - const name = ev.target.getAttribute('data-room-name'); - const jid = ev.target.getAttribute('data-room-jid'); - - if (confirm(__("Are you sure you want to leave the groupchat %1$s?", name))) { - // TODO: replace with API call - _converse.chatboxviews.get(jid).close(); - } - }, - - showOrHide(item) { - if (!this.model.models.length) { - u.hideElement(this.el); - } else { - u.showElement(this.el); - } - }, - - removeBookmark: _converse.removeBookmarkViaEvent, - addBookmark: _converse.addBookmarkViaEvent, - - toggleRoomsList(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const icon_el = ev.target.querySelector('.fa'); - - if (icon_el.classList.contains("fa-caret-down")) { - u.slideIn(this.el.querySelector('.open-rooms-list')).then(() => { - this.list_model.save({ - 'toggle-state': _converse.CLOSED - }); - icon_el.classList.remove("fa-caret-down"); - icon_el.classList.add("fa-caret-right"); - }); - } else { - u.slideOut(this.el.querySelector('.open-rooms-list')).then(() => { - this.list_model.save({ - 'toggle-state': _converse.OPENED - }); - icon_el.classList.remove("fa-caret-right"); - icon_el.classList.add("fa-caret-down"); - }); - } + onChatBoxRemoved(item) { + if (item.get('type') === 'chatroom') { + const room = this.get(item.get('jid')); + this.remove(room); } - - }); - - const initRoomsListView = function initRoomsListView() { - const storage = _converse.config.get('storage'), - id = b64_sha1(`converse.open-rooms-{_converse.bare_jid}`), - model = new _converse.OpenRooms(); - - model.browserStorage = new Backbone.BrowserStorage[storage](id); - _converse.rooms_list_view = new _converse.RoomsListView({ - 'model': model - }); - }; - - if (_converse.allow_bookmarks) { - u.onMultipleEvents([{ - 'object': _converse, - 'event': 'chatBoxesFetched' - }, { - 'object': _converse, - 'event': 'roomsPanelRendered' - }, { - 'object': _converse, - 'event': 'bookmarksInitialized' - }], initRoomsListView); - } else { - u.onMultipleEvents([{ - 'object': _converse, - 'event': 'chatBoxesFetched' - }, { - 'object': _converse, - 'event': 'roomsPanelRendered' - }], initRoomsListView); } - _converse.api.listen.on('reconnected', initRoomsListView); + }); + _converse.RoomsList = Backbone.Model.extend({ + defaults: { + "toggle-state": _converse.OPENED + } + }); + _converse.RoomsListElementView = Backbone.VDOMView.extend({ + events: { + 'click .room-info': 'showRoomDetailsModal' + }, + + initialize() { + this.model.on('destroy', this.remove, this); + this.model.on('remove', this.remove, this); + this.model.on('change:bookmarked', this.render, this); + this.model.on('change:hidden', this.render, this); + this.model.on('change:name', this.render, this); + this.model.on('change:num_unread', this.render, this); + this.model.on('change:num_unread_general', this.render, this); + }, + + toHTML() { + return templates_rooms_list_item_html__WEBPACK_IMPORTED_MODULE_3___default()(_.extend(this.model.toJSON(), { + // XXX: By the time this renders, the _converse.bookmarks + // collection should already exist if bookmarks are + // supported by the XMPP server. So we can use it + // as a check for support (other ways of checking are async). + 'allow_bookmarks': _converse.allow_bookmarks && _converse.bookmarks, + 'currently_open': _converse.isSingleton() && !this.model.get('hidden'), + 'info_leave_room': __('Leave this groupchat'), + 'info_remove_bookmark': __('Unbookmark this groupchat'), + 'info_add_bookmark': __('Bookmark this groupchat'), + 'info_title': __('Show more information on this groupchat'), + 'name': this.getRoomsListElementName(), + 'open_title': __('Click to open this groupchat') + })); + }, + + showRoomDetailsModal(ev) { + const room = _converse.chatboxes.get(this.model.get('jid')); + + ev.preventDefault(); + + if (_.isUndefined(room.room_details_modal)) { + room.room_details_modal = new _converse.RoomDetailsModal({ + 'model': room + }); + } + + room.room_details_modal.show(ev); + }, + + getRoomsListElementName() { + if (this.model.get('bookmarked') && _converse.bookmarksview) { + const bookmark = _.head(_converse.bookmarksview.model.where({ + 'jid': this.model.get('jid') + })); + + return bookmark.get('name'); + } else { + return this.model.get('name'); + } + } + + }); + _converse.RoomsListView = Backbone.OrderedListView.extend({ + tagName: 'div', + className: 'open-rooms-list list-container rooms-list-container', + events: { + 'click .add-bookmark': 'addBookmark', + 'click .close-room': 'closeRoom', + 'click .list-toggle': 'toggleRoomsList', + 'click .remove-bookmark': 'removeBookmark', + 'click .open-room': 'openRoom' + }, + listSelector: '.rooms-list', + ItemView: _converse.RoomsListElementView, + subviewIndex: 'jid', + + initialize() { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + this.model.on('add', this.showOrHide, this); + this.model.on('remove', this.showOrHide, this); + + const storage = _converse.config.get('storage'), + id = b64_sha1(`converse.roomslist${_converse.bare_jid}`); + + this.list_model = new _converse.RoomsList({ + 'id': id + }); + this.list_model.browserStorage = new Backbone.BrowserStorage[storage](id); + this.list_model.fetch(); + this.render(); + this.sortAndPositionAllItems(); + }, + + render() { + this.el.innerHTML = templates_rooms_list_html__WEBPACK_IMPORTED_MODULE_2___default()({ + 'toggle_state': this.list_model.get('toggle-state'), + 'desc_rooms': __('Click to toggle the list of open groupchats'), + 'label_rooms': __('Open Groupchats'), + '_converse': _converse + }); + + if (this.list_model.get('toggle-state') !== _converse.OPENED) { + this.el.querySelector('.open-rooms-list').classList.add('collapsed'); + } + + this.showOrHide(); + this.insertIntoControlBox(); + return this; + }, + + insertIntoControlBox() { + const controlboxview = _converse.chatboxviews.get('controlbox'); + + if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) { + const el = controlboxview.el.querySelector('.open-rooms-list'); + + if (!_.isNull(el)) { + el.parentNode.replaceChild(this.el, el); + } + } + }, + + hide() { + u.hideElement(this.el); + }, + + show() { + u.showElement(this.el); + }, + + openRoom(ev) { + ev.preventDefault(); + const name = ev.target.textContent; + const jid = ev.target.getAttribute('data-room-jid'); + const data = { + 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid + }; + + _converse.api.rooms.open(jid, data); + }, + + closeRoom(ev) { + ev.preventDefault(); + const name = ev.target.getAttribute('data-room-name'); + const jid = ev.target.getAttribute('data-room-jid'); + + if (confirm(__("Are you sure you want to leave the groupchat %1$s?", name))) { + // TODO: replace with API call + _converse.chatboxviews.get(jid).close(); + } + }, + + showOrHide(item) { + if (!this.model.models.length) { + u.hideElement(this.el); + } else { + u.showElement(this.el); + } + }, + + removeBookmark: _converse.removeBookmarkViaEvent, + addBookmark: _converse.addBookmarkViaEvent, + + toggleRoomsList(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + const icon_el = ev.target.querySelector('.fa'); + + if (icon_el.classList.contains("fa-caret-down")) { + u.slideIn(this.el.querySelector('.open-rooms-list')).then(() => { + this.list_model.save({ + 'toggle-state': _converse.CLOSED + }); + icon_el.classList.remove("fa-caret-down"); + icon_el.classList.add("fa-caret-right"); + }); + } else { + u.slideOut(this.el.querySelector('.open-rooms-list')).then(() => { + this.list_model.save({ + 'toggle-state': _converse.OPENED + }); + icon_el.classList.remove("fa-caret-right"); + icon_el.classList.add("fa-caret-down"); + }); + } + } + + }); + + const initRoomsListView = function initRoomsListView() { + const storage = _converse.config.get('storage'), + id = b64_sha1(`converse.open-rooms-{_converse.bare_jid}`), + model = new _converse.OpenRooms(); + + model.browserStorage = new Backbone.BrowserStorage[storage](id); + _converse.rooms_list_view = new _converse.RoomsListView({ + 'model': model + }); + }; + + if (_converse.allow_bookmarks) { + u.onMultipleEvents([{ + 'object': _converse, + 'event': 'chatBoxesFetched' + }, { + 'object': _converse, + 'event': 'roomsPanelRendered' + }, { + 'object': _converse, + 'event': 'bookmarksInitialized' + }], initRoomsListView); + } else { + u.onMultipleEvents([{ + 'object': _converse, + 'event': 'chatBoxesFetched' + }, { + 'object': _converse, + 'event': 'roomsPanelRendered' + }], initRoomsListView); } - }); + _converse.api.listen.on('reconnected', initRoomsListView); + } + }); /***/ }), @@ -67741,1062 +67956,1057 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!********************************!*\ !*** ./src/converse-roster.js ***! \********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - $iq = _converse$env.$iq, - $pres = _converse$env.$pres, - b64_sha1 = _converse$env.b64_sha1, - moment = _converse$env.moment, - sizzle = _converse$env.sizzle, - _ = _converse$env._; - const u = converse.env.utils; - converse.plugins.add('converse-roster', { - dependencies: ["converse-vcard"], +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq, + $pres = _converse$env.$pres, + b64_sha1 = _converse$env.b64_sha1, + moment = _converse$env.moment, + sizzle = _converse$env.sizzle, + _ = _converse$env._; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-roster', { + dependencies: ["converse-vcard"], - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + + _converse.api.settings.update({ + 'allow_contact_requests': true, + 'auto_subscribe': false, + 'synchronize_availability': true + }); + + _converse.api.promises.add(['cachedRoster', 'roster', 'rosterContactsFetched', 'rosterGroupsFetched', 'rosterInitialized']); + + _converse.registerPresenceHandler = function () { + _converse.unregisterPresenceHandler(); + + _converse.presence_ref = _converse.connection.addHandler(function (presence) { + _converse.roster.presenceHandler(presence); + + return true; + }, null, 'presence', null); + }; + + _converse.initRoster = function () { + /* Initialize the Bakcbone collections that represent the contats + * roster and the roster groups. */ - const _converse = this._converse, - __ = _converse.__; + const storage = _converse.config.get('storage'); - _converse.api.settings.update({ - 'allow_contact_requests': true, - 'auto_subscribe': false, - 'synchronize_availability': true - }); + _converse.roster = new _converse.RosterContacts(); + _converse.roster.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(`converse.contacts-${_converse.bare_jid}`)); + _converse.roster.data = new Backbone.Model(); + const id = b64_sha1(`converse-roster-model-${_converse.bare_jid}`); + _converse.roster.data.id = id; + _converse.roster.data.browserStorage = new Backbone.BrowserStorage[storage](id); - _converse.api.promises.add(['cachedRoster', 'roster', 'rosterContactsFetched', 'rosterGroupsFetched', 'rosterInitialized']); + _converse.roster.data.fetch(); - _converse.registerPresenceHandler = function () { - _converse.unregisterPresenceHandler(); + _converse.rostergroups = new _converse.RosterGroups(); + _converse.rostergroups.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(`converse.roster.groups${_converse.bare_jid}`)); - _converse.presence_ref = _converse.connection.addHandler(function (presence) { - _converse.roster.presenceHandler(presence); + _converse.emit('rosterInitialized'); + }; - return true; - }, null, 'presence', null); - }; + _converse.populateRoster = function (ignore_cache = false) { + /* Fetch all the roster groups, and then the roster contacts. + * Emit an event after fetching is done in each case. + * + * Parameters: + * (Bool) ignore_cache - If set to to true, the local cache + * will be ignored it's guaranteed that the XMPP server + * will be queried for the roster. + */ + if (ignore_cache) { + _converse.send_initial_presence = true; - _converse.initRoster = function () { - /* Initialize the Bakcbone collections that represent the contats - * roster and the roster groups. - */ - const storage = _converse.config.get('storage'); + _converse.roster.fetchFromServer().then(() => { + _converse.emit('rosterContactsFetched'); - _converse.roster = new _converse.RosterContacts(); - _converse.roster.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(`converse.contacts-${_converse.bare_jid}`)); - _converse.roster.data = new Backbone.Model(); - const id = b64_sha1(`converse-roster-model-${_converse.bare_jid}`); - _converse.roster.data.id = id; - _converse.roster.data.browserStorage = new Backbone.BrowserStorage[storage](id); + _converse.sendInitialPresence(); + }).catch(reason => { + _converse.log(reason, Strophe.LogLevel.ERROR); - _converse.roster.data.fetch(); + _converse.sendInitialPresence(); + }); + } else { + _converse.rostergroups.fetchRosterGroups().then(() => { + _converse.emit('rosterGroupsFetched'); - _converse.rostergroups = new _converse.RosterGroups(); - _converse.rostergroups.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(`converse.roster.groups${_converse.bare_jid}`)); + return _converse.roster.fetchRosterContacts(); + }).then(() => { + _converse.emit('rosterContactsFetched'); - _converse.emit('rosterInitialized'); - }; + _converse.sendInitialPresence(); + }).catch(reason => { + _converse.log(reason, Strophe.LogLevel.ERROR); - _converse.populateRoster = function (ignore_cache = false) { - /* Fetch all the roster groups, and then the roster contacts. - * Emit an event after fetching is done in each case. + _converse.sendInitialPresence(); + }); + } + }; + + _converse.Presence = Backbone.Model.extend({ + defaults() { + return { + 'show': 'offline', + 'resources': {} + }; + }, + + getHighestPriorityResource() { + /* Return the resource with the highest priority. * - * Parameters: - * (Bool) ignore_cache - If set to to true, the local cache - * will be ignored it's guaranteed that the XMPP server - * will be queried for the roster. + * If multiple resources have the same priority, take the + * latest one. */ - if (ignore_cache) { - _converse.send_initial_presence = true; + const resources = this.get('resources'); - _converse.roster.fetchFromServer().then(() => { - _converse.emit('rosterContactsFetched'); + if (_.isObject(resources) && _.size(resources)) { + const val = _.flow(_.values, _.partial(_.sortBy, _, ['priority', 'timestamp']), _.reverse)(resources)[0]; - _converse.sendInitialPresence(); - }).catch(reason => { - _converse.log(reason, Strophe.LogLevel.ERROR); + if (!_.isUndefined(val)) { + return val; + } + } + }, - _converse.sendInitialPresence(); - }); + addResource(presence) { + /* Adds a new resource and it's associated attributes as taken + * from the passed in presence stanza. + * + * Also updates the presence if the resource has higher priority (and is newer). + */ + const jid = presence.getAttribute('from'), + show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online', + resource = Strophe.getResourceFromJid(jid), + delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(), + timestamp = _.isNil(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format(); + let priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0; + priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10); + const resources = _.isObject(this.get('resources')) ? this.get('resources') : {}; + resources[resource] = { + 'name': resource, + 'priority': priority, + 'show': show, + 'timestamp': timestamp + }; + const changed = { + 'resources': resources + }; + const hpr = this.getHighestPriorityResource(); + + if (priority == hpr.priority && timestamp == hpr.timestamp) { + // Only set the "global" presence if this is the newest resource + // with the highest priority + changed.show = show; + } + + this.save(changed); + return resources; + }, + + removeResource(resource) { + /* Remove the passed in resource from the resources map. + * + * Also redetermines the presence given that there's one less + * resource. + */ + let resources = this.get('resources'); + + if (!_.isObject(resources)) { + resources = {}; } else { - _converse.rostergroups.fetchRosterGroups().then(() => { - _converse.emit('rosterGroupsFetched'); - - return _converse.roster.fetchRosterContacts(); - }).then(() => { - _converse.emit('rosterContactsFetched'); - - _converse.sendInitialPresence(); - }).catch(reason => { - _converse.log(reason, Strophe.LogLevel.ERROR); - - _converse.sendInitialPresence(); - }); - } - }; - - _converse.Presence = Backbone.Model.extend({ - defaults() { - return { - 'show': 'offline', - 'resources': {} - }; - }, - - getHighestPriorityResource() { - /* Return the resource with the highest priority. - * - * If multiple resources have the same priority, take the - * latest one. - */ - const resources = this.get('resources'); - - if (_.isObject(resources) && _.size(resources)) { - const val = _.flow(_.values, _.partial(_.sortBy, _, ['priority', 'timestamp']), _.reverse)(resources)[0]; - - if (!_.isUndefined(val)) { - return val; - } - } - }, - - addResource(presence) { - /* Adds a new resource and it's associated attributes as taken - * from the passed in presence stanza. - * - * Also updates the presence if the resource has higher priority (and is newer). - */ - const jid = presence.getAttribute('from'), - show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online', - resource = Strophe.getResourceFromJid(jid), - delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(), - timestamp = _.isNil(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format(); - let priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0; - priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10); - const resources = _.isObject(this.get('resources')) ? this.get('resources') : {}; - resources[resource] = { - 'name': resource, - 'priority': priority, - 'show': show, - 'timestamp': timestamp - }; - const changed = { - 'resources': resources - }; - const hpr = this.getHighestPriorityResource(); - - if (priority == hpr.priority && timestamp == hpr.timestamp) { - // Only set the "global" presence if this is the newest resource - // with the highest priority - changed.show = show; - } - - this.save(changed); - return resources; - }, - - removeResource(resource) { - /* Remove the passed in resource from the resources map. - * - * Also redetermines the presence given that there's one less - * resource. - */ - let resources = this.get('resources'); - - if (!_.isObject(resources)) { - resources = {}; - } else { - delete resources[resource]; - } - - this.save({ - 'resources': resources, - 'show': _.propertyOf(this.getHighestPriorityResource())('show') || 'offline' - }); + delete resources[resource]; } - }); - _converse.Presences = Backbone.Collection.extend({ - model: _converse.Presence - }); - _converse.ModelWithVCardAndPresence = Backbone.Model.extend({ - initialize() { - this.setVCard(); - this.setPresence(); - }, + this.save({ + 'resources': resources, + 'show': _.propertyOf(this.getHighestPriorityResource())('show') || 'offline' + }); + } - setVCard() { - const jid = this.get('jid'); - this.vcard = _converse.vcards.findWhere({ - 'jid': jid - }) || _converse.vcards.create({ - 'jid': jid - }); - }, + }); + _converse.Presences = Backbone.Collection.extend({ + model: _converse.Presence + }); + _converse.ModelWithVCardAndPresence = Backbone.Model.extend({ + initialize() { + this.setVCard(); + this.setPresence(); + }, - setPresence() { - const jid = this.get('jid'); - this.presence = _converse.presences.findWhere({ - 'jid': jid - }) || _converse.presences.create({ - 'jid': jid - }); + setVCard() { + const jid = this.get('jid'); + this.vcard = _converse.vcards.findWhere({ + 'jid': jid + }) || _converse.vcards.create({ + 'jid': jid + }); + }, + + setPresence() { + const jid = this.get('jid'); + this.presence = _converse.presences.findWhere({ + 'jid': jid + }) || _converse.presences.create({ + 'jid': jid + }); + } + + }); + _converse.RosterContact = _converse.ModelWithVCardAndPresence.extend({ + defaults: { + 'chat_state': undefined, + 'image': _converse.DEFAULT_IMAGE, + 'image_type': _converse.DEFAULT_IMAGE_TYPE, + 'num_unread': 0, + 'status': '' + }, + + initialize(attributes) { + _converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments); + + const jid = attributes.jid, + bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(), + resource = Strophe.getResourceFromJid(jid); + attributes.jid = bare_jid; + this.set(_.assignIn({ + 'groups': [], + 'id': bare_jid, + 'jid': bare_jid, + 'user_id': Strophe.getNodeFromJid(jid) + }, attributes)); + this.setChatBox(); + this.presence.on('change:show', () => _converse.emit('contactPresenceChanged', this)); + this.presence.on('change:show', () => this.trigger('presenceChanged')); + }, + + setChatBox(chatbox = null) { + chatbox = chatbox || _converse.chatboxes.get(this.get('jid')); + + if (chatbox) { + this.chatbox = chatbox; + this.chatbox.on('change:hidden', this.render, this); + } + }, + + getDisplayName() { + return this.get('nickname') || this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid'); + }, + + getFullname() { + return this.vcard.get('fullname'); + }, + + subscribe(message) { + /* Send a presence subscription request to this roster contact + * + * Parameters: + * (String) message - An optional message to explain the + * reason for the subscription request. + */ + const pres = $pres({ + to: this.get('jid'), + type: "subscribe" + }); + + if (message && message !== "") { + pres.c("status").t(message).up(); } - }); - _converse.RosterContact = _converse.ModelWithVCardAndPresence.extend({ - defaults: { - 'chat_state': undefined, - 'image': _converse.DEFAULT_IMAGE, - 'image_type': _converse.DEFAULT_IMAGE_TYPE, - 'num_unread': 0, - 'status': '' - }, + const nick = _converse.xmppstatus.vcard.get('nickname') || _converse.xmppstatus.vcard.get('fullname'); - initialize(attributes) { - _converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments); - - const jid = attributes.jid, - bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(), - resource = Strophe.getResourceFromJid(jid); - attributes.jid = bare_jid; - this.set(_.assignIn({ - 'groups': [], - 'id': bare_jid, - 'jid': bare_jid, - 'user_id': Strophe.getNodeFromJid(jid) - }, attributes)); - this.setChatBox(); - this.presence.on('change:show', () => _converse.emit('contactPresenceChanged', this)); - this.presence.on('change:show', () => this.trigger('presenceChanged')); - }, - - setChatBox(chatbox = null) { - chatbox = chatbox || _converse.chatboxes.get(this.get('jid')); - - if (chatbox) { - this.chatbox = chatbox; - this.chatbox.on('change:hidden', this.render, this); - } - }, - - getDisplayName() { - return this.get('nickname') || this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid'); - }, - - getFullname() { - return this.vcard.get('fullname'); - }, - - subscribe(message) { - /* Send a presence subscription request to this roster contact - * - * Parameters: - * (String) message - An optional message to explain the - * reason for the subscription request. - */ - const pres = $pres({ - to: this.get('jid'), - type: "subscribe" - }); - - if (message && message !== "") { - pres.c("status").t(message).up(); - } - - const nick = _converse.xmppstatus.vcard.get('nickname') || _converse.xmppstatus.vcard.get('fullname'); - - if (nick) { - pres.c('nick', { - 'xmlns': Strophe.NS.NICK - }).t(nick).up(); - } - - _converse.connection.send(pres); - - this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them. - - return this; - }, - - ackSubscribe() { - /* Upon receiving the presence stanza of type "subscribed", - * the user SHOULD acknowledge receipt of that subscription - * state notification by sending a presence stanza of type - * "subscribe" to the contact - */ - _converse.connection.send($pres({ - 'type': 'subscribe', - 'to': this.get('jid') - })); - }, - - ackUnsubscribe() { - /* Upon receiving the presence stanza of type "unsubscribed", - * the user SHOULD acknowledge receipt of that subscription state - * notification by sending a presence stanza of type "unsubscribe" - * this step lets the user's server know that it MUST no longer - * send notification of the subscription state change to the user. - * Parameters: - * (String) jid - The Jabber ID of the user who is unsubscribing - */ - _converse.connection.send($pres({ - 'type': 'unsubscribe', - 'to': this.get('jid') - })); - - this.removeFromRoster(); - this.destroy(); - }, - - unauthorize(message) { - /* Unauthorize this contact's presence subscription - * Parameters: - * (String) message - Optional message to send to the person being unauthorized - */ - _converse.rejectPresenceSubscription(this.get('jid'), message); - - return this; - }, - - authorize(message) { - /* Authorize presence subscription - * Parameters: - * (String) message - Optional message to send to the person being authorized - */ - const pres = $pres({ - 'to': this.get('jid'), - 'type': "subscribed" - }); - - if (message && message !== "") { - pres.c("status").t(message); - } - - _converse.connection.send(pres); - - return this; - }, - - removeFromRoster(callback, errback) { - /* Instruct the XMPP server to remove this contact from our roster - * Parameters: - * (Function) callback - */ - const iq = $iq({ - type: 'set' - }).c('query', { - xmlns: Strophe.NS.ROSTER - }).c('item', { - jid: this.get('jid'), - subscription: "remove" - }); - - _converse.connection.sendIQ(iq, callback, errback); - - return this; + if (nick) { + pres.c('nick', { + 'xmlns': Strophe.NS.NICK + }).t(nick).up(); } - }); - _converse.RosterContacts = Backbone.Collection.extend({ - model: _converse.RosterContact, + _converse.connection.send(pres); - comparator(contact1, contact2) { - const status1 = contact1.presence.get('show') || 'offline'; - const status2 = contact2.presence.get('show') || 'offline'; + this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them. - if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) { - const name1 = contact1.getDisplayName().toLowerCase(); - const name2 = contact2.getDisplayName().toLowerCase(); - return name1 < name2 ? -1 : name1 > name2 ? 1 : 0; - } else { - return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1; - } - }, + return this; + }, - onConnected() { - /* Called as soon as the connection has been established - * (either after initial login, or after reconnection). - * - * Use the opportunity to register stanza handlers. - */ - this.registerRosterHandler(); - this.registerRosterXHandler(); - }, + ackSubscribe() { + /* Upon receiving the presence stanza of type "subscribed", + * the user SHOULD acknowledge receipt of that subscription + * state notification by sending a presence stanza of type + * "subscribe" to the contact + */ + _converse.connection.send($pres({ + 'type': 'subscribe', + 'to': this.get('jid') + })); + }, - registerRosterHandler() { - /* Register a handler for roster IQ "set" stanzas, which update - * roster contacts. - */ - _converse.connection.addHandler(iq => { - _converse.roster.onRosterPush(iq); + ackUnsubscribe() { + /* Upon receiving the presence stanza of type "unsubscribed", + * the user SHOULD acknowledge receipt of that subscription state + * notification by sending a presence stanza of type "unsubscribe" + * this step lets the user's server know that it MUST no longer + * send notification of the subscription state change to the user. + * Parameters: + * (String) jid - The Jabber ID of the user who is unsubscribing + */ + _converse.connection.send($pres({ + 'type': 'unsubscribe', + 'to': this.get('jid') + })); - return true; - }, Strophe.NS.ROSTER, 'iq', "set"); - }, + this.removeFromRoster(); + this.destroy(); + }, - registerRosterXHandler() { - /* Register a handler for RosterX message stanzas, which are - * used to suggest roster contacts to a user. - */ - let t = 0; + unauthorize(message) { + /* Unauthorize this contact's presence subscription + * Parameters: + * (String) message - Optional message to send to the person being unauthorized + */ + _converse.rejectPresenceSubscription(this.get('jid'), message); - _converse.connection.addHandler(function (msg) { - window.setTimeout(function () { - _converse.connection.flush(); + return this; + }, - _converse.roster.subscribeToSuggestedItems.bind(_converse.roster)(msg); - }, t); - t += msg.querySelectorAll('item').length * 250; - return true; - }, Strophe.NS.ROSTERX, 'message', null); - }, + authorize(message) { + /* Authorize presence subscription + * Parameters: + * (String) message - Optional message to send to the person being authorized + */ + const pres = $pres({ + 'to': this.get('jid'), + 'type': "subscribed" + }); - fetchRosterContacts() { - /* Fetches the roster contacts, first by trying the - * sessionStorage cache, and if that's empty, then by querying - * the XMPP server. - * - * Returns a promise which resolves once the contacts have been - * fetched. - */ - const that = this; - return new Promise((resolve, reject) => { - this.fetch({ - 'add': true, - 'silent': true, + if (message && message !== "") { + pres.c("status").t(message); + } - success(collection) { - if (collection.length === 0 || that.rosterVersioningSupported() && !_converse.session.get('roster_fetched')) { - _converse.send_initial_presence = true; + _converse.connection.send(pres); - _converse.roster.fetchFromServer().then(resolve).catch(reject); - } else { - _converse.emit('cachedRoster', collection); + return this; + }, - resolve(); - } - } + removeFromRoster(callback, errback) { + /* Instruct the XMPP server to remove this contact from our roster + * Parameters: + * (Function) callback + */ + const iq = $iq({ + type: 'set' + }).c('query', { + xmlns: Strophe.NS.ROSTER + }).c('item', { + jid: this.get('jid'), + subscription: "remove" + }); - }); - }); - }, + _converse.connection.sendIQ(iq, callback, errback); - subscribeToSuggestedItems(msg) { - _.each(msg.querySelectorAll('item'), function (item) { - if (item.getAttribute('action') === 'add') { - _converse.roster.addAndSubscribe(item.getAttribute('jid'), _converse.xmppstatus.vcard.get('nickname') || _converse.xmppstatus.vcard.get('fullname')); - } - }); + return this; + } + + }); + _converse.RosterContacts = Backbone.Collection.extend({ + model: _converse.RosterContact, + + comparator(contact1, contact2) { + const status1 = contact1.presence.get('show') || 'offline'; + const status2 = contact2.presence.get('show') || 'offline'; + + if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) { + const name1 = contact1.getDisplayName().toLowerCase(); + const name2 = contact2.getDisplayName().toLowerCase(); + return name1 < name2 ? -1 : name1 > name2 ? 1 : 0; + } else { + return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1; + } + }, + + onConnected() { + /* Called as soon as the connection has been established + * (either after initial login, or after reconnection). + * + * Use the opportunity to register stanza handlers. + */ + this.registerRosterHandler(); + this.registerRosterXHandler(); + }, + + registerRosterHandler() { + /* Register a handler for roster IQ "set" stanzas, which update + * roster contacts. + */ + _converse.connection.addHandler(iq => { + _converse.roster.onRosterPush(iq); return true; - }, + }, Strophe.NS.ROSTER, 'iq', "set"); + }, - isSelf(jid) { - return u.isSameBareJID(jid, _converse.connection.jid); - }, + registerRosterXHandler() { + /* Register a handler for RosterX message stanzas, which are + * used to suggest roster contacts to a user. + */ + let t = 0; - addAndSubscribe(jid, name, groups, message, attributes) { - /* Add a roster contact and then once we have confirmation from - * the XMPP server we subscribe to that contact's presence updates. - * Parameters: - * (String) jid - The Jabber ID of the user being added and subscribed to. - * (String) name - The name of that user - * (Array of Strings) groups - Any roster groups the user might belong to - * (String) message - An optional message to explain the - * reason for the subscription request. - * (Object) attributes - Any additional attributes to be stored on the user's model. - */ - const handler = contact => { - if (contact instanceof _converse.RosterContact) { - contact.subscribe(message); - } - }; + _converse.connection.addHandler(function (msg) { + window.setTimeout(function () { + _converse.connection.flush(); - this.addContactToRoster(jid, name, groups, attributes).then(handler, handler); - }, + _converse.roster.subscribeToSuggestedItems.bind(_converse.roster)(msg); + }, t); + t += msg.querySelectorAll('item').length * 250; + return true; + }, Strophe.NS.ROSTERX, 'message', null); + }, - sendContactAddIQ(jid, name, groups, callback, errback) { - /* Send an IQ stanza to the XMPP server to add a new roster contact. - * - * Parameters: - * (String) jid - The Jabber ID of the user being added - * (String) name - The name of that user - * (Array of Strings) groups - Any roster groups the user might belong to - * (Function) callback - A function to call once the IQ is returned - * (Function) errback - A function to call if an error occurred - */ - name = _.isEmpty(name) ? jid : name; - const iq = $iq({ - type: 'set' - }).c('query', { - xmlns: Strophe.NS.ROSTER - }).c('item', { - jid, - name - }); + fetchRosterContacts() { + /* Fetches the roster contacts, first by trying the + * sessionStorage cache, and if that's empty, then by querying + * the XMPP server. + * + * Returns a promise which resolves once the contacts have been + * fetched. + */ + const that = this; + return new Promise((resolve, reject) => { + this.fetch({ + 'add': true, + 'silent': true, - _.each(groups, function (group) { - iq.c('group').t(group).up(); - }); + success(collection) { + if (collection.length === 0 || that.rosterVersioningSupported() && !_converse.session.get('roster_fetched')) { + _converse.send_initial_presence = true; - _converse.connection.sendIQ(iq, callback, errback); - }, + _converse.roster.fetchFromServer().then(resolve).catch(reject); + } else { + _converse.emit('cachedRoster', collection); - addContactToRoster(jid, name, groups, attributes) { - /* Adds a RosterContact instance to _converse.roster and - * registers the contact on the XMPP server. - * Returns a promise which is resolved once the XMPP server has - * responded. - * - * Parameters: - * (String) jid - The Jabber ID of the user being added and subscribed to. - * (String) name - The name of that user - * (Array of Strings) groups - Any roster groups the user might belong to - * (Object) attributes - Any additional attributes to be stored on the user's model. - */ - return new Promise((resolve, reject) => { - groups = groups || []; - this.sendContactAddIQ(jid, name, groups, () => { - const contact = this.create(_.assignIn({ - 'ask': undefined, - 'nickname': name, - groups, - jid, - 'requesting': false, - 'subscription': 'none' - }, attributes), { - sort: false - }); - resolve(contact); - }, function (err) { - alert(__('Sorry, there was an error while trying to add %1$s as a contact.', name)); - - _converse.log(err, Strophe.LogLevel.ERROR); - - resolve(err); - }); - }); - }, - - subscribeBack(bare_jid, presence) { - const contact = this.get(bare_jid); - - if (contact instanceof _converse.RosterContact) { - contact.authorize().subscribe(); - } else { - // Can happen when a subscription is retried or roster was deleted - const handler = contact => { - if (contact instanceof _converse.RosterContact) { - contact.authorize().subscribe(); + resolve(); } - }; - - const nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null); - - this.addContactToRoster(bare_jid, nickname, [], { - 'subscription': 'from' - }).then(handler, handler); - } - }, - - getNumOnlineContacts() { - let ignored = ['offline', 'unavailable']; - - if (_converse.show_only_online_users) { - ignored = _.union(ignored, ['dnd', 'xa', 'away']); - } - - return _.sum(this.models.filter(model => !_.includes(ignored, model.presence.get('show')))); - }, - - onRosterPush(iq) { - /* Handle roster updates from the XMPP server. - * See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push - * - * Parameters: - * (XMLElement) IQ - The IQ stanza received from the XMPP server. - */ - const id = iq.getAttribute('id'); - const from = iq.getAttribute('from'); - - if (from && from !== _converse.bare_jid) { - // https://tools.ietf.org/html/rfc6121#page-15 - // - // A receiving client MUST ignore the stanza unless it has no 'from' - // attribute (i.e., implicitly from the bare JID of the user's - // account) or it has a 'from' attribute whose value matches the - // user's bare JID . - return; - } - - _converse.connection.send($iq({ - type: 'result', - id, - from: _converse.connection.jid - })); - - const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(); - this.data.save('version', query.getAttribute('ver')); - const items = sizzle(`item`, query); - - if (items.length > 1) { - _converse.log(iq, Strophe.LogLevel.ERROR); - - throw new Error('Roster push query may not contain more than one "item" element.'); - } - - if (items.length === 0) { - _converse.log(iq, Strophe.LogLevel.WARN); - - _converse.log('Received a roster push stanza without an "item" element.', Strophe.LogLevel.WARN); - - return; - } - - this.updateContact(items.pop()); - - _converse.emit('rosterPush', iq); - - return; - }, - - rosterVersioningSupported() { - return _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') && this.data.get('version'); - }, - - fetchFromServer() { - /* Fetch the roster from the XMPP server */ - return new Promise((resolve, reject) => { - const iq = $iq({ - 'type': 'get', - 'id': _converse.connection.getUniqueId('roster') - }).c('query', { - xmlns: Strophe.NS.ROSTER - }); - - if (this.rosterVersioningSupported()) { - iq.attrs({ - 'ver': this.data.get('version') - }); } - const callback = _.flow(this.onReceivedFromServer.bind(this), resolve); - - const errback = function errback(iq) { - const errmsg = "Error while trying to fetch roster from the server"; - - _converse.log(errmsg, Strophe.LogLevel.ERROR); - - reject(new Error(errmsg)); - }; - - return _converse.connection.sendIQ(iq, callback, errback); }); - }, + }); + }, - onReceivedFromServer(iq) { - /* An IQ stanza containing the roster has been received from - * the XMPP server. - */ - const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(); - - if (query) { - const items = sizzle(`item`, query); - - _.each(items, item => this.updateContact(item)); - - this.data.save('version', query.getAttribute('ver')); - - _converse.session.save('roster_fetched', true); + subscribeToSuggestedItems(msg) { + _.each(msg.querySelectorAll('item'), function (item) { + if (item.getAttribute('action') === 'add') { + _converse.roster.addAndSubscribe(item.getAttribute('jid'), _converse.xmppstatus.vcard.get('nickname') || _converse.xmppstatus.vcard.get('fullname')); } + }); - _converse.emit('roster', iq); - }, + return true; + }, - updateContact(item) { - /* Update or create RosterContact models based on items - * received in the IQ from the server. - */ - const jid = item.getAttribute('jid'); + isSelf(jid) { + return u.isSameBareJID(jid, _converse.connection.jid); + }, - if (this.isSelf(jid)) { - return; + addAndSubscribe(jid, name, groups, message, attributes) { + /* Add a roster contact and then once we have confirmation from + * the XMPP server we subscribe to that contact's presence updates. + * Parameters: + * (String) jid - The Jabber ID of the user being added and subscribed to. + * (String) name - The name of that user + * (Array of Strings) groups - Any roster groups the user might belong to + * (String) message - An optional message to explain the + * reason for the subscription request. + * (Object) attributes - Any additional attributes to be stored on the user's model. + */ + const handler = contact => { + if (contact instanceof _converse.RosterContact) { + contact.subscribe(message); } + }; - const contact = this.get(jid), - subscription = item.getAttribute("subscription"), - ask = item.getAttribute("ask"), - groups = _.map(item.getElementsByTagName('group'), Strophe.getText); + this.addContactToRoster(jid, name, groups, attributes).then(handler, handler); + }, - if (!contact) { - if (subscription === "none" && ask === null || subscription === "remove") { - return; // We're lazy when adding contacts. - } + sendContactAddIQ(jid, name, groups, callback, errback) { + /* Send an IQ stanza to the XMPP server to add a new roster contact. + * + * Parameters: + * (String) jid - The Jabber ID of the user being added + * (String) name - The name of that user + * (Array of Strings) groups - Any roster groups the user might belong to + * (Function) callback - A function to call once the IQ is returned + * (Function) errback - A function to call if an error occurred + */ + name = _.isEmpty(name) ? jid : name; + const iq = $iq({ + type: 'set' + }).c('query', { + xmlns: Strophe.NS.ROSTER + }).c('item', { + jid, + name + }); - this.create({ - 'ask': ask, - 'nickname': item.getAttribute("name"), - 'groups': groups, - 'jid': jid, - 'subscription': subscription - }, { + _.each(groups, function (group) { + iq.c('group').t(group).up(); + }); + + _converse.connection.sendIQ(iq, callback, errback); + }, + + addContactToRoster(jid, name, groups, attributes) { + /* Adds a RosterContact instance to _converse.roster and + * registers the contact on the XMPP server. + * Returns a promise which is resolved once the XMPP server has + * responded. + * + * Parameters: + * (String) jid - The Jabber ID of the user being added and subscribed to. + * (String) name - The name of that user + * (Array of Strings) groups - Any roster groups the user might belong to + * (Object) attributes - Any additional attributes to be stored on the user's model. + */ + return new Promise((resolve, reject) => { + groups = groups || []; + this.sendContactAddIQ(jid, name, groups, () => { + const contact = this.create(_.assignIn({ + 'ask': undefined, + 'nickname': name, + groups, + jid, + 'requesting': false, + 'subscription': 'none' + }, attributes), { sort: false }); - } else { - if (subscription === "remove") { - return contact.destroy(); - } // We only find out about requesting contacts via the - // presence handler, so if we receive a contact - // here, we know they aren't requesting anymore. - // see docs/DEVELOPER.rst + resolve(contact); + }, function (err) { + alert(__('Sorry, there was an error while trying to add %1$s as a contact.', name)); + _converse.log(err, Strophe.LogLevel.ERROR); - contact.save({ - 'subscription': subscription, - 'ask': ask, - 'requesting': null, - 'groups': groups - }); - } - }, + resolve(err); + }); + }); + }, - createRequestingContact(presence) { - const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')), - nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null); + subscribeBack(bare_jid, presence) { + const contact = this.get(bare_jid); - const user_data = { - 'jid': bare_jid, - 'subscription': 'none', - 'ask': null, - 'requesting': true, - 'nickname': nickname + if (contact instanceof _converse.RosterContact) { + contact.authorize().subscribe(); + } else { + // Can happen when a subscription is retried or roster was deleted + const handler = contact => { + if (contact instanceof _converse.RosterContact) { + contact.authorize().subscribe(); + } }; - _converse.emit('contactRequest', this.create(user_data)); - }, + const nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null); - handleIncomingSubscription(presence) { - const jid = presence.getAttribute('from'), - bare_jid = Strophe.getBareJidFromJid(jid), - contact = this.get(bare_jid); + this.addContactToRoster(bare_jid, nickname, [], { + 'subscription': 'from' + }).then(handler, handler); + } + }, - if (!_converse.allow_contact_requests) { - _converse.rejectPresenceSubscription(jid, __("This client does not allow presence subscriptions")); + getNumOnlineContacts() { + let ignored = ['offline', 'unavailable']; + + if (_converse.show_only_online_users) { + ignored = _.union(ignored, ['dnd', 'xa', 'away']); + } + + return _.sum(this.models.filter(model => !_.includes(ignored, model.presence.get('show')))); + }, + + onRosterPush(iq) { + /* Handle roster updates from the XMPP server. + * See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push + * + * Parameters: + * (XMLElement) IQ - The IQ stanza received from the XMPP server. + */ + const id = iq.getAttribute('id'); + const from = iq.getAttribute('from'); + + if (from && from !== _converse.bare_jid) { + // https://tools.ietf.org/html/rfc6121#page-15 + // + // A receiving client MUST ignore the stanza unless it has no 'from' + // attribute (i.e., implicitly from the bare JID of the user's + // account) or it has a 'from' attribute whose value matches the + // user's bare JID . + return; + } + + _converse.connection.send($iq({ + type: 'result', + id, + from: _converse.connection.jid + })); + + const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(); + this.data.save('version', query.getAttribute('ver')); + const items = sizzle(`item`, query); + + if (items.length > 1) { + _converse.log(iq, Strophe.LogLevel.ERROR); + + throw new Error('Roster push query may not contain more than one "item" element.'); + } + + if (items.length === 0) { + _converse.log(iq, Strophe.LogLevel.WARN); + + _converse.log('Received a roster push stanza without an "item" element.', Strophe.LogLevel.WARN); + + return; + } + + this.updateContact(items.pop()); + + _converse.emit('rosterPush', iq); + + return; + }, + + rosterVersioningSupported() { + return _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') && this.data.get('version'); + }, + + fetchFromServer() { + /* Fetch the roster from the XMPP server */ + return new Promise((resolve, reject) => { + const iq = $iq({ + 'type': 'get', + 'id': _converse.connection.getUniqueId('roster') + }).c('query', { + xmlns: Strophe.NS.ROSTER + }); + + if (this.rosterVersioningSupported()) { + iq.attrs({ + 'ver': this.data.get('version') + }); } - if (_converse.auto_subscribe) { - if (!contact || contact.get('subscription') !== 'to') { - this.subscribeBack(bare_jid, presence); - } else { + const callback = _.flow(this.onReceivedFromServer.bind(this), resolve); + + const errback = function errback(iq) { + const errmsg = "Error while trying to fetch roster from the server"; + + _converse.log(errmsg, Strophe.LogLevel.ERROR); + + reject(new Error(errmsg)); + }; + + return _converse.connection.sendIQ(iq, callback, errback); + }); + }, + + onReceivedFromServer(iq) { + /* An IQ stanza containing the roster has been received from + * the XMPP server. + */ + const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(); + + if (query) { + const items = sizzle(`item`, query); + + _.each(items, item => this.updateContact(item)); + + this.data.save('version', query.getAttribute('ver')); + + _converse.session.save('roster_fetched', true); + } + + _converse.emit('roster', iq); + }, + + updateContact(item) { + /* Update or create RosterContact models based on items + * received in the IQ from the server. + */ + const jid = item.getAttribute('jid'); + + if (this.isSelf(jid)) { + return; + } + + const contact = this.get(jid), + subscription = item.getAttribute("subscription"), + ask = item.getAttribute("ask"), + groups = _.map(item.getElementsByTagName('group'), Strophe.getText); + + if (!contact) { + if (subscription === "none" && ask === null || subscription === "remove") { + return; // We're lazy when adding contacts. + } + + this.create({ + 'ask': ask, + 'nickname': item.getAttribute("name"), + 'groups': groups, + 'jid': jid, + 'subscription': subscription + }, { + sort: false + }); + } else { + if (subscription === "remove") { + return contact.destroy(); + } // We only find out about requesting contacts via the + // presence handler, so if we receive a contact + // here, we know they aren't requesting anymore. + // see docs/DEVELOPER.rst + + + contact.save({ + 'subscription': subscription, + 'ask': ask, + 'requesting': null, + 'groups': groups + }); + } + }, + + createRequestingContact(presence) { + const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')), + nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null); + + const user_data = { + 'jid': bare_jid, + 'subscription': 'none', + 'ask': null, + 'requesting': true, + 'nickname': nickname + }; + + _converse.emit('contactRequest', this.create(user_data)); + }, + + handleIncomingSubscription(presence) { + const jid = presence.getAttribute('from'), + bare_jid = Strophe.getBareJidFromJid(jid), + contact = this.get(bare_jid); + + if (!_converse.allow_contact_requests) { + _converse.rejectPresenceSubscription(jid, __("This client does not allow presence subscriptions")); + } + + if (_converse.auto_subscribe) { + if (!contact || contact.get('subscription') !== 'to') { + this.subscribeBack(bare_jid, presence); + } else { + contact.authorize(); + } + } else { + if (contact) { + if (contact.get('subscription') !== 'none') { + contact.authorize(); + } else if (contact.get('ask') === "subscribe") { contact.authorize(); } } else { - if (contact) { - if (contact.get('subscription') !== 'none') { - contact.authorize(); - } else if (contact.get('ask') === "subscribe") { - contact.authorize(); - } - } else { - this.createRequestingContact(presence); - } + this.createRequestingContact(presence); } - }, + } + }, - handleOwnPresence(presence) { - const jid = presence.getAttribute('from'), - resource = Strophe.getResourceFromJid(jid), - presence_type = presence.getAttribute('type'); + handleOwnPresence(presence) { + const jid = presence.getAttribute('from'), + resource = Strophe.getResourceFromJid(jid), + presence_type = presence.getAttribute('type'); - if (_converse.connection.jid !== jid && presence_type !== 'unavailable' && (_converse.synchronize_availability === true || _converse.synchronize_availability === resource)) { - // Another resource has changed its status and - // synchronize_availability option set to update, - // we'll update ours as well. - const show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online'; + if (_converse.connection.jid !== jid && presence_type !== 'unavailable' && (_converse.synchronize_availability === true || _converse.synchronize_availability === resource)) { + // Another resource has changed its status and + // synchronize_availability option set to update, + // we'll update ours as well. + const show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online'; + _converse.xmppstatus.save({ + 'status': show + }, { + 'silent': true + }); + + const status_message = _.propertyOf(presence.querySelector('status'))('textContent'); + + if (status_message) { _converse.xmppstatus.save({ - 'status': show - }, { - 'silent': true - }); - - const status_message = _.propertyOf(presence.querySelector('status'))('textContent'); - - if (status_message) { - _converse.xmppstatus.save({ - 'status_message': status_message - }); - } - } - - if (_converse.jid === jid && presence_type === 'unavailable') { - // XXX: We've received an "unavailable" presence from our - // own resource. Apparently this happens due to a - // Prosody bug, whereby we send an IQ stanza to remove - // a roster contact, and Prosody then sends - // "unavailable" globally, instead of directed to the - // particular user that's removed. - // - // Here is the bug report: https://prosody.im/issues/1121 - // - // I'm not sure whether this might legitimately happen - // in other cases. - // - // As a workaround for now we simply send our presence again, - // otherwise we're treated as offline. - _converse.xmppstatus.sendPresence(); - } - }, - - presenceHandler(presence) { - const presence_type = presence.getAttribute('type'); - - if (presence_type === 'error') { - return true; - } - - const jid = presence.getAttribute('from'), - bare_jid = Strophe.getBareJidFromJid(jid); - - if (this.isSelf(bare_jid)) { - return this.handleOwnPresence(presence); - } else if (sizzle(`query[xmlns="${Strophe.NS.MUC}"]`, presence).length) { - return; // Ignore MUC - } - - const status_message = _.propertyOf(presence.querySelector('status'))('textContent'), - contact = this.get(bare_jid); - - if (contact && status_message !== contact.get('status')) { - contact.save({ - 'status': status_message + 'status_message': status_message }); } - - if (presence_type === 'subscribed' && contact) { - contact.ackSubscribe(); - } else if (presence_type === 'unsubscribed' && contact) { - contact.ackUnsubscribe(); - } else if (presence_type === 'unsubscribe') { - return; - } else if (presence_type === 'subscribe') { - this.handleIncomingSubscription(presence); - } else if (presence_type === 'unavailable' && contact) { - const resource = Strophe.getResourceFromJid(jid); - contact.presence.removeResource(resource); - } else if (contact) { - // presence_type is undefined - contact.presence.addResource(presence); - } } - }); - _converse.RosterGroup = Backbone.Model.extend({ - initialize(attributes) { - this.set(_.assignIn({ - description: __('Click to hide these contacts'), - state: _converse.OPENED - }, attributes)); // Collection of contacts belonging to this group. + if (_converse.jid === jid && presence_type === 'unavailable') { + // XXX: We've received an "unavailable" presence from our + // own resource. Apparently this happens due to a + // Prosody bug, whereby we send an IQ stanza to remove + // a roster contact, and Prosody then sends + // "unavailable" globally, instead of directed to the + // particular user that's removed. + // + // Here is the bug report: https://prosody.im/issues/1121 + // + // I'm not sure whether this might legitimately happen + // in other cases. + // + // As a workaround for now we simply send our presence again, + // otherwise we're treated as offline. + _converse.xmppstatus.sendPresence(); + } + }, - this.contacts = new _converse.RosterContacts(); + presenceHandler(presence) { + const presence_type = presence.getAttribute('type'); + + if (presence_type === 'error') { + return true; } - }); - _converse.RosterGroups = Backbone.Collection.extend({ - model: _converse.RosterGroup, + const jid = presence.getAttribute('from'), + bare_jid = Strophe.getBareJidFromJid(jid); - fetchRosterGroups() { - /* Fetches all the roster groups from sessionStorage. - * - * Returns a promise which resolves once the groups have been - * returned. - */ - return new Promise((resolve, reject) => { - this.fetch({ - silent: true, - // We need to first have all groups before - // we can start positioning them, so we set - // 'silent' to true. - success: resolve - }); - }); + if (this.isSelf(bare_jid)) { + return this.handleOwnPresence(presence); + } else if (sizzle(`query[xmlns="${Strophe.NS.MUC}"]`, presence).length) { + return; // Ignore MUC } - }); + const status_message = _.propertyOf(presence.querySelector('status'))('textContent'), + contact = this.get(bare_jid); - _converse.unregisterPresenceHandler = function () { - if (!_.isUndefined(_converse.presence_ref)) { - _converse.connection.deleteHandler(_converse.presence_ref); - - delete _converse.presence_ref; - } - }; - /********** Event Handlers *************/ - - - function updateUnreadCounter(chatbox) { - const contact = _converse.roster.findWhere({ - 'jid': chatbox.get('jid') - }); - - if (!_.isUndefined(contact)) { + if (contact && status_message !== contact.get('status')) { contact.save({ - 'num_unread': chatbox.get('num_unread') + 'status': status_message }); } + + if (presence_type === 'subscribed' && contact) { + contact.ackSubscribe(); + } else if (presence_type === 'unsubscribed' && contact) { + contact.ackUnsubscribe(); + } else if (presence_type === 'unsubscribe') { + return; + } else if (presence_type === 'subscribe') { + this.handleIncomingSubscription(presence); + } else if (presence_type === 'unavailable' && contact) { + const resource = Strophe.getResourceFromJid(jid); + contact.presence.removeResource(resource); + } else if (contact) { + // presence_type is undefined + contact.presence.addResource(presence); + } } - _converse.api.listen.on('chatBoxesInitialized', () => { - _converse.chatboxes.on('change:num_unread', updateUnreadCounter); + }); + _converse.RosterGroup = Backbone.Model.extend({ + initialize(attributes) { + this.set(_.assignIn({ + description: __('Click to hide these contacts'), + state: _converse.OPENED + }, attributes)); // Collection of contacts belonging to this group. + + this.contacts = new _converse.RosterContacts(); + } + + }); + _converse.RosterGroups = Backbone.Collection.extend({ + model: _converse.RosterGroup, + + fetchRosterGroups() { + /* Fetches all the roster groups from sessionStorage. + * + * Returns a promise which resolves once the groups have been + * returned. + */ + return new Promise((resolve, reject) => { + this.fetch({ + silent: true, + // We need to first have all groups before + // we can start positioning them, so we set + // 'silent' to true. + success: resolve + }); + }); + } + + }); + + _converse.unregisterPresenceHandler = function () { + if (!_.isUndefined(_converse.presence_ref)) { + _converse.connection.deleteHandler(_converse.presence_ref); + + delete _converse.presence_ref; + } + }; + /********** Event Handlers *************/ + + + function updateUnreadCounter(chatbox) { + const contact = _converse.roster.findWhere({ + 'jid': chatbox.get('jid') }); - _converse.api.listen.on('beforeTearDown', _converse.unregisterPresenceHandler()); - - _converse.api.listen.on('afterTearDown', () => { - if (_converse.presences) { - _converse.presences.off().reset(); // Remove presences - - } - }); - - _converse.api.listen.on('clearSession', () => { - if (_converse.presences) { - _converse.presences.browserStorage._clear(); - } - }); - - _converse.api.listen.on('statusInitialized', reconnecting => { - if (!reconnecting) { - _converse.presences = new _converse.Presences(); - _converse.presences.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`)); - - _converse.presences.fetch(); - } - - _converse.emit('presencesInitialized', reconnecting); - }); - - _converse.api.listen.on('presencesInitialized', reconnecting => { - if (reconnecting) { - // No need to recreate the roster, otherwise we lose our - // cached data. However we still emit an event, to give - // event handlers a chance to register views for the - // roster and its groups, before we start populating. - _converse.emit('rosterReadyAfterReconnection'); - } else { - _converse.registerIntervalHandler(); - - _converse.initRoster(); - } - - _converse.roster.onConnected(); - - _converse.populateRoster(reconnecting); - - _converse.registerPresenceHandler(); - }); - /************************ API ************************/ - // API methods only available to plugins - - - _.extend(_converse.api, { - /** - * @namespace _converse.api.contacts - * @memberOf _converse.api - */ - 'contacts': { - /** - * This method is used to retrieve roster contacts. - * - * @method _converse.api.contacts.get - * @params {(string[]|string)} jid|jids The JID or JIDs of - * the contacts to be returned. - * @returns {(RosterContact[]|RosterContact)} [Backbone.Model](http://backbonejs.org/#Model) - * (or an array of them) representing the contact. - * - * @example - * // Fetch a single contact - * _converse.api.listen.on('rosterContactsFetched', function () { - * const contact = _converse.api.contacts.get('buddy@example.com') - * // ... - * }); - * - * @example - * // To get multiple contacts, pass in an array of JIDs: - * _converse.api.listen.on('rosterContactsFetched', function () { - * const contacts = _converse.api.contacts.get( - * ['buddy1@example.com', 'buddy2@example.com'] - * ) - * // ... - * }); - * - * @example - * // To return all contacts, simply call ``get`` without any parameters: - * _converse.api.listen.on('rosterContactsFetched', function () { - * const contacts = _converse.api.contacts.get(); - * // ... - * }); - */ - 'get'(jids) { - const _getter = function _getter(jid) { - return _converse.roster.get(Strophe.getBareJidFromJid(jid)) || null; - }; - - if (_.isUndefined(jids)) { - jids = _converse.roster.pluck('jid'); - } else if (_.isString(jids)) { - return _getter(jids); - } - - return _.map(jids, _getter); - }, - - /** - * Add a contact. - * - * @method _converse.api.contacts.add - * @param {string} jid The JID of the contact to be added - * @param {string} [name] A custom name to show the user by - * in the roster. - * @example - * _converse.api.contacts.add('buddy@example.com') - * @example - * _converse.api.contacts.add('buddy@example.com', 'Buddy') - */ - 'add'(jid, name) { - if (!_.isString(jid) || !_.includes(jid, '@')) { - throw new TypeError('contacts.add: invalid jid'); - } - - _converse.roster.addAndSubscribe(jid, _.isEmpty(name) ? jid : name); - } - - } - }); + if (!_.isUndefined(contact)) { + contact.save({ + 'num_unread': chatbox.get('num_unread') + }); + } } - }); + _converse.api.listen.on('chatBoxesInitialized', () => { + _converse.chatboxes.on('change:num_unread', updateUnreadCounter); + }); + + _converse.api.listen.on('beforeTearDown', _converse.unregisterPresenceHandler()); + + _converse.api.listen.on('afterTearDown', () => { + if (_converse.presences) { + _converse.presences.off().reset(); // Remove presences + + } + }); + + _converse.api.listen.on('clearSession', () => { + if (_converse.presences) { + _converse.presences.browserStorage._clear(); + } + }); + + _converse.api.listen.on('statusInitialized', reconnecting => { + if (!reconnecting) { + _converse.presences = new _converse.Presences(); + _converse.presences.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`)); + + _converse.presences.fetch(); + } + + _converse.emit('presencesInitialized', reconnecting); + }); + + _converse.api.listen.on('presencesInitialized', reconnecting => { + if (reconnecting) { + // No need to recreate the roster, otherwise we lose our + // cached data. However we still emit an event, to give + // event handlers a chance to register views for the + // roster and its groups, before we start populating. + _converse.emit('rosterReadyAfterReconnection'); + } else { + _converse.registerIntervalHandler(); + + _converse.initRoster(); + } + + _converse.roster.onConnected(); + + _converse.populateRoster(reconnecting); + + _converse.registerPresenceHandler(); + }); + /************************ API ************************/ + // API methods only available to plugins + + + _.extend(_converse.api, { + /** + * @namespace _converse.api.contacts + * @memberOf _converse.api + */ + 'contacts': { + /** + * This method is used to retrieve roster contacts. + * + * @method _converse.api.contacts.get + * @params {(string[]|string)} jid|jids The JID or JIDs of + * the contacts to be returned. + * @returns {(RosterContact[]|RosterContact)} [Backbone.Model](http://backbonejs.org/#Model) + * (or an array of them) representing the contact. + * + * @example + * // Fetch a single contact + * _converse.api.listen.on('rosterContactsFetched', function () { + * const contact = _converse.api.contacts.get('buddy@example.com') + * // ... + * }); + * + * @example + * // To get multiple contacts, pass in an array of JIDs: + * _converse.api.listen.on('rosterContactsFetched', function () { + * const contacts = _converse.api.contacts.get( + * ['buddy1@example.com', 'buddy2@example.com'] + * ) + * // ... + * }); + * + * @example + * // To return all contacts, simply call ``get`` without any parameters: + * _converse.api.listen.on('rosterContactsFetched', function () { + * const contacts = _converse.api.contacts.get(); + * // ... + * }); + */ + 'get'(jids) { + const _getter = function _getter(jid) { + return _converse.roster.get(Strophe.getBareJidFromJid(jid)) || null; + }; + + if (_.isUndefined(jids)) { + jids = _converse.roster.pluck('jid'); + } else if (_.isString(jids)) { + return _getter(jids); + } + + return _.map(jids, _getter); + }, + + /** + * Add a contact. + * + * @method _converse.api.contacts.add + * @param {string} jid The JID of the contact to be added + * @param {string} [name] A custom name to show the user by + * in the roster. + * @example + * _converse.api.contacts.add('buddy@example.com') + * @example + * _converse.api.contacts.add('buddy@example.com', 'Buddy') + */ + 'add'(jid, name) { + if (!_.isString(jid) || !_.includes(jid, '@')) { + throw new TypeError('contacts.add: invalid jid'); + } + + _converse.roster.addAndSubscribe(jid, _.isEmpty(name) ? jid : name); + } + + } + }); + } + }); /***/ }), @@ -68805,1094 +69015,1123 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!************************************!*\ !*** ./src/converse-rosterview.js ***! \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_chatboxes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-chatboxes */ "./src/headless/converse-chatboxes.js"); +/* harmony import */ var converse_modal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! converse-modal */ "./src/converse-modal.js"); +/* harmony import */ var awesomplete__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! awesomplete */ "./node_modules/awesomplete-avoid-xss/awesomplete.js"); +/* harmony import */ var awesomplete__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(awesomplete__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"); +/* harmony import */ var formdata_polyfill__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(formdata_polyfill__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var templates_add_contact_modal_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! templates/add_contact_modal.html */ "./src/templates/add_contact_modal.html"); +/* harmony import */ var templates_add_contact_modal_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(templates_add_contact_modal_html__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var templates_group_header_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! templates/group_header.html */ "./src/templates/group_header.html"); +/* harmony import */ var templates_group_header_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(templates_group_header_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var templates_pending_contact_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! templates/pending_contact.html */ "./src/templates/pending_contact.html"); +/* harmony import */ var templates_pending_contact_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(templates_pending_contact_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var templates_requesting_contact_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! templates/requesting_contact.html */ "./src/templates/requesting_contact.html"); +/* harmony import */ var templates_requesting_contact_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(templates_requesting_contact_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var templates_roster_html__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! templates/roster.html */ "./src/templates/roster.html"); +/* harmony import */ var templates_roster_html__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(templates_roster_html__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var templates_roster_filter_html__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! templates/roster_filter.html */ "./src/templates/roster_filter.html"); +/* harmony import */ var templates_roster_filter_html__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(templates_roster_filter_html__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var templates_roster_item_html__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! templates/roster_item.html */ "./src/templates/roster_item.html"); +/* harmony import */ var templates_roster_item_html__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(templates_roster_item_html__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var templates_search_contact_html__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! templates/search_contact.html */ "./src/templates/search_contact.html"); +/* harmony import */ var templates_search_contact_html__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(templates_search_contact_html__WEBPACK_IMPORTED_MODULE_12__); +// Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! formdata-polyfill */ "./node_modules/formdata-polyfill/FormData.js"), __webpack_require__(/*! templates/add_contact_modal.html */ "./src/templates/add_contact_modal.html"), __webpack_require__(/*! templates/group_header.html */ "./src/templates/group_header.html"), __webpack_require__(/*! templates/pending_contact.html */ "./src/templates/pending_contact.html"), __webpack_require__(/*! templates/requesting_contact.html */ "./src/templates/requesting_contact.html"), __webpack_require__(/*! templates/roster.html */ "./src/templates/roster.html"), __webpack_require__(/*! templates/roster_filter.html */ "./src/templates/roster_filter.html"), __webpack_require__(/*! templates/roster_item.html */ "./src/templates/roster_item.html"), __webpack_require__(/*! templates/search_contact.html */ "./src/templates/search_contact.html"), __webpack_require__(/*! awesomplete */ "./node_modules/awesomplete-avoid-xss/awesomplete.js"), __webpack_require__(/*! @converse/headless/converse-chatboxes */ "./src/headless/converse-chatboxes.js"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, _FormData, tpl_add_contact_modal, tpl_group_header, tpl_pending_contact, tpl_requesting_contact, tpl_roster, tpl_roster_filter, tpl_roster_item, tpl_search_contact, Awesomplete) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Strophe = _converse$env.Strophe, - $iq = _converse$env.$iq, - b64_sha1 = _converse$env.b64_sha1, - sizzle = _converse$env.sizzle, - _ = _converse$env._; - const u = converse.env.utils; - converse.plugins.add('converse-rosterview', { - dependencies: ["converse-roster", "converse-modal"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - afterReconnected() { - this.__super__.afterReconnected.apply(this, arguments); - }, - tearDown() { - /* Remove the rosterview when tearing down. It gets created - * anew when reconnecting or logging in. - */ - this.__super__.tearDown.apply(this, arguments); - if (!_.isUndefined(this.rosterview)) { - this.rosterview.remove(); - } - }, - RosterGroups: { - comparator() { - // RosterGroupsComparator only gets set later (once i18n is - // set up), so we need to wrap it in this nameless function. - const _converse = this.__super__._converse; - return _converse.RosterGroupsComparator.apply(this, arguments); - } + + + + + + + + +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].env, + Backbone = _converse$env.Backbone, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq, + b64_sha1 = _converse$env.b64_sha1, + sizzle = _converse$env.sizzle, + _ = _converse$env._; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].env.utils; +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins.add('converse-rosterview', { + dependencies: ["converse-roster", "converse-modal"], + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + afterReconnected() { + this.__super__.afterReconnected.apply(this, arguments); + }, + + tearDown() { + /* Remove the rosterview when tearing down. It gets created + * anew when reconnecting or logging in. + */ + this.__super__.tearDown.apply(this, arguments); + + if (!_.isUndefined(this.rosterview)) { + this.rosterview.remove(); } }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. + RosterGroups: { + comparator() { + // RosterGroupsComparator only gets set later (once i18n is + // set up), so we need to wrap it in this nameless function. + const _converse = this.__super__._converse; + return _converse.RosterGroupsComparator.apply(this, arguments); + } + + } + }, + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; + + _converse.api.settings.update({ + 'allow_chat_pending_contacts': true, + 'allow_contact_removal': true, + 'hide_offline_users': false, + 'roster_groups': true, + 'show_only_online_users': false, + 'show_toolbar': true, + 'xhr_user_search_url': null + }); + + _converse.api.promises.add('rosterViewInitialized'); + + const STATUSES = { + 'dnd': __('This contact is busy'), + 'online': __('This contact is online'), + 'offline': __('This contact is offline'), + 'unavailable': __('This contact is unavailable'), + 'xa': __('This contact is away for an extended period'), + 'away': __('This contact is away') + }; + + const LABEL_GROUPS = __('Groups'); + + const HEADER_CURRENT_CONTACTS = __('My contacts'); + + const HEADER_PENDING_CONTACTS = __('Pending contacts'); + + const HEADER_REQUESTING_CONTACTS = __('Contact requests'); + + const HEADER_UNGROUPED = __('Ungrouped'); + + const HEADER_WEIGHTS = {}; + HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 0; + HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 1; + HEADER_WEIGHTS[HEADER_UNGROUPED] = 2; + HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 3; + + _converse.RosterGroupsComparator = function (a, b) { + /* Groups are sorted alphabetically, ignoring case. + * However, Ungrouped, Requesting Contacts and Pending Contacts + * appear last and in that order. */ - const _converse = this._converse, - __ = _converse.__; + a = a.get('name'); + b = b.get('name'); - _converse.api.settings.update({ - 'allow_chat_pending_contacts': true, - 'allow_contact_removal': true, - 'hide_offline_users': false, - 'roster_groups': true, - 'show_only_online_users': false, - 'show_toolbar': true, - 'xhr_user_search_url': null - }); + const special_groups = _.keys(HEADER_WEIGHTS); - _converse.api.promises.add('rosterViewInitialized'); + const a_is_special = _.includes(special_groups, a); - const STATUSES = { - 'dnd': __('This contact is busy'), - 'online': __('This contact is online'), - 'offline': __('This contact is offline'), - 'unavailable': __('This contact is unavailable'), - 'xa': __('This contact is away for an extended period'), - 'away': __('This contact is away') - }; + const b_is_special = _.includes(special_groups, b); - const LABEL_GROUPS = __('Groups'); + if (!a_is_special && !b_is_special) { + return a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0; + } else if (a_is_special && b_is_special) { + return HEADER_WEIGHTS[a] < HEADER_WEIGHTS[b] ? -1 : HEADER_WEIGHTS[a] > HEADER_WEIGHTS[b] ? 1 : 0; + } else if (!a_is_special && b_is_special) { + return b === HEADER_REQUESTING_CONTACTS ? 1 : -1; + } else if (a_is_special && !b_is_special) { + return a === HEADER_REQUESTING_CONTACTS ? -1 : 1; + } + }; - const HEADER_CURRENT_CONTACTS = __('My contacts'); + _converse.AddContactModal = _converse.BootstrapModal.extend({ + events: { + 'submit form': 'addContactFromForm' + }, - const HEADER_PENDING_CONTACTS = __('Pending contacts'); + initialize() { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - const HEADER_REQUESTING_CONTACTS = __('Contact requests'); + this.model.on('change', this.render, this); + }, - const HEADER_UNGROUPED = __('Ungrouped'); + toHTML() { + const label_nickname = _converse.xhr_user_search_url ? __('Contact name') : __('Optional nickname'); + return templates_add_contact_modal_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), { + '_converse': _converse, + 'heading_new_contact': __('Add a Contact'), + 'label_xmpp_address': __('XMPP Address'), + 'label_nickname': label_nickname, + 'contact_placeholder': __('name@example.org'), + 'label_add': __('Add'), + 'error_message': __('Please enter a valid XMPP address') + })); + }, - const HEADER_WEIGHTS = {}; - HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 0; - HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 1; - HEADER_WEIGHTS[HEADER_UNGROUPED] = 2; - HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 3; - - _converse.RosterGroupsComparator = function (a, b) { - /* Groups are sorted alphabetically, ignoring case. - * However, Ungrouped, Requesting Contacts and Pending Contacts - * appear last and in that order. - */ - a = a.get('name'); - b = b.get('name'); - - const special_groups = _.keys(HEADER_WEIGHTS); - - const a_is_special = _.includes(special_groups, a); - - const b_is_special = _.includes(special_groups, b); - - if (!a_is_special && !b_is_special) { - return a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0; - } else if (a_is_special && b_is_special) { - return HEADER_WEIGHTS[a] < HEADER_WEIGHTS[b] ? -1 : HEADER_WEIGHTS[a] > HEADER_WEIGHTS[b] ? 1 : 0; - } else if (!a_is_special && b_is_special) { - return b === HEADER_REQUESTING_CONTACTS ? 1 : -1; - } else if (a_is_special && !b_is_special) { - return a === HEADER_REQUESTING_CONTACTS ? -1 : 1; + afterRender() { + if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) { + this.initXHRAutoComplete(this.el); + } else { + this.initJIDAutoComplete(this.el); } - }; - _converse.AddContactModal = _converse.BootstrapModal.extend({ - events: { - 'submit form': 'addContactFromForm' - }, + const jid_input = this.el.querySelector('input[name="jid"]'); + this.el.addEventListener('shown.bs.modal', () => { + jid_input.focus(); + }, false); + }, - initialize() { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + initJIDAutoComplete(root) { + const jid_input = root.querySelector('input[name="jid"]'); - this.model.on('change', this.render, this); - }, + const list = _.uniq(_converse.roster.map(item => Strophe.getDomainFromJid(item.get('jid')))); - toHTML() { - const label_nickname = _converse.xhr_user_search_url ? __('Contact name') : __('Optional nickname'); - return tpl_add_contact_modal(_.extend(this.model.toJSON(), { - '_converse': _converse, - 'heading_new_contact': __('Add a Contact'), - 'label_xmpp_address': __('XMPP Address'), - 'label_nickname': label_nickname, - 'contact_placeholder': __('name@example.org'), - 'label_add': __('Add'), - 'error_message': __('Please enter a valid XMPP address') - })); - }, + new awesomplete__WEBPACK_IMPORTED_MODULE_2___default.a(jid_input, { + 'list': list, + 'data': function data(text, input) { + return input.slice(0, input.indexOf("@")) + "@" + text; + }, + 'filter': awesomplete__WEBPACK_IMPORTED_MODULE_2___default.a.FILTER_STARTSWITH + }); + }, - afterRender() { - if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) { - this.initXHRAutoComplete(this.el); - } else { - this.initJIDAutoComplete(this.el); + initXHRAutoComplete(root) { + const name_input = this.el.querySelector('input[name="name"]'); + const jid_input = this.el.querySelector('input[name="jid"]'); + const awesomplete = new awesomplete__WEBPACK_IMPORTED_MODULE_2___default.a(name_input, { + 'minChars': 1, + 'list': [] + }); + const xhr = new window.XMLHttpRequest(); // `open` must be called after `onload` for mock/testing purposes. + + xhr.onload = function () { + if (xhr.responseText) { + awesomplete.list = JSON.parse(xhr.responseText).map(i => { + //eslint-disable-line arrow-body-style + return { + 'label': i.fullname || i.jid, + 'value': i.jid + }; + }); + awesomplete.evaluate(); } + }; - const jid_input = this.el.querySelector('input[name="jid"]'); - this.el.addEventListener('shown.bs.modal', () => { - jid_input.focus(); - }, false); - }, + name_input.addEventListener('input', _.debounce(() => { + xhr.open("GET", `${_converse.xhr_user_search_url}q=${name_input.value}`, true); + xhr.send(); + }, 300)); + this.el.addEventListener('awesomplete-selectcomplete', ev => { + jid_input.value = ev.text.value; + name_input.value = ev.text.label; + }); + }, - initJIDAutoComplete(root) { - const jid_input = root.querySelector('input[name="jid"]'); + addContactFromForm(ev) { + ev.preventDefault(); + const data = new FormData(ev.target), + jid = data.get('jid'), + name = data.get('name'); - const list = _.uniq(_converse.roster.map(item => Strophe.getDomainFromJid(item.get('jid')))); + if (!jid || _.compact(jid.split('@')).length < 2) { + // XXX: we have to do this manually, instead of via + // toHTML because Awesomplete messes things up and + // confuses Snabbdom + u.addClass('is-invalid', this.el.querySelector('input[name="jid"]')); + u.addClass('d-block', this.el.querySelector('.invalid-feedback')); + } else { + ev.target.reset(); - new Awesomplete(jid_input, { - 'list': list, - 'data': function data(text, input) { - return input.slice(0, input.indexOf("@")) + "@" + text; - }, - 'filter': Awesomplete.FILTER_STARTSWITH - }); - }, + _converse.roster.addAndSubscribe(jid, name); - initXHRAutoComplete(root) { - const name_input = this.el.querySelector('input[name="name"]'); - const jid_input = this.el.querySelector('input[name="jid"]'); - const awesomplete = new Awesomplete(name_input, { - 'minChars': 1, - 'list': [] - }); - const xhr = new window.XMLHttpRequest(); // `open` must be called after `onload` for mock/testing purposes. + this.model.clear(); + this.modal.hide(); + } + } - xhr.onload = function () { - if (xhr.responseText) { - awesomplete.list = JSON.parse(xhr.responseText).map(i => { - //eslint-disable-line arrow-body-style - return { - 'label': i.fullname || i.jid, - 'value': i.jid - }; - }); - awesomplete.evaluate(); - } - }; + }); + _converse.RosterFilter = Backbone.Model.extend({ + initialize() { + this.set({ + 'filter_text': '', + 'filter_type': 'contacts', + 'chat_state': '' + }); + } - name_input.addEventListener('input', _.debounce(() => { - xhr.open("GET", `${_converse.xhr_user_search_url}q=${name_input.value}`, true); - xhr.send(); - }, 300)); - this.el.addEventListener('awesomplete-selectcomplete', ev => { - jid_input.value = ev.text.value; - name_input.value = ev.text.label; - }); - }, + }); + _converse.RosterFilterView = Backbone.VDOMView.extend({ + tagName: 'form', + className: 'roster-filter-form', + events: { + "keydown .roster-filter": "liveFilter", + "submit form.roster-filter-form": "submitFilter", + "click .clear-input": "clearFilter", + "click .filter-by span": "changeTypeFilter", + "change .state-type": "changeChatStateFilter" + }, - addContactFromForm(ev) { + initialize() { + this.model.on('change:filter_type', this.render, this); + this.model.on('change:filter_text', this.render, this); + }, + + toHTML() { + return templates_roster_filter_html__WEBPACK_IMPORTED_MODULE_10___default()(_.extend(this.model.toJSON(), { + visible: this.shouldBeVisible(), + placeholder: __('Filter'), + title_contact_filter: __('Filter by contact name'), + title_group_filter: __('Filter by group name'), + title_status_filter: __('Filter by status'), + label_any: __('Any'), + label_unread_messages: __('Unread'), + label_online: __('Online'), + label_chatty: __('Chatty'), + label_busy: __('Busy'), + label_away: __('Away'), + label_xa: __('Extended Away'), + label_offline: __('Offline') + })); + }, + + changeChatStateFilter(ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); - const data = new FormData(ev.target), - jid = data.get('jid'), - name = data.get('name'); - - if (!jid || _.compact(jid.split('@')).length < 2) { - // XXX: we have to do this manually, instead of via - // toHTML because Awesomplete messes things up and - // confuses Snabbdom - u.addClass('is-invalid', this.el.querySelector('input[name="jid"]')); - u.addClass('d-block', this.el.querySelector('.invalid-feedback')); - } else { - ev.target.reset(); - - _converse.roster.addAndSubscribe(jid, name); - - this.model.clear(); - this.modal.hide(); - } } - }); - _converse.RosterFilter = Backbone.Model.extend({ - initialize() { - this.set({ - 'filter_text': '', - 'filter_type': 'contacts', - 'chat_state': '' - }); + this.model.save({ + 'chat_state': this.el.querySelector('.state-type').value + }); + }, + + changeTypeFilter(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); } - }); - _converse.RosterFilterView = Backbone.VDOMView.extend({ - tagName: 'form', - className: 'roster-filter-form', - events: { - "keydown .roster-filter": "liveFilter", - "submit form.roster-filter-form": "submitFilter", - "click .clear-input": "clearFilter", - "click .filter-by span": "changeTypeFilter", - "change .state-type": "changeChatStateFilter" - }, - - initialize() { - this.model.on('change:filter_type', this.render, this); - this.model.on('change:filter_text', this.render, this); - }, - - toHTML() { - return tpl_roster_filter(_.extend(this.model.toJSON(), { - visible: this.shouldBeVisible(), - placeholder: __('Filter'), - title_contact_filter: __('Filter by contact name'), - title_group_filter: __('Filter by group name'), - title_status_filter: __('Filter by status'), - label_any: __('Any'), - label_unread_messages: __('Unread'), - label_online: __('Online'), - label_chatty: __('Chatty'), - label_busy: __('Busy'), - label_away: __('Away'), - label_xa: __('Extended Away'), - label_offline: __('Offline') - })); - }, - - changeChatStateFilter(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } + const type = ev.target.dataset.type; + if (type === 'state') { this.model.save({ + 'filter_type': type, 'chat_state': this.el.querySelector('.state-type').value }); - }, - - changeTypeFilter(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const type = ev.target.dataset.type; - - if (type === 'state') { - this.model.save({ - 'filter_type': type, - 'chat_state': this.el.querySelector('.state-type').value - }); - } else { - this.model.save({ - 'filter_type': type, - 'filter_text': this.el.querySelector('.roster-filter').value - }); - } - }, - - liveFilter: _.debounce(function (ev) { + } else { this.model.save({ + 'filter_type': type, 'filter_text': this.el.querySelector('.roster-filter').value }); - }, 250), + } + }, - submitFilter(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); + liveFilter: _.debounce(function (ev) { + this.model.save({ + 'filter_text': this.el.querySelector('.roster-filter').value + }); + }, 250), + + submitFilter(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + this.liveFilter(); + this.render(); + }, + + isActive() { + /* Returns true if the filter is enabled (i.e. if the user + * has added values to the filter). + */ + if (this.model.get('filter_type') === 'state' || this.model.get('filter_text')) { + return true; + } + + return false; + }, + + shouldBeVisible() { + return _converse.roster.length >= 5 || this.isActive(); + }, + + showOrHide() { + if (this.shouldBeVisible()) { + this.show(); + } else { + this.hide(); + } + }, + + show() { + if (u.isVisible(this.el)) { + return this; + } + + this.el.classList.add('fade-in'); + this.el.classList.remove('hidden'); + return this; + }, + + hide() { + if (!u.isVisible(this.el)) { + return this; + } + + this.model.save({ + 'filter_text': '', + 'chat_state': '' + }); + this.el.classList.add('hidden'); + return this; + }, + + clearFilter(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + u.hideElement(this.el.querySelector('.clear-input')); + } + + const roster_filter = this.el.querySelector('.roster-filter'); + roster_filter.value = ''; + this.model.save({ + 'filter_text': '' + }); + } + + }); + _converse.RosterContactView = Backbone.NativeView.extend({ + tagName: 'li', + className: 'list-item d-flex hidden controlbox-padded', + events: { + "click .accept-xmpp-request": "acceptRequest", + "click .decline-xmpp-request": "declineRequest", + "click .open-chat": "openChat", + "click .remove-xmpp-contact": "removeContact" + }, + + initialize() { + this.model.on("change", this.render, this); + this.model.on("highlight", this.highlight, this); + this.model.on("destroy", this.remove, this); + this.model.on("open", this.openChat, this); + this.model.on("remove", this.remove, this); + this.model.presence.on("change:show", this.render, this); + this.model.vcard.on('change:fullname', this.render, this); + }, + + render() { + const that = this; + + if (!this.mayBeShown()) { + u.hideElement(this.el); + return this; + } + + const ask = this.model.get('ask'), + show = this.model.presence.get('show'), + requesting = this.model.get('requesting'), + subscription = this.model.get('subscription'); + const classes_to_remove = ['current-xmpp-contact', 'pending-xmpp-contact', 'requesting-xmpp-contact'].concat(_.keys(STATUSES)); + + _.each(classes_to_remove, function (cls) { + if (_.includes(that.el.className, cls)) { + that.el.classList.remove(cls); } + }); - this.liveFilter(); - this.render(); - }, + this.el.classList.add(show); + this.el.setAttribute('data-status', show); + this.highlight(); - isActive() { - /* Returns true if the filter is enabled (i.e. if the user - * has added values to the filter). + if (_converse.isSingleton()) { + const chatbox = _converse.chatboxes.get(this.model.get('jid')); + + if (chatbox) { + if (chatbox.get('hidden')) { + this.el.classList.remove('open'); + } else { + this.el.classList.add('open'); + } + } + } + + if (ask === 'subscribe' || subscription === 'from') { + /* ask === 'subscribe' + * Means we have asked to subscribe to them. + * + * subscription === 'from' + * They are subscribed to use, but not vice versa. + * We assume that there is a pending subscription + * from us to them (otherwise we're in a state not + * supported by converse.js). + * + * So in both cases the user is a "pending" contact. */ - if (this.model.get('filter_type') === 'state' || this.model.get('filter_text')) { + const display_name = this.model.getDisplayName(); + this.el.classList.add('pending-xmpp-contact'); + this.el.innerHTML = templates_pending_contact_html__WEBPACK_IMPORTED_MODULE_7___default()(_.extend(this.model.toJSON(), { + 'display_name': display_name, + 'desc_remove': __('Click to remove %1$s as a contact', display_name), + 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts + })); + } else if (requesting === true) { + const display_name = this.model.getDisplayName(); + this.el.classList.add('requesting-xmpp-contact'); + this.el.innerHTML = templates_requesting_contact_html__WEBPACK_IMPORTED_MODULE_8___default()(_.extend(this.model.toJSON(), { + 'display_name': display_name, + 'desc_accept': __("Click to accept the contact request from %1$s", display_name), + 'desc_decline': __("Click to decline the contact request from %1$s", display_name), + 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts + })); + } else if (subscription === 'both' || subscription === 'to') { + this.el.classList.add('current-xmpp-contact'); + this.el.classList.remove(_.without(['both', 'to'], subscription)[0]); + this.el.classList.add(subscription); + this.renderRosterItem(this.model); + } + + return this; + }, + + highlight() { + /* If appropriate, highlight the contact (by adding the 'open' class). + */ + if (_converse.isSingleton()) { + const chatbox = _converse.chatboxes.get(this.model.get('jid')); + + if (chatbox) { + if (chatbox.get('hidden')) { + this.el.classList.remove('open'); + } else { + this.el.classList.add('open'); + } + } + } + }, + + renderRosterItem(item) { + let status_icon = 'fa fa-times-circle'; + const show = item.presence.get('show') || 'offline'; + + if (show === 'online') { + status_icon = 'fa fa-circle chat-status chat-status--online'; + } else if (show === 'away') { + status_icon = 'fa fa-circle chat-status chat-status--away'; + } else if (show === 'xa') { + status_icon = 'far fa-circle chat-status'; + } else if (show === 'dnd') { + status_icon = 'fa fa-minus-circle chat-status chat-status--busy'; + } + + const display_name = item.getDisplayName(); + this.el.innerHTML = templates_roster_item_html__WEBPACK_IMPORTED_MODULE_11___default()(_.extend(item.toJSON(), { + 'display_name': display_name, + 'desc_status': STATUSES[show], + 'status_icon': status_icon, + 'desc_chat': __('Click to chat with %1$s (JID: %2$s)', display_name, item.get('jid')), + 'desc_remove': __('Click to remove %1$s as a contact', display_name), + 'allow_contact_removal': _converse.allow_contact_removal, + 'num_unread': item.get('num_unread') || 0 + })); + return this; + }, + + mayBeShown() { + /* Return a boolean indicating whether this contact should + * generally be visible in the roster. + * + * It doesn't check for the more specific case of whether + * the group it's in is collapsed. + */ + const chatStatus = this.model.presence.get('show'); + + if (_converse.show_only_online_users && chatStatus !== 'online' || _converse.hide_offline_users && chatStatus === 'offline') { + // If pending or requesting, show + if (this.model.get('ask') === 'subscribe' || this.model.get('subscription') === 'from' || this.model.get('requesting') === true) { return true; } return false; - }, - - shouldBeVisible() { - return _converse.roster.length >= 5 || this.isActive(); - }, - - showOrHide() { - if (this.shouldBeVisible()) { - this.show(); - } else { - this.hide(); - } - }, - - show() { - if (u.isVisible(this.el)) { - return this; - } - - this.el.classList.add('fade-in'); - this.el.classList.remove('hidden'); - return this; - }, - - hide() { - if (!u.isVisible(this.el)) { - return this; - } - - this.model.save({ - 'filter_text': '', - 'chat_state': '' - }); - this.el.classList.add('hidden'); - return this; - }, - - clearFilter(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - u.hideElement(this.el.querySelector('.clear-input')); - } - - const roster_filter = this.el.querySelector('.roster-filter'); - roster_filter.value = ''; - this.model.save({ - 'filter_text': '' - }); } - }); - _converse.RosterContactView = Backbone.NativeView.extend({ - tagName: 'li', - className: 'list-item d-flex hidden controlbox-padded', - events: { - "click .accept-xmpp-request": "acceptRequest", - "click .decline-xmpp-request": "declineRequest", - "click .open-chat": "openChat", - "click .remove-xmpp-contact": "removeContact" - }, + return true; + }, - initialize() { - this.model.on("change", this.render, this); - this.model.on("highlight", this.highlight, this); - this.model.on("destroy", this.remove, this); - this.model.on("open", this.openChat, this); - this.model.on("remove", this.remove, this); - this.model.presence.on("change:show", this.render, this); - this.model.vcard.on('change:fullname', this.render, this); - }, - - render() { - const that = this; - - if (!this.mayBeShown()) { - u.hideElement(this.el); - return this; - } - - const ask = this.model.get('ask'), - show = this.model.presence.get('show'), - requesting = this.model.get('requesting'), - subscription = this.model.get('subscription'); - const classes_to_remove = ['current-xmpp-contact', 'pending-xmpp-contact', 'requesting-xmpp-contact'].concat(_.keys(STATUSES)); - - _.each(classes_to_remove, function (cls) { - if (_.includes(that.el.className, cls)) { - that.el.classList.remove(cls); - } - }); - - this.el.classList.add(show); - this.el.setAttribute('data-status', show); - this.highlight(); - - if (_converse.isSingleton()) { - const chatbox = _converse.chatboxes.get(this.model.get('jid')); - - if (chatbox) { - if (chatbox.get('hidden')) { - this.el.classList.remove('open'); - } else { - this.el.classList.add('open'); - } - } - } - - if (ask === 'subscribe' || subscription === 'from') { - /* ask === 'subscribe' - * Means we have asked to subscribe to them. - * - * subscription === 'from' - * They are subscribed to use, but not vice versa. - * We assume that there is a pending subscription - * from us to them (otherwise we're in a state not - * supported by converse.js). - * - * So in both cases the user is a "pending" contact. - */ - const display_name = this.model.getDisplayName(); - this.el.classList.add('pending-xmpp-contact'); - this.el.innerHTML = tpl_pending_contact(_.extend(this.model.toJSON(), { - 'display_name': display_name, - 'desc_remove': __('Click to remove %1$s as a contact', display_name), - 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts - })); - } else if (requesting === true) { - const display_name = this.model.getDisplayName(); - this.el.classList.add('requesting-xmpp-contact'); - this.el.innerHTML = tpl_requesting_contact(_.extend(this.model.toJSON(), { - 'display_name': display_name, - 'desc_accept': __("Click to accept the contact request from %1$s", display_name), - 'desc_decline': __("Click to decline the contact request from %1$s", display_name), - 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts - })); - } else if (subscription === 'both' || subscription === 'to') { - this.el.classList.add('current-xmpp-contact'); - this.el.classList.remove(_.without(['both', 'to'], subscription)[0]); - this.el.classList.add(subscription); - this.renderRosterItem(this.model); - } - - return this; - }, - - highlight() { - /* If appropriate, highlight the contact (by adding the 'open' class). - */ - if (_converse.isSingleton()) { - const chatbox = _converse.chatboxes.get(this.model.get('jid')); - - if (chatbox) { - if (chatbox.get('hidden')) { - this.el.classList.remove('open'); - } else { - this.el.classList.add('open'); - } - } - } - }, - - renderRosterItem(item) { - let status_icon = 'fa fa-times-circle'; - const show = item.presence.get('show') || 'offline'; - - if (show === 'online') { - status_icon = 'fa fa-circle chat-status chat-status--online'; - } else if (show === 'away') { - status_icon = 'fa fa-circle chat-status chat-status--away'; - } else if (show === 'xa') { - status_icon = 'far fa-circle chat-status'; - } else if (show === 'dnd') { - status_icon = 'fa fa-minus-circle chat-status chat-status--busy'; - } - - const display_name = item.getDisplayName(); - this.el.innerHTML = tpl_roster_item(_.extend(item.toJSON(), { - 'display_name': display_name, - 'desc_status': STATUSES[show], - 'status_icon': status_icon, - 'desc_chat': __('Click to chat with %1$s (JID: %2$s)', display_name, item.get('jid')), - 'desc_remove': __('Click to remove %1$s as a contact', display_name), - 'allow_contact_removal': _converse.allow_contact_removal, - 'num_unread': item.get('num_unread') || 0 - })); - return this; - }, - - mayBeShown() { - /* Return a boolean indicating whether this contact should - * generally be visible in the roster. - * - * It doesn't check for the more specific case of whether - * the group it's in is collapsed. - */ - const chatStatus = this.model.presence.get('show'); - - if (_converse.show_only_online_users && chatStatus !== 'online' || _converse.hide_offline_users && chatStatus === 'offline') { - // If pending or requesting, show - if (this.model.get('ask') === 'subscribe' || this.model.get('subscription') === 'from' || this.model.get('requesting') === true) { - return true; - } - - return false; - } - - return true; - }, - - openChat(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const attrs = this.model.attributes; - - _converse.api.chats.open(attrs.jid, attrs); - }, - - removeContact(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - if (!_converse.allow_contact_removal) { - return; - } - - const result = confirm(__("Are you sure you want to remove this contact?")); - - if (result === true) { - this.model.removeFromRoster(iq => { - this.model.destroy(); - this.remove(); - }, function (err) { - alert(__('Sorry, there was an error while trying to remove %1$s as a contact.', name)); - - _converse.log(err, Strophe.LogLevel.ERROR); - }); - } - }, - - acceptRequest(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - _converse.roster.sendContactAddIQ(this.model.get('jid'), this.model.getFullname(), [], () => { - this.model.authorize().subscribe(); - }); - }, - - declineRequest(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const result = confirm(__("Are you sure you want to decline this contact request?")); - - if (result === true) { - this.model.unauthorize().destroy(); - } - - return this; + openChat(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); } - }); - _converse.RosterGroupView = Backbone.OrderedListView.extend({ - tagName: 'div', - className: 'roster-group hidden', - events: { - "click a.group-toggle": "toggle" - }, - ItemView: _converse.RosterContactView, - listItems: 'model.contacts', - listSelector: '.roster-group-contacts', - sortEvent: 'presenceChanged', + const attrs = this.model.attributes; - initialize() { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - this.model.contacts.on("change:subscription", this.onContactSubscriptionChange, this); - this.model.contacts.on("change:requesting", this.onContactRequestChange, this); - this.model.contacts.on("remove", this.onRemove, this); + _converse.api.chats.open(attrs.jid, attrs); + }, - _converse.roster.on('change:groups', this.onContactGroupChange, this); // This event gets triggered once *all* contacts (i.e. not - // just this group's) have been fetched from browser - // storage or the XMPP server and once they've been - // assigned to their various groups. - - - _converse.rosterview.on('rosterContactsFetchedAndProcessed', this.sortAndPositionAllItems.bind(this)); - }, - - render() { - this.el.setAttribute('data-group', this.model.get('name')); - this.el.innerHTML = tpl_group_header({ - 'label_group': this.model.get('name'), - 'desc_group_toggle': this.model.get('description'), - 'toggle_state': this.model.get('state'), - '_converse': _converse - }); - this.contacts_el = this.el.querySelector('.roster-group-contacts'); - return this; - }, - - show() { - u.showElement(this.el); - - _.each(this.getAll(), contact_view => { - if (contact_view.mayBeShown() && this.model.get('state') === _converse.OPENED) { - u.showElement(contact_view.el); - } - }); - - return this; - }, - - collapse() { - return u.slideIn(this.contacts_el); - }, - - filterOutContacts(contacts = []) { - /* Given a list of contacts, make sure they're filtered out - * (aka hidden) and that all other contacts are visible. - * - * If all contacts are hidden, then also hide the group - * title. - */ - let shown = 0; - const all_contact_views = this.getAll(); - - _.each(this.model.contacts.models, contact => { - const contact_view = this.get(contact.get('id')); - - if (_.includes(contacts, contact)) { - u.hideElement(contact_view.el); - } else if (contact_view.mayBeShown()) { - u.showElement(contact_view.el); - shown += 1; - } - }); - - if (shown) { - u.showElement(this.el); - } else { - u.hideElement(this.el); - } - }, - - getFilterMatches(q, type) { - /* Given the filter query "q" and the filter type "type", - * return a list of contacts that need to be filtered out. - */ - if (q.length === 0) { - return []; - } - - let matches; - q = q.toLowerCase(); - - if (type === 'state') { - if (this.model.get('name') === HEADER_REQUESTING_CONTACTS) { - // When filtering by chat state, we still want to - // show requesting contacts, even though they don't - // have the state in question. - matches = this.model.contacts.filter(contact => !_.includes(contact.presence.get('show'), q) && !contact.get('requesting')); - } else if (q === 'unread_messages') { - matches = this.model.contacts.filter({ - 'num_unread': 0 - }); - } else { - matches = this.model.contacts.filter(contact => !_.includes(contact.presence.get('show'), q)); - } - } else { - matches = this.model.contacts.filter(contact => { - return !_.includes(contact.getDisplayName().toLowerCase(), q.toLowerCase()); - }); - } - - return matches; - }, - - filter(q, type) { - /* Filter the group's contacts based on the query "q". - * - * If all contacts are filtered out (i.e. hidden), then the - * group must be filtered out as well. - */ - if (_.isNil(q)) { - type = type || _converse.rosterview.filter_view.model.get('filter_type'); - - if (type === 'state') { - q = _converse.rosterview.filter_view.model.get('chat_state'); - } else { - q = _converse.rosterview.filter_view.model.get('filter_text'); - } - } - - this.filterOutContacts(this.getFilterMatches(q, type)); - }, - - toggle(ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - const icon_el = ev.target.querySelector('.fa'); - - if (_.includes(icon_el.classList, "fa-caret-down")) { - this.model.save({ - state: _converse.CLOSED - }); - this.collapse().then(() => { - icon_el.classList.remove("fa-caret-down"); - icon_el.classList.add("fa-caret-right"); - }); - } else { - icon_el.classList.remove("fa-caret-right"); - icon_el.classList.add("fa-caret-down"); - this.model.save({ - state: _converse.OPENED - }); - this.filter(); - u.showElement(this.el); - u.slideOut(this.contacts_el); - } - }, - - onContactGroupChange(contact) { - const in_this_group = _.includes(contact.get('groups'), this.model.get('name')); - - const cid = contact.get('id'); - const in_this_overview = !this.get(cid); - - if (in_this_group && !in_this_overview) { - this.items.trigger('add', contact); - } else if (!in_this_group) { - this.removeContact(contact); - } - }, - - onContactSubscriptionChange(contact) { - if (this.model.get('name') === HEADER_PENDING_CONTACTS && contact.get('subscription') !== 'from') { - this.removeContact(contact); - } - }, - - onContactRequestChange(contact) { - if (this.model.get('name') === HEADER_REQUESTING_CONTACTS && !contact.get('requesting')) { - this.removeContact(contact); - } - }, - - removeContact(contact) { - // We suppress events, otherwise the remove event will - // also cause the contact's view to be removed from the - // "Pending Contacts" group. - this.model.contacts.remove(contact, { - 'silent': true - }); - this.onRemove(contact); - }, - - onRemove(contact) { - this.remove(contact.get('jid')); - - if (this.model.contacts.length === 0) { - this.remove(); - } + removeContact(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); } - }); - _converse.RosterView = Backbone.OrderedListView.extend({ - tagName: 'div', - id: 'converse-roster', - className: 'controlbox-section', - ItemView: _converse.RosterGroupView, - listItems: 'model', - listSelector: '.roster-contacts', - sortEvent: null, - // Groups are immutable, so they don't get re-sorted - subviewIndex: 'name', - events: { - 'click a.chatbox-btn.add-contact': 'showAddContactModal' - }, - - initialize() { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - - _converse.roster.on("add", this.onContactAdded, this); - - _converse.roster.on('change:groups', this.onContactAdded, this); - - _converse.roster.on('change', this.onContactChange, this); - - _converse.roster.on("destroy", this.update, this); - - _converse.roster.on("remove", this.update, this); - - _converse.presences.on('change:show', () => { - this.update(); - this.updateFilter(); - }); - - this.model.on("reset", this.reset, this); // This event gets triggered once *all* contacts (i.e. not - // just this group's) have been fetched from browser - // storage or the XMPP server and once they've been - // assigned to their various groups. - - _converse.on('rosterGroupsFetched', this.sortAndPositionAllItems.bind(this)); - - _converse.on('rosterContactsFetched', () => { - _converse.roster.each(contact => this.addRosterContact(contact, { - 'silent': true - })); - - this.update(); - this.updateFilter(); - this.trigger('rosterContactsFetchedAndProcessed'); - }); - - this.createRosterFilter(); - }, - - render() { - this.el.innerHTML = tpl_roster({ - 'allow_contact_requests': _converse.allow_contact_requests, - 'heading_contacts': __('Contacts'), - 'title_add_contact': __('Add a contact') - }); - const form = this.el.querySelector('.roster-filter-form'); - this.el.replaceChild(this.filter_view.render().el, form); - this.roster_el = this.el.querySelector('.roster-contacts'); - return this; - }, - - showAddContactModal(ev) { - if (_.isUndefined(this.add_contact_modal)) { - this.add_contact_modal = new _converse.AddContactModal({ - 'model': new Backbone.Model() - }); - } - - this.add_contact_modal.show(ev); - }, - - createRosterFilter() { - // Create a model on which we can store filter properties - const model = new _converse.RosterFilter(); - model.id = b64_sha1(`_converse.rosterfilter${_converse.bare_jid}`); - model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id); - this.filter_view = new _converse.RosterFilterView({ - 'model': model - }); - this.filter_view.model.on('change', this.updateFilter, this); - this.filter_view.model.fetch(); - }, - - updateFilter: _.debounce(function () { - /* Filter the roster again. - * Called whenever the filter settings have been changed or - * when contacts have been added, removed or changed. - * - * Debounced so that it doesn't get called for every - * contact fetched from browser storage. - */ - const type = this.filter_view.model.get('filter_type'); - - if (type === 'state') { - this.filter(this.filter_view.model.get('chat_state'), type); - } else { - this.filter(this.filter_view.model.get('filter_text'), type); - } - }, 100), - update: _.debounce(function () { - if (!u.isVisible(this.roster_el)) { - u.showElement(this.roster_el); - } - - this.filter_view.showOrHide(); - return this; - }, _converse.animate ? 100 : 0), - - filter(query, type) { - // First we make sure the filter is restored to its - // original state - _.each(this.getAll(), function (view) { - if (view.model.contacts.length > 0) { - view.show().filter(''); - } - }); // Now we can filter - - - query = query.toLowerCase(); - - if (type === 'groups') { - _.each(this.getAll(), function (view, idx) { - if (!_.includes(view.model.get('name').toLowerCase(), query.toLowerCase())) { - u.slideIn(view.el); - } else if (view.model.contacts.length > 0) { - u.slideOut(view.el); - } - }); - } else { - _.each(this.getAll(), function (view) { - view.filter(query, type); - }); - } - }, - - reset() { - _converse.roster.reset(); - - this.removeAll(); - this.render().update(); - return this; - }, - - onContactAdded(contact) { - this.addRosterContact(contact); - this.update(); - this.updateFilter(); - }, - - onContactChange(contact) { - this.updateChatBox(contact); - this.update(); - - if (_.has(contact.changed, 'subscription')) { - if (contact.changed.subscription === 'from') { - this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); - } else if (_.includes(['both', 'to'], contact.get('subscription'))) { - this.addExistingContact(contact); - } - } - - if (_.has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') { - this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); - } - - if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') { - this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS); - } - - this.updateFilter(); - }, - - updateChatBox(contact) { - if (!this.model.chatbox) { - return this; - } - - const changes = {}; - - if (_.has(contact.changed, 'status')) { - changes.status = contact.get('status'); - } - - this.model.chatbox.save(changes); - return this; - }, - - getGroup(name) { - /* Returns the group as specified by name. - * Creates the group if it doesn't exist. - */ - const view = this.get(name); - - if (view) { - return view.model; - } - - return this.model.create({ - name, - id: b64_sha1(name) - }); - }, - - addContactToGroup(contact, name, options) { - this.getGroup(name).contacts.add(contact, options); - this.sortAndPositionAllItems(); - }, - - addExistingContact(contact, options) { - let groups; - - if (_converse.roster_groups) { - groups = contact.get('groups'); - - if (groups.length === 0) { - groups = [HEADER_UNGROUPED]; - } - } else { - groups = [HEADER_CURRENT_CONTACTS]; - } - - _.each(groups, _.bind(this.addContactToGroup, this, contact, _, options)); - }, - - addRosterContact(contact, options) { - if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to') { - this.addExistingContact(contact, options); - } else { - if (!_converse.allow_contact_requests) { - _converse.log(`Not adding requesting or pending contact ${contact.get('jid')} ` + `because allow_contact_requests is false`, Strophe.LogLevel.DEBUG); - - return; - } - - if (contact.get('ask') === 'subscribe' || contact.get('subscription') === 'from') { - this.addContactToGroup(contact, HEADER_PENDING_CONTACTS, options); - } else if (contact.get('requesting') === true) { - this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS, options); - } - } - - return this; - } - - }); - /* -------- Event Handlers ----------- */ - - _converse.api.listen.on('chatBoxesInitialized', () => { - _converse.chatboxes.on('change:hidden', chatbox => { - const contact = _converse.roster.findWhere({ - 'jid': chatbox.get('jid') - }); - - if (!_.isUndefined(contact)) { - contact.trigger('highlight', contact); - } - }); - }); - - function initRoster() { - /* Create an instance of RosterView once the RosterGroups - * collection has been created (in @converse/headless/converse-core.js) - */ - if (_converse.authentication === _converse.ANONYMOUS) { + if (!_converse.allow_contact_removal) { return; } - _converse.rosterview = new _converse.RosterView({ - 'model': _converse.rostergroups + const result = confirm(__("Are you sure you want to remove this contact?")); + + if (result === true) { + this.model.removeFromRoster(iq => { + this.model.destroy(); + this.remove(); + }, function (err) { + alert(__('Sorry, there was an error while trying to remove %1$s as a contact.', name)); + + _converse.log(err, Strophe.LogLevel.ERROR); + }); + } + }, + + acceptRequest(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + _converse.roster.sendContactAddIQ(this.model.get('jid'), this.model.getFullname(), [], () => { + this.model.authorize().subscribe(); }); + }, - _converse.rosterview.render(); + declineRequest(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } - _converse.emit('rosterViewInitialized'); + const result = confirm(__("Are you sure you want to decline this contact request?")); + + if (result === true) { + this.model.unauthorize().destroy(); + } + + return this; } - _converse.api.listen.on('rosterInitialized', initRoster); + }); + _converse.RosterGroupView = Backbone.OrderedListView.extend({ + tagName: 'div', + className: 'roster-group hidden', + events: { + "click a.group-toggle": "toggle" + }, + ItemView: _converse.RosterContactView, + listItems: 'model.contacts', + listSelector: '.roster-group-contacts', + sortEvent: 'presenceChanged', - _converse.api.listen.on('rosterReadyAfterReconnection', initRoster); + initialize() { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + this.model.contacts.on("change:subscription", this.onContactSubscriptionChange, this); + this.model.contacts.on("change:requesting", this.onContactRequestChange, this); + this.model.contacts.on("remove", this.onRemove, this); + + _converse.roster.on('change:groups', this.onContactGroupChange, this); // This event gets triggered once *all* contacts (i.e. not + // just this group's) have been fetched from browser + // storage or the XMPP server and once they've been + // assigned to their various groups. + + + _converse.rosterview.on('rosterContactsFetchedAndProcessed', this.sortAndPositionAllItems.bind(this)); + }, + + render() { + this.el.setAttribute('data-group', this.model.get('name')); + this.el.innerHTML = templates_group_header_html__WEBPACK_IMPORTED_MODULE_6___default()({ + 'label_group': this.model.get('name'), + 'desc_group_toggle': this.model.get('description'), + 'toggle_state': this.model.get('state'), + '_converse': _converse + }); + this.contacts_el = this.el.querySelector('.roster-group-contacts'); + return this; + }, + + show() { + u.showElement(this.el); + + _.each(this.getAll(), contact_view => { + if (contact_view.mayBeShown() && this.model.get('state') === _converse.OPENED) { + u.showElement(contact_view.el); + } + }); + + return this; + }, + + collapse() { + return u.slideIn(this.contacts_el); + }, + + filterOutContacts(contacts = []) { + /* Given a list of contacts, make sure they're filtered out + * (aka hidden) and that all other contacts are visible. + * + * If all contacts are hidden, then also hide the group + * title. + */ + let shown = 0; + const all_contact_views = this.getAll(); + + _.each(this.model.contacts.models, contact => { + const contact_view = this.get(contact.get('id')); + + if (_.includes(contacts, contact)) { + u.hideElement(contact_view.el); + } else if (contact_view.mayBeShown()) { + u.showElement(contact_view.el); + shown += 1; + } + }); + + if (shown) { + u.showElement(this.el); + } else { + u.hideElement(this.el); + } + }, + + getFilterMatches(q, type) { + /* Given the filter query "q" and the filter type "type", + * return a list of contacts that need to be filtered out. + */ + if (q.length === 0) { + return []; + } + + let matches; + q = q.toLowerCase(); + + if (type === 'state') { + if (this.model.get('name') === HEADER_REQUESTING_CONTACTS) { + // When filtering by chat state, we still want to + // show requesting contacts, even though they don't + // have the state in question. + matches = this.model.contacts.filter(contact => !_.includes(contact.presence.get('show'), q) && !contact.get('requesting')); + } else if (q === 'unread_messages') { + matches = this.model.contacts.filter({ + 'num_unread': 0 + }); + } else { + matches = this.model.contacts.filter(contact => !_.includes(contact.presence.get('show'), q)); + } + } else { + matches = this.model.contacts.filter(contact => { + return !_.includes(contact.getDisplayName().toLowerCase(), q.toLowerCase()); + }); + } + + return matches; + }, + + filter(q, type) { + /* Filter the group's contacts based on the query "q". + * + * If all contacts are filtered out (i.e. hidden), then the + * group must be filtered out as well. + */ + if (_.isNil(q)) { + type = type || _converse.rosterview.filter_view.model.get('filter_type'); + + if (type === 'state') { + q = _converse.rosterview.filter_view.model.get('chat_state'); + } else { + q = _converse.rosterview.filter_view.model.get('filter_text'); + } + } + + this.filterOutContacts(this.getFilterMatches(q, type)); + }, + + toggle(ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + const icon_el = ev.target.querySelector('.fa'); + + if (_.includes(icon_el.classList, "fa-caret-down")) { + this.model.save({ + state: _converse.CLOSED + }); + this.collapse().then(() => { + icon_el.classList.remove("fa-caret-down"); + icon_el.classList.add("fa-caret-right"); + }); + } else { + icon_el.classList.remove("fa-caret-right"); + icon_el.classList.add("fa-caret-down"); + this.model.save({ + state: _converse.OPENED + }); + this.filter(); + u.showElement(this.el); + u.slideOut(this.contacts_el); + } + }, + + onContactGroupChange(contact) { + const in_this_group = _.includes(contact.get('groups'), this.model.get('name')); + + const cid = contact.get('id'); + const in_this_overview = !this.get(cid); + + if (in_this_group && !in_this_overview) { + this.items.trigger('add', contact); + } else if (!in_this_group) { + this.removeContact(contact); + } + }, + + onContactSubscriptionChange(contact) { + if (this.model.get('name') === HEADER_PENDING_CONTACTS && contact.get('subscription') !== 'from') { + this.removeContact(contact); + } + }, + + onContactRequestChange(contact) { + if (this.model.get('name') === HEADER_REQUESTING_CONTACTS && !contact.get('requesting')) { + this.removeContact(contact); + } + }, + + removeContact(contact) { + // We suppress events, otherwise the remove event will + // also cause the contact's view to be removed from the + // "Pending Contacts" group. + this.model.contacts.remove(contact, { + 'silent': true + }); + this.onRemove(contact); + }, + + onRemove(contact) { + this.remove(contact.get('jid')); + + if (this.model.contacts.length === 0) { + this.remove(); + } + } + + }); + _converse.RosterView = Backbone.OrderedListView.extend({ + tagName: 'div', + id: 'converse-roster', + className: 'controlbox-section', + ItemView: _converse.RosterGroupView, + listItems: 'model', + listSelector: '.roster-contacts', + sortEvent: null, + // Groups are immutable, so they don't get re-sorted + subviewIndex: 'name', + events: { + 'click a.chatbox-btn.add-contact': 'showAddContactModal' + }, + + initialize() { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + + _converse.roster.on("add", this.onContactAdded, this); + + _converse.roster.on('change:groups', this.onContactAdded, this); + + _converse.roster.on('change', this.onContactChange, this); + + _converse.roster.on("destroy", this.update, this); + + _converse.roster.on("remove", this.update, this); + + _converse.presences.on('change:show', () => { + this.update(); + this.updateFilter(); + }); + + this.model.on("reset", this.reset, this); // This event gets triggered once *all* contacts (i.e. not + // just this group's) have been fetched from browser + // storage or the XMPP server and once they've been + // assigned to their various groups. + + _converse.on('rosterGroupsFetched', this.sortAndPositionAllItems.bind(this)); + + _converse.on('rosterContactsFetched', () => { + _converse.roster.each(contact => this.addRosterContact(contact, { + 'silent': true + })); + + this.update(); + this.updateFilter(); + this.trigger('rosterContactsFetchedAndProcessed'); + }); + + this.createRosterFilter(); + }, + + render() { + this.el.innerHTML = templates_roster_html__WEBPACK_IMPORTED_MODULE_9___default()({ + 'allow_contact_requests': _converse.allow_contact_requests, + 'heading_contacts': __('Contacts'), + 'title_add_contact': __('Add a contact') + }); + const form = this.el.querySelector('.roster-filter-form'); + this.el.replaceChild(this.filter_view.render().el, form); + this.roster_el = this.el.querySelector('.roster-contacts'); + return this; + }, + + showAddContactModal(ev) { + if (_.isUndefined(this.add_contact_modal)) { + this.add_contact_modal = new _converse.AddContactModal({ + 'model': new Backbone.Model() + }); + } + + this.add_contact_modal.show(ev); + }, + + createRosterFilter() { + // Create a model on which we can store filter properties + const model = new _converse.RosterFilter(); + model.id = b64_sha1(`_converse.rosterfilter${_converse.bare_jid}`); + model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id); + this.filter_view = new _converse.RosterFilterView({ + 'model': model + }); + this.filter_view.model.on('change', this.updateFilter, this); + this.filter_view.model.fetch(); + }, + + updateFilter: _.debounce(function () { + /* Filter the roster again. + * Called whenever the filter settings have been changed or + * when contacts have been added, removed or changed. + * + * Debounced so that it doesn't get called for every + * contact fetched from browser storage. + */ + const type = this.filter_view.model.get('filter_type'); + + if (type === 'state') { + this.filter(this.filter_view.model.get('chat_state'), type); + } else { + this.filter(this.filter_view.model.get('filter_text'), type); + } + }, 100), + update: _.debounce(function () { + if (!u.isVisible(this.roster_el)) { + u.showElement(this.roster_el); + } + + this.filter_view.showOrHide(); + return this; + }, _converse.animate ? 100 : 0), + + filter(query, type) { + // First we make sure the filter is restored to its + // original state + _.each(this.getAll(), function (view) { + if (view.model.contacts.length > 0) { + view.show().filter(''); + } + }); // Now we can filter + + + query = query.toLowerCase(); + + if (type === 'groups') { + _.each(this.getAll(), function (view, idx) { + if (!_.includes(view.model.get('name').toLowerCase(), query.toLowerCase())) { + u.slideIn(view.el); + } else if (view.model.contacts.length > 0) { + u.slideOut(view.el); + } + }); + } else { + _.each(this.getAll(), function (view) { + view.filter(query, type); + }); + } + }, + + reset() { + _converse.roster.reset(); + + this.removeAll(); + this.render().update(); + return this; + }, + + onContactAdded(contact) { + this.addRosterContact(contact); + this.update(); + this.updateFilter(); + }, + + onContactChange(contact) { + this.updateChatBox(contact); + this.update(); + + if (_.has(contact.changed, 'subscription')) { + if (contact.changed.subscription === 'from') { + this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); + } else if (_.includes(['both', 'to'], contact.get('subscription'))) { + this.addExistingContact(contact); + } + } + + if (_.has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') { + this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); + } + + if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') { + this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS); + } + + this.updateFilter(); + }, + + updateChatBox(contact) { + if (!this.model.chatbox) { + return this; + } + + const changes = {}; + + if (_.has(contact.changed, 'status')) { + changes.status = contact.get('status'); + } + + this.model.chatbox.save(changes); + return this; + }, + + getGroup(name) { + /* Returns the group as specified by name. + * Creates the group if it doesn't exist. + */ + const view = this.get(name); + + if (view) { + return view.model; + } + + return this.model.create({ + name, + id: b64_sha1(name) + }); + }, + + addContactToGroup(contact, name, options) { + this.getGroup(name).contacts.add(contact, options); + this.sortAndPositionAllItems(); + }, + + addExistingContact(contact, options) { + let groups; + + if (_converse.roster_groups) { + groups = contact.get('groups'); + + if (groups.length === 0) { + groups = [HEADER_UNGROUPED]; + } + } else { + groups = [HEADER_CURRENT_CONTACTS]; + } + + _.each(groups, _.bind(this.addContactToGroup, this, contact, _, options)); + }, + + addRosterContact(contact, options) { + if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to') { + this.addExistingContact(contact, options); + } else { + if (!_converse.allow_contact_requests) { + _converse.log(`Not adding requesting or pending contact ${contact.get('jid')} ` + `because allow_contact_requests is false`, Strophe.LogLevel.DEBUG); + + return; + } + + if (contact.get('ask') === 'subscribe' || contact.get('subscription') === 'from') { + this.addContactToGroup(contact, HEADER_PENDING_CONTACTS, options); + } else if (contact.get('requesting') === true) { + this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS, options); + } + } + + return this; + } + + }); + /* -------- Event Handlers ----------- */ + + _converse.api.listen.on('chatBoxesInitialized', () => { + _converse.chatboxes.on('change:hidden', chatbox => { + const contact = _converse.roster.findWhere({ + 'jid': chatbox.get('jid') + }); + + if (!_.isUndefined(contact)) { + contact.trigger('highlight', contact); + } + }); + }); + + function initRoster() { + /* Create an instance of RosterView once the RosterGroups + * collection has been created (in @converse/headless/converse-core.js) + */ + if (_converse.authentication === _converse.ANONYMOUS) { + return; + } + + _converse.rosterview = new _converse.RosterView({ + 'model': _converse.rostergroups + }); + + _converse.rosterview.render(); + + _converse.emit('rosterViewInitialized'); } - }); + _converse.api.listen.on('rosterInitialized', initRoster); + + _converse.api.listen.on('rosterReadyAfterReconnection', initRoster); + } + }); /***/ }), @@ -69901,13 +70140,17 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!***********************************!*\ !*** ./src/converse-singleton.js ***! \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var converse_chatview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +// Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) /* converse-singleton @@ -69919,127 +70162,119 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ * * This plugin makes sense in mobile or fullscreen chat environments (as * configured by the `view_mode` setting). - * */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - "use strict"; - const _converse$env = converse.env, - _ = _converse$env._, - Strophe = _converse$env.Strophe; - const u = converse.env.utils; - function hideChat(view) { - if (view.model.get('id') === 'controlbox') { - return; - } +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env, + _ = _converse$env._, + Strophe = _converse$env.Strophe; +const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env.utils; - u.safeSave(view.model, { - 'hidden': true - }); - view.hide(); +function hideChat(view) { + if (view.model.get('id') === 'controlbox') { + return; } - converse.plugins.add('converse-singleton', { - // It's possible however to make optional dependencies non-optional. - // If the setting "strict_plugin_dependencies" is set to true, - // an error will be raised if the plugin is not found. - // - // NB: These plugins need to have already been loaded via require.js. - dependencies: ['converse-chatboxes', 'converse-muc', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'], - overrides: { - // overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // new functions which don't exist yet can also be added. - ChatBoxes: { - chatBoxMayBeShown(chatbox) { - const _converse = this.__super__._converse; + u.safeSave(view.model, { + 'hidden': true + }); + view.hide(); +} - if (chatbox.get('id') === 'controlbox') { +_converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-singleton', { + // It's possible however to make optional dependencies non-optional. + // If the setting "strict_plugin_dependencies" is set to true, + // an error will be raised if the plugin is not found. + // + // NB: These plugins need to have already been loaded via require.js. + dependencies: ['converse-chatboxes', 'converse-muc', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'], + overrides: { + // overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // new functions which don't exist yet can also be added. + ChatBoxes: { + chatBoxMayBeShown(chatbox) { + const _converse = this.__super__._converse; + + if (chatbox.get('id') === 'controlbox') { + return true; + } + + if (_converse.isSingleton()) { + const any_chats_visible = _converse.chatboxes.filter(cb => cb.get('id') != 'controlbox').filter(cb => !cb.get('hidden')).length > 0; + + if (any_chats_visible) { + return !chatbox.get('hidden'); + } else { return true; } - - if (_converse.isSingleton()) { - const any_chats_visible = _converse.chatboxes.filter(cb => cb.get('id') != 'controlbox').filter(cb => !cb.get('hidden')).length > 0; - - if (any_chats_visible) { - return !chatbox.get('hidden'); - } else { - return true; - } - } else { - return this.__super__.chatBoxMayBeShown.apply(this, arguments); - } - }, - - createChatBox(jid, attrs) { - /* Make sure new chat boxes are hidden by default. */ - const _converse = this.__super__._converse; - - if (_converse.isSingleton()) { - attrs = attrs || {}; - attrs.hidden = true; - } - - return this.__super__.createChatBox.call(this, jid, attrs); + } else { + return this.__super__.chatBoxMayBeShown.apply(this, arguments); } - }, - ChatBoxView: { - shouldShowOnTextMessage() { - const _converse = this.__super__._converse; - if (_converse.isSingleton()) { - return false; - } else { - return this.__super__.shouldShowOnTextMessage.apply(this, arguments); - } - }, + createChatBox(jid, attrs) { + /* Make sure new chat boxes are hidden by default. */ + const _converse = this.__super__._converse; - _show(focus) { - /* We only have one chat visible at any one - * time. So before opening a chat, we make sure all other - * chats are hidden. - */ - const _converse = this.__super__._converse; - - if (_converse.isSingleton()) { - _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat); - - u.safeSave(this.model, { - 'hidden': false - }); - } - - return this.__super__._show.apply(this, arguments); - } - - }, - ChatRoomView: { - show(focus) { - const _converse = this.__super__._converse; - - if (_converse.isSingleton()) { - _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat); - - u.safeSave(this.model, { - 'hidden': false - }); - } - - return this.__super__.show.apply(this, arguments); + if (_converse.isSingleton()) { + attrs = attrs || {}; + attrs.hidden = true; } + return this.__super__.createChatBox.call(this, jid, attrs); } + + }, + ChatBoxView: { + shouldShowOnTextMessage() { + const _converse = this.__super__._converse; + + if (_converse.isSingleton()) { + return false; + } else { + return this.__super__.shouldShowOnTextMessage.apply(this, arguments); + } + }, + + _show(focus) { + /* We only have one chat visible at any one + * time. So before opening a chat, we make sure all other + * chats are hidden. + */ + const _converse = this.__super__._converse; + + if (_converse.isSingleton()) { + _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat); + + u.safeSave(this.model, { + 'hidden': false + }); + } + + return this.__super__._show.apply(this, arguments); + } + + }, + ChatRoomView: { + show(focus) { + const _converse = this.__super__._converse; + + if (_converse.isSingleton()) { + _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat); + + u.safeSave(this.model, { + 'hidden': false + }); + } + + return this.__super__.show.apply(this, arguments); + } + } - }); + } }); /***/ }), @@ -70089,6 +70324,7 @@ if (true) { /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { +/*global module, exports, _ */ (function webpackUniversalModuleDefinition(root, factory) { if (true) module.exports = factory();else {} })(this, function () { @@ -70178,11 +70414,7 @@ if (true) { return __webpack_require__(0); /******/ - }( - /************************************************************************/ - - /******/ - [ + }([ /* 0 */ /***/ @@ -70205,7 +70437,7 @@ if (true) { if (typeof _ == 'function' && typeof _.runInContext == 'function') { // XXX: Customization in order to be able to run both _ and fp in the // non-AMD usecase. - fp = browserConvert(_.runInContext()); + window.fp = browserConvert(_.runInContext()); } module.exports = browserConvert; @@ -70382,7 +70614,7 @@ if (true) { name = undefined; } - if (func == null) { + if (func === null) { throw new TypeError(); } @@ -70588,11 +70820,11 @@ if (true) { result = clone(Object(object)), nested = result; - while (nested != null && ++index < length) { + while (nested !== null && ++index < length) { var key = path[index], value = nested[key]; - if (value != null) { + if (value !== null) { nested[path[index]] = clone(index == lastIndex ? value : Object(value)); } @@ -71081,12 +71313,14 @@ if (true) { result = {}; for (var key in object) { - var value = object[key]; + if (Object.prototype.hasOwnProperty.call(object, key)) { + var value = object[key]; - if (hasOwnProperty.call(result, value)) { - result[value].push(key); - } else { - result[value] = [key]; + if (hasOwnProperty.call(result, value)) { + result[value].push(key); + } else { + result[value] = [key]; + } } } @@ -71191,8 +71425,6 @@ if (true) { ); }); -; - /***/ }), /***/ "./src/headless/backbone.noconflict.js": @@ -71214,1058 +71446,1059 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*global define /*!********************************************!*\ !*** ./src/headless/converse-chatboxes.js ***! \********************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _utils_emoji__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/emoji */ "./src/headless/utils/emoji.js"); +/* harmony import */ var _utils_form__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils/form */ "./src/headless/utils/form.js"); +/* harmony import */ var _converse_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"); +/* harmony import */ var filesize__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(filesize__WEBPACK_IMPORTED_MODULE_3__); +// Converse.js // http://conversejs.org // // Copyright (c) 2012-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! filesize */ "./node_modules/filesize/lib/filesize.js"), __webpack_require__(/*! ./utils/form */ "./src/headless/utils/form.js"), __webpack_require__(/*! ./utils/emoji */ "./src/headless/utils/emoji.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, filesize) { - "use strict"; - const _converse$env = converse.env, - $msg = _converse$env.$msg, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - b64_sha1 = _converse$env.b64_sha1, - moment = _converse$env.moment, - sizzle = _converse$env.sizzle, - utils = _converse$env.utils, - _ = _converse$env._; - const u = converse.env.utils; - Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0'); - Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0'); - converse.plugins.add('converse-chatboxes', { - dependencies: ["converse-roster", "converse-vcard"], - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; // Configuration values for this plugin - // ==================================== - // Refer to docs/source/configuration.rst for explanations of these - // configuration settings. - _converse.api.settings.update({ - 'auto_join_private_chats': [], - 'filter_by_resource': false, - 'forward_messages': false, - 'send_chat_state_notifications': true - }); - _converse.api.promises.add(['chatBoxesFetched', 'chatBoxesInitialized', 'privateChatsAutoJoined']); +const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env, + $msg = _converse$env.$msg, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + b64_sha1 = _converse$env.b64_sha1, + moment = _converse$env.moment, + sizzle = _converse$env.sizzle, + utils = _converse$env.utils, + _ = _converse$env._; +const u = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.utils; +Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0'); +Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0'); +_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-chatboxes', { + dependencies: ["converse-roster", "converse-vcard"], - function openChat(jid) { - if (!utils.isValidJID(jid)) { - return _converse.log(`Invalid JID "${jid}" provided in URL fragment`, Strophe.LogLevel.WARN); - } + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; // Configuration values for this plugin + // ==================================== + // Refer to docs/source/configuration.rst for explanations of these + // configuration settings. - _converse.api.chats.open(jid); + _converse.api.settings.update({ + 'auto_join_private_chats': [], + 'filter_by_resource': false, + 'forward_messages': false, + 'send_chat_state_notifications': true + }); + + _converse.api.promises.add(['chatBoxesFetched', 'chatBoxesInitialized', 'privateChatsAutoJoined']); + + function openChat(jid) { + if (!utils.isValidJID(jid)) { + return _converse.log(`Invalid JID "${jid}" provided in URL fragment`, Strophe.LogLevel.WARN); } - _converse.router.route('converse/chat?jid=:jid', openChat); + _converse.api.chats.open(jid); + } - _converse.Message = Backbone.Model.extend({ - defaults() { - return { - 'msgid': _converse.connection.getUniqueId(), - 'time': moment().format() - }; - }, + _converse.router.route('converse/chat?jid=:jid', openChat); - initialize() { - this.setVCard(); + _converse.Message = Backbone.Model.extend({ + defaults() { + return { + 'msgid': _converse.connection.getUniqueId(), + 'time': moment().format() + }; + }, - if (this.get('file')) { - this.on('change:put', this.uploadFile, this); + initialize() { + this.setVCard(); - if (!_.includes([_converse.SUCCESS, _converse.FAILURE], this.get('upload'))) { - this.getRequestSlotURL(); - } + if (this.get('file')) { + this.on('change:put', this.uploadFile, this); + + if (!_.includes([_converse.SUCCESS, _converse.FAILURE], this.get('upload'))) { + this.getRequestSlotURL(); + } + } + + if (this.isOnlyChatStateNotification()) { + window.setTimeout(this.destroy.bind(this), 20000); + } + }, + + getVCardForChatroomOccupant() { + const chatbox = this.collection.chatbox, + nick = Strophe.getResourceFromJid(this.get('from')); + + if (chatbox.get('nick') === nick) { + return _converse.xmppstatus.vcard; + } else { + let vcard; + + if (this.get('vcard_jid')) { + vcard = _converse.vcards.findWhere({ + 'jid': this.get('vcard_jid') + }); } - if (this.isOnlyChatStateNotification()) { - window.setTimeout(this.destroy.bind(this), 20000); - } - }, + if (!vcard) { + let jid; + const occupant = chatbox.occupants.findWhere({ + 'nick': nick + }); - getVCardForChatroomOccupant() { - const chatbox = this.collection.chatbox, - nick = Strophe.getResourceFromJid(this.get('from')); - - if (chatbox.get('nick') === nick) { - return _converse.xmppstatus.vcard; - } else { - let vcard; - - if (this.get('vcard_jid')) { - vcard = _converse.vcards.findWhere({ - 'jid': this.get('vcard_jid') + if (occupant && occupant.get('jid')) { + jid = occupant.get('jid'); + this.save({ + 'vcard_jid': jid + }, { + 'silent': true }); + } else { + jid = this.get('from'); } - if (!vcard) { - let jid; - const occupant = chatbox.occupants.findWhere({ - 'nick': nick - }); - - if (occupant && occupant.get('jid')) { - jid = occupant.get('jid'); - this.save({ - 'vcard_jid': jid - }, { - 'silent': true - }); - } else { - jid = this.get('from'); - } - - vcard = _converse.vcards.findWhere({ - 'jid': jid - }) || _converse.vcards.create({ - 'jid': jid - }); - } - - return vcard; - } - }, - - setVCard() { - if (this.get('type') === 'error') { - return; - } else if (this.get('type') === 'groupchat') { - this.vcard = this.getVCardForChatroomOccupant(); - } else { - const jid = this.get('from'); - this.vcard = _converse.vcards.findWhere({ + vcard = _converse.vcards.findWhere({ 'jid': jid }) || _converse.vcards.create({ 'jid': jid }); } - }, - isOnlyChatStateNotification() { - return u.isOnlyChatStateNotification(this); - }, + return vcard; + } + }, - getDisplayName() { - if (this.get('type') === 'groupchat') { - return this.get('nick'); - } else { - return this.vcard.get('fullname') || this.get('from'); - } - }, - - sendSlotRequestStanza() { - /* Send out an IQ stanza to request a file upload slot. - * - * https://xmpp.org/extensions/xep-0363.html#request - */ - const file = this.get('file'); - return new Promise((resolve, reject) => { - const iq = converse.env.$iq({ - 'from': _converse.jid, - 'to': this.get('slot_request_url'), - 'type': 'get' - }).c('request', { - 'xmlns': Strophe.NS.HTTPUPLOAD, - 'filename': file.name, - 'size': file.size, - 'content-type': file.type - }); - - _converse.connection.sendIQ(iq, resolve, reject); + setVCard() { + if (this.get('type') === 'error') { + return; + } else if (this.get('type') === 'groupchat') { + this.vcard = this.getVCardForChatroomOccupant(); + } else { + const jid = this.get('from'); + this.vcard = _converse.vcards.findWhere({ + 'jid': jid + }) || _converse.vcards.create({ + 'jid': jid }); - }, + } + }, - getRequestSlotURL() { - this.sendSlotRequestStanza().then(stanza => { - const slot = stanza.querySelector('slot'); + isOnlyChatStateNotification() { + return u.isOnlyChatStateNotification(this); + }, - if (slot) { - this.save({ - 'get': slot.querySelector('get').getAttribute('url'), - 'put': slot.querySelector('put').getAttribute('url') - }); - } else { - return this.save({ - 'type': 'error', - 'message': __("Sorry, could not determine file upload URL.") - }); - } - }).catch(e => { - _converse.log(e, Strophe.LogLevel.ERROR); + getDisplayName() { + if (this.get('type') === 'groupchat') { + return this.get('nick'); + } else { + return this.vcard.get('fullname') || this.get('from'); + } + }, + sendSlotRequestStanza() { + /* Send out an IQ stanza to request a file upload slot. + * + * https://xmpp.org/extensions/xep-0363.html#request + */ + const file = this.get('file'); + return new Promise((resolve, reject) => { + const iq = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.$iq({ + 'from': _converse.jid, + 'to': this.get('slot_request_url'), + 'type': 'get' + }).c('request', { + 'xmlns': Strophe.NS.HTTPUPLOAD, + 'filename': file.name, + 'size': file.size, + 'content-type': file.type + }); + + _converse.connection.sendIQ(iq, resolve, reject); + }); + }, + + getRequestSlotURL() { + this.sendSlotRequestStanza().then(stanza => { + const slot = stanza.querySelector('slot'); + + if (slot) { + this.save({ + 'get': slot.querySelector('get').getAttribute('url'), + 'put': slot.querySelector('put').getAttribute('url') + }); + } else { return this.save({ 'type': 'error', - 'message': __("Sorry, could not determine upload URL.") + 'message': __("Sorry, could not determine file upload URL.") }); + } + }).catch(e => { + _converse.log(e, Strophe.LogLevel.ERROR); + + return this.save({ + 'type': 'error', + 'message': __("Sorry, could not determine upload URL.") }); - }, + }); + }, - uploadFile() { - const xhr = new XMLHttpRequest(); + uploadFile() { + const xhr = new XMLHttpRequest(); - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - _converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO); + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + _converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO); - if (xhr.status === 200 || xhr.status === 201) { - this.save({ - 'upload': _converse.SUCCESS, - 'oob_url': this.get('get'), - 'message': this.get('get') - }); - } else { - xhr.onerror(); - } - } - }; - - xhr.upload.addEventListener("progress", evt => { - if (evt.lengthComputable) { - this.set('progress', evt.loaded / evt.total); - } - }, false); - - xhr.onerror = () => { - let message; - - if (xhr.responseText) { - message = __('Sorry, could not succesfully upload your file. Your server’s response: "%1$s"', xhr.responseText); + if (xhr.status === 200 || xhr.status === 201) { + this.save({ + 'upload': _converse.SUCCESS, + 'oob_url': this.get('get'), + 'message': this.get('get') + }); } else { - message = __('Sorry, could not succesfully upload your file.'); + xhr.onerror(); } + } + }; - this.save({ - 'type': 'error', - 'upload': _converse.FAILURE, - 'message': message - }); - }; + xhr.upload.addEventListener("progress", evt => { + if (evt.lengthComputable) { + this.set('progress', evt.loaded / evt.total); + } + }, false); - xhr.open('PUT', this.get('put'), true); - xhr.setRequestHeader("Content-type", this.get('file').type); - xhr.send(this.get('file')); + xhr.onerror = () => { + let message; + + if (xhr.responseText) { + message = __('Sorry, could not succesfully upload your file. Your server’s response: "%1$s"', xhr.responseText); + } else { + message = __('Sorry, could not succesfully upload your file.'); + } + + this.save({ + 'type': 'error', + 'upload': _converse.FAILURE, + 'message': message + }); + }; + + xhr.open('PUT', this.get('put'), true); + xhr.setRequestHeader("Content-type", this.get('file').type); + xhr.send(this.get('file')); + } + + }); + _converse.Messages = Backbone.Collection.extend({ + model: _converse.Message, + comparator: 'time' + }); + _converse.ChatBox = _converse.ModelWithVCardAndPresence.extend({ + defaults() { + return { + 'bookmarked': false, + 'chat_state': undefined, + 'num_unread': 0, + 'type': _converse.PRIVATE_CHAT_TYPE, + 'message_type': 'chat', + 'url': '', + 'hidden': _.includes(['mobile', 'fullscreen'], _converse.view_mode) + }; + }, + + initialize() { + _converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments); + + _converse.api.waitUntil('rosterContactsFetched').then(() => { + this.addRelatedContact(_converse.roster.findWhere({ + 'jid': this.get('jid') + })); + }); + + this.messages = new _converse.Messages(); + + const storage = _converse.config.get('storage'); + + this.messages.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`)); + this.messages.chatbox = this; + this.messages.on('change:upload', message => { + if (message.get('upload') === _converse.SUCCESS) { + this.sendMessageStanza(this.createMessageStanza(message)); + } + }); + this.on('change:chat_state', this.sendChatState, this); + this.save({ + // The chat_state will be set to ACTIVE once the chat box is opened + // and we listen for change:chat_state, so shouldn't set it to ACTIVE here. + 'box_id': b64_sha1(this.get('jid')), + 'time_opened': this.get('time_opened') || moment().valueOf(), + 'user_id': Strophe.getNodeFromJid(this.get('jid')) + }); + }, + + addRelatedContact(contact) { + if (!_.isUndefined(contact)) { + this.contact = contact; + this.trigger('contactAdded', contact); + } + }, + + getDisplayName() { + return this.vcard.get('fullname') || this.get('jid'); + }, + + handleMessageCorrection(stanza) { + const replace = sizzle(`replace[xmlns="${Strophe.NS.MESSAGE_CORRECT}"]`, stanza).pop(); + + if (replace) { + const msgid = replace && replace.getAttribute('id') || stanza.getAttribute('id'), + message = msgid && this.messages.findWhere({ + msgid + }); + + if (!message) { + // XXX: Looks like we received a correction for a + // non-existing message, probably due to MAM. + // Not clear what can be done about this... we'll + // just create it as a separate message for now. + return false; + } + + const older_versions = message.get('older_versions') || []; + older_versions.push(message.get('message')); + message.save({ + 'message': _converse.chatboxes.getMessageBody(stanza), + 'references': this.getReferencesFromStanza(stanza), + 'older_versions': older_versions, + 'edited': moment().format() + }); + return true; } - }); - _converse.Messages = Backbone.Collection.extend({ - model: _converse.Message, - comparator: 'time' - }); - _converse.ChatBox = _converse.ModelWithVCardAndPresence.extend({ - defaults() { - return { - 'bookmarked': false, - 'chat_state': undefined, - 'num_unread': 0, - 'type': _converse.PRIVATE_CHAT_TYPE, - 'message_type': 'chat', - 'url': '', - 'hidden': _.includes(['mobile', 'fullscreen'], _converse.view_mode) - }; - }, + return false; + }, - initialize() { - _converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments); + createMessageStanza(message) { + /* Given a _converse.Message Backbone.Model, return the XML + * stanza that represents it. + * + * Parameters: + * (Object) message - The Backbone.Model representing the message + */ + const stanza = $msg({ + 'from': _converse.connection.jid, + 'to': this.get('jid'), + 'type': this.get('message_type'), + 'id': message.get('edited') && _converse.connection.getUniqueId() || message.get('msgid') + }).c('body').t(message.get('message')).up().c(_converse.ACTIVE, { + 'xmlns': Strophe.NS.CHATSTATES + }).up(); - _converse.api.waitUntil('rosterContactsFetched').then(() => { - this.addRelatedContact(_converse.roster.findWhere({ - 'jid': this.get('jid') - })); - }); - - this.messages = new _converse.Messages(); - - const storage = _converse.config.get('storage'); - - this.messages.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`)); - this.messages.chatbox = this; - this.messages.on('change:upload', message => { - if (message.get('upload') === _converse.SUCCESS) { - this.sendMessageStanza(this.createMessageStanza(message)); - } - }); - this.on('change:chat_state', this.sendChatState, this); - this.save({ - // The chat_state will be set to ACTIVE once the chat box is opened - // and we listen for change:chat_state, so shouldn't set it to ACTIVE here. - 'box_id': b64_sha1(this.get('jid')), - 'time_opened': this.get('time_opened') || moment().valueOf(), - 'user_id': Strophe.getNodeFromJid(this.get('jid')) - }); - }, - - addRelatedContact(contact) { - if (!_.isUndefined(contact)) { - this.contact = contact; - this.trigger('contactAdded', contact); - } - }, - - getDisplayName() { - return this.vcard.get('fullname') || this.get('jid'); - }, - - handleMessageCorrection(stanza) { - const replace = sizzle(`replace[xmlns="${Strophe.NS.MESSAGE_CORRECT}"]`, stanza).pop(); - - if (replace) { - const msgid = replace && replace.getAttribute('id') || stanza.getAttribute('id'), - message = msgid && this.messages.findWhere({ - msgid - }); - - if (!message) { - // XXX: Looks like we received a correction for a - // non-existing message, probably due to MAM. - // Not clear what can be done about this... we'll - // just create it as a separate message for now. - return false; - } - - const older_versions = message.get('older_versions') || []; - older_versions.push(message.get('message')); - message.save({ - 'message': _converse.chatboxes.getMessageBody(stanza), - 'references': this.getReferencesFromStanza(stanza), - 'older_versions': older_versions, - 'edited': moment().format() - }); - return true; - } - - return false; - }, - - createMessageStanza(message) { - /* Given a _converse.Message Backbone.Model, return the XML - * stanza that represents it. - * - * Parameters: - * (Object) message - The Backbone.Model representing the message - */ - const stanza = $msg({ - 'from': _converse.connection.jid, - 'to': this.get('jid'), - 'type': this.get('message_type'), - 'id': message.get('edited') && _converse.connection.getUniqueId() || message.get('msgid') - }).c('body').t(message.get('message')).up().c(_converse.ACTIVE, { - 'xmlns': Strophe.NS.CHATSTATES - }).up(); - - if (message.get('is_spoiler')) { - if (message.get('spoiler_hint')) { - stanza.c('spoiler', { - 'xmlns': Strophe.NS.SPOILER - }, message.get('spoiler_hint')).up(); - } else { - stanza.c('spoiler', { - 'xmlns': Strophe.NS.SPOILER - }).up(); - } - } - - (message.get('references') || []).forEach(reference => { - const attrs = { - 'xmlns': Strophe.NS.REFERENCE, - 'begin': reference.begin, - 'end': reference.end, - 'type': reference.type - }; - - if (reference.uri) { - attrs.uri = reference.uri; - } - - stanza.c('reference', attrs).up(); - }); - - if (message.get('file')) { - stanza.c('x', { - 'xmlns': Strophe.NS.OUTOFBAND - }).c('url').t(message.get('message')).up(); - } - - if (message.get('edited')) { - stanza.c('replace', { - 'xmlns': Strophe.NS.MESSAGE_CORRECT, - 'id': message.get('msgid') + if (message.get('is_spoiler')) { + if (message.get('spoiler_hint')) { + stanza.c('spoiler', { + 'xmlns': Strophe.NS.SPOILER + }, message.get('spoiler_hint')).up(); + } else { + stanza.c('spoiler', { + 'xmlns': Strophe.NS.SPOILER }).up(); } + } - return stanza; - }, + (message.get('references') || []).forEach(reference => { + const attrs = { + 'xmlns': Strophe.NS.REFERENCE, + 'begin': reference.begin, + 'end': reference.end, + 'type': reference.type + }; - sendMessageStanza(stanza) { - _converse.connection.send(stanza); - - if (_converse.forward_messages) { - // Forward the message, so that other connected resources are also aware of it. - _converse.connection.send($msg({ - 'to': _converse.bare_jid, - 'type': this.get('message_type') - }).c('forwarded', { - 'xmlns': Strophe.NS.FORWARD - }).c('delay', { - 'xmns': Strophe.NS.DELAY, - 'stamp': moment().format() - }).up().cnode(stanza.tree())); + if (reference.uri) { + attrs.uri = reference.uri; } - }, - getOutgoingMessageAttributes(text, spoiler_hint) { - const is_spoiler = this.get('composing_spoiler'); - return _.extend(this.toJSON(), { - 'id': _converse.connection.getUniqueId(), - 'fullname': _converse.xmppstatus.get('fullname'), - 'from': _converse.bare_jid, - 'sender': 'me', - 'time': moment().format(), - 'message': text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined, - 'is_spoiler': is_spoiler, - 'spoiler_hint': is_spoiler ? spoiler_hint : undefined, + stanza.c('reference', attrs).up(); + }); + + if (message.get('file')) { + stanza.c('x', { + 'xmlns': Strophe.NS.OUTOFBAND + }).c('url').t(message.get('message')).up(); + } + + if (message.get('edited')) { + stanza.c('replace', { + 'xmlns': Strophe.NS.MESSAGE_CORRECT, + 'id': message.get('msgid') + }).up(); + } + + return stanza; + }, + + sendMessageStanza(stanza) { + _converse.connection.send(stanza); + + if (_converse.forward_messages) { + // Forward the message, so that other connected resources are also aware of it. + _converse.connection.send($msg({ + 'to': _converse.bare_jid, 'type': this.get('message_type') + }).c('forwarded', { + 'xmlns': Strophe.NS.FORWARD + }).c('delay', { + 'xmns': Strophe.NS.DELAY, + 'stamp': moment().format() + }).up().cnode(stanza.tree())); + } + }, + + getOutgoingMessageAttributes(text, spoiler_hint) { + const is_spoiler = this.get('composing_spoiler'); + return _.extend(this.toJSON(), { + 'id': _converse.connection.getUniqueId(), + 'fullname': _converse.xmppstatus.get('fullname'), + 'from': _converse.bare_jid, + 'sender': 'me', + 'time': moment().format(), + 'message': text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined, + 'is_spoiler': is_spoiler, + 'spoiler_hint': is_spoiler ? spoiler_hint : undefined, + 'type': this.get('message_type') + }); + }, + + sendMessage(attrs) { + /* Responsible for sending off a text message. + * + * Parameters: + * (Message) message - The chat message + */ + let message = this.messages.findWhere('correcting'); + + if (message) { + const older_versions = message.get('older_versions') || []; + older_versions.push(message.get('message')); + message.save({ + 'correcting': false, + 'edited': moment().format(), + 'message': attrs.message, + 'older_versions': older_versions, + 'references': attrs.references }); - }, + } else { + message = this.messages.create(attrs); + } - sendMessage(attrs) { - /* Responsible for sending off a text message. - * - * Parameters: - * (Message) message - The chat message - */ - let message = this.messages.findWhere('correcting'); + return this.sendMessageStanza(this.createMessageStanza(message)); + }, - if (message) { - const older_versions = message.get('older_versions') || []; - older_versions.push(message.get('message')); - message.save({ - 'correcting': false, - 'edited': moment().format(), - 'message': attrs.message, - 'older_versions': older_versions, - 'references': attrs.references + sendChatState() { + /* Sends a message with the status of the user in this chat session + * as taken from the 'chat_state' attribute of the chat box. + * See XEP-0085 Chat State Notifications. + */ + if (_converse.send_chat_state_notifications) { + _converse.connection.send($msg({ + 'to': this.get('jid'), + 'type': 'chat' + }).c(this.get('chat_state'), { + 'xmlns': Strophe.NS.CHATSTATES + }).up().c('no-store', { + 'xmlns': Strophe.NS.HINTS + }).up().c('no-permanent-store', { + 'xmlns': Strophe.NS.HINTS + })); + } + }, + + sendFiles(files) { + _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(result => { + const item = result.pop(), + data = item.dataforms.where({ + 'FORM_TYPE': { + 'value': Strophe.NS.HTTPUPLOAD, + 'type': "hidden" + } + }).pop(), + max_file_size = window.parseInt(_.get(data, 'attributes.max-file-size.value')), + slot_request_url = _.get(item, 'id'); + + if (!slot_request_url) { + this.messages.create({ + 'message': __("Sorry, looks like file upload is not supported by your server."), + 'type': 'error' }); - } else { - message = this.messages.create(attrs); + return; } - return this.sendMessageStanza(this.createMessageStanza(message)); - }, - - sendChatState() { - /* Sends a message with the status of the user in this chat session - * as taken from the 'chat_state' attribute of the chat box. - * See XEP-0085 Chat State Notifications. - */ - if (_converse.send_chat_state_notifications) { - _converse.connection.send($msg({ - 'to': this.get('jid'), - 'type': 'chat' - }).c(this.get('chat_state'), { - 'xmlns': Strophe.NS.CHATSTATES - }).up().c('no-store', { - 'xmlns': Strophe.NS.HINTS - }).up().c('no-permanent-store', { - 'xmlns': Strophe.NS.HINTS - })); - } - }, - - sendFiles(files) { - _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(result => { - const item = result.pop(), - data = item.dataforms.where({ - 'FORM_TYPE': { - 'value': Strophe.NS.HTTPUPLOAD, - 'type': "hidden" - } - }).pop(), - max_file_size = window.parseInt(_.get(data, 'attributes.max-file-size.value')), - slot_request_url = _.get(item, 'id'); - - if (!slot_request_url) { - this.messages.create({ - 'message': __("Sorry, looks like file upload is not supported by your server."), + _.each(files, file => { + if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) { + return this.messages.create({ + 'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.', file.name, filesize__WEBPACK_IMPORTED_MODULE_3___default()(max_file_size)), 'type': 'error' }); - return; - } - - _.each(files, file => { - if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) { - return this.messages.create({ - 'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.', file.name, filesize(max_file_size)), - 'type': 'error' - }); - } else { - this.messages.create(_.extend(this.getOutgoingMessageAttributes(), { - 'file': file, - 'progress': 0, - 'slot_request_url': slot_request_url - })); - } - }); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - getReferencesFromStanza(stanza) { - const text = _.propertyOf(stanza.querySelector('body'))('textContent'); - - return sizzle(`reference[xmlns="${Strophe.NS.REFERENCE}"]`, stanza).map(ref => { - const begin = ref.getAttribute('begin'), - end = ref.getAttribute('end'); - return { - 'begin': begin, - 'end': end, - 'type': ref.getAttribute('type'), - 'value': text.slice(begin, end), - 'uri': ref.getAttribute('uri') - }; - }); - }, - - getMessageAttributesFromStanza(stanza, original_stanza) { - /* Parses a passed in message stanza and returns an object - * of attributes. - * - * Parameters: - * (XMLElement) stanza - The message stanza - * (XMLElement) delay - The node from the - * stanza, if there was one. - * (XMLElement) original_stanza - The original stanza, - * that contains the message stanza, if it was - * contained, otherwise it's the message stanza itself. - */ - const archive = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop(), - spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(), - delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(), - chat_state = stanza.getElementsByTagName(_converse.COMPOSING).length && _converse.COMPOSING || stanza.getElementsByTagName(_converse.PAUSED).length && _converse.PAUSED || stanza.getElementsByTagName(_converse.INACTIVE).length && _converse.INACTIVE || stanza.getElementsByTagName(_converse.ACTIVE).length && _converse.ACTIVE || stanza.getElementsByTagName(_converse.GONE).length && _converse.GONE; - - const attrs = { - 'chat_state': chat_state, - 'is_archived': !_.isNil(archive), - 'is_delayed': !_.isNil(delay), - 'is_spoiler': !_.isNil(spoiler), - 'message': _converse.chatboxes.getMessageBody(stanza) || undefined, - 'references': this.getReferencesFromStanza(stanza), - 'msgid': stanza.getAttribute('id'), - 'time': delay ? delay.getAttribute('stamp') : moment().format(), - 'type': stanza.getAttribute('type') - }; - - if (attrs.type === 'groupchat') { - attrs.from = stanza.getAttribute('from'); - attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from)); - attrs.sender = attrs.nick === this.get('nick') ? 'me' : 'them'; - } else { - attrs.from = Strophe.getBareJidFromJid(stanza.getAttribute('from')); - - if (attrs.from === _converse.bare_jid) { - attrs.sender = 'me'; - attrs.fullname = _converse.xmppstatus.get('fullname'); } else { - attrs.sender = 'them'; - attrs.fullname = this.get('fullname'); - } - } - - _.each(sizzle(`x[xmlns="${Strophe.NS.OUTOFBAND}"]`, stanza), xform => { - attrs['oob_url'] = xform.querySelector('url').textContent; - attrs['oob_desc'] = xform.querySelector('url').textContent; - }); - - if (spoiler) { - attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : ''; - } - - return attrs; - }, - - createMessage(message, original_stanza) { - /* Create a Backbone.Message object inside this chat box - * based on the identified message stanza. - */ - const that = this; - - function _create(attrs) { - const is_csn = u.isOnlyChatStateNotification(attrs); - - if (is_csn && (attrs.is_delayed || attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == that.get('nick'))) { - // XXX: MUC leakage - // No need showing delayed or our own CSN messages - return; - } else if (!is_csn && !attrs.file && !attrs.plaintext && !attrs.message && !attrs.oob_url && attrs.type !== 'error') { - // TODO: handle messages (currently being done by ChatRoom) - return; - } else { - return that.messages.create(attrs); - } - } - - const result = this.getMessageAttributesFromStanza(message, original_stanza); - - if (typeof result.then === "function") { - return new Promise((resolve, reject) => result.then(attrs => resolve(_create(attrs)))); - } else { - const message = _create(result); - - return Promise.resolve(message); - } - }, - - isHidden() { - /* Returns a boolean to indicate whether a newly received - * message will be visible to the user or not. - */ - return this.get('hidden') || this.get('minimized') || this.isScrolledUp() || _converse.windowState === 'hidden'; - }, - - incrementUnreadMsgCounter(message) { - /* Given a newly received message, update the unread counter if - * necessary. - */ - if (!message) { - return; - } - - if (_.isNil(message.get('message'))) { - return; - } - - if (utils.isNewMessage(message) && this.isHidden()) { - this.save({ - 'num_unread': this.get('num_unread') + 1 - }); - - _converse.incrementMsgCounter(); - } - }, - - clearUnreadMsgCounter() { - u.safeSave(this, { - 'num_unread': 0 - }); - }, - - isScrolledUp() { - return this.get('scrolled', true); - } - - }); - _converse.ChatBoxes = Backbone.Collection.extend({ - comparator: 'time_opened', - - model(attrs, options) { - return new _converse.ChatBox(attrs, options); - }, - - registerMessageHandler() { - _converse.connection.addHandler(stanza => { - this.onMessage(stanza); - return true; - }, null, 'message', 'chat'); - - _converse.connection.addHandler(stanza => { - this.onErrorMessage(stanza); - return true; - }, null, 'message', 'error'); - }, - - chatBoxMayBeShown(chatbox) { - return true; - }, - - onChatBoxesFetched(collection) { - /* Show chat boxes upon receiving them from sessionStorage */ - collection.each(chatbox => { - if (this.chatBoxMayBeShown(chatbox)) { - chatbox.trigger('show'); + this.messages.create(_.extend(this.getOutgoingMessageAttributes(), { + 'file': file, + 'progress': 0, + 'slot_request_url': slot_request_url + })); } }); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, - _converse.emit('chatBoxesFetched'); - }, - - onConnected() { - this.browserStorage = new Backbone.BrowserStorage.session(`converse.chatboxes-${_converse.bare_jid}`); - this.registerMessageHandler(); - this.fetch({ - 'add': true, - 'success': this.onChatBoxesFetched.bind(this) - }); - }, - - onErrorMessage(message) { - /* Handler method for all incoming error message stanzas - */ - const from_jid = Strophe.getBareJidFromJid(message.getAttribute('from')); - - if (utils.isSameBareJID(from_jid, _converse.bare_jid)) { - return true; - } - - const chatbox = this.getChatBox(from_jid); - - if (!chatbox) { - return true; - } - - chatbox.createMessage(message, message); - return true; - }, - - getMessageBody(stanza) { - /* Given a message stanza, return the text contained in its body. - */ - const type = stanza.getAttribute('type'); - - if (type === 'error') { - const error = stanza.querySelector('error'); - return _.propertyOf(error.querySelector('text'))('textContent') || __('Sorry, an error occurred:') + ' ' + error.innerHTML; - } else { - return _.propertyOf(stanza.querySelector('body'))('textContent'); - } - }, - - onMessage(stanza) { - /* Handler method for all incoming single-user chat "message" - * stanzas. - * - * Parameters: - * (XMLElement) stanza - The incoming message stanza - */ - let to_jid = stanza.getAttribute('to'); - const to_resource = Strophe.getResourceFromJid(to_jid); - - if (_converse.filter_by_resource && to_resource && to_resource !== _converse.resource) { - _converse.log(`onMessage: Ignoring incoming message intended for a different resource: ${to_jid}`, Strophe.LogLevel.INFO); - - return true; - } else if (utils.isHeadlineMessage(_converse, stanza)) { - // XXX: Ideally we wouldn't have to check for headline - // messages, but Prosody sends headline messages with the - // wrong type ('chat'), so we need to filter them out here. - _converse.log(`onMessage: Ignoring incoming headline message sent with type 'chat' from JID: ${stanza.getAttribute('from')}`, Strophe.LogLevel.INFO); - - return true; - } - - let from_jid = stanza.getAttribute('from'); - const forwarded = stanza.querySelector('forwarded'), - original_stanza = stanza; - - if (!_.isNull(forwarded)) { - const forwarded_message = forwarded.querySelector('message'), - forwarded_from = forwarded_message.getAttribute('from'), - is_carbon = !_.isNull(stanza.querySelector(`received[xmlns="${Strophe.NS.CARBONS}"]`)); - - if (is_carbon && Strophe.getBareJidFromJid(forwarded_from) !== from_jid) { - // Prevent message forging via carbons - // https://xmpp.org/extensions/xep-0280.html#security - return true; - } - - stanza = forwarded_message; - from_jid = stanza.getAttribute('from'); - to_jid = stanza.getAttribute('to'); - } - - const from_bare_jid = Strophe.getBareJidFromJid(from_jid), - from_resource = Strophe.getResourceFromJid(from_jid), - is_me = from_bare_jid === _converse.bare_jid; - let contact_jid; - - if (is_me) { - // I am the sender, so this must be a forwarded message... - if (_.isNull(to_jid)) { - return _converse.log(`Don't know how to handle message stanza without 'to' attribute. ${stanza.outerHTML}`, Strophe.LogLevel.ERROR); - } - - contact_jid = Strophe.getBareJidFromJid(to_jid); - } else { - contact_jid = from_bare_jid; - } - - const attrs = { - 'fullname': _.get(_converse.api.contacts.get(contact_jid), 'attributes.fullname') // Get chat box, but only create a new one when the message has a body. + getReferencesFromStanza(stanza) { + const text = _.propertyOf(stanza.querySelector('body'))('textContent'); + return sizzle(`reference[xmlns="${Strophe.NS.REFERENCE}"]`, stanza).map(ref => { + const begin = ref.getAttribute('begin'), + end = ref.getAttribute('end'); + return { + 'begin': begin, + 'end': end, + 'type': ref.getAttribute('type'), + 'value': text.slice(begin, end), + 'uri': ref.getAttribute('uri') }; - const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}`).length > 0; - const chatbox = this.getChatBox(contact_jid, attrs, has_body); - - if (chatbox && !chatbox.handleMessageCorrection(stanza)) { - const msgid = stanza.getAttribute('id'), - message = msgid && chatbox.messages.findWhere({ - msgid - }); - - if (!message) { - // Only create the message when we're sure it's not a duplicate - chatbox.createMessage(stanza, original_stanza).then(msg => chatbox.incrementUnreadMsgCounter(msg)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - } - - _converse.emit('message', { - 'stanza': original_stanza, - 'chatbox': chatbox - }); - - return true; - }, - - getChatBox(jid, attrs = {}, create) { - /* Returns a chat box or optionally return a newly - * created one if one doesn't exist. - * - * Parameters: - * (String) jid - The JID of the user whose chat box we want - * (Boolean) create - Should a new chat box be created if none exists? - * (Object) attrs - Optional chat box atributes. - */ - if (_.isObject(jid)) { - create = attrs; - attrs = jid; - jid = attrs.jid; - } - - jid = Strophe.getBareJidFromJid(jid.toLowerCase()); - let chatbox = this.get(Strophe.getBareJidFromJid(jid)); - - if (!chatbox && create) { - _.extend(attrs, { - 'jid': jid, - 'id': jid - }); - - chatbox = this.create(attrs, { - 'error'(model, response) { - _converse.log(response.responseText); - } - - }); - } - - return chatbox; - } - - }); - - function autoJoinChats() { - /* Automatically join private chats, based on the - * "auto_join_private_chats" configuration setting. - */ - _.each(_converse.auto_join_private_chats, function (jid) { - if (_converse.chatboxes.where({ - 'jid': jid - }).length) { - return; - } - - if (_.isString(jid)) { - _converse.api.chats.open(jid); - } else { - _converse.log('Invalid jid criteria specified for "auto_join_private_chats"', Strophe.LogLevel.ERROR); - } }); + }, - _converse.emit('privateChatsAutoJoined'); - } - /************************ BEGIN Event Handlers ************************/ - - - _converse.on('chatBoxesFetched', autoJoinChats); - - _converse.api.waitUntil('rosterContactsFetched').then(() => { - _converse.roster.on('add', contact => { - /* When a new contact is added, check if we already have a - * chatbox open for it, and if so attach it to the chatbox. - */ - const chatbox = _converse.chatboxes.findWhere({ - 'jid': contact.get('jid') - }); - - if (chatbox) { - chatbox.addRelatedContact(contact); - } - }); - }); - - _converse.on('addClientFeatures', () => { - _converse.api.disco.own.features.add(Strophe.NS.MESSAGE_CORRECT); - - _converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD); - - _converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND); - }); - - _converse.api.listen.on('pluginsInitialized', () => { - _converse.chatboxes = new _converse.ChatBoxes(); - - _converse.emit('chatBoxesInitialized'); - }); - - _converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected()); - /************************ END Event Handlers ************************/ - - /************************ BEGIN API ************************/ - - - _.extend(_converse.api, { - /** - * The "chats" namespace (used for one-on-one chats) + getMessageAttributesFromStanza(stanza, original_stanza) { + /* Parses a passed in message stanza and returns an object + * of attributes. * - * @namespace _converse.api.chats - * @memberOf _converse.api + * Parameters: + * (XMLElement) stanza - The message stanza + * (XMLElement) delay - The node from the + * stanza, if there was one. + * (XMLElement) original_stanza - The original stanza, + * that contains the message stanza, if it was + * contained, otherwise it's the message stanza itself. */ - 'chats': { - /** - * @method _converse.api.chats.create - * @param {string|string[]} jid|jids An jid or array of jids - * @param {object} attrs An object containing configuration attributes. - */ - 'create'(jids, attrs) { - if (_.isUndefined(jids)) { - _converse.log("chats.create: You need to provide at least one JID", Strophe.LogLevel.ERROR); + const archive = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop(), + spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(), + delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(), + chat_state = stanza.getElementsByTagName(_converse.COMPOSING).length && _converse.COMPOSING || stanza.getElementsByTagName(_converse.PAUSED).length && _converse.PAUSED || stanza.getElementsByTagName(_converse.INACTIVE).length && _converse.INACTIVE || stanza.getElementsByTagName(_converse.ACTIVE).length && _converse.ACTIVE || stanza.getElementsByTagName(_converse.GONE).length && _converse.GONE; - return null; - } + const attrs = { + 'chat_state': chat_state, + 'is_archived': !_.isNil(archive), + 'is_delayed': !_.isNil(delay), + 'is_spoiler': !_.isNil(spoiler), + 'message': _converse.chatboxes.getMessageBody(stanza) || undefined, + 'references': this.getReferencesFromStanza(stanza), + 'msgid': stanza.getAttribute('id'), + 'time': delay ? delay.getAttribute('stamp') : moment().format(), + 'type': stanza.getAttribute('type') + }; - if (_.isString(jids)) { - if (attrs && !_.get(attrs, 'fullname')) { - attrs.fullname = _.get(_converse.api.contacts.get(jids), 'attributes.fullname'); - } + if (attrs.type === 'groupchat') { + attrs.from = stanza.getAttribute('from'); + attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from)); + attrs.sender = attrs.nick === this.get('nick') ? 'me' : 'them'; + } else { + attrs.from = Strophe.getBareJidFromJid(stanza.getAttribute('from')); - const chatbox = _converse.chatboxes.getChatBox(jids, attrs, true); + if (attrs.from === _converse.bare_jid) { + attrs.sender = 'me'; + attrs.fullname = _converse.xmppstatus.get('fullname'); + } else { + attrs.sender = 'them'; + attrs.fullname = this.get('fullname'); + } + } - if (_.isNil(chatbox)) { - _converse.log("Could not open chatbox for JID: " + jids, Strophe.LogLevel.ERROR); + _.each(sizzle(`x[xmlns="${Strophe.NS.OUTOFBAND}"]`, stanza), xform => { + attrs['oob_url'] = xform.querySelector('url').textContent; + attrs['oob_desc'] = xform.querySelector('url').textContent; + }); - return; - } + if (spoiler) { + attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : ''; + } - return chatbox; - } + return attrs; + }, - return _.map(jids, jid => { - attrs.fullname = _.get(_converse.api.contacts.get(jid), 'attributes.fullname'); - return _converse.chatboxes.getChatBox(jid, attrs, true).trigger('show'); - }); - }, + createMessage(message, original_stanza) { + /* Create a Backbone.Message object inside this chat box + * based on the identified message stanza. + */ + const that = this; - /** - * Opens a new one-on-one chat. - * - * @method _converse.api.chats.open - * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] - * @returns {Promise} Promise which resolves with the Backbone.Model representing the chat. - * - * @example - * // To open a single chat, provide the JID of the contact you're chatting with in that chat: - * converse.plugins.add('myplugin', { - * initialize: function() { - * var _converse = this._converse; - * // Note, buddy@example.org must be in your contacts roster! - * _converse.api.chats.open('buddy@example.com').then((chat) => { - * // Now you can do something with the chat model - * }); - * } - * }); - * - * @example - * // To open an array of chats, provide an array of JIDs: - * converse.plugins.add('myplugin', { - * initialize: function () { - * var _converse = this._converse; - * // Note, these users must first be in your contacts roster! - * _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']).then((chats) => { - * // Now you can do something with the chat models - * }); - * } - * }); - * - */ - 'open'(jids, attrs) { - return new Promise((resolve, reject) => { - Promise.all([_converse.api.waitUntil('rosterContactsFetched'), _converse.api.waitUntil('chatBoxesFetched')]).then(() => { - if (_.isUndefined(jids)) { - const err_msg = "chats.open: You need to provide at least one JID"; + function _create(attrs) { + const is_csn = u.isOnlyChatStateNotification(attrs); - _converse.log(err_msg, Strophe.LogLevel.ERROR); + if (is_csn && (attrs.is_delayed || attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == that.get('nick'))) { + // XXX: MUC leakage + // No need showing delayed or our own CSN messages + return; + } else if (!is_csn && !attrs.file && !attrs.plaintext && !attrs.message && !attrs.oob_url && attrs.type !== 'error') { + // TODO: handle messages (currently being done by ChatRoom) + return; + } else { + return that.messages.create(attrs); + } + } - reject(new Error(err_msg)); - } else if (_.isString(jids)) { - resolve(_converse.api.chats.create(jids, attrs).trigger('show')); - } else { - resolve(_.map(jids, jid => _converse.api.chats.create(jid, attrs).trigger('show'))); - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }); - }, + const result = this.getMessageAttributesFromStanza(message, original_stanza); - /** - * Returns a chat model. The chat should already be open. - * - * @method _converse.api.chats.get - * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] - * @returns {Backbone.Model} - * - * @example - * // To return a single chat, provide the JID of the contact you're chatting with in that chat: - * const model = _converse.api.chats.get('buddy@example.com'); - * - * @example - * // To return an array of chats, provide an array of JIDs: - * const models = _converse.api.chats.get(['buddy1@example.com', 'buddy2@example.com']); - * - * @example - * // To return all open chats, call the method without any parameters:: - * const models = _converse.api.chats.get(); - * - */ - 'get'(jids) { - if (_.isUndefined(jids)) { - const result = []; + if (typeof result.then === "function") { + return new Promise((resolve, reject) => result.then(attrs => resolve(_create(attrs)))); + } else { + const message = _create(result); - _converse.chatboxes.each(function (chatbox) { - // FIXME: Leaky abstraction from MUC. We need to add a - // base type for chat boxes, and check for that. - if (chatbox.get('type') !== _converse.CHATROOMS_TYPE) { - result.push(chatbox); - } - }); + return Promise.resolve(message); + } + }, - return result; - } else if (_.isString(jids)) { - return _converse.chatboxes.getChatBox(jids); - } + isHidden() { + /* Returns a boolean to indicate whether a newly received + * message will be visible to the user or not. + */ + return this.get('hidden') || this.get('minimized') || this.isScrolledUp() || _converse.windowState === 'hidden'; + }, - return _.map(jids, _.partial(_converse.chatboxes.getChatBox.bind(_converse.chatboxes), _, {}, true)); + incrementUnreadMsgCounter(message) { + /* Given a newly received message, update the unread counter if + * necessary. + */ + if (!message) { + return; + } + + if (_.isNil(message.get('message'))) { + return; + } + + if (utils.isNewMessage(message) && this.isHidden()) { + this.save({ + 'num_unread': this.get('num_unread') + 1 + }); + + _converse.incrementMsgCounter(); + } + }, + + clearUnreadMsgCounter() { + u.safeSave(this, { + 'num_unread': 0 + }); + }, + + isScrolledUp() { + return this.get('scrolled', true); + } + + }); + _converse.ChatBoxes = Backbone.Collection.extend({ + comparator: 'time_opened', + + model(attrs, options) { + return new _converse.ChatBox(attrs, options); + }, + + registerMessageHandler() { + _converse.connection.addHandler(stanza => { + this.onMessage(stanza); + return true; + }, null, 'message', 'chat'); + + _converse.connection.addHandler(stanza => { + this.onErrorMessage(stanza); + return true; + }, null, 'message', 'error'); + }, + + chatBoxMayBeShown(chatbox) { + return true; + }, + + onChatBoxesFetched(collection) { + /* Show chat boxes upon receiving them from sessionStorage */ + collection.each(chatbox => { + if (this.chatBoxMayBeShown(chatbox)) { + chatbox.trigger('show'); + } + }); + + _converse.emit('chatBoxesFetched'); + }, + + onConnected() { + this.browserStorage = new Backbone.BrowserStorage.session(`converse.chatboxes-${_converse.bare_jid}`); + this.registerMessageHandler(); + this.fetch({ + 'add': true, + 'success': this.onChatBoxesFetched.bind(this) + }); + }, + + onErrorMessage(message) { + /* Handler method for all incoming error message stanzas + */ + const from_jid = Strophe.getBareJidFromJid(message.getAttribute('from')); + + if (utils.isSameBareJID(from_jid, _converse.bare_jid)) { + return true; + } + + const chatbox = this.getChatBox(from_jid); + + if (!chatbox) { + return true; + } + + chatbox.createMessage(message, message); + return true; + }, + + getMessageBody(stanza) { + /* Given a message stanza, return the text contained in its body. + */ + const type = stanza.getAttribute('type'); + + if (type === 'error') { + const error = stanza.querySelector('error'); + return _.propertyOf(error.querySelector('text'))('textContent') || __('Sorry, an error occurred:') + ' ' + error.innerHTML; + } else { + return _.propertyOf(stanza.querySelector('body'))('textContent'); + } + }, + + onMessage(stanza) { + /* Handler method for all incoming single-user chat "message" + * stanzas. + * + * Parameters: + * (XMLElement) stanza - The incoming message stanza + */ + let to_jid = stanza.getAttribute('to'); + const to_resource = Strophe.getResourceFromJid(to_jid); + + if (_converse.filter_by_resource && to_resource && to_resource !== _converse.resource) { + _converse.log(`onMessage: Ignoring incoming message intended for a different resource: ${to_jid}`, Strophe.LogLevel.INFO); + + return true; + } else if (utils.isHeadlineMessage(_converse, stanza)) { + // XXX: Ideally we wouldn't have to check for headline + // messages, but Prosody sends headline messages with the + // wrong type ('chat'), so we need to filter them out here. + _converse.log(`onMessage: Ignoring incoming headline message sent with type 'chat' from JID: ${stanza.getAttribute('from')}`, Strophe.LogLevel.INFO); + + return true; + } + + let from_jid = stanza.getAttribute('from'); + const forwarded = stanza.querySelector('forwarded'), + original_stanza = stanza; + + if (!_.isNull(forwarded)) { + const forwarded_message = forwarded.querySelector('message'), + forwarded_from = forwarded_message.getAttribute('from'), + is_carbon = !_.isNull(stanza.querySelector(`received[xmlns="${Strophe.NS.CARBONS}"]`)); + + if (is_carbon && Strophe.getBareJidFromJid(forwarded_from) !== from_jid) { + // Prevent message forging via carbons + // https://xmpp.org/extensions/xep-0280.html#security + return true; } + stanza = forwarded_message; + from_jid = stanza.getAttribute('from'); + to_jid = stanza.getAttribute('to'); + } + + const from_bare_jid = Strophe.getBareJidFromJid(from_jid), + from_resource = Strophe.getResourceFromJid(from_jid), + is_me = from_bare_jid === _converse.bare_jid; + let contact_jid; + + if (is_me) { + // I am the sender, so this must be a forwarded message... + if (_.isNull(to_jid)) { + return _converse.log(`Don't know how to handle message stanza without 'to' attribute. ${stanza.outerHTML}`, Strophe.LogLevel.ERROR); + } + + contact_jid = Strophe.getBareJidFromJid(to_jid); + } else { + contact_jid = from_bare_jid; + } + + const attrs = { + 'fullname': _.get(_converse.api.contacts.get(contact_jid), 'attributes.fullname') // Get chat box, but only create a new one when the message has a body. + + }; + const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}`).length > 0; + const chatbox = this.getChatBox(contact_jid, attrs, has_body); + + if (chatbox && !chatbox.handleMessageCorrection(stanza)) { + const msgid = stanza.getAttribute('id'), + message = msgid && chatbox.messages.findWhere({ + msgid + }); + + if (!message) { + // Only create the message when we're sure it's not a duplicate + chatbox.createMessage(stanza, original_stanza).then(msg => chatbox.incrementUnreadMsgCounter(msg)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + } + + _converse.emit('message', { + 'stanza': original_stanza, + 'chatbox': chatbox + }); + + return true; + }, + + getChatBox(jid, attrs = {}, create) { + /* Returns a chat box or optionally return a newly + * created one if one doesn't exist. + * + * Parameters: + * (String) jid - The JID of the user whose chat box we want + * (Boolean) create - Should a new chat box be created if none exists? + * (Object) attrs - Optional chat box atributes. + */ + if (_.isObject(jid)) { + create = attrs; + attrs = jid; + jid = attrs.jid; + } + + jid = Strophe.getBareJidFromJid(jid.toLowerCase()); + let chatbox = this.get(Strophe.getBareJidFromJid(jid)); + + if (!chatbox && create) { + _.extend(attrs, { + 'jid': jid, + 'id': jid + }); + + chatbox = this.create(attrs, { + 'error'(model, response) { + _converse.log(response.responseText); + } + + }); + } + + return chatbox; + } + + }); + + function autoJoinChats() { + /* Automatically join private chats, based on the + * "auto_join_private_chats" configuration setting. + */ + _.each(_converse.auto_join_private_chats, function (jid) { + if (_converse.chatboxes.where({ + 'jid': jid + }).length) { + return; + } + + if (_.isString(jid)) { + _converse.api.chats.open(jid); + } else { + _converse.log('Invalid jid criteria specified for "auto_join_private_chats"', Strophe.LogLevel.ERROR); } }); - /************************ END API ************************/ + _converse.emit('privateChatsAutoJoined'); } + /************************ BEGIN Event Handlers ************************/ + + + _converse.on('chatBoxesFetched', autoJoinChats); + + _converse.api.waitUntil('rosterContactsFetched').then(() => { + _converse.roster.on('add', contact => { + /* When a new contact is added, check if we already have a + * chatbox open for it, and if so attach it to the chatbox. + */ + const chatbox = _converse.chatboxes.findWhere({ + 'jid': contact.get('jid') + }); + + if (chatbox) { + chatbox.addRelatedContact(contact); + } + }); + }); + + _converse.on('addClientFeatures', () => { + _converse.api.disco.own.features.add(Strophe.NS.MESSAGE_CORRECT); + + _converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD); + + _converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND); + }); + + _converse.api.listen.on('pluginsInitialized', () => { + _converse.chatboxes = new _converse.ChatBoxes(); + + _converse.emit('chatBoxesInitialized'); + }); + + _converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected()); + /************************ END Event Handlers ************************/ + + /************************ BEGIN API ************************/ + + + _.extend(_converse.api, { + /** + * The "chats" namespace (used for one-on-one chats) + * + * @namespace _converse.api.chats + * @memberOf _converse.api + */ + 'chats': { + /** + * @method _converse.api.chats.create + * @param {string|string[]} jid|jids An jid or array of jids + * @param {object} attrs An object containing configuration attributes. + */ + 'create'(jids, attrs) { + if (_.isUndefined(jids)) { + _converse.log("chats.create: You need to provide at least one JID", Strophe.LogLevel.ERROR); + + return null; + } + + if (_.isString(jids)) { + if (attrs && !_.get(attrs, 'fullname')) { + attrs.fullname = _.get(_converse.api.contacts.get(jids), 'attributes.fullname'); + } + + const chatbox = _converse.chatboxes.getChatBox(jids, attrs, true); + + if (_.isNil(chatbox)) { + _converse.log("Could not open chatbox for JID: " + jids, Strophe.LogLevel.ERROR); + + return; + } + + return chatbox; + } + + return _.map(jids, jid => { + attrs.fullname = _.get(_converse.api.contacts.get(jid), 'attributes.fullname'); + return _converse.chatboxes.getChatBox(jid, attrs, true).trigger('show'); + }); + }, + + /** + * Opens a new one-on-one chat. + * + * @method _converse.api.chats.open + * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] + * @returns {Promise} Promise which resolves with the Backbone.Model representing the chat. + * + * @example + * // To open a single chat, provide the JID of the contact you're chatting with in that chat: + * converse.plugins.add('myplugin', { + * initialize: function() { + * var _converse = this._converse; + * // Note, buddy@example.org must be in your contacts roster! + * _converse.api.chats.open('buddy@example.com').then((chat) => { + * // Now you can do something with the chat model + * }); + * } + * }); + * + * @example + * // To open an array of chats, provide an array of JIDs: + * converse.plugins.add('myplugin', { + * initialize: function () { + * var _converse = this._converse; + * // Note, these users must first be in your contacts roster! + * _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']).then((chats) => { + * // Now you can do something with the chat models + * }); + * } + * }); + * + */ + 'open'(jids, attrs) { + return new Promise((resolve, reject) => { + Promise.all([_converse.api.waitUntil('rosterContactsFetched'), _converse.api.waitUntil('chatBoxesFetched')]).then(() => { + if (_.isUndefined(jids)) { + const err_msg = "chats.open: You need to provide at least one JID"; + + _converse.log(err_msg, Strophe.LogLevel.ERROR); + + reject(new Error(err_msg)); + } else if (_.isString(jids)) { + resolve(_converse.api.chats.create(jids, attrs).trigger('show')); + } else { + resolve(_.map(jids, jid => _converse.api.chats.create(jid, attrs).trigger('show'))); + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }); + }, + + /** + * Returns a chat model. The chat should already be open. + * + * @method _converse.api.chats.get + * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] + * @returns {Backbone.Model} + * + * @example + * // To return a single chat, provide the JID of the contact you're chatting with in that chat: + * const model = _converse.api.chats.get('buddy@example.com'); + * + * @example + * // To return an array of chats, provide an array of JIDs: + * const models = _converse.api.chats.get(['buddy1@example.com', 'buddy2@example.com']); + * + * @example + * // To return all open chats, call the method without any parameters:: + * const models = _converse.api.chats.get(); + * + */ + 'get'(jids) { + if (_.isUndefined(jids)) { + const result = []; + + _converse.chatboxes.each(function (chatbox) { + // FIXME: Leaky abstraction from MUC. We need to add a + // base type for chat boxes, and check for that. + if (chatbox.get('type') !== _converse.CHATROOMS_TYPE) { + result.push(chatbox); + } + }); + + return result; + } else if (_.isString(jids)) { + return _converse.chatboxes.getChatBox(jids); + } + + return _.map(jids, _.partial(_converse.chatboxes.getChatBox.bind(_converse.chatboxes), _, {}, true)); + } + + } + }); + /************************ END API ************************/ + + } - }); - return converse; }); /***/ }), @@ -72274,1861 +72507,1886 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!***************************************!*\ !*** ./src/headless/converse-core.js ***! \***************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var strophe_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! strophe.js */ "./node_modules/strophe.js/dist/strophe.js"); +/* harmony import */ var strophe_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(strophe_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./backbone.noconflict */ "./src/headless/backbone.noconflict.js"); +/* harmony import */ var _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_backbone_noconflict__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! es6-promise/dist/es6-promise.auto */ "./node_modules/es6-promise/dist/es6-promise.auto.js"); +/* harmony import */ var es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./lodash.noconflict */ "./src/headless/lodash.noconflict.js"); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var backbone_browserStorage__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! backbone.browserStorage */ "./node_modules/backbone.browserStorage/backbone.browserStorage.js"); +/* harmony import */ var backbone_browserStorage__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(backbone_browserStorage__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var _lodash_fp__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./lodash.fp */ "./src/headless/lodash.fp.js"); +/* harmony import */ var _lodash_fp__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_lodash_fp__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var _i18n__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./i18n */ "./src/headless/i18n.js"); +/* harmony import */ var _i18n__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_i18n__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! moment */ "./node_modules/moment/moment.js"); +/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(moment__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var pluggable_js_dist_pluggable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! pluggable.js/dist/pluggable */ "./node_modules/pluggable.js/dist/pluggable.js"); +/* harmony import */ var pluggable_js_dist_pluggable__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(pluggable_js_dist_pluggable__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var _polyfill__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./polyfill */ "./src/headless/polyfill.js"); +/* harmony import */ var _polyfill__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(_polyfill__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(sizzle__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./utils/core */ "./src/headless/utils/core.js"); +// Converse.js // https://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"), __webpack_require__(/*! es6-promise/dist/es6-promise.auto */ "./node_modules/es6-promise/dist/es6-promise.auto.js"), __webpack_require__(/*! ./lodash.noconflict */ "./src/headless/lodash.noconflict.js"), __webpack_require__(/*! ./lodash.fp */ "./src/headless/lodash.fp.js"), __webpack_require__(/*! ./polyfill */ "./src/headless/polyfill.js"), __webpack_require__(/*! ./i18n */ "./src/headless/i18n.js"), __webpack_require__(/*! ./utils/core */ "./src/headless/utils/core.js"), __webpack_require__(/*! moment */ "./node_modules/moment/moment.js"), __webpack_require__(/*! strophe.js */ "./node_modules/strophe.js/dist/strophe.js"), __webpack_require__(/*! pluggable.js/dist/pluggable */ "./node_modules/pluggable.js/dist/pluggable.js"), __webpack_require__(/*! ./backbone.noconflict */ "./src/headless/backbone.noconflict.js"), __webpack_require__(/*! backbone.browserStorage */ "./node_modules/backbone.browserStorage/backbone.browserStorage.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (sizzle, Promise, _, f, polyfill, i18n, u, moment, Strophe, pluggable, Backbone) { - "use strict"; // Strophe globals - const _Strophe = Strophe, - $build = _Strophe.$build, - $iq = _Strophe.$iq, - $msg = _Strophe.$msg, - $pres = _Strophe.$pres; - const b64_sha1 = Strophe.SHA1.b64_sha1; - Strophe = Strophe.Strophe; // Add Strophe Namespaces - Strophe.addNamespace('CARBONS', 'urn:xmpp:carbons:2'); - Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates'); - Strophe.addNamespace('CSI', 'urn:xmpp:csi:0'); - Strophe.addNamespace('DELAY', 'urn:xmpp:delay'); - Strophe.addNamespace('FORWARD', 'urn:xmpp:forward:0'); - Strophe.addNamespace('HINTS', 'urn:xmpp:hints'); - Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0'); - Strophe.addNamespace('MAM', 'urn:xmpp:mam:2'); - Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick'); - Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl"); - Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob'); - Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub'); - Strophe.addNamespace('REGISTER', 'jabber:iq:register'); - Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx'); - Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm'); - Strophe.addNamespace('SID', 'urn:xmpp:sid:0'); - Strophe.addNamespace('SPOILER', 'urn:xmpp:spoiler:0'); - Strophe.addNamespace('VCARD', 'vcard-temp'); - Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update'); - Strophe.addNamespace('XFORM', 'jabber:x:data'); // Use Mustache style syntax for variable interpolation - /* Configuration of Lodash templates (this config is distinct to the - * config of requirejs-tpl in main.js). This one is for normal inline templates. - */ - _.templateSettings = { - 'escape': /\{\{\{([\s\S]+?)\}\}\}/g, - 'evaluate': /\{\[([\s\S]+?)\]\}/g, - 'interpolate': /\{\{([\s\S]+?)\}\}/g, - 'imports': { - '_': _ - } - }; - /** - * A private, closured object containing the private api (via `_converse.api`) - * as well as private methods and internal data-structures. + + + + + + + + + + // Strophe globals + +const b64_sha1 = strophe_js__WEBPACK_IMPORTED_MODULE_0__["SHA1"].b64_sha1; // Add Strophe Namespaces + +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('CARBONS', 'urn:xmpp:carbons:2'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('CSI', 'urn:xmpp:csi:0'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('DELAY', 'urn:xmpp:delay'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('FORWARD', 'urn:xmpp:forward:0'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('HINTS', 'urn:xmpp:hints'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('MAM', 'urn:xmpp:mam:2'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('NICK', 'http://jabber.org/protocol/nick'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('OMEMO', "eu.siacs.conversations.axolotl"); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('OUTOFBAND', 'jabber:x:oob'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('REGISTER', 'jabber:iq:register'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('RSM', 'http://jabber.org/protocol/rsm'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('SID', 'urn:xmpp:sid:0'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('SPOILER', 'urn:xmpp:spoiler:0'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('VCARD', 'vcard-temp'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('VCARDUPDATE', 'vcard-temp:x:update'); +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].addNamespace('XFORM', 'jabber:x:data'); // Use Mustache style syntax for variable interpolation + +/* Configuration of Lodash templates (this config is distinct to the + * config of requirejs-tpl in main.js). This one is for normal inline templates. + */ + +_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.templateSettings = { + 'escape': /\{\{\{([\s\S]+?)\}\}\}/g, + 'evaluate': /\{\[([\s\S]+?)\]\}/g, + 'interpolate': /\{\{([\s\S]+?)\}\}/g, + 'imports': { + '_': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a + } +}; +/** + * A private, closured object containing the private api (via `_converse.api`) + * as well as private methods and internal data-structures. + * + * @namespace _converse + */ + +const _converse = { + 'templates': {}, + 'promises': {} +}; + +_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.extend(_converse, _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.Events); // Core plugins are whitelisted automatically + + +_converse.core_plugins = ['converse-autocomplete', 'converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatboxviews', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-omemo', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the +// webserver, which is often also set to 60 and might therefore sometimes +// return a 504 error page instead of passing through to the BOSH proxy. + +const BOSH_WAIT = 59; // Make converse pluggable + +pluggable_js_dist_pluggable__WEBPACK_IMPORTED_MODULE_8___default.a.enable(_converse, '_converse', 'pluggable'); +_converse.keycodes = { + TAB: 9, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + ESCAPE: 27, + UP_ARROW: 38, + DOWN_ARROW: 40, + FORWARD_SLASH: 47, + AT: 50, + META: 91, + META_RIGHT: 93 +}; // Module-level constants + +_converse.STATUS_WEIGHTS = { + 'offline': 6, + 'unavailable': 5, + 'xa': 4, + 'away': 3, + 'dnd': 2, + 'chat': 1, + // We currently don't differentiate between "chat" and "online" + 'online': 1 +}; +_converse.PRETTY_CHAT_STATUS = { + 'offline': 'Offline', + 'unavailable': 'Unavailable', + 'xa': 'Extended Away', + 'away': 'Away', + 'dnd': 'Do not disturb', + 'chat': 'Chattty', + 'online': 'Online' +}; +_converse.ANONYMOUS = "anonymous"; +_converse.CLOSED = 'closed'; +_converse.EXTERNAL = "external"; +_converse.LOGIN = "login"; +_converse.LOGOUT = "logout"; +_converse.OPENED = 'opened'; +_converse.PREBIND = "prebind"; +_converse.IQ_TIMEOUT = 20000; +_converse.CONNECTION_STATUS = { + 0: 'ERROR', + 1: 'CONNECTING', + 2: 'CONNFAIL', + 3: 'AUTHENTICATING', + 4: 'AUTHFAIL', + 5: 'CONNECTED', + 6: 'DISCONNECTED', + 7: 'DISCONNECTING', + 8: 'ATTACHED', + 9: 'REDIRECT', + 10: 'RECONNECTING' +}; +_converse.SUCCESS = 'success'; +_converse.FAILURE = 'failure'; +_converse.DEFAULT_IMAGE_TYPE = 'image/png'; +_converse.DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg=="; +_converse.TIMEOUTS = { + // Set as module attr so that we can override in tests. + 'PAUSED': 10000, + 'INACTIVE': 90000 +}; // XEP-0085 Chat states +// http://xmpp.org/extensions/xep-0085.html + +_converse.INACTIVE = 'inactive'; +_converse.ACTIVE = 'active'; +_converse.COMPOSING = 'composing'; +_converse.PAUSED = 'paused'; +_converse.GONE = 'gone'; // Chat types + +_converse.PRIVATE_CHAT_TYPE = 'chatbox'; +_converse.CHATROOMS_TYPE = 'chatroom'; +_converse.HEADLINES_TYPE = 'headline'; +_converse.CONTROLBOX_TYPE = 'controlbox'; // Default configuration values +// ---------------------------- + +_converse.default_settings = { + allow_non_roster_messaging: false, + animate: true, + authentication: 'login', + // Available values are "login", "prebind", "anonymous" and "external". + auto_away: 0, + // Seconds after which user status is set to 'away' + auto_login: false, + // Currently only used in connection with anonymous login + auto_reconnect: true, + auto_xa: 0, + // Seconds after which user status is set to 'xa' + blacklisted_plugins: [], + bosh_service_url: undefined, + connection_options: {}, + credentials_url: null, + // URL from where login credentials can be fetched + csi_waiting_time: 0, + // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out. + debug: false, + default_state: 'online', + expose_rid_and_sid: false, + geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g, + geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2', + jid: undefined, + keepalive: true, + locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json', + locales: ['af', 'ar', 'bg', 'ca', 'cs', 'de', 'es', 'eu', 'en', 'fr', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ro', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'], + message_carbons: true, + nickname: undefined, + password: undefined, + prebind_url: null, + priority: 0, + rid: undefined, + root: window.document, + sid: undefined, + strict_plugin_dependencies: false, + trusted: true, + view_mode: 'overlayed', + // Choices are 'overlayed', 'fullscreen', 'mobile' + websocket_url: undefined, + whitelisted_plugins: [] +}; + +_converse.log = function (message, level, style = '') { + /* Logs messages to the browser's developer console. * - * @namespace _converse + * Parameters: + * (String) message - The message to be logged. + * (Integer) level - The loglevel which allows for filtering of log + * messages. + * + * Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn', + * 3 for 'error' and 4 for 'fatal'. + * + * When using the 'error' or 'warn' loglevels, a full stacktrace will be + * logged as well. */ - - const _converse = { - 'templates': {}, - 'promises': {} - }; - - _.extend(_converse, Backbone.Events); // Core plugins are whitelisted automatically - - - _converse.core_plugins = ['converse-autocomplete', 'converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatboxviews', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-omemo', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the - // webserver, which is often also set to 60 and might therefore sometimes - // return a 504 error page instead of passing through to the BOSH proxy. - - const BOSH_WAIT = 59; // Make converse pluggable - - pluggable.enable(_converse, '_converse', 'pluggable'); - _converse.keycodes = { - TAB: 9, - ENTER: 13, - SHIFT: 16, - CTRL: 17, - ALT: 18, - ESCAPE: 27, - UP_ARROW: 38, - DOWN_ARROW: 40, - FORWARD_SLASH: 47, - AT: 50, - META: 91, - META_RIGHT: 93 - }; // Module-level constants - - _converse.STATUS_WEIGHTS = { - 'offline': 6, - 'unavailable': 5, - 'xa': 4, - 'away': 3, - 'dnd': 2, - 'chat': 1, - // We currently don't differentiate between "chat" and "online" - 'online': 1 - }; - _converse.PRETTY_CHAT_STATUS = { - 'offline': 'Offline', - 'unavailable': 'Unavailable', - 'xa': 'Extended Away', - 'away': 'Away', - 'dnd': 'Do not disturb', - 'chat': 'Chattty', - 'online': 'Online' - }; - _converse.ANONYMOUS = "anonymous"; - _converse.CLOSED = 'closed'; - _converse.EXTERNAL = "external"; - _converse.LOGIN = "login"; - _converse.LOGOUT = "logout"; - _converse.OPENED = 'opened'; - _converse.PREBIND = "prebind"; - _converse.IQ_TIMEOUT = 20000; - _converse.CONNECTION_STATUS = { - 0: 'ERROR', - 1: 'CONNECTING', - 2: 'CONNFAIL', - 3: 'AUTHENTICATING', - 4: 'AUTHFAIL', - 5: 'CONNECTED', - 6: 'DISCONNECTED', - 7: 'DISCONNECTING', - 8: 'ATTACHED', - 9: 'REDIRECT', - 10: 'RECONNECTING' - }; - _converse.SUCCESS = 'success'; - _converse.FAILURE = 'failure'; - _converse.DEFAULT_IMAGE_TYPE = 'image/png'; - _converse.DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg=="; - _converse.TIMEOUTS = { - // Set as module attr so that we can override in tests. - 'PAUSED': 10000, - 'INACTIVE': 90000 - }; // XEP-0085 Chat states - // http://xmpp.org/extensions/xep-0085.html - - _converse.INACTIVE = 'inactive'; - _converse.ACTIVE = 'active'; - _converse.COMPOSING = 'composing'; - _converse.PAUSED = 'paused'; - _converse.GONE = 'gone'; // Chat types - - _converse.PRIVATE_CHAT_TYPE = 'chatbox'; - _converse.CHATROOMS_TYPE = 'chatroom'; - _converse.HEADLINES_TYPE = 'headline'; - _converse.CONTROLBOX_TYPE = 'controlbox'; // Default configuration values - // ---------------------------- - - _converse.default_settings = { - allow_non_roster_messaging: false, - animate: true, - authentication: 'login', - // Available values are "login", "prebind", "anonymous" and "external". - auto_away: 0, - // Seconds after which user status is set to 'away' - auto_login: false, - // Currently only used in connection with anonymous login - auto_reconnect: true, - auto_xa: 0, - // Seconds after which user status is set to 'xa' - blacklisted_plugins: [], - bosh_service_url: undefined, - connection_options: {}, - credentials_url: null, - // URL from where login credentials can be fetched - csi_waiting_time: 0, - // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out. - debug: false, - default_state: 'online', - expose_rid_and_sid: false, - geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g, - geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2', - jid: undefined, - keepalive: true, - locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json', - locales: ['af', 'ar', 'bg', 'ca', 'cs', 'de', 'es', 'eu', 'en', 'fr', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ro', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'], - message_carbons: true, - nickname: undefined, - password: undefined, - prebind_url: null, - priority: 0, - rid: undefined, - root: window.document, - sid: undefined, - strict_plugin_dependencies: false, - trusted: true, - view_mode: 'overlayed', - // Choices are 'overlayed', 'fullscreen', 'mobile' - websocket_url: undefined, - whitelisted_plugins: [] - }; - - _converse.log = function (message, level, style = '') { - /* Logs messages to the browser's developer console. - * - * Parameters: - * (String) message - The message to be logged. - * (Integer) level - The loglevel which allows for filtering of log - * messages. - * - * Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn', - * 3 for 'error' and 4 for 'fatal'. - * - * When using the 'error' or 'warn' loglevels, a full stacktrace will be - * logged as well. - */ - if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) { - style = style || 'color: maroon'; - } - - if (message instanceof Error) { - message = message.stack; - } else if (_.isElement(message)) { - message = message.outerHTML; - } - - const prefix = style ? '%c' : ''; - - const logger = _.assign({ - 'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop, - 'error': _.get(console, 'log') ? console.log.bind(console) : _.noop, - 'info': _.get(console, 'log') ? console.log.bind(console) : _.noop, - 'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop - }, console); - - if (level === Strophe.LogLevel.ERROR) { - logger.error(`${prefix} ERROR: ${message}`, style); - } else if (level === Strophe.LogLevel.WARN) { - if (_converse.debug) { - logger.warn(`${prefix} ${moment().format()} WARNING: ${message}`, style); - } - } else if (level === Strophe.LogLevel.FATAL) { - logger.error(`${prefix} FATAL: ${message}`, style); - } else if (_converse.debug) { - if (level === Strophe.LogLevel.DEBUG) { - logger.debug(`${prefix} ${moment().format()} DEBUG: ${message}`, style); - } else { - logger.info(`${prefix} ${moment().format()} INFO: ${message}`, style); - } - } - }; - - Strophe.log = function (level, msg) { - _converse.log(level + ' ' + msg, level); - }; - - Strophe.error = function (msg) { - _converse.log(msg, Strophe.LogLevel.ERROR); - }; - - _converse.__ = function (str) { - /* Translate the given string based on the current locale. - * - * Parameters: - * (String) str - The string to translate. - */ - if (_.isUndefined(i18n)) { - return str; - } - - return i18n.translate.apply(i18n, arguments); - }; - - const __ = _converse.__; - const PROMISES = ['initialized', 'connectionInitialized', 'pluginsInitialized', 'statusInitialized']; - - function addPromise(promise) { - /* Private function, used to add a new promise to the ones already - * available via the `waitUntil` api method. - */ - _converse.promises[promise] = u.getResolveablePromise(); + if (level === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.ERROR || level === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.FATAL) { + style = style || 'color: maroon'; } - _converse.emit = function (name) { - /* Event emitter and promise resolver */ - _converse.trigger.apply(this, arguments); + if (message instanceof Error) { + message = message.stack; + } else if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isElement(message)) { + message = message.outerHTML; + } - const promise = _converse.promises[name]; + const prefix = style ? '%c' : ''; - if (!_.isUndefined(promise)) { - promise.resolve(); + const logger = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assign({ + 'debug': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.get(console, 'log') ? console.log.bind(console) : _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.noop, + 'error': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.get(console, 'log') ? console.log.bind(console) : _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.noop, + 'info': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.get(console, 'log') ? console.log.bind(console) : _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.noop, + 'warn': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.get(console, 'log') ? console.log.bind(console) : _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.noop + }, console); + + if (level === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.ERROR) { + logger.error(`${prefix} ERROR: ${message}`, style); + } else if (level === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.WARN) { + if (_converse.debug) { + logger.warn(`${prefix} ${moment__WEBPACK_IMPORTED_MODULE_7___default()().format()} WARNING: ${message}`, style); } - }; - - _converse.isSingleton = function () { - return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode); - }; - - _converse.router = new Backbone.Router(); - - _converse.initialize = function (settings, callback) { - settings = !_.isUndefined(settings) ? settings : {}; - const init_promise = u.getResolveablePromise(); - - _.each(PROMISES, addPromise); - - if (!_.isUndefined(_converse.connection)) { - // Looks like _converse.initialized was called again without logging - // out or disconnecting in the previous session. - // This happens in tests. We therefore first clean up. - Backbone.history.stop(); - - _converse.chatboxviews.closeAllChatBoxes(); - - if (_converse.bookmarks) { - _converse.bookmarks.reset(); - } - - delete _converse.controlboxtoggle; - delete _converse.chatboxviews; - - _converse.connection.reset(); - - _converse.stopListening(); - - _converse.tearDown(); - - delete _converse.config; - - _converse.initClientConfig(); - - _converse.off(); - } - - if ('onpagehide' in window) { - // Pagehide gets thrown in more cases than unload. Specifically it - // gets thrown when the page is cached and not just - // closed/destroyed. It's the only viable event on mobile Safari. - // https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/ - _converse.unloadevent = 'pagehide'; - } else if ('onbeforeunload' in window) { - _converse.unloadevent = 'beforeunload'; - } else if ('onunload' in window) { - _converse.unloadevent = 'unload'; - } - - _.assignIn(this, this.default_settings); // Allow only whitelisted configuration attributes to be overwritten - - - _.assignIn(this, _.pick(settings, _.keys(this.default_settings))); - - if (this.authentication === _converse.ANONYMOUS) { - if (this.auto_login && !this.jid) { - throw new Error("Config Error: you need to provide the server's " + "domain via the 'jid' option when using anonymous " + "authentication with auto_login."); - } - } - /* Localisation */ - - - if (!_.isUndefined(i18n)) { - i18n.setLocales(settings.i18n, _converse); + } else if (level === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.FATAL) { + logger.error(`${prefix} FATAL: ${message}`, style); + } else if (_converse.debug) { + if (level === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.DEBUG) { + logger.debug(`${prefix} ${moment__WEBPACK_IMPORTED_MODULE_7___default()().format()} DEBUG: ${message}`, style); } else { - _converse.locale = 'en'; - } // Module-level variables - // ---------------------- - - - this.callback = callback || _.noop; - /* When reloading the page: - * For new sessions, we need to send out a presence stanza to notify - * the server/network that we're online. - * When re-attaching to an existing session (e.g. via the keepalive - * option), we don't need to again send out a presence stanza, because - * it's as if "we never left" (see onConnectStatusChanged). - * https://github.com/jcbrand/converse.js/issues/521 - */ - - this.send_initial_presence = true; - this.msg_counter = 0; - this.user_settings = settings; // Save the user settings so that they can be used by plugins - // Module-level functions - // ---------------------- - - this.generateResource = () => `/converse.js-${Math.floor(Math.random() * 139749528).toString()}`; - - this.sendCSI = function (stat) { - /* Send out a Chat Status Notification (XEP-0352) - * - * Parameters: - * (String) stat: The user's chat status - */ - - /* Send out a Chat Status Notification (XEP-0352) */ - // XXX if (converse.features[Strophe.NS.CSI] || true) { - _converse.connection.send($build(stat, { - xmlns: Strophe.NS.CSI - })); - - _converse.inactive = stat === _converse.INACTIVE ? true : false; - }; - - this.onUserActivity = function () { - /* Resets counters and flags relating to CSI and auto_away/auto_xa */ - if (_converse.idle_seconds > 0) { - _converse.idle_seconds = 0; - } - - if (!_converse.connection.authenticated) { - // We can't send out any stanzas when there's no authenticated connection. - // converse can happen when the connection reconnects. - return; - } - - if (_converse.inactive) { - _converse.sendCSI(_converse.ACTIVE); - } - - if (_converse.auto_changed_status === true) { - _converse.auto_changed_status = false; // XXX: we should really remember the original state here, and - // then set it back to that... - - _converse.xmppstatus.set('status', _converse.default_state); - } - }; - - this.onEverySecond = function () { - /* An interval handler running every second. - * Used for CSI and the auto_away and auto_xa features. - */ - if (!_converse.connection.authenticated) { - // We can't send out any stanzas when there's no authenticated connection. - // This can happen when the connection reconnects. - return; - } - - const stat = _converse.xmppstatus.get('status'); - - _converse.idle_seconds++; - - if (_converse.csi_waiting_time > 0 && _converse.idle_seconds > _converse.csi_waiting_time && !_converse.inactive) { - _converse.sendCSI(_converse.INACTIVE); - } - - if (_converse.auto_away > 0 && _converse.idle_seconds > _converse.auto_away && stat !== 'away' && stat !== 'xa' && stat !== 'dnd') { - _converse.auto_changed_status = true; - - _converse.xmppstatus.set('status', 'away'); - } else if (_converse.auto_xa > 0 && _converse.idle_seconds > _converse.auto_xa && stat !== 'xa' && stat !== 'dnd') { - _converse.auto_changed_status = true; - - _converse.xmppstatus.set('status', 'xa'); - } - }; - - this.registerIntervalHandler = function () { - /* Set an interval of one second and register a handler for it. - * Required for the auto_away, auto_xa and csi_waiting_time features. - */ - if (_converse.auto_away < 1 && _converse.auto_xa < 1 && _converse.csi_waiting_time < 1) { - // Waiting time of less then one second means features aren't used. - return; - } - - _converse.idle_seconds = 0; - _converse.auto_changed_status = false; // Was the user's status changed by _converse.js? - - window.addEventListener('click', _converse.onUserActivity); - window.addEventListener('focus', _converse.onUserActivity); - window.addEventListener('keypress', _converse.onUserActivity); - window.addEventListener('mousemove', _converse.onUserActivity); - const options = { - 'once': true, - 'passive': true - }; - window.addEventListener(_converse.unloadevent, _converse.onUserActivity, options); - _converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000); - }; - - this.setConnectionStatus = function (connection_status, message) { - _converse.connfeedback.set({ - 'connection_status': connection_status, - 'message': message - }); - }; - - this.rejectPresenceSubscription = function (jid, message) { - /* Reject or cancel another user's subscription to our presence updates. - * - * Parameters: - * (String) jid - The Jabber ID of the user whose subscription - * is being canceled. - * (String) message - An optional message to the user - */ - const pres = $pres({ - to: jid, - type: "unsubscribed" - }); - - if (message && message !== "") { - pres.c("status").t(message); - } - - _converse.connection.send(pres); - }; - - this.reconnect = _.debounce(function () { - _converse.log('RECONNECTING'); - - _converse.log('The connection has dropped, attempting to reconnect.'); - - _converse.setConnectionStatus(Strophe.Status.RECONNECTING, __('The connection has dropped, attempting to reconnect.')); - - _converse.connection.reconnecting = true; - - _converse.tearDown(); - - _converse.logIn(null, true); - }, 3000, { - 'leading': true - }); - - this.disconnect = function () { - _converse.log('DISCONNECTED'); - - delete _converse.connection.reconnecting; - - _converse.connection.reset(); - - _converse.tearDown(); - - _converse.clearSession(); - - _converse.emit('disconnected'); - }; - - this.onDisconnected = function () { - /* Gets called once strophe's status reaches Strophe.Status.DISCONNECTED. - * Will either start a teardown process for converse.js or attempt - * to reconnect. - */ - const reason = _converse.disconnection_reason; - - if (_converse.disconnection_cause === Strophe.Status.AUTHFAIL) { - if (_converse.credentials_url && _converse.auto_reconnect) { - /* In this case, we reconnect, because we might be receiving - * expirable tokens from the credentials_url. - */ - _converse.emit('will-reconnect'); - - return _converse.reconnect(); - } else { - return _converse.disconnect(); - } - } else if (_converse.disconnection_cause === _converse.LOGOUT || !_.isUndefined(reason) && reason === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH') || reason === "host-unknown" || reason === "remote-connection-failed" || !_converse.auto_reconnect) { - return _converse.disconnect(); - } - - _converse.emit('will-reconnect'); - - _converse.reconnect(); - }; - - this.setDisconnectionCause = function (cause, reason, override) { - /* Used to keep track of why we got disconnected, so that we can - * decide on what the next appropriate action is (in onDisconnected) - */ - if (_.isUndefined(cause)) { - delete _converse.disconnection_cause; - delete _converse.disconnection_reason; - } else if (_.isUndefined(_converse.disconnection_cause) || override) { - _converse.disconnection_cause = cause; - _converse.disconnection_reason = reason; - } - }; - - this.onConnectStatusChanged = function (status, message) { - /* Callback method called by Strophe as the Strophe.Connection goes - * through various states while establishing or tearing down a - * connection. - */ - _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`); - - if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) { - _converse.setConnectionStatus(status); // By default we always want to send out an initial presence stanza. - - - _converse.send_initial_presence = true; - - _converse.setDisconnectionCause(); - - if (_converse.connection.reconnecting) { - _converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached'); - - _converse.onConnected(true); - } else { - _converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached'); - - if (_converse.connection.restored) { - // No need to send an initial presence stanza when - // we're restoring an existing session. - _converse.send_initial_presence = false; - } - - _converse.onConnected(); - } - } else if (status === Strophe.Status.DISCONNECTED) { - _converse.setDisconnectionCause(status, message); - - _converse.onDisconnected(); - } else if (status === Strophe.Status.ERROR) { - _converse.setConnectionStatus(status, __('An error occurred while connecting to the chat server.')); - } else if (status === Strophe.Status.CONNECTING) { - _converse.setConnectionStatus(status); - } else if (status === Strophe.Status.AUTHENTICATING) { - _converse.setConnectionStatus(status); - } else if (status === Strophe.Status.AUTHFAIL) { - if (!message) { - message = __('Your Jabber ID and/or password is incorrect. Please try again.'); - } - - _converse.setConnectionStatus(status, message); - - _converse.setDisconnectionCause(status, message, true); - - _converse.onDisconnected(); - } else if (status === Strophe.Status.CONNFAIL) { - let feedback = message; - - if (message === "host-unknown" || message == "remote-connection-failed") { - feedback = __("Sorry, we could not connect to the XMPP host with domain: %1$s", `\"${Strophe.getDomainFromJid(_converse.connection.jid)}\"`); - } else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) { - feedback = __("The XMPP server did not offer a supported authentication mechanism"); - } - - _converse.setConnectionStatus(status, feedback); - - _converse.setDisconnectionCause(status, message); - } else if (status === Strophe.Status.DISCONNECTING) { - _converse.setDisconnectionCause(status, message); - } - }; - - this.incrementMsgCounter = function () { - this.msg_counter += 1; - const unreadMsgCount = this.msg_counter; - let title = document.title; - - if (_.isNil(title)) { - return; - } - - if (title.search(/^Messages \(\d+\) /) === -1) { - title = `Messages (${unreadMsgCount}) ${title}`; - } else { - title = title.replace(/^Messages \(\d+\) /, `Messages (${unreadMsgCount})`); - } - }; - - this.clearMsgCounter = function () { - this.msg_counter = 0; - let title = document.title; - - if (_.isNil(title)) { - return; - } - - if (title.search(/^Messages \(\d+\) /) !== -1) { - title = title.replace(/^Messages \(\d+\) /, ""); - } - }; - - this.initStatus = reconnecting => { - // If there's no xmppstatus obj, then we were never connected to - // begin with, so we set reconnecting to false. - reconnecting = _.isUndefined(_converse.xmppstatus) ? false : reconnecting; - - if (reconnecting) { - _converse.onStatusInitialized(reconnecting); - } else { - const id = `converse.xmppstatus-${_converse.bare_jid}`; - this.xmppstatus = new this.XMPPStatus({ - 'id': id - }); - this.xmppstatus.browserStorage = new Backbone.BrowserStorage.session(id); - this.xmppstatus.fetch({ - 'success': _.partial(_converse.onStatusInitialized, reconnecting), - 'error': _.partial(_converse.onStatusInitialized, reconnecting) - }); - } - }; - - this.initClientConfig = function () { - /* The client config refers to configuration of the client which is - * independent of any particular user. - * What this means is that config values need to persist across - * user sessions. - */ - const id = b64_sha1('converse.client-config'); - _converse.config = new Backbone.Model({ - 'id': id, - 'trusted': _converse.trusted && true || false, - 'storage': _converse.trusted ? 'local' : 'session' - }); - _converse.config.browserStorage = new Backbone.BrowserStorage.session(id); - - _converse.config.fetch(); - - _converse.emit('clientConfigInitialized'); - }; - - this.initSession = function () { - const id = b64_sha1('converse.bosh-session'); - _converse.session = new Backbone.Model({ - 'id': id - }); - _converse.session.browserStorage = new Backbone.BrowserStorage.session(id); - - _converse.session.fetch(); - - _converse.emit('sessionInitialized'); - }; - - this.clearSession = function () { - if (!_converse.config.get('trusted')) { - window.localStorage.clear(); - window.sessionStorage.clear(); - } else if (!_.isUndefined(this.session) && this.session.browserStorage) { - this.session.browserStorage._clear(); - } - - _converse.emit('clearSession'); - }; - - this.logOut = function () { - _converse.clearSession(); - - _converse.setDisconnectionCause(_converse.LOGOUT, undefined, true); - - if (!_.isUndefined(_converse.connection)) { - _converse.connection.disconnect(); - } else { - _converse.tearDown(); - } // Recreate all the promises - - - _.each(_.keys(_converse.promises), addPromise); - - _converse.emit('logout'); - }; - - this.saveWindowState = function (ev, hidden) { - // XXX: eventually we should be able to just use - // document.visibilityState (when we drop support for older - // browsers). - let state; - const event_map = { - 'focus': "visible", - 'focusin': "visible", - 'pageshow': "visible", - 'blur': "hidden", - 'focusout': "hidden", - 'pagehide': "hidden" - }; - ev = ev || document.createEvent('Events'); - - if (ev.type in event_map) { - state = event_map[ev.type]; - } else { - state = document[hidden] ? "hidden" : "visible"; - } - - if (state === 'visible') { - _converse.clearMsgCounter(); - } - - _converse.windowState = state; - - _converse.emit('windowStateChanged', { - state - }); - }; - - this.registerGlobalEventHandlers = function () { - // Taken from: - // http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active - let hidden = "hidden"; // Standards: - - if (hidden in document) { - document.addEventListener("visibilitychange", _.partial(_converse.saveWindowState, _, hidden)); - } else if ((hidden = "mozHidden") in document) { - document.addEventListener("mozvisibilitychange", _.partial(_converse.saveWindowState, _, hidden)); - } else if ((hidden = "webkitHidden") in document) { - document.addEventListener("webkitvisibilitychange", _.partial(_converse.saveWindowState, _, hidden)); - } else if ((hidden = "msHidden") in document) { - document.addEventListener("msvisibilitychange", _.partial(_converse.saveWindowState, _, hidden)); - } else if ("onfocusin" in document) { - // IE 9 and lower: - document.onfocusin = document.onfocusout = _.partial(_converse.saveWindowState, _, hidden); - } else { - // All others: - window.onpageshow = window.onpagehide = window.onfocus = window.onblur = _.partial(_converse.saveWindowState, _, hidden); - } // set the initial state (but only if browser supports the Page Visibility API) - - - if (document[hidden] !== undefined) { - _.partial(_converse.saveWindowState, _, hidden)({ - type: document[hidden] ? "blur" : "focus" - }); - } - - _converse.emit('registeredGlobalEventHandlers'); - }; - - this.enableCarbons = function () { - /* Ask the XMPP server to enable Message Carbons - * See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling - */ - if (!this.message_carbons || this.session.get('carbons_enabled')) { - return; - } - - const carbons_iq = new Strophe.Builder('iq', { - 'from': this.connection.jid, - 'id': 'enablecarbons', - 'type': 'set' - }).c('enable', { - xmlns: Strophe.NS.CARBONS - }); - this.connection.addHandler(iq => { - if (iq.querySelectorAll('error').length > 0) { - _converse.log('An error occurred while trying to enable message carbons.', Strophe.LogLevel.WARN); - } else { - this.session.save({ - 'carbons_enabled': true - }); - - _converse.log('Message carbons have been enabled.'); - } - }, null, "iq", null, "enablecarbons"); - this.connection.send(carbons_iq); - }; - - this.sendInitialPresence = function () { - if (_converse.send_initial_presence) { - _converse.xmppstatus.sendPresence(); - } - }; - - this.onStatusInitialized = function (reconnecting) { - _converse.emit('statusInitialized', reconnecting); - - if (reconnecting) { - _converse.emit('reconnected'); - } else { - init_promise.resolve(); - - _converse.emit('initialized'); - - _converse.emit('connected'); - } - }; - - this.setUserJID = function () { - _converse.jid = _converse.connection.jid; - _converse.bare_jid = Strophe.getBareJidFromJid(_converse.connection.jid); - _converse.resource = Strophe.getResourceFromJid(_converse.connection.jid); - _converse.domain = Strophe.getDomainFromJid(_converse.connection.jid); - - _converse.emit('setUserJID'); - }; - - this.onConnected = function (reconnecting) { - /* Called as soon as a new connection has been established, either - * by logging in or by attaching to an existing BOSH session. - */ - _converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser - - - _converse.setUserJID(); - - _converse.initSession(); - - _converse.enableCarbons(); - - _converse.initStatus(reconnecting); - }; - - this.ConnectionFeedback = Backbone.Model.extend({ - defaults: { - 'connection_status': Strophe.Status.DISCONNECTED, - 'message': '' - }, - - initialize() { - this.on('change', () => { - _converse.emit('connfeedback', _converse.connfeedback); - }); - } - - }); - this.connfeedback = new this.ConnectionFeedback(); - this.XMPPStatus = Backbone.Model.extend({ - defaults() { - return { - "jid": _converse.bare_jid, - "status": _converse.default_state - }; - }, - - initialize() { - this.vcard = _converse.vcards.findWhere({ - 'jid': this.get('jid') - }); - - if (_.isNil(this.vcard)) { - this.vcard = _converse.vcards.create({ - 'jid': this.get('jid') - }); - } - - this.on('change:status', item => { - const status = this.get('status'); - this.sendPresence(status); - - _converse.emit('statusChanged', status); - }); - this.on('change:status_message', () => { - const status_message = this.get('status_message'); - this.sendPresence(this.get('status'), status_message); - - _converse.emit('statusMessageChanged', status_message); - }); - }, - - constructPresence(type, status_message) { - let presence; - type = _.isString(type) ? type : this.get('status') || _converse.default_state; - status_message = _.isString(status_message) ? status_message : this.get('status_message'); // Most of these presence types are actually not explicitly sent, - // but I add all of them here for reference and future proofing. - - if (type === 'unavailable' || type === 'probe' || type === 'error' || type === 'unsubscribe' || type === 'unsubscribed' || type === 'subscribe' || type === 'subscribed') { - presence = $pres({ - 'type': type - }); - } else if (type === 'offline') { - presence = $pres({ - 'type': 'unavailable' - }); - } else if (type === 'online') { - presence = $pres(); - } else { - presence = $pres().c('show').t(type).up(); - } - - if (status_message) { - presence.c('status').t(status_message).up(); - } - - presence.c('priority').t(_.isNaN(Number(_converse.priority)) ? 0 : _converse.priority); - return presence; - }, - - sendPresence(type, status_message) { - _converse.connection.send(this.constructPresence(type, status_message)); - } - - }); - - this.setUpXMLLogging = function () { - Strophe.log = function (level, msg) { - _converse.log(msg, level); - }; - - if (this.debug) { - this.connection.xmlInput = function (body) { - _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkgoldenrod'); - }; - - this.connection.xmlOutput = function (body) { - _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkcyan'); - }; - } - }; - - this.fetchLoginCredentials = () => new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', _converse.credentials_url, true); - xhr.setRequestHeader('Accept', "application/json, text/javascript"); - - xhr.onload = function () { - if (xhr.status >= 200 && xhr.status < 400) { - const data = JSON.parse(xhr.responseText); - resolve({ - 'jid': data.jid, - 'password': data.password - }); - } else { - xhr.onerror(); - } - }; - - xhr.onerror = function () { - delete _converse.connection; - - _converse.emit('noResumeableSession', this); - - reject(xhr.responseText); - }; - - xhr.send(); - }); - - this.startNewBOSHSession = function () { - const xhr = new XMLHttpRequest(); - xhr.open('GET', _converse.prebind_url, true); - xhr.setRequestHeader('Accept', "application/json, text/javascript"); - - xhr.onload = function () { - if (xhr.status >= 200 && xhr.status < 400) { - const data = JSON.parse(xhr.responseText); - - _converse.connection.attach(data.jid, data.sid, data.rid, _converse.onConnectStatusChanged); - } else { - xhr.onerror(); - } - }; - - xhr.onerror = function () { - delete _converse.connection; - - _converse.emit('noResumeableSession', this); - }; - - xhr.send(); - }; - - this.restoreBOSHSession = function (jid_is_required) { - /* Tries to restore a cached BOSH session. */ - if (!this.jid) { - const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session " + "but we don't have the JID for the user!"; - - if (jid_is_required) { - throw new Error(msg); - } else { - _converse.log(msg); - } - } - - try { - this.connection.restore(this.jid, this.onConnectStatusChanged); - return true; - } catch (e) { - _converse.log("Could not restore session for jid: " + this.jid + " Error message: " + e.message, Strophe.LogLevel.WARN); - - this.clearSession(); // We want to clear presences (see #555) - - return false; - } - }; - - this.attemptPreboundSession = function (reconnecting) { - /* Handle session resumption or initialization when prebind is - * being used. - */ - if (!reconnecting) { - if (this.keepalive && this.restoreBOSHSession(true)) { - return; - } // No keepalive, or session resumption has failed. - - - if (this.jid && this.sid && this.rid) { - return this.connection.attach(this.jid, this.sid, this.rid, this.onConnectStatusChanged); - } - } - - if (this.prebind_url) { - return this.startNewBOSHSession(); - } else { - throw new Error("attemptPreboundSession: If you use prebind and not keepalive, " + "then you MUST supply JID, RID and SID values or a prebind_url."); - } - }; - - this.attemptNonPreboundSession = function (credentials, reconnecting) { - /* Handle session resumption or initialization when prebind is not being used. - * - * Two potential options exist and are handled in this method: - * 1. keepalive - * 2. auto_login - */ - if (!reconnecting && this.keepalive && this.restoreBOSHSession()) { - return; - } - - if (credentials) { - // When credentials are passed in, they override prebinding - // or credentials fetching via HTTP - this.autoLogin(credentials); - } else if (this.auto_login) { - if (this.credentials_url) { - this.fetchLoginCredentials().then(this.autoLogin.bind(this), this.autoLogin.bind(this)); - } else if (!this.jid) { - throw new Error("attemptNonPreboundSession: If you use auto_login, " + "you also need to give either a jid value (and if " + "applicable a password) or you need to pass in a URL " + "from where the username and password can be fetched " + "(via credentials_url)."); - } else { - this.autoLogin(); // Could be ANONYMOUS or EXTERNAL - } - } else if (reconnecting) { - this.autoLogin(); - } - }; - - this.autoLogin = function (credentials) { - if (credentials) { - // If passed in, the credentials come from credentials_url, - // so we set them on the converse object. - this.jid = credentials.jid; - } - - if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) { - if (!this.jid) { - throw new Error("Config Error: when using anonymous login " + "you need to provide the server's domain via the 'jid' option. " + "Either when calling converse.initialize, or when calling " + "_converse.api.user.login."); - } - - if (!this.connection.reconnecting) { - this.connection.reset(); - } - - this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged, BOSH_WAIT); - } else if (this.authentication === _converse.LOGIN) { - const password = _.isNil(credentials) ? _converse.connection.pass || this.password : credentials.password; - - if (!password) { - if (this.auto_login) { - throw new Error("initConnection: If you use auto_login and " + "authentication='login' then you also need to provide a password."); - } - - _converse.setDisconnectionCause(Strophe.Status.AUTHFAIL, undefined, true); - - _converse.disconnect(); - - return; - } - - const resource = Strophe.getResourceFromJid(this.jid); - - if (!resource) { - this.jid = this.jid.toLowerCase() + _converse.generateResource(); - } else { - this.jid = Strophe.getBareJidFromJid(this.jid).toLowerCase() + '/' + resource; - } - - if (!this.connection.reconnecting) { - this.connection.reset(); - } - - this.connection.connect(this.jid, password, this.onConnectStatusChanged, BOSH_WAIT); - } - }; - - this.logIn = function (credentials, reconnecting) { - // We now try to resume or automatically set up a new session. - // Otherwise the user will be shown a login form. - if (this.authentication === _converse.PREBIND) { - this.attemptPreboundSession(reconnecting); - } else { - this.attemptNonPreboundSession(credentials, reconnecting); - } - }; - - this.initConnection = function () { - /* Creates a new Strophe.Connection instance if we don't already have one. - */ - if (!this.connection) { - if (!this.bosh_service_url && !this.websocket_url) { - throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both."); - } - - if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) { - this.connection = new Strophe.Connection(this.websocket_url, this.connection_options); - } else if (this.bosh_service_url) { - this.connection = new Strophe.Connection(this.bosh_service_url, _.assignIn(this.connection_options, { - 'keepalive': this.keepalive - })); - } else { - throw new Error("initConnection: this browser does not support websockets and bosh_service_url wasn't specified."); - } - } - - _converse.emit('connectionInitialized'); - }; - - this.tearDown = function () { - /* Remove those views which are only allowed with a valid - * connection. - */ - _converse.emit('beforeTearDown'); - - if (!_.isUndefined(_converse.session)) { - _converse.session.destroy(); - } - - window.removeEventListener('click', _converse.onUserActivity); - window.removeEventListener('focus', _converse.onUserActivity); - window.removeEventListener('keypress', _converse.onUserActivity); - window.removeEventListener('mousemove', _converse.onUserActivity); - window.removeEventListener(_converse.unloadevent, _converse.onUserActivity); - window.clearInterval(_converse.everySecondTrigger); - - _converse.emit('afterTearDown'); - - return _converse; - }; - - this.initPlugins = function () { - // If initialize gets called a second time (e.g. during tests), then we - // need to re-apply all plugins (for a new converse instance), and we - // therefore need to clear this array that prevents plugins from being - // initialized twice. - // If initialize is called for the first time, then this array is empty - // in any case. - _converse.pluggable.initialized_plugins = []; - - const whitelist = _converse.core_plugins.concat(_converse.whitelisted_plugins); - - if (_converse.view_mode === 'embedded') { - _.forEach([// eslint-disable-line lodash/prefer-map - "converse-bookmarks", "converse-controlbox", "converse-headline", "converse-register"], name => { - _converse.blacklisted_plugins.push(name); - }); - } - - _converse.pluggable.initializePlugins({ - 'updateSettings'() { - _converse.log("(DEPRECATION) " + "The `updateSettings` method has been deprecated. " + "Please use `_converse.api.settings.update` instead.", Strophe.LogLevel.WARN); - - _converse.api.settings.update.apply(_converse, arguments); - }, - - '_converse': _converse - }, whitelist, _converse.blacklisted_plugins); - - _converse.emit('pluginsInitialized'); - }; // Initialization - // -------------- - // This is the end of the initialize method. - - - if (settings.connection) { - this.connection = settings.connection; + logger.info(`${prefix} ${moment__WEBPACK_IMPORTED_MODULE_7___default()().format()} INFO: ${message}`, style); + } + } +}; + +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].log = function (level, msg) { + _converse.log(level + ' ' + msg, level); +}; + +strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].error = function (msg) { + _converse.log(msg, strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.ERROR); +}; + +_converse.__ = function (str) { + /* Translate the given string based on the current locale. + * + * Parameters: + * (String) str - The string to translate. + */ + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_i18n__WEBPACK_IMPORTED_MODULE_6___default.a)) { + return str; + } + + return _i18n__WEBPACK_IMPORTED_MODULE_6___default.a.translate.apply(_i18n__WEBPACK_IMPORTED_MODULE_6___default.a, arguments); +}; + +const __ = _converse.__; +const PROMISES = ['initialized', 'connectionInitialized', 'pluginsInitialized', 'statusInitialized']; + +function addPromise(promise) { + /* Private function, used to add a new promise to the ones already + * available via the `waitUntil` api method. + */ + _converse.promises[promise] = _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"].getResolveablePromise(); +} + +_converse.emit = function (name) { + /* Event emitter and promise resolver */ + _converse.trigger.apply(this, arguments); + + const promise = _converse.promises[name]; + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(promise)) { + promise.resolve(); + } +}; + +_converse.isSingleton = function () { + return _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode); +}; + +_converse.router = new _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.Router(); + +_converse.initialize = function (settings, callback) { + settings = !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(settings) ? settings : {}; + const init_promise = _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"].getResolveablePromise(); + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.each(PROMISES, addPromise); + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.connection)) { + // Looks like _converse.initialized was called again without logging + // out or disconnecting in the previous session. + // This happens in tests. We therefore first clean up. + _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.history.stop(); + + _converse.chatboxviews.closeAllChatBoxes(); + + if (_converse.bookmarks) { + _converse.bookmarks.reset(); } - function finishInitialization() { - _converse.initPlugins(); + delete _converse.controlboxtoggle; + delete _converse.chatboxviews; - _converse.initClientConfig(); + _converse.connection.reset(); - _converse.initConnection(); + _converse.stopListening(); - _converse.setUpXMLLogging(); + _converse.tearDown(); - _converse.logIn(); + delete _converse.config; - _converse.registerGlobalEventHandlers(); + _converse.initClientConfig(); - if (!Backbone.history.started) { - Backbone.history.start(); - } + _converse.off(); + } + + if ('onpagehide' in window) { + // Pagehide gets thrown in more cases than unload. Specifically it + // gets thrown when the page is cached and not just + // closed/destroyed. It's the only viable event on mobile Safari. + // https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/ + _converse.unloadevent = 'pagehide'; + } else if ('onbeforeunload' in window) { + _converse.unloadevent = 'beforeunload'; + } else if ('onunload' in window) { + _converse.unloadevent = 'unload'; + } + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assignIn(this, this.default_settings); // Allow only whitelisted configuration attributes to be overwritten + + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assignIn(this, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.pick(settings, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.keys(this.default_settings))); + + if (this.authentication === _converse.ANONYMOUS) { + if (this.auto_login && !this.jid) { + throw new Error("Config Error: you need to provide the server's " + "domain via the 'jid' option when using anonymous " + "authentication with auto_login."); } + } + /* Localisation */ - if (!_.isUndefined(_converse.connection) && _converse.connection.service === 'jasmine tests') { - finishInitialization(); - return _converse; - } else if (_.isUndefined(i18n)) { - finishInitialization(); - } else { - i18n.fetchTranslations(_converse.locale, _converse.locales, u.interpolate(_converse.locales_url, { - 'locale': _converse.locale - })).catch(e => _converse.log(e.message, Strophe.LogLevel.FATAL)).then(finishInitialization).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - return init_promise; - }; - /** - * ### The private API - * - * The private API methods are only accessible via the closured {@link _converse} - * object, which is only available to plugins. - * - * These methods are kept private (i.e. not global) because they may return - * sensitive data which should be kept off-limits to other 3rd-party scripts - * that might be running in the page. - * - * @namespace _converse.api - * @memberOf _converse + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_i18n__WEBPACK_IMPORTED_MODULE_6___default.a)) { + _i18n__WEBPACK_IMPORTED_MODULE_6___default.a.setLocales(settings.i18n, _converse); + } else { + _converse.locale = 'en'; + } // Module-level variables + // ---------------------- + + + this.callback = callback || _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.noop; + /* When reloading the page: + * For new sessions, we need to send out a presence stanza to notify + * the server/network that we're online. + * When re-attaching to an existing session (e.g. via the keepalive + * option), we don't need to again send out a presence stanza, because + * it's as if "we never left" (see onConnectStatusChanged). + * https://github.com/jcbrand/converse.js/issues/521 */ + this.send_initial_presence = true; + this.msg_counter = 0; + this.user_settings = settings; // Save the user settings so that they can be used by plugins + // Module-level functions + // ---------------------- - _converse.api = { - /** - * This grouping collects API functions related to the XMPP connection. + this.generateResource = () => `/converse.js-${Math.floor(Math.random() * 139749528).toString()}`; + + this.sendCSI = function (stat) { + /* Send out a Chat Status Notification (XEP-0352) * - * @namespace _converse.api.connection - * @memberOf _converse.api + * Parameters: + * (String) stat: The user's chat status */ - 'connection': { - /** - * @method _converse.api.connection.connected - * @memberOf _converse.api.connection - * @returns {boolean} Whether there is an established connection or not. - */ - 'connected'() { - return _converse.connection && _converse.connection.connected || false; - }, - /** - * Terminates the connection. - * - * @method _converse.api.connection.disconnect - * @memberOf _converse.api.connection - */ - 'disconnect'() { - _converse.connection.disconnect(); + /* Send out a Chat Status Notification (XEP-0352) */ + // XXX if (converse.features[Strophe.NS.CSI] || true) { + _converse.connection.send(Object(strophe_js__WEBPACK_IMPORTED_MODULE_0__["$build"])(stat, { + xmlns: strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].NS.CSI + })); + + _converse.inactive = stat === _converse.INACTIVE ? true : false; + }; + + this.onUserActivity = function () { + /* Resets counters and flags relating to CSI and auto_away/auto_xa */ + if (_converse.idle_seconds > 0) { + _converse.idle_seconds = 0; + } + + if (!_converse.connection.authenticated) { + // We can't send out any stanzas when there's no authenticated connection. + // converse can happen when the connection reconnects. + return; + } + + if (_converse.inactive) { + _converse.sendCSI(_converse.ACTIVE); + } + + if (_converse.auto_changed_status === true) { + _converse.auto_changed_status = false; // XXX: we should really remember the original state here, and + // then set it back to that... + + _converse.xmppstatus.set('status', _converse.default_state); + } + }; + + this.onEverySecond = function () { + /* An interval handler running every second. + * Used for CSI and the auto_away and auto_xa features. + */ + if (!_converse.connection.authenticated) { + // We can't send out any stanzas when there's no authenticated connection. + // This can happen when the connection reconnects. + return; + } + + const stat = _converse.xmppstatus.get('status'); + + _converse.idle_seconds++; + + if (_converse.csi_waiting_time > 0 && _converse.idle_seconds > _converse.csi_waiting_time && !_converse.inactive) { + _converse.sendCSI(_converse.INACTIVE); + } + + if (_converse.auto_away > 0 && _converse.idle_seconds > _converse.auto_away && stat !== 'away' && stat !== 'xa' && stat !== 'dnd') { + _converse.auto_changed_status = true; + + _converse.xmppstatus.set('status', 'away'); + } else if (_converse.auto_xa > 0 && _converse.idle_seconds > _converse.auto_xa && stat !== 'xa' && stat !== 'dnd') { + _converse.auto_changed_status = true; + + _converse.xmppstatus.set('status', 'xa'); + } + }; + + this.registerIntervalHandler = function () { + /* Set an interval of one second and register a handler for it. + * Required for the auto_away, auto_xa and csi_waiting_time features. + */ + if (_converse.auto_away < 1 && _converse.auto_xa < 1 && _converse.csi_waiting_time < 1) { + // Waiting time of less then one second means features aren't used. + return; + } + + _converse.idle_seconds = 0; + _converse.auto_changed_status = false; // Was the user's status changed by _converse.js? + + window.addEventListener('click', _converse.onUserActivity); + window.addEventListener('focus', _converse.onUserActivity); + window.addEventListener('keypress', _converse.onUserActivity); + window.addEventListener('mousemove', _converse.onUserActivity); + const options = { + 'once': true, + 'passive': true + }; + window.addEventListener(_converse.unloadevent, _converse.onUserActivity, options); + _converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000); + }; + + this.setConnectionStatus = function (connection_status, message) { + _converse.connfeedback.set({ + 'connection_status': connection_status, + 'message': message + }); + }; + + this.rejectPresenceSubscription = function (jid, message) { + /* Reject or cancel another user's subscription to our presence updates. + * + * Parameters: + * (String) jid - The Jabber ID of the user whose subscription + * is being canceled. + * (String) message - An optional message to the user + */ + const pres = Object(strophe_js__WEBPACK_IMPORTED_MODULE_0__["$pres"])({ + to: jid, + type: "unsubscribed" + }); + + if (message && message !== "") { + pres.c("status").t(message); + } + + _converse.connection.send(pres); + }; + + this.reconnect = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.debounce(function () { + _converse.log('RECONNECTING'); + + _converse.log('The connection has dropped, attempting to reconnect.'); + + _converse.setConnectionStatus(strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.RECONNECTING, __('The connection has dropped, attempting to reconnect.')); + + _converse.connection.reconnecting = true; + + _converse.tearDown(); + + _converse.logIn(null, true); + }, 3000, { + 'leading': true + }); + + this.disconnect = function () { + _converse.log('DISCONNECTED'); + + delete _converse.connection.reconnecting; + + _converse.connection.reset(); + + _converse.tearDown(); + + _converse.clearSession(); + + _converse.emit('disconnected'); + }; + + this.onDisconnected = function () { + /* Gets called once strophe's status reaches Strophe.Status.DISCONNECTED. + * Will either start a teardown process for converse.js or attempt + * to reconnect. + */ + const reason = _converse.disconnection_reason; + + if (_converse.disconnection_cause === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.AUTHFAIL) { + if (_converse.credentials_url && _converse.auto_reconnect) { + /* In this case, we reconnect, because we might be receiving + * expirable tokens from the credentials_url. + */ + _converse.emit('will-reconnect'); + + return _converse.reconnect(); + } else { + return _converse.disconnect(); + } + } else if (_converse.disconnection_cause === _converse.LOGOUT || !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(reason) && reason === _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.get(strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"], 'ErrorCondition.NO_AUTH_MECH') || reason === "host-unknown" || reason === "remote-connection-failed" || !_converse.auto_reconnect) { + return _converse.disconnect(); + } + + _converse.emit('will-reconnect'); + + _converse.reconnect(); + }; + + this.setDisconnectionCause = function (cause, reason, override) { + /* Used to keep track of why we got disconnected, so that we can + * decide on what the next appropriate action is (in onDisconnected) + */ + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(cause)) { + delete _converse.disconnection_cause; + delete _converse.disconnection_reason; + } else if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.disconnection_cause) || override) { + _converse.disconnection_cause = cause; + _converse.disconnection_reason = reason; + } + }; + + this.onConnectStatusChanged = function (status, message) { + /* Callback method called by Strophe as the Strophe.Connection goes + * through various states while establishing or tearing down a + * connection. + */ + _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`); + + if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.CONNECTED || status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.ATTACHED) { + _converse.setConnectionStatus(status); // By default we always want to send out an initial presence stanza. + + + _converse.send_initial_presence = true; + + _converse.setDisconnectionCause(); + + if (_converse.connection.reconnecting) { + _converse.log(status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.CONNECTED ? 'Reconnected' : 'Reattached'); + + _converse.onConnected(true); + } else { + _converse.log(status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.CONNECTED ? 'Connected' : 'Attached'); + + if (_converse.connection.restored) { + // No need to send an initial presence stanza when + // we're restoring an existing session. + _converse.send_initial_presence = false; + } + + _converse.onConnected(); + } + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.DISCONNECTED) { + _converse.setDisconnectionCause(status, message); + + _converse.onDisconnected(); + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.ERROR) { + _converse.setConnectionStatus(status, __('An error occurred while connecting to the chat server.')); + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.CONNECTING) { + _converse.setConnectionStatus(status); + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.AUTHENTICATING) { + _converse.setConnectionStatus(status); + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.AUTHFAIL) { + if (!message) { + message = __('Your Jabber ID and/or password is incorrect. Please try again.'); } + _converse.setConnectionStatus(status, message); + + _converse.setDisconnectionCause(status, message, true); + + _converse.onDisconnected(); + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.CONNFAIL) { + let feedback = message; + + if (message === "host-unknown" || message == "remote-connection-failed") { + feedback = __("Sorry, we could not connect to the XMPP host with domain: %1$s", `\"${strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].getDomainFromJid(_converse.connection.jid)}\"`); + } else if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(message) && message === _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.get(strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"], 'ErrorCondition.NO_AUTH_MECH')) { + feedback = __("The XMPP server did not offer a supported authentication mechanism"); + } + + _converse.setConnectionStatus(status, feedback); + + _converse.setDisconnectionCause(status, message); + } else if (status === strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.DISCONNECTING) { + _converse.setDisconnectionCause(status, message); + } + }; + + this.incrementMsgCounter = function () { + this.msg_counter += 1; + const unreadMsgCount = this.msg_counter; + let title = document.title; + + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isNil(title)) { + return; + } + + if (title.search(/^Messages \(\d+\) /) === -1) { + title = `Messages (${unreadMsgCount}) ${title}`; + } else { + title = title.replace(/^Messages \(\d+\) /, `Messages (${unreadMsgCount})`); + } + }; + + this.clearMsgCounter = function () { + this.msg_counter = 0; + let title = document.title; + + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isNil(title)) { + return; + } + + if (title.search(/^Messages \(\d+\) /) !== -1) { + title = title.replace(/^Messages \(\d+\) /, ""); + } + }; + + this.initStatus = reconnecting => { + // If there's no xmppstatus obj, then we were never connected to + // begin with, so we set reconnecting to false. + reconnecting = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.xmppstatus) ? false : reconnecting; + + if (reconnecting) { + _converse.onStatusInitialized(reconnecting); + } else { + const id = `converse.xmppstatus-${_converse.bare_jid}`; + this.xmppstatus = new this.XMPPStatus({ + 'id': id + }); + this.xmppstatus.browserStorage = new _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.BrowserStorage.session(id); + this.xmppstatus.fetch({ + 'success': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.onStatusInitialized, reconnecting), + 'error': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.onStatusInitialized, reconnecting) + }); + } + }; + + this.initClientConfig = function () { + /* The client config refers to configuration of the client which is + * independent of any particular user. + * What this means is that config values need to persist across + * user sessions. + */ + const id = b64_sha1('converse.client-config'); + _converse.config = new _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.Model({ + 'id': id, + 'trusted': _converse.trusted && true || false, + 'storage': _converse.trusted ? 'local' : 'session' + }); + _converse.config.browserStorage = new _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.BrowserStorage.session(id); + + _converse.config.fetch(); + + _converse.emit('clientConfigInitialized'); + }; + + this.initSession = function () { + const id = b64_sha1('converse.bosh-session'); + _converse.session = new _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.Model({ + 'id': id + }); + _converse.session.browserStorage = new _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.BrowserStorage.session(id); + + _converse.session.fetch(); + + _converse.emit('sessionInitialized'); + }; + + this.clearSession = function () { + if (!_converse.config.get('trusted')) { + window.localStorage.clear(); + window.sessionStorage.clear(); + } else if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(this.session) && this.session.browserStorage) { + this.session.browserStorage._clear(); + } + + _converse.emit('clearSession'); + }; + + this.logOut = function () { + _converse.clearSession(); + + _converse.setDisconnectionCause(_converse.LOGOUT, undefined, true); + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.connection)) { + _converse.connection.disconnect(); + } else { + _converse.tearDown(); + } // Recreate all the promises + + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.each(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.keys(_converse.promises), addPromise); + + _converse.emit('logout'); + }; + + this.saveWindowState = function (ev, hidden) { + // XXX: eventually we should be able to just use + // document.visibilityState (when we drop support for older + // browsers). + let state; + const event_map = { + 'focus': "visible", + 'focusin': "visible", + 'pageshow': "visible", + 'blur': "hidden", + 'focusout': "hidden", + 'pagehide': "hidden" + }; + ev = ev || document.createEvent('Events'); + + if (ev.type in event_map) { + state = event_map[ev.type]; + } else { + state = document[hidden] ? "hidden" : "visible"; + } + + if (state === 'visible') { + _converse.clearMsgCounter(); + } + + _converse.windowState = state; + + _converse.emit('windowStateChanged', { + state + }); + }; + + this.registerGlobalEventHandlers = function () { + // Taken from: + // http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active + let hidden = "hidden"; // Standards: + + if (hidden in document) { + document.addEventListener("visibilitychange", _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden)); + } else if ((hidden = "mozHidden") in document) { + document.addEventListener("mozvisibilitychange", _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden)); + } else if ((hidden = "webkitHidden") in document) { + document.addEventListener("webkitvisibilitychange", _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden)); + } else if ((hidden = "msHidden") in document) { + document.addEventListener("msvisibilitychange", _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden)); + } else if ("onfocusin" in document) { + // IE 9 and lower: + document.onfocusin = document.onfocusout = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden); + } else { + // All others: + window.onpageshow = window.onpagehide = window.onfocus = window.onblur = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden); + } // set the initial state (but only if browser supports the Page Visibility API) + + + if (document[hidden] !== undefined) { + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.saveWindowState, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, hidden)({ + type: document[hidden] ? "blur" : "focus" + }); + } + + _converse.emit('registeredGlobalEventHandlers'); + }; + + this.enableCarbons = function () { + /* Ask the XMPP server to enable Message Carbons + * See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling + */ + if (!this.message_carbons || this.session.get('carbons_enabled')) { + return; + } + + const carbons_iq = new strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Builder('iq', { + 'from': this.connection.jid, + 'id': 'enablecarbons', + 'type': 'set' + }).c('enable', { + xmlns: strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].NS.CARBONS + }); + this.connection.addHandler(iq => { + if (iq.querySelectorAll('error').length > 0) { + _converse.log('An error occurred while trying to enable message carbons.', strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.WARN); + } else { + this.session.save({ + 'carbons_enabled': true + }); + + _converse.log('Message carbons have been enabled.'); + } + }, null, "iq", null, "enablecarbons"); + this.connection.send(carbons_iq); + }; + + this.sendInitialPresence = function () { + if (_converse.send_initial_presence) { + _converse.xmppstatus.sendPresence(); + } + }; + + this.onStatusInitialized = function (reconnecting) { + _converse.emit('statusInitialized', reconnecting); + + if (reconnecting) { + _converse.emit('reconnected'); + } else { + init_promise.resolve(); + + _converse.emit('initialized'); + + _converse.emit('connected'); + } + }; + + this.setUserJID = function () { + _converse.jid = _converse.connection.jid; + _converse.bare_jid = strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].getBareJidFromJid(_converse.connection.jid); + _converse.resource = strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].getResourceFromJid(_converse.connection.jid); + _converse.domain = strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].getDomainFromJid(_converse.connection.jid); + + _converse.emit('setUserJID'); + }; + + this.onConnected = function (reconnecting) { + /* Called as soon as a new connection has been established, either + * by logging in or by attaching to an existing BOSH session. + */ + _converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser + + + _converse.setUserJID(); + + _converse.initSession(); + + _converse.enableCarbons(); + + _converse.initStatus(reconnecting); + }; + + this.ConnectionFeedback = _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.Model.extend({ + defaults: { + 'connection_status': strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.DISCONNECTED, + 'message': '' + }, + + initialize() { + this.on('change', () => { + _converse.emit('connfeedback', _converse.connfeedback); + }); + } + + }); + this.connfeedback = new this.ConnectionFeedback(); + this.XMPPStatus = _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.Model.extend({ + defaults() { + return { + "jid": _converse.bare_jid, + "status": _converse.default_state + }; + }, + + initialize() { + this.vcard = _converse.vcards.findWhere({ + 'jid': this.get('jid') + }); + + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isNil(this.vcard)) { + this.vcard = _converse.vcards.create({ + 'jid': this.get('jid') + }); + } + + this.on('change:status', item => { + const status = this.get('status'); + this.sendPresence(status); + + _converse.emit('statusChanged', status); + }); + this.on('change:status_message', () => { + const status_message = this.get('status_message'); + this.sendPresence(this.get('status'), status_message); + + _converse.emit('statusMessageChanged', status_message); + }); + }, + + constructPresence(type, status_message) { + let presence; + type = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isString(type) ? type : this.get('status') || _converse.default_state; + status_message = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isString(status_message) ? status_message : this.get('status_message'); // Most of these presence types are actually not explicitly sent, + // but I add all of them here for reference and future proofing. + + if (type === 'unavailable' || type === 'probe' || type === 'error' || type === 'unsubscribe' || type === 'unsubscribed' || type === 'subscribe' || type === 'subscribed') { + presence = Object(strophe_js__WEBPACK_IMPORTED_MODULE_0__["$pres"])({ + 'type': type + }); + } else if (type === 'offline') { + presence = Object(strophe_js__WEBPACK_IMPORTED_MODULE_0__["$pres"])({ + 'type': 'unavailable' + }); + } else if (type === 'online') { + presence = Object(strophe_js__WEBPACK_IMPORTED_MODULE_0__["$pres"])(); + } else { + presence = Object(strophe_js__WEBPACK_IMPORTED_MODULE_0__["$pres"])().c('show').t(type).up(); + } + + if (status_message) { + presence.c('status').t(status_message).up(); + } + + presence.c('priority').t(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isNaN(Number(_converse.priority)) ? 0 : _converse.priority); + return presence; + }, + + sendPresence(type, status_message) { + _converse.connection.send(this.constructPresence(type, status_message)); + } + + }); + + this.setUpXMLLogging = function () { + strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].log = function (level, msg) { + _converse.log(msg, level); + }; + + if (this.debug) { + this.connection.xmlInput = function (body) { + _converse.log(body.outerHTML, strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.DEBUG, 'color: darkgoldenrod'); + }; + + this.connection.xmlOutput = function (body) { + _converse.log(body.outerHTML, strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.DEBUG, 'color: darkcyan'); + }; + } + }; + + this.fetchLoginCredentials = () => new es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_2___default.a((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', _converse.credentials_url, true); + xhr.setRequestHeader('Accept', "application/json, text/javascript"); + + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 400) { + const data = JSON.parse(xhr.responseText); + resolve({ + 'jid': data.jid, + 'password': data.password + }); + } else { + xhr.onerror(); + } + }; + + xhr.onerror = function () { + delete _converse.connection; + + _converse.emit('noResumeableSession', this); + + reject(xhr.responseText); + }; + + xhr.send(); + }); + + this.startNewBOSHSession = function () { + const xhr = new XMLHttpRequest(); + xhr.open('GET', _converse.prebind_url, true); + xhr.setRequestHeader('Accept', "application/json, text/javascript"); + + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 400) { + const data = JSON.parse(xhr.responseText); + + _converse.connection.attach(data.jid, data.sid, data.rid, _converse.onConnectStatusChanged); + } else { + xhr.onerror(); + } + }; + + xhr.onerror = function () { + delete _converse.connection; + + _converse.emit('noResumeableSession', this); + }; + + xhr.send(); + }; + + this.restoreBOSHSession = function (jid_is_required) { + /* Tries to restore a cached BOSH session. */ + if (!this.jid) { + const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session " + "but we don't have the JID for the user!"; + + if (jid_is_required) { + throw new Error(msg); + } else { + _converse.log(msg); + } + } + + try { + this.connection.restore(this.jid, this.onConnectStatusChanged); + return true; + } catch (e) { + _converse.log("Could not restore session for jid: " + this.jid + " Error message: " + e.message, strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.WARN); + + this.clearSession(); // We want to clear presences (see #555) + + return false; + } + }; + + this.attemptPreboundSession = function (reconnecting) { + /* Handle session resumption or initialization when prebind is + * being used. + */ + if (!reconnecting) { + if (this.keepalive && this.restoreBOSHSession(true)) { + return; + } // No keepalive, or session resumption has failed. + + + if (this.jid && this.sid && this.rid) { + return this.connection.attach(this.jid, this.sid, this.rid, this.onConnectStatusChanged); + } + } + + if (this.prebind_url) { + return this.startNewBOSHSession(); + } else { + throw new Error("attemptPreboundSession: If you use prebind and not keepalive, " + "then you MUST supply JID, RID and SID values or a prebind_url."); + } + }; + + this.attemptNonPreboundSession = function (credentials, reconnecting) { + /* Handle session resumption or initialization when prebind is not being used. + * + * Two potential options exist and are handled in this method: + * 1. keepalive + * 2. auto_login + */ + if (!reconnecting && this.keepalive && this.restoreBOSHSession()) { + return; + } + + if (credentials) { + // When credentials are passed in, they override prebinding + // or credentials fetching via HTTP + this.autoLogin(credentials); + } else if (this.auto_login) { + if (this.credentials_url) { + this.fetchLoginCredentials().then(this.autoLogin.bind(this), this.autoLogin.bind(this)); + } else if (!this.jid) { + throw new Error("attemptNonPreboundSession: If you use auto_login, " + "you also need to give either a jid value (and if " + "applicable a password) or you need to pass in a URL " + "from where the username and password can be fetched " + "(via credentials_url)."); + } else { + this.autoLogin(); // Could be ANONYMOUS or EXTERNAL + } + } else if (reconnecting) { + this.autoLogin(); + } + }; + + this.autoLogin = function (credentials) { + if (credentials) { + // If passed in, the credentials come from credentials_url, + // so we set them on the converse object. + this.jid = credentials.jid; + } + + if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) { + if (!this.jid) { + throw new Error("Config Error: when using anonymous login " + "you need to provide the server's domain via the 'jid' option. " + "Either when calling converse.initialize, or when calling " + "_converse.api.user.login."); + } + + if (!this.connection.reconnecting) { + this.connection.reset(); + } + + this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged, BOSH_WAIT); + } else if (this.authentication === _converse.LOGIN) { + const password = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isNil(credentials) ? _converse.connection.pass || this.password : credentials.password; + + if (!password) { + if (this.auto_login) { + throw new Error("initConnection: If you use auto_login and " + "authentication='login' then you also need to provide a password."); + } + + _converse.setDisconnectionCause(strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Status.AUTHFAIL, undefined, true); + + _converse.disconnect(); + + return; + } + + const resource = strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].getResourceFromJid(this.jid); + + if (!resource) { + this.jid = this.jid.toLowerCase() + _converse.generateResource(); + } else { + this.jid = strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].getBareJidFromJid(this.jid).toLowerCase() + '/' + resource; + } + + if (!this.connection.reconnecting) { + this.connection.reset(); + } + + this.connection.connect(this.jid, password, this.onConnectStatusChanged, BOSH_WAIT); + } + }; + + this.logIn = function (credentials, reconnecting) { + // We now try to resume or automatically set up a new session. + // Otherwise the user will be shown a login form. + if (this.authentication === _converse.PREBIND) { + this.attemptPreboundSession(reconnecting); + } else { + this.attemptNonPreboundSession(credentials, reconnecting); + } + }; + + this.initConnection = function () { + /* Creates a new Strophe.Connection instance if we don't already have one. + */ + if (!this.connection) { + if (!this.bosh_service_url && !this.websocket_url) { + throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both."); + } + + if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) { + this.connection = new strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Connection(this.websocket_url, this.connection_options); + } else if (this.bosh_service_url) { + this.connection = new strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].Connection(this.bosh_service_url, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assignIn(this.connection_options, { + 'keepalive': this.keepalive + })); + } else { + throw new Error("initConnection: this browser does not support websockets and bosh_service_url wasn't specified."); + } + } + + _converse.emit('connectionInitialized'); + }; + + this.tearDown = function () { + /* Remove those views which are only allowed with a valid + * connection. + */ + _converse.emit('beforeTearDown'); + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.session)) { + _converse.session.destroy(); + } + + window.removeEventListener('click', _converse.onUserActivity); + window.removeEventListener('focus', _converse.onUserActivity); + window.removeEventListener('keypress', _converse.onUserActivity); + window.removeEventListener('mousemove', _converse.onUserActivity); + window.removeEventListener(_converse.unloadevent, _converse.onUserActivity); + window.clearInterval(_converse.everySecondTrigger); + + _converse.emit('afterTearDown'); + + return _converse; + }; + + this.initPlugins = function () { + // If initialize gets called a second time (e.g. during tests), then we + // need to re-apply all plugins (for a new converse instance), and we + // therefore need to clear this array that prevents plugins from being + // initialized twice. + // If initialize is called for the first time, then this array is empty + // in any case. + _converse.pluggable.initialized_plugins = []; + + const whitelist = _converse.core_plugins.concat(_converse.whitelisted_plugins); + + if (_converse.view_mode === 'embedded') { + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.forEach([// eslint-disable-line lodash/prefer-map + "converse-bookmarks", "converse-controlbox", "converse-headline", "converse-register"], name => { + _converse.blacklisted_plugins.push(name); + }); + } + + _converse.pluggable.initializePlugins({ + 'updateSettings'() { + _converse.log("(DEPRECATION) " + "The `updateSettings` method has been deprecated. " + "Please use `_converse.api.settings.update` instead.", strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.WARN); + + _converse.api.settings.update.apply(_converse, arguments); + }, + + '_converse': _converse + }, whitelist, _converse.blacklisted_plugins); + + _converse.emit('pluginsInitialized'); + }; // Initialization + // -------------- + // This is the end of the initialize method. + + + if (settings.connection) { + this.connection = settings.connection; + } + + function finishInitialization() { + _converse.initPlugins(); + + _converse.initClientConfig(); + + _converse.initConnection(); + + _converse.setUpXMLLogging(); + + _converse.logIn(); + + _converse.registerGlobalEventHandlers(); + + if (!_backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.history.started) { + _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.history.start(); + } + } + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.connection) && _converse.connection.service === 'jasmine tests') { + finishInitialization(); + return _converse; + } else if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_i18n__WEBPACK_IMPORTED_MODULE_6___default.a)) { + finishInitialization(); + } else { + _i18n__WEBPACK_IMPORTED_MODULE_6___default.a.fetchTranslations(_converse.locale, _converse.locales, _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"].interpolate(_converse.locales_url, { + 'locale': _converse.locale + })).catch(e => _converse.log(e.message, strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.FATAL)).then(finishInitialization).catch(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(_converse.log, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"].LogLevel.FATAL)); + } + + return init_promise; +}; +/** + * ### The private API + * + * The private API methods are only accessible via the closured {@link _converse} + * object, which is only available to plugins. + * + * These methods are kept private (i.e. not global) because they may return + * sensitive data which should be kept off-limits to other 3rd-party scripts + * that might be running in the page. + * + * @namespace _converse.api + * @memberOf _converse + */ + + +_converse.api = { + /** + * This grouping collects API functions related to the XMPP connection. + * + * @namespace _converse.api.connection + * @memberOf _converse.api + */ + 'connection': { + /** + * @method _converse.api.connection.connected + * @memberOf _converse.api.connection + * @returns {boolean} Whether there is an established connection or not. + */ + 'connected'() { + return _converse.connection && _converse.connection.connected || false; }, /** - * Lets you emit (i.e. trigger) events, which can be listened to via - * {@link _converse.api.listen.on} or {@link _converse.api.listen.once} - * (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)). + * Terminates the connection. * - * @method _converse.api.emit + * @method _converse.api.connection.disconnect + * @memberOf _converse.api.connection */ - 'emit'() { - _converse.emit.apply(_converse, arguments); + 'disconnect'() { + _converse.connection.disconnect(); + } + + }, + + /** + * Lets you emit (i.e. trigger) events, which can be listened to via + * {@link _converse.api.listen.on} or {@link _converse.api.listen.once} + * (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)). + * + * @method _converse.api.emit + */ + 'emit'() { + _converse.emit.apply(_converse, arguments); + }, + + /** + * This grouping collects API functions related to the current logged in user. + * + * @namespace _converse.api.user + * @memberOf _converse.api + */ + 'user': { + /** + * @method _converse.api.user.jid + * @returns {string} The current user's full JID (Jabber ID) + * @example _converse.api.user.jid()) + */ + 'jid'() { + return _converse.connection.jid; }, /** - * This grouping collects API functions related to the current logged in user. + * Logs the user in. * - * @namespace _converse.api.user - * @memberOf _converse.api + * If called without any parameters, Converse will try + * to log the user in by calling the `prebind_url` or `credentials_url` depending + * on whether prebinding is used or not. + * + * @method _converse.api.user.login + * @param {object} [credentials] An object with the credentials. + * @example + * converse.plugins.add('myplugin', { + * initialize: function () { + * + * this._converse.api.user.login({ + * 'jid': 'dummy@example.com', + * 'password': 'secret' + * }); + * + * } + * }); */ - 'user': { - /** - * @method _converse.api.user.jid - * @returns {string} The current user's full JID (Jabber ID) - * @example _converse.api.user.jid()) + 'login'(credentials) { + _converse.logIn(credentials); + }, + + /** + * Logs the user out of the current XMPP session. + * + * @method _converse.api.user.logout + * @example _converse.api.user.logout(); + */ + 'logout'() { + _converse.logOut(); + }, + + /** + * Set and get the user's chat status, also called their *availability*. + * + * @namespace _converse.api.user.status + * @memberOf _converse.api.user + */ + 'status': { + /** Return the current user's availability status. + * + * @method _converse.api.user.status.get + * @example _converse.api.user.status.get(); */ - 'jid'() { - return _converse.connection.jid; + 'get'() { + return _converse.xmppstatus.get('status'); }, /** - * Logs the user in. + * The user's status can be set to one of the following values: * - * If called without any parameters, Converse will try - * to log the user in by calling the `prebind_url` or `credentials_url` depending - * on whether prebinding is used or not. + * @method _converse.api.user.status.set + * @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa') + * @param {string} [message] A custom status message * - * @method _converse.api.user.login - * @param {object} [credentials] An object with the credentials. - * @example - * converse.plugins.add('myplugin', { - * initialize: function () { - * - * this._converse.api.user.login({ - * 'jid': 'dummy@example.com', - * 'password': 'secret' - * }); - * - * } - * }); + * @example this._converse.api.user.status.set('dnd'); + * @example this._converse.api.user.status.set('dnd', 'In a meeting'); */ - 'login'(credentials) { - _converse.logIn(credentials); + 'set'(value, message) { + const data = { + 'status': value + }; + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.includes(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.keys(_converse.STATUS_WEIGHTS), value)) { + throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1'); + } + + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isString(message)) { + data.status_message = message; + } + + _converse.xmppstatus.sendPresence(value); + + _converse.xmppstatus.save(data); }, /** - * Logs the user out of the current XMPP session. + * Set and retrieve the user's custom status message. * - * @method _converse.api.user.logout - * @example _converse.api.user.logout(); + * @namespace _converse.api.user.status.message + * @memberOf _converse.api.user.status */ - 'logout'() { - _converse.logOut(); - }, - - /** - * Set and get the user's chat status, also called their *availability*. - * - * @namespace _converse.api.user.status - * @memberOf _converse.api.user - */ - 'status': { - /** Return the current user's availability status. - * - * @method _converse.api.user.status.get - * @example _converse.api.user.status.get(); + 'message': { + /** + * @method _converse.api.user.status.message.get + * @returns {string} The status message + * @example const message = _converse.api.user.status.message.get() */ 'get'() { - return _converse.xmppstatus.get('status'); + return _converse.xmppstatus.get('status_message'); }, /** - * The user's status can be set to one of the following values: - * - * @method _converse.api.user.status.set - * @param {string} value The user's chat status (e.g. 'away', 'dnd', 'offline', 'online', 'unavailable' or 'xa') - * @param {string} [message] A custom status message - * - * @example this._converse.api.user.status.set('dnd'); - * @example this._converse.api.user.status.set('dnd', 'In a meeting'); + * @method _converse.api.user.status.message.set + * @param {string} status The status message + * @example _converse.api.user.status.message.set('In a meeting'); */ - 'set'(value, message) { - const data = { - 'status': value - }; - - if (!_.includes(_.keys(_converse.STATUS_WEIGHTS), value)) { - throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1'); - } - - if (_.isString(message)) { - data.status_message = message; - } - - _converse.xmppstatus.sendPresence(value); - - _converse.xmppstatus.save(data); - }, - - /** - * Set and retrieve the user's custom status message. - * - * @namespace _converse.api.user.status.message - * @memberOf _converse.api.user.status - */ - 'message': { - /** - * @method _converse.api.user.status.message.get - * @returns {string} The status message - * @example const message = _converse.api.user.status.message.get() - */ - 'get'() { - return _converse.xmppstatus.get('status_message'); - }, - - /** - * @method _converse.api.user.status.message.set - * @param {string} status The status message - * @example _converse.api.user.status.message.set('In a meeting'); - */ - 'set'(status) { - _converse.xmppstatus.save({ - 'status_message': status - }); - } - + 'set'(status) { + _converse.xmppstatus.save({ + 'status_message': status + }); } + + } + } + }, + + /** + * This grouping allows access to the + * [configuration settings](/docs/html/configuration.html#configuration-settings) + * of Converse. + * + * @namespace _converse.api.settings + * @memberOf _converse.api + */ + 'settings': { + /** + * Allows new configuration settings to be specified, or new default values for + * existing configuration settings to be specified. + * + * @method _converse.api.settings.update + * @param {object} settings The configuration settings + * @example + * _converse.api.settings.update({ + * 'enable_foo': true + * }); + * + * // The user can then override the default value of the configuration setting when + * // calling `converse.initialize`. + * converse.initialize({ + * 'enable_foo': false + * }); + */ + 'update'(settings) { + _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"].merge(_converse.default_settings, settings); + _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"].merge(_converse, settings); + _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"].applyUserSettings(_converse, settings, _converse.user_settings); + }, + + /** + * @method _converse.api.settings.get + * @returns {*} Value of the particular configuration setting. + * @example _converse.api.settings.get("play_sounds"); + */ + 'get'(key) { + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.includes(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.keys(_converse.default_settings), key)) { + return _converse[key]; } }, /** - * This grouping allows access to the - * [configuration settings](/docs/html/configuration.html#configuration-settings) - * of Converse. + * Set one or many configuration settings. * - * @namespace _converse.api.settings - * @memberOf _converse.api + * Note, this is not an alternative to calling {@link converse.initialize}, which still needs + * to be called. Generally, you'd use this method after Converse is already + * running and you want to change the configuration on-the-fly. + * + * @method _converse.api.settings.set + * @param {Object} [settings] An object containing configuration settings. + * @param {string} [key] Alternatively to passing in an object, you can pass in a key and a value. + * @param {string} [value] + * @example _converse.api.settings.set("play_sounds", true); + * @example + * _converse.api.settings.set({ + * "play_sounds", true, + * "hide_offline_users" true + * }); */ - 'settings': { - /** - * Allows new configuration settings to be specified, or new default values for - * existing configuration settings to be specified. - * - * @method _converse.api.settings.update - * @param {object} settings The configuration settings - * @example - * _converse.api.settings.update({ - * 'enable_foo': true - * }); - * - * // The user can then override the default value of the configuration setting when - * // calling `converse.initialize`. - * converse.initialize({ - * 'enable_foo': false - * }); - */ - 'update'(settings) { - u.merge(_converse.default_settings, settings); - u.merge(_converse, settings); - u.applyUserSettings(_converse, settings, _converse.user_settings); - }, + 'set'(key, val) { + const o = {}; - /** - * @method _converse.api.settings.get - * @returns {*} Value of the particular configuration setting. - * @example _converse.api.settings.get("play_sounds"); - */ - 'get'(key) { - if (_.includes(_.keys(_converse.default_settings), key)) { - return _converse[key]; - } - }, + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isObject(key)) { + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assignIn(_converse, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.pick(key, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.keys(_converse.default_settings))); + } else if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isString("string")) { + o[key] = val; - /** - * Set one or many configuration settings. - * - * Note, this is not an alternative to calling {@link converse.initialize}, which still needs - * to be called. Generally, you'd use this method after Converse is already - * running and you want to change the configuration on-the-fly. - * - * @method _converse.api.settings.set - * @param {Object} [settings] An object containing configuration settings. - * @param {string} [key] Alternatively to passing in an object, you can pass in a key and a value. - * @param {string} [value] - * @example _converse.api.settings.set("play_sounds", true); - * @example - * _converse.api.settings.set({ - * "play_sounds", true, - * "hide_offline_users" true - * }); - */ - 'set'(key, val) { - const o = {}; - - if (_.isObject(key)) { - _.assignIn(_converse, _.pick(key, _.keys(_converse.default_settings))); - } else if (_.isString("string")) { - o[key] = val; - - _.assignIn(_converse, _.pick(o, _.keys(_converse.default_settings))); - } + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assignIn(_converse, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.pick(o, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.keys(_converse.default_settings))); } + } - }, + }, + /** + * Converse and its plugins emit various events which you can listen to via the + * {@link _converse.api.listen} namespace. + * + * Some of these events are also available as [ES2015 Promises](http://es6-features.org/#PromiseUsage) + * although not all of them could logically act as promises, since some events + * might be fired multpile times whereas promises are to be resolved (or + * rejected) only once. + * + * Events which are also promises include: + * + * * [cachedRoster](/docs/html/events.html#cachedroster) + * * [chatBoxesFetched](/docs/html/events.html#chatBoxesFetched) + * * [pluginsInitialized](/docs/html/events.html#pluginsInitialized) + * * [roster](/docs/html/events.html#roster) + * * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched) + * * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched) + * * [rosterInitialized](/docs/html/events.html#rosterInitialized) + * * [statusInitialized](/docs/html/events.html#statusInitialized) + * * [roomsPanelRendered](/docs/html/events.html#roomsPanelRendered) + * + * The various plugins might also provide promises, and they do this by using the + * `promises.add` api method. + * + * @namespace _converse.api.promises + * @memberOf _converse.api + */ + 'promises': { /** - * Converse and its plugins emit various events which you can listen to via the - * {@link _converse.api.listen} namespace. + * By calling `promises.add`, a new [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) + * is made available for other code or plugins to depend on via the + * {@link _converse.api.waitUntil} method. * - * Some of these events are also available as [ES2015 Promises](http://es6-features.org/#PromiseUsage) - * although not all of them could logically act as promises, since some events - * might be fired multpile times whereas promises are to be resolved (or - * rejected) only once. + * Generally, it's the responsibility of the plugin which adds the promise to + * also resolve it. * - * Events which are also promises include: + * This is done by calling {@link _converse.api.emit}, which not only resolves the + * promise, but also emits an event with the same name (which can be listened to + * via {@link _converse.api.listen}). * - * * [cachedRoster](/docs/html/events.html#cachedroster) - * * [chatBoxesFetched](/docs/html/events.html#chatBoxesFetched) - * * [pluginsInitialized](/docs/html/events.html#pluginsInitialized) - * * [roster](/docs/html/events.html#roster) - * * [rosterContactsFetched](/docs/html/events.html#rosterContactsFetched) - * * [rosterGroupsFetched](/docs/html/events.html#rosterGroupsFetched) - * * [rosterInitialized](/docs/html/events.html#rosterInitialized) - * * [statusInitialized](/docs/html/events.html#statusInitialized) - * * [roomsPanelRendered](/docs/html/events.html#roomsPanelRendered) - * - * The various plugins might also provide promises, and they do this by using the - * `promises.add` api method. - * - * @namespace _converse.api.promises - * @memberOf _converse.api + * @method _converse.api.promises.add + * @param {string|array} [name|names] The name or an array of names for the promise(s) to be added + * @example _converse.api.promises.add('foo-completed'); */ - 'promises': { - /** - * By calling `promises.add`, a new [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - * is made available for other code or plugins to depend on via the - * {@link _converse.api.waitUntil} method. - * - * Generally, it's the responsibility of the plugin which adds the promise to - * also resolve it. - * - * This is done by calling {@link _converse.api.emit}, which not only resolves the - * promise, but also emits an event with the same name (which can be listened to - * via {@link _converse.api.listen}). - * - * @method _converse.api.promises.add - * @param {string|array} [name|names] The name or an array of names for the promise(s) to be added - * @example _converse.api.promises.add('foo-completed'); - */ - 'add'(promises) { - promises = _.isArray(promises) ? promises : [promises]; + 'add'(promises) { + promises = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isArray(promises) ? promises : [promises]; - _.each(promises, addPromise); - } + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.each(promises, addPromise); + } - }, + }, + /** + * This namespace lets you access the BOSH tokens + * + * @namespace _converse.api.tokens + * @memberOf _converse.api + */ + 'tokens': { /** - * This namespace lets you access the BOSH tokens - * - * @namespace _converse.api.tokens - * @memberOf _converse.api + * @method _converse.api.tokens.get + * @param {string} [id] The type of token to return ('rid' or 'sid'). + * @returns 'string' A token, either the RID or SID token depending on what's asked for. + * @example _converse.api.tokens.get('rid'); */ - 'tokens': { - /** - * @method _converse.api.tokens.get - * @param {string} [id] The type of token to return ('rid' or 'sid'). - * @returns 'string' A token, either the RID or SID token depending on what's asked for. - * @example _converse.api.tokens.get('rid'); - */ - 'get'(id) { - if (!_converse.expose_rid_and_sid || _.isUndefined(_converse.connection)) { - return null; - } - - if (id.toLowerCase() === 'rid') { - return _converse.connection.rid || _converse.connection._proto.rid; - } else if (id.toLowerCase() === 'sid') { - return _converse.connection.sid || _converse.connection._proto.sid; - } - } - - }, - - /** - * Converse emits events to which you can subscribe to. - * - * The `listen` namespace exposes methods for creating event listeners - * (aka handlers) for these events. - * - * @namespace _converse.api.listen - * @memberOf _converse - */ - 'listen': { - /** - * Lets you listen to an event exactly once. - * - * @method _converse.api.listen.once - * @param {string} name The event's name - * @param {function} callback The callback method to be called when the event is emitted. - * @param {object} [context] The value of the `this` parameter for the callback. - * @example _converse.api.listen.once('message', function (messageXML) { ... }); - */ - 'once': _converse.once.bind(_converse), - - /** - * Lets you subscribe to an event. - * - * Every time the event fires, the callback method specified by `callback` will be called. - * - * @method _converse.api.listen.on - * @param {string} name The event's name - * @param {function} callback The callback method to be called when the event is emitted. - * @param {object} [context] The value of the `this` parameter for the callback. - * @example _converse.api.listen.on('message', function (messageXML) { ... }); - */ - 'on': _converse.on.bind(_converse), - - /** - * To stop listening to an event, you can use the `not` method. - * - * Every time the event fires, the callback method specified by `callback` will be called. - * - * @method _converse.api.listen.not - * @param {string} name The event's name - * @param {function} callback The callback method that is to no longer be called when the event fires - * @example _converse.api.listen.not('message', function (messageXML); - */ - 'not': _converse.off.bind(_converse), - - /** - * Subscribe to an incoming stanza - * - * Every a matched stanza is received, the callback method specified by `callback` will be called. - * - * @method _converse.api.listen.stanza - * @param {string} name The stanza's name - * @param {object} options Matching options - * (e.g. 'ns' for namespace, 'type' for stanza type, also 'id' and 'from'); - * @param {function} handler The callback method to be called when the stanza appears - */ - 'stanza'(name, options, handler) { - if (_.isFunction(options)) { - handler = options; - options = {}; - } else { - options = options || {}; - } - - _converse.connection.addHandler(handler, options.ns, name, options.type, options.id, options.from, options); - } - - }, - - /** - * Wait until a promise is resolved - * - * @method _converse.api.waitUntil - * @param {string} name The name of the promise - * @returns {Promise} - */ - 'waitUntil'(name) { - const promise = _converse.promises[name]; - - if (_.isUndefined(promise)) { + 'get'(id) { + if (!_converse.expose_rid_and_sid || _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.connection)) { return null; } - return promise; - }, - - /** - * Allows you to send XML stanzas. - * - * @method _converse.api.send - * @example - * const msg = converse.env.$msg({ - * 'from': 'juliet@example.com/balcony', - * 'to': 'romeo@example.net', - * 'type':'chat' - * }); - * _converse.api.send(msg); - */ - 'send'(stanza) { - _converse.connection.send(stanza); - }, - - /** - * Send an IQ stanza and receive a promise - * - * @method _converse.api.sendIQ - * @returns {Promise} A promise which resolves when we receive a `result` stanza - * or is rejected when we receive an `error` stanza. - */ - 'sendIQ'(stanza) { - return new Promise((resolve, reject) => { - _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT); - }); + if (id.toLowerCase() === 'rid') { + return _converse.connection.rid || _converse.connection._proto.rid; + } else if (id.toLowerCase() === 'sid') { + return _converse.connection.sid || _converse.connection._proto.sid; + } } - }; + }, + /** - * ### The Public API + * Converse emits events to which you can subscribe to. * - * This namespace contains public API methods which are are - * accessible on the global `converse` object. - * They are public, because any JavaScript in the - * page can call them. Public methods therefore don’t expose any sensitive - * or closured data. To do that, you’ll need to create a plugin, which has - * access to the private API method. + * The `listen` namespace exposes methods for creating event listeners + * (aka handlers) for these events. * - * @namespace converse + * @namespace _converse.api.listen + * @memberOf _converse */ - - const converse = { + 'listen': { /** - * Public API method which initializes Converse. - * This method must always be called when using Converse. + * Lets you listen to an event exactly once. * - * @memberOf converse - * @method initialize - * @param {object} config A map of [configuration-settings](https://conversejs.org/docs/html/configuration.html#configuration-settings). - * - * @example - * converse.initialize({ - * allow_otr: true, - * auto_list_rooms: false, - * auto_subscribe: false, - * bosh_service_url: 'https://bind.example.com', - * hide_muc_server: false, - * i18n: locales['en'], - * keepalive: true, - * play_sounds: true, - * prebind: false, - * show_controlbox_by_default: true, - * debug: false, - * roster_groups: true - * }); + * @method _converse.api.listen.once + * @param {string} name The event's name + * @param {function} callback The callback method to be called when the event is emitted. + * @param {object} [context] The value of the `this` parameter for the callback. + * @example _converse.api.listen.once('message', function (messageXML) { ... }); */ - 'initialize'(settings, callback) { - return _converse.initialize(settings, callback); - }, + 'once': _converse.once.bind(_converse), /** - * Exposes methods for adding and removing plugins. You'll need to write a plugin - * if you want to have access to the private API methods defined further down below. + * Lets you subscribe to an event. * - * For more information on plugins, read the documentation on [writing a plugin](/docs/html/plugin_development.html). + * Every time the event fires, the callback method specified by `callback` will be called. * - * @namespace plugins - * @memberOf converse + * @method _converse.api.listen.on + * @param {string} name The event's name + * @param {function} callback The callback method to be called when the event is emitted. + * @param {object} [context] The value of the `this` parameter for the callback. + * @example _converse.api.listen.on('message', function (messageXML) { ... }); */ - 'plugins': { - /** Registers a new plugin. - * - * @method converse.plugins.add - * @param {string} name The name of the plugin - * @param {object} plugin The plugin object - * - * @example - * - * const plugin = { - * initialize: function () { - * // Gets called as soon as the plugin has been loaded. - * - * // Inside this method, you have access to the private - * // API via `_covnerse.api`. - * - * // The private _converse object contains the core logic - * // and data-structures of Converse. - * } - * } - * converse.plugins.add('myplugin', plugin); - */ - 'add'(name, plugin) { - plugin.__name__ = name; + 'on': _converse.on.bind(_converse), - if (!_.isUndefined(_converse.pluggable.plugins[name])) { - throw new TypeError(`Error: plugin with name "${name}" has already been ` + 'registered!'); - } else { - _converse.pluggable.plugins[name] = plugin; - } + /** + * To stop listening to an event, you can use the `not` method. + * + * Every time the event fires, the callback method specified by `callback` will be called. + * + * @method _converse.api.listen.not + * @param {string} name The event's name + * @param {function} callback The callback method that is to no longer be called when the event fires + * @example _converse.api.listen.not('message', function (messageXML); + */ + 'not': _converse.off.bind(_converse), + + /** + * Subscribe to an incoming stanza + * + * Every a matched stanza is received, the callback method specified by `callback` will be called. + * + * @method _converse.api.listen.stanza + * @param {string} name The stanza's name + * @param {object} options Matching options + * (e.g. 'ns' for namespace, 'type' for stanza type, also 'id' and 'from'); + * @param {function} handler The callback method to be called when the stanza appears + */ + 'stanza'(name, options, handler) { + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isFunction(options)) { + handler = options; + options = {}; + } else { + options = options || {}; } - }, - - /** - * Utility methods and globals from bundled 3rd party libraries. - * @memberOf converse - * - * @property {function} converse.env.$build - Creates a Strophe.Builder, for creating stanza objects. - * @property {function} converse.env.$iq - Creates a Strophe.Builder with an element as the root. - * @property {function} converse.env.$msg - Creates a Strophe.Builder with an element as the root. - * @property {function} converse.env.$pres - Creates a Strophe.Builder with an element as the root. - * @property {object} converse.env.Backbone - The [Backbone](http://backbonejs.org) object used by Converse to create models and views. - * @property {function} converse.env.Promise - The Promise implementation used by Converse. - * @property {function} converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse. - * @property {object} converse.env._ - The instance of [lodash](http://lodash.com) used by Converse. - * @property {function} converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods. - * @property {function} converse.env.b64_sha1 - Utility method from Strophe for creating base64 encoded sha1 hashes. - * @property {object} converse.env.moment - [Moment](https://momentjs.com) date manipulation library. - * @property {function} converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine. - * @property {object} converse.env.utils - Module containing common utility methods used by Converse. - */ - 'env': { - '$build': $build, - '$iq': $iq, - '$msg': $msg, - '$pres': $pres, - 'Backbone': Backbone, - 'Promise': Promise, - 'Strophe': Strophe, - '_': _, - 'f': f, - 'b64_sha1': b64_sha1, - 'moment': moment, - 'sizzle': sizzle, - 'utils': u + _converse.connection.addHandler(handler, options.ns, name, options.type, options.id, options.from, options); } - }; - window.converse = converse; - window.dispatchEvent(new CustomEvent('converse-loaded')); - return converse; -}); + + }, + + /** + * Wait until a promise is resolved + * + * @method _converse.api.waitUntil + * @param {string} name The name of the promise + * @returns {Promise} + */ + 'waitUntil'(name) { + const promise = _converse.promises[name]; + + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(promise)) { + return null; + } + + return promise; + }, + + /** + * Allows you to send XML stanzas. + * + * @method _converse.api.send + * @example + * const msg = converse.env.$msg({ + * 'from': 'juliet@example.com/balcony', + * 'to': 'romeo@example.net', + * 'type':'chat' + * }); + * _converse.api.send(msg); + */ + 'send'(stanza) { + _converse.connection.send(stanza); + }, + + /** + * Send an IQ stanza and receive a promise + * + * @method _converse.api.sendIQ + * @returns {Promise} A promise which resolves when we receive a `result` stanza + * or is rejected when we receive an `error` stanza. + */ + 'sendIQ'(stanza) { + return new es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_2___default.a((resolve, reject) => { + _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT); + }); + } + +}; +/** + * ### The Public API + * + * This namespace contains public API methods which are are + * accessible on the global `converse` object. + * They are public, because any JavaScript in the + * page can call them. Public methods therefore don’t expose any sensitive + * or closured data. To do that, you’ll need to create a plugin, which has + * access to the private API method. + * + * @namespace converse + */ + +const converse = { + /** + * Public API method which initializes Converse. + * This method must always be called when using Converse. + * + * @memberOf converse + * @method initialize + * @param {object} config A map of [configuration-settings](https://conversejs.org/docs/html/configuration.html#configuration-settings). + * + * @example + * converse.initialize({ + * allow_otr: true, + * auto_list_rooms: false, + * auto_subscribe: false, + * bosh_service_url: 'https://bind.example.com', + * hide_muc_server: false, + * i18n: locales['en'], + * keepalive: true, + * play_sounds: true, + * prebind: false, + * show_controlbox_by_default: true, + * debug: false, + * roster_groups: true + * }); + */ + 'initialize'(settings, callback) { + return _converse.initialize(settings, callback); + }, + + /** + * Exposes methods for adding and removing plugins. You'll need to write a plugin + * if you want to have access to the private API methods defined further down below. + * + * For more information on plugins, read the documentation on [writing a plugin](/docs/html/plugin_development.html). + * + * @namespace plugins + * @memberOf converse + */ + 'plugins': { + /** Registers a new plugin. + * + * @method converse.plugins.add + * @param {string} name The name of the plugin + * @param {object} plugin The plugin object + * + * @example + * + * const plugin = { + * initialize: function () { + * // Gets called as soon as the plugin has been loaded. + * + * // Inside this method, you have access to the private + * // API via `_covnerse.api`. + * + * // The private _converse object contains the core logic + * // and data-structures of Converse. + * } + * } + * converse.plugins.add('myplugin', plugin); + */ + 'add'(name, plugin) { + plugin.__name__ = name; + + if (!_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(_converse.pluggable.plugins[name])) { + throw new TypeError(`Error: plugin with name "${name}" has already been ` + 'registered!'); + } else { + _converse.pluggable.plugins[name] = plugin; + } + } + + }, + + /** + * Utility methods and globals from bundled 3rd party libraries. + * @memberOf converse + * + * @property {function} converse.env.$build - Creates a Strophe.Builder, for creating stanza objects. + * @property {function} converse.env.$iq - Creates a Strophe.Builder with an element as the root. + * @property {function} converse.env.$msg - Creates a Strophe.Builder with an element as the root. + * @property {function} converse.env.$pres - Creates a Strophe.Builder with an element as the root. + * @property {object} converse.env.Backbone - The [Backbone](http://backbonejs.org) object used by Converse to create models and views. + * @property {function} converse.env.Promise - The Promise implementation used by Converse. + * @property {function} converse.env.Strophe - The [Strophe](http://strophe.im/strophejs) XMPP library used by Converse. + * @property {object} converse.env._ - The instance of [lodash](http://lodash.com) used by Converse. + * @property {function} converse.env.f - And instance of Lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods. + * @property {function} converse.env.b64_sha1 - Utility method from Strophe for creating base64 encoded sha1 hashes. + * @property {object} converse.env.moment - [Moment](https://momentjs.com) date manipulation library. + * @property {function} converse.env.sizzle - [Sizzle](https://sizzlejs.com) CSS selector engine. + * @property {object} converse.env.utils - Module containing common utility methods used by Converse. + */ + 'env': { + '$build': strophe_js__WEBPACK_IMPORTED_MODULE_0__["$build"], + '$iq': strophe_js__WEBPACK_IMPORTED_MODULE_0__["$iq"], + '$msg': strophe_js__WEBPACK_IMPORTED_MODULE_0__["$msg"], + '$pres': strophe_js__WEBPACK_IMPORTED_MODULE_0__["$pres"], + 'Backbone': _backbone_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a, + 'Promise': es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_2___default.a, + 'Strophe': strophe_js__WEBPACK_IMPORTED_MODULE_0__["Strophe"], + '_': _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, + 'f': _lodash_fp__WEBPACK_IMPORTED_MODULE_5___default.a, + 'b64_sha1': b64_sha1, + 'moment': moment__WEBPACK_IMPORTED_MODULE_7___default.a, + 'sizzle': sizzle__WEBPACK_IMPORTED_MODULE_10___default.a, + 'utils': _utils_core__WEBPACK_IMPORTED_MODULE_11__["default"] + } +}; +window.converse = converse; +window.dispatchEvent(new CustomEvent('converse-loaded')); +/* harmony default export */ __webpack_exports__["default"] = (converse); /***/ }), @@ -74136,751 +74394,751 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!****************************************!*\ !*** ./src/headless/converse-disco.js ***! \****************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(sizzle__WEBPACK_IMPORTED_MODULE_1__); +// Converse.js // http://conversejs.org // // Copyright (c) 2013-2018, the Converse developers // Licensed under the Mozilla Public License (MPLv2) /* This is a Converse plugin which add support for XEP-0030: Service Discovery */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, sizzle) { - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - $iq = _converse$env.$iq, - b64_sha1 = _converse$env.b64_sha1, - utils = _converse$env.utils, - _ = _converse$env._, - f = _converse$env.f; - converse.plugins.add('converse-disco', { - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. + + +const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq, + b64_sha1 = _converse$env.b64_sha1, + utils = _converse$env.utils, + _ = _converse$env._, + f = _converse$env.f; +_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-disco', { + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse; // Promises exposed by this plugin + + _converse.api.promises.add('discoInitialized'); + + _converse.DiscoEntity = Backbone.Model.extend({ + /* A Disco Entity is a JID addressable entity that can be queried + * for features. + * + * See XEP-0030: https://xmpp.org/extensions/xep-0030.html */ - const _converse = this._converse; // Promises exposed by this plugin + idAttribute: 'jid', - _converse.api.promises.add('discoInitialized'); + initialize() { + this.waitUntilFeaturesDiscovered = utils.getResolveablePromise(); + this.dataforms = new Backbone.Collection(); + this.dataforms.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.dataforms-{this.get('jid')}`)); + this.features = new Backbone.Collection(); + this.features.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.features-${this.get('jid')}`)); + this.features.on('add', this.onFeatureAdded, this); + this.fields = new Backbone.Collection(); + this.fields.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.fields-${this.get('jid')}`)); + this.fields.on('add', this.onFieldAdded, this); + this.identities = new Backbone.Collection(); + this.identities.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.identities-${this.get('jid')}`)); + this.fetchFeatures(); + this.items = new _converse.DiscoEntities(); + this.items.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.disco-items-${this.get('jid')}`)); + this.items.fetch(); + }, - _converse.DiscoEntity = Backbone.Model.extend({ - /* A Disco Entity is a JID addressable entity that can be queried - * for features. + getIdentity(category, type) { + /* Returns a Promise which resolves with a map indicating + * whether a given identity is provided. * - * See XEP-0030: https://xmpp.org/extensions/xep-0030.html + * Parameters: + * (String) category - The identity category + * (String) type - The identity type */ - idAttribute: 'jid', - - initialize() { - this.waitUntilFeaturesDiscovered = utils.getResolveablePromise(); - this.dataforms = new Backbone.Collection(); - this.dataforms.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.dataforms-{this.get('jid')}`)); - this.features = new Backbone.Collection(); - this.features.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.features-${this.get('jid')}`)); - this.features.on('add', this.onFeatureAdded, this); - this.fields = new Backbone.Collection(); - this.fields.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.fields-${this.get('jid')}`)); - this.fields.on('add', this.onFieldAdded, this); - this.identities = new Backbone.Collection(); - this.identities.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.identities-${this.get('jid')}`)); - this.fetchFeatures(); - this.items = new _converse.DiscoEntities(); - this.items.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.disco-items-${this.get('jid')}`)); - this.items.fetch(); - }, - - getIdentity(category, type) { - /* Returns a Promise which resolves with a map indicating - * whether a given identity is provided. - * - * Parameters: - * (String) category - The identity category - * (String) type - The identity type - */ - const entity = this; - return new Promise((resolve, reject) => { - function fulfillPromise() { - const model = entity.identities.findWhere({ - 'category': category, - 'type': type - }); - resolve(model); - } - - entity.waitUntilFeaturesDiscovered.then(fulfillPromise).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }); - }, - - hasFeature(feature) { - /* Returns a Promise which resolves with a map indicating - * whether a given feature is supported. - * - * Parameters: - * (String) feature - The feature that might be supported. - */ - const entity = this; - return new Promise((resolve, reject) => { - function fulfillPromise() { - if (entity.features.findWhere({ - 'var': feature - })) { - resolve(entity); - } else { - resolve(); - } - } - - entity.waitUntilFeaturesDiscovered.then(fulfillPromise).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }); - }, - - onFeatureAdded(feature) { - feature.entity = this; - - _converse.emit('serviceDiscovered', feature); - }, - - onFieldAdded(field) { - field.entity = this; - - _converse.emit('discoExtensionFieldDiscovered', field); - }, - - fetchFeatures() { - if (this.features.browserStorage.records.length === 0) { - this.queryInfo(); - } else { - this.features.fetch({ - add: true, - success: () => { - this.waitUntilFeaturesDiscovered.resolve(this); - this.trigger('featuresDiscovered'); - } - }); - this.identities.fetch({ - add: true + const entity = this; + return new Promise((resolve, reject) => { + function fulfillPromise() { + const model = entity.identities.findWhere({ + 'category': category, + 'type': type }); + resolve(model); } - }, - queryInfo() { - _converse.api.disco.info(this.get('jid'), null).then(stanza => this.onInfo(stanza)).catch(iq => { - this.waitUntilFeaturesDiscovered.resolve(this); + entity.waitUntilFeaturesDiscovered.then(fulfillPromise).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }); + }, - _converse.log(iq, Strophe.LogLevel.ERROR); - }); - }, - - onDiscoItems(stanza) { - _.each(sizzle(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"] item`, stanza), item => { - if (item.getAttribute("node")) { - // XXX: ignore nodes for now. - // See: https://xmpp.org/extensions/xep-0030.html#items-nodes - return; + hasFeature(feature) { + /* Returns a Promise which resolves with a map indicating + * whether a given feature is supported. + * + * Parameters: + * (String) feature - The feature that might be supported. + */ + const entity = this; + return new Promise((resolve, reject) => { + function fulfillPromise() { + if (entity.features.findWhere({ + 'var': feature + })) { + resolve(entity); + } else { + resolve(); } + } - const jid = item.getAttribute('jid'); + entity.waitUntilFeaturesDiscovered.then(fulfillPromise).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }); + }, - if (_.isUndefined(this.items.get(jid))) { - const entity = _converse.disco_entities.get(jid); + onFeatureAdded(feature) { + feature.entity = this; - if (entity) { - this.items.add(entity); - } else { - this.items.create({ - 'jid': jid - }); - } + _converse.emit('serviceDiscovered', feature); + }, + + onFieldAdded(field) { + field.entity = this; + + _converse.emit('discoExtensionFieldDiscovered', field); + }, + + fetchFeatures() { + if (this.features.browserStorage.records.length === 0) { + this.queryInfo(); + } else { + this.features.fetch({ + add: true, + success: () => { + this.waitUntilFeaturesDiscovered.resolve(this); + this.trigger('featuresDiscovered'); } }); - }, + this.identities.fetch({ + add: true + }); + } + }, - queryForItems() { - if (_.isEmpty(this.identities.where({ - 'category': 'server' - }))) { - // Don't fetch features and items if this is not a - // server or a conference component. + queryInfo() { + _converse.api.disco.info(this.get('jid'), null).then(stanza => this.onInfo(stanza)).catch(iq => { + this.waitUntilFeaturesDiscovered.resolve(this); + + _converse.log(iq, Strophe.LogLevel.ERROR); + }); + }, + + onDiscoItems(stanza) { + _.each(sizzle__WEBPACK_IMPORTED_MODULE_1___default()(`query[xmlns="${Strophe.NS.DISCO_ITEMS}"] item`, stanza), item => { + if (item.getAttribute("node")) { + // XXX: ignore nodes for now. + // See: https://xmpp.org/extensions/xep-0030.html#items-nodes return; } - _converse.api.disco.items(this.get('jid')).then(stanza => this.onDiscoItems(stanza)); - }, + const jid = item.getAttribute('jid'); - onInfo(stanza) { - _.forEach(stanza.querySelectorAll('identity'), identity => { - this.identities.create({ - 'category': identity.getAttribute('category'), - 'type': identity.getAttribute('type'), - 'name': identity.getAttribute('name') - }); - }); + if (_.isUndefined(this.items.get(jid))) { + const entity = _converse.disco_entities.get(jid); - _.each(sizzle(`x[type="result"][xmlns="${Strophe.NS.XFORM}"]`, stanza), form => { - const data = {}; - - _.each(form.querySelectorAll('field'), field => { - data[field.getAttribute('var')] = { - 'value': _.get(field.querySelector('value'), 'textContent'), - 'type': field.getAttribute('type') - }; - }); - - this.dataforms.create(data); - }); - - if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) { - this.queryForItems(); - } - - _.forEach(stanza.querySelectorAll('feature'), feature => { - this.features.create({ - 'var': feature.getAttribute('var'), - 'from': stanza.getAttribute('from') - }); - }); // XEP-0128 Service Discovery Extensions - - - _.forEach(sizzle('x[type="result"][xmlns="jabber:x:data"] field', stanza), field => { - this.fields.create({ - 'var': field.getAttribute('var'), - 'value': _.get(field.querySelector('value'), 'textContent'), - 'from': stanza.getAttribute('from') - }); - }); - - this.waitUntilFeaturesDiscovered.resolve(this); - this.trigger('featuresDiscovered'); - } - - }); - _converse.DiscoEntities = Backbone.Collection.extend({ - model: _converse.DiscoEntity, - - fetchEntities() { - return new Promise((resolve, reject) => { - this.fetch({ - add: true, - success: resolve, - - error() { - reject(new Error("Could not fetch disco entities")); - } - - }); - }); - } - - }); - - function addClientFeatures() { - // See http://xmpp.org/registrar/disco-categories.html - _converse.api.disco.own.identities.add('client', 'web', 'Converse'); - - _converse.api.disco.own.features.add(Strophe.NS.BOSH); - - _converse.api.disco.own.features.add(Strophe.NS.CHATSTATES); - - _converse.api.disco.own.features.add(Strophe.NS.DISCO_INFO); - - _converse.api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support - - - if (_converse.message_carbons) { - _converse.api.disco.own.features.add(Strophe.NS.CARBONS); - } - - _converse.emit('addClientFeatures'); - - return this; - } - - function initStreamFeatures() { - _converse.stream_features = new Backbone.Collection(); - _converse.stream_features.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.stream-features-${_converse.bare_jid}`)); - - _converse.stream_features.fetch({ - success(collection) { - if (collection.length === 0 && _converse.connection.features) { - _.forEach(_converse.connection.features.childNodes, feature => { - _converse.stream_features.create({ - 'name': feature.nodeName, - 'xmlns': feature.getAttribute('xmlns') - }); + if (entity) { + this.items.add(entity); + } else { + this.items.create({ + 'jid': jid }); } } + }); + }, + queryForItems() { + if (_.isEmpty(this.identities.where({ + 'category': 'server' + }))) { + // Don't fetch features and items if this is not a + // server or a conference component. + return; + } + + _converse.api.disco.items(this.get('jid')).then(stanza => this.onDiscoItems(stanza)); + }, + + onInfo(stanza) { + _.forEach(stanza.querySelectorAll('identity'), identity => { + this.identities.create({ + 'category': identity.getAttribute('category'), + 'type': identity.getAttribute('type'), + 'name': identity.getAttribute('name') + }); }); - _converse.emit('streamFeaturesAdded'); - } + _.each(sizzle__WEBPACK_IMPORTED_MODULE_1___default()(`x[type="result"][xmlns="${Strophe.NS.XFORM}"]`, stanza), form => { + const data = {}; - function initializeDisco() { - addClientFeatures(); - - _converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); - - _converse.disco_entities = new _converse.DiscoEntities(); - _converse.disco_entities.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)); - - _converse.disco_entities.fetchEntities().then(collection => { - if (collection.length === 0 || !collection.get(_converse.domain)) { - // If we don't have an entity for our own XMPP server, - // create one. - _converse.disco_entities.create({ - 'jid': _converse.domain - }); - } - - _converse.emit('discoInitialized'); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - - _converse.api.listen.on('sessionInitialized', initStreamFeatures); - - _converse.api.listen.on('reconnected', initializeDisco); - - _converse.api.listen.on('connected', initializeDisco); - - _converse.api.listen.on('beforeTearDown', () => { - if (_converse.disco_entities) { - _converse.disco_entities.each(entity => { - entity.features.reset(); - - entity.features.browserStorage._clear(); + _.each(form.querySelectorAll('field'), field => { + data[field.getAttribute('var')] = { + 'value': _.get(field.querySelector('value'), 'textContent'), + 'type': field.getAttribute('type') + }; }); - _converse.disco_entities.reset(); + this.dataforms.create(data); + }); - _converse.disco_entities.browserStorage._clear(); + if (stanza.querySelector(`feature[var="${Strophe.NS.DISCO_ITEMS}"]`)) { + this.queryForItems(); } + + _.forEach(stanza.querySelectorAll('feature'), feature => { + this.features.create({ + 'var': feature.getAttribute('var'), + 'from': stanza.getAttribute('from') + }); + }); // XEP-0128 Service Discovery Extensions + + + _.forEach(sizzle__WEBPACK_IMPORTED_MODULE_1___default()('x[type="result"][xmlns="jabber:x:data"] field', stanza), field => { + this.fields.create({ + 'var': field.getAttribute('var'), + 'value': _.get(field.querySelector('value'), 'textContent'), + 'from': stanza.getAttribute('from') + }); + }); + + this.waitUntilFeaturesDiscovered.resolve(this); + this.trigger('featuresDiscovered'); + } + + }); + _converse.DiscoEntities = Backbone.Collection.extend({ + model: _converse.DiscoEntity, + + fetchEntities() { + return new Promise((resolve, reject) => { + this.fetch({ + add: true, + success: resolve, + + error() { + reject(new Error("Could not fetch disco entities")); + } + + }); + }); + } + + }); + + function addClientFeatures() { + // See http://xmpp.org/registrar/disco-categories.html + _converse.api.disco.own.identities.add('client', 'web', 'Converse'); + + _converse.api.disco.own.features.add(Strophe.NS.BOSH); + + _converse.api.disco.own.features.add(Strophe.NS.CHATSTATES); + + _converse.api.disco.own.features.add(Strophe.NS.DISCO_INFO); + + _converse.api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support + + + if (_converse.message_carbons) { + _converse.api.disco.own.features.add(Strophe.NS.CARBONS); + } + + _converse.emit('addClientFeatures'); + + return this; + } + + function initStreamFeatures() { + _converse.stream_features = new Backbone.Collection(); + _converse.stream_features.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.stream-features-${_converse.bare_jid}`)); + + _converse.stream_features.fetch({ + success(collection) { + if (collection.length === 0 && _converse.connection.features) { + _.forEach(_converse.connection.features.childNodes, feature => { + _converse.stream_features.create({ + 'name': feature.nodeName, + 'xmlns': feature.getAttribute('xmlns') + }); + }); + } + } + }); - const plugin = this; - plugin._identities = []; - plugin._features = []; + _converse.emit('streamFeaturesAdded'); + } - function onDiscoInfoRequest(stanza) { - const node = stanza.getElementsByTagName('query')[0].getAttribute('node'); + function initializeDisco() { + addClientFeatures(); + + _converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); + + _converse.disco_entities = new _converse.DiscoEntities(); + _converse.disco_entities.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)); + + _converse.disco_entities.fetchEntities().then(collection => { + if (collection.length === 0 || !collection.get(_converse.domain)) { + // If we don't have an entity for our own XMPP server, + // create one. + _converse.disco_entities.create({ + 'jid': _converse.domain + }); + } + + _converse.emit('discoInitialized'); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + + _converse.api.listen.on('sessionInitialized', initStreamFeatures); + + _converse.api.listen.on('reconnected', initializeDisco); + + _converse.api.listen.on('connected', initializeDisco); + + _converse.api.listen.on('beforeTearDown', () => { + if (_converse.disco_entities) { + _converse.disco_entities.each(entity => { + entity.features.reset(); + + entity.features.browserStorage._clear(); + }); + + _converse.disco_entities.reset(); + + _converse.disco_entities.browserStorage._clear(); + } + }); + + const plugin = this; + plugin._identities = []; + plugin._features = []; + + function onDiscoInfoRequest(stanza) { + const node = stanza.getElementsByTagName('query')[0].getAttribute('node'); + const attrs = { + xmlns: Strophe.NS.DISCO_INFO + }; + + if (node) { + attrs.node = node; + } + + const iqresult = $iq({ + 'type': 'result', + 'id': stanza.getAttribute('id') + }); + const from = stanza.getAttribute('from'); + + if (from !== null) { + iqresult.attrs({ + 'to': from + }); + } + + iqresult.c('query', attrs); + + _.each(plugin._identities, identity => { const attrs = { - xmlns: Strophe.NS.DISCO_INFO + 'category': identity.category, + 'type': identity.type }; - if (node) { - attrs.node = node; + if (identity.name) { + attrs.name = identity.name; } - const iqresult = $iq({ - 'type': 'result', - 'id': stanza.getAttribute('id') - }); - const from = stanza.getAttribute('from'); - - if (from !== null) { - iqresult.attrs({ - 'to': from - }); + if (identity.lang) { + attrs['xml:lang'] = identity.lang; } - iqresult.c('query', attrs); + iqresult.c('identity', attrs).up(); + }); - _.each(plugin._identities, identity => { - const attrs = { - 'category': identity.category, - 'type': identity.type - }; + _.each(plugin._features, feature => { + iqresult.c('feature', { + 'var': feature + }).up(); + }); - if (identity.name) { - attrs.name = identity.name; - } + _converse.connection.send(iqresult.tree()); - if (identity.lang) { - attrs['xml:lang'] = identity.lang; - } + return true; + } - iqresult.c('identity', attrs).up(); - }); - - _.each(plugin._features, feature => { - iqresult.c('feature', { - 'var': feature - }).up(); - }); - - _converse.connection.send(iqresult.tree()); - - return true; - } - - _.extend(_converse.api, { + _.extend(_converse.api, { + /** + * The XEP-0030 service discovery API + * + * This API lets you discover information about entities on the + * XMPP network. + * + * @namespace _converse.api.disco + * @memberOf _converse.api + */ + 'disco': { /** - * The XEP-0030 service discovery API - * - * This API lets you discover information about entities on the - * XMPP network. - * - * @namespace _converse.api.disco - * @memberOf _converse.api + * @namespace _converse.api.disco.stream + * @memberOf _converse.api.disco */ - 'disco': { + 'stream': { /** - * @namespace _converse.api.disco.stream - * @memberOf _converse.api.disco + * @method _converse.api.disco.stream.getFeature + * @param {String} name The feature name + * @param {String} xmlns The XML namespace + * @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') */ - 'stream': { - /** - * @method _converse.api.disco.stream.getFeature - * @param {String} name The feature name - * @param {String} xmlns The XML namespace - * @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') - */ - 'getFeature': function getFeature(name, xmlns) { - if (_.isNil(name) || _.isNil(xmlns)) { - throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature"); - } - - return _converse.stream_features.findWhere({ - 'name': name, - 'xmlns': xmlns - }); + 'getFeature': function getFeature(name, xmlns) { + if (_.isNil(name) || _.isNil(xmlns)) { + throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature"); } - }, + return _converse.stream_features.findWhere({ + 'name': name, + 'xmlns': xmlns + }); + } + }, + + /** + * @namespace _converse.api.disco.own + * @memberOf _converse.api.disco + */ + 'own': { /** - * @namespace _converse.api.disco.own - * @memberOf _converse.api.disco + * @namespace _converse.api.disco.own.identities + * @memberOf _converse.api.disco.own */ - 'own': { + 'identities': { /** - * @namespace _converse.api.disco.own.identities - * @memberOf _converse.api.disco.own + * Lets you add new identities for this client (i.e. instance of Converse) + * @method _converse.api.disco.own.identities.add + * + * @param {String} category - server, client, gateway, directory, etc. + * @param {String} type - phone, pc, web, etc. + * @param {String} name - "Converse" + * @param {String} lang - en, el, de, etc. + * + * @example _converse.api.disco.own.identities.clear(); */ - 'identities': { - /** - * Lets you add new identities for this client (i.e. instance of Converse) - * @method _converse.api.disco.own.identities.add - * - * @param {String} category - server, client, gateway, directory, etc. - * @param {String} type - phone, pc, web, etc. - * @param {String} name - "Converse" - * @param {String} lang - en, el, de, etc. - * - * @example _converse.api.disco.own.identities.clear(); - */ - add(category, type, name, lang) { - for (var i = 0; i < plugin._identities.length; i++) { - if (plugin._identities[i].category == category && plugin._identities[i].type == type && plugin._identities[i].name == name && plugin._identities[i].lang == lang) { - return false; - } + add(category, type, name, lang) { + for (var i = 0; i < plugin._identities.length; i++) { + if (plugin._identities[i].category == category && plugin._identities[i].type == type && plugin._identities[i].name == name && plugin._identities[i].lang == lang) { + return false; } - - plugin._identities.push({ - category: category, - type: type, - name: name, - lang: lang - }); - }, - - /** - * Clears all previously registered identities. - * @method _converse.api.disco.own.identities.clear - * @example _converse.api.disco.own.identities.clear(); - */ - clear() { - plugin._identities = []; - }, - - /** - * Returns all of the identities registered for this client - * (i.e. instance of Converse). - * @method _converse.api.disco.identities.get - * @example const identities = _converse.api.disco.own.identities.get(); - */ - get() { - return plugin._identities; } + plugin._identities.push({ + category: category, + type: type, + name: name, + lang: lang + }); }, /** - * @namespace _converse.api.disco.own.features - * @memberOf _converse.api.disco.own + * Clears all previously registered identities. + * @method _converse.api.disco.own.identities.clear + * @example _converse.api.disco.own.identities.clear(); */ - 'features': { - /** - * Lets you register new disco features for this client (i.e. instance of Converse) - * @method _converse.api.disco.own.features.add - * @param {String} name - e.g. http://jabber.org/protocol/caps - * @example _converse.api.disco.own.features.add("http://jabber.org/protocol/caps"); - */ - add(name) { - for (var i = 0; i < plugin._features.length; i++) { - if (plugin._features[i] == name) { - return false; - } + clear() { + plugin._identities = []; + }, + + /** + * Returns all of the identities registered for this client + * (i.e. instance of Converse). + * @method _converse.api.disco.identities.get + * @example const identities = _converse.api.disco.own.identities.get(); + */ + get() { + return plugin._identities; + } + + }, + + /** + * @namespace _converse.api.disco.own.features + * @memberOf _converse.api.disco.own + */ + 'features': { + /** + * Lets you register new disco features for this client (i.e. instance of Converse) + * @method _converse.api.disco.own.features.add + * @param {String} name - e.g. http://jabber.org/protocol/caps + * @example _converse.api.disco.own.features.add("http://jabber.org/protocol/caps"); + */ + add(name) { + for (var i = 0; i < plugin._features.length; i++) { + if (plugin._features[i] == name) { + return false; } - - plugin._features.push(name); - }, - - /** - * Clears all previously registered features. - * @method _converse.api.disco.own.features.clear - * @example _converse.api.disco.own.features.clear(); - */ - clear() { - plugin._features = []; - }, - - /** - * Returns all of the features registered for this client (i.e. instance of Converse). - * @method _converse.api.disco.own.features.get - * @example const features = _converse.api.disco.own.features.get(); - */ - get() { - return plugin._features; } - } - }, + plugin._features.push(name); + }, - /** - * Query for information about an XMPP entity - * - * @method _converse.api.disco.info - * @param {string} jid The Jabber ID of the entity to query - * @param {string} [node] A specific node identifier associated with the JID - * @returns {promise} Promise which resolves once we have a result from the server. - */ - 'info'(jid, node) { - const attrs = { - xmlns: Strophe.NS.DISCO_INFO - }; - - if (node) { - attrs.node = node; - } - - const info = $iq({ - 'from': _converse.connection.jid, - 'to': jid, - 'type': 'get' - }).c('query', attrs); - return _converse.api.sendIQ(info); - }, - - /** - * Query for items associated with an XMPP entity - * - * @method _converse.api.disco.items - * @param {string} jid The Jabber ID of the entity to query for items - * @param {string} [node] A specific node identifier associated with the JID - * @returns {promise} Promise which resolves once we have a result from the server. - */ - 'items'(jid, node) { - const attrs = { - 'xmlns': Strophe.NS.DISCO_ITEMS - }; - - if (node) { - attrs.node = node; - } - - return _converse.api.sendIQ($iq({ - 'from': _converse.connection.jid, - 'to': jid, - 'type': 'get' - }).c('query', attrs)); - }, - - /** - * Namespace for methods associated with disco entities - * - * @namespace _converse.api.disco.entities - * @memberOf _converse.api.disco - */ - 'entities': { /** - * Get the the corresponding `DiscoEntity` instance. - * - * @method _converse.api.disco.entities.get - * @param {string} jid The Jabber ID of the entity - * @param {boolean} [create] Whether the entity should be created if it doesn't exist. - * @example _converse.api.disco.entities.get(jid); + * Clears all previously registered features. + * @method _converse.api.disco.own.features.clear + * @example _converse.api.disco.own.features.clear(); */ - 'get'(jid, create = false) { - return _converse.api.waitUntil('discoInitialized').then(() => { - if (_.isNil(jid)) { - return _converse.disco_entities; - } + clear() { + plugin._features = []; + }, - const entity = _converse.disco_entities.get(jid); - - if (entity || !create) { - return entity; - } - - return _converse.disco_entities.create({ - 'jid': jid - }); - }); + /** + * Returns all of the features registered for this client (i.e. instance of Converse). + * @method _converse.api.disco.own.features.get + * @example const features = _converse.api.disco.own.features.get(); + */ + get() { + return plugin._features; } - }, + } + }, - /** - * Used to determine whether an entity supports a given feature. - * - * @method _converse.api.disco.supports - * @param {string} feature The feature that might be - * supported. In the XML stanza, this is the `var` - * attribute of the `` element. For - * example: `http://jabber.org/protocol/muc` - * @param {string} jid The JID of the entity - * (and its associated items) which should be queried - * @returns {promise} A promise which resolves with a list containing - * _converse.Entity instances representing the entity - * itself or those items associated with the entity if - * they support the given feature. - * - * @example - * _converse.api.disco.supports(Strophe.NS.MAM, _converse.bare_jid) - * .then(value => { - * // `value` is a map with two keys, `supported` and `feature`. - * if (value.supported) { - * // The feature is supported - * } else { - * // The feature is not supported - * } - * }).catch(() => { - * _converse.log( - * "Error or timeout while checking for feature support", - * Strophe.LogLevel.ERROR - * ); - * }); - */ - 'supports'(feature, jid) { - if (_.isNil(jid)) { - throw new TypeError('api.disco.supports: You need to provide an entity JID'); - } + /** + * Query for information about an XMPP entity + * + * @method _converse.api.disco.info + * @param {string} jid The Jabber ID of the entity to query + * @param {string} [node] A specific node identifier associated with the JID + * @returns {promise} Promise which resolves once we have a result from the server. + */ + 'info'(jid, node) { + const attrs = { + xmlns: Strophe.NS.DISCO_INFO + }; - return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => entity.waitUntilFeaturesDiscovered).then(entity => { - const promises = _.concat(entity.items.map(item => item.hasFeature(feature)), entity.hasFeature(feature)); - - return Promise.all(promises); - }).then(result => f.filter(f.isObject, result)); - }, - - /** - * Refresh the features (and fields and identities) associated with a - * disco entity by refetching them from the server - * - * @method _converse.api.disco.refreshFeatures - * @param {string} jid The JID of the entity whose features are refreshed. - * @returns {promise} A promise which resolves once the features have been refreshed - * @example - * await _converse.api.disco.refreshFeatures('room@conference.example.org'); - */ - 'refreshFeatures'(jid) { - if (_.isNil(jid)) { - throw new TypeError('api.disco.refreshFeatures: You need to provide an entity JID'); - } - - return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => { - entity.features.reset(); - entity.fields.reset(); - entity.identities.reset(); - entity.waitUntilFeaturesDiscovered = utils.getResolveablePromise(); - entity.queryInfo(); - return entity.waitUntilFeaturesDiscovered; - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - /** - * Return all the features associated with a disco entity - * - * @method _converse.api.disco.getFeatures - * @param {string} jid The JID of the entity whose features are returned. - * @returns {promise} A promise which resolves with the returned features - * @example - * const features = await _converse.api.disco.getFeatures('room@conference.example.org'); - */ - 'getFeatures'(jid) { - if (_.isNil(jid)) { - throw new TypeError('api.disco.getFeatures: You need to provide an entity JID'); - } - - return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => entity.waitUntilFeaturesDiscovered).then(entity => entity.features).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - /** - * Return all the service discovery extensions fields - * associated with an entity. - * - * See [XEP-0129: Service Discovery Extensions](https://xmpp.org/extensions/xep-0128.html) - * - * @method _converse.api.disco.getFields - * @param {string} jid The JID of the entity whose fields are returned. - * @example - * const fields = await _converse.api.disco.getFields('room@conference.example.org'); - */ - 'getFields'(jid) { - if (_.isNil(jid)) { - throw new TypeError('api.disco.getFields: You need to provide an entity JID'); - } - - return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => entity.waitUntilFeaturesDiscovered).then(entity => entity.fields).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - /** - * Get the identity (with the given category and type) for a given disco entity. - * - * For example, when determining support for PEP (personal eventing protocol), you - * want to know whether the user's own JID has an identity with - * `category='pubsub'` and `type='pep'` as explained in this section of - * XEP-0163: https://xmpp.org/extensions/xep-0163.html#support - * - * @method _converse.api.disco.getIdentity - * @param {string} The identity category. - * In the XML stanza, this is the `category` - * attribute of the `` element. - * For example: 'pubsub' - * @param {string} type The identity type. - * In the XML stanza, this is the `type` - * attribute of the `` element. - * For example: 'pep' - * @param {string} jid The JID of the entity which might have the identity - * @returns {promise} A promise which resolves with a map indicating - * whether an identity with a given type is provided by the entity. - * @example - * _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then( - * function (identity) { - * if (_.isNil(identity)) { - * // The entity DOES NOT have this identity - * } else { - * // The entity DOES have this identity - * } - * } - * ).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - */ - 'getIdentity'(category, type, jid) { - return _converse.api.disco.entities.get(jid, true).then(e => e.getIdentity(category, type)); + if (node) { + attrs.node = node; } - } - }); - } + const info = $iq({ + 'from': _converse.connection.jid, + 'to': jid, + 'type': 'get' + }).c('query', attrs); + return _converse.api.sendIQ(info); + }, + + /** + * Query for items associated with an XMPP entity + * + * @method _converse.api.disco.items + * @param {string} jid The Jabber ID of the entity to query for items + * @param {string} [node] A specific node identifier associated with the JID + * @returns {promise} Promise which resolves once we have a result from the server. + */ + 'items'(jid, node) { + const attrs = { + 'xmlns': Strophe.NS.DISCO_ITEMS + }; + + if (node) { + attrs.node = node; + } + + return _converse.api.sendIQ($iq({ + 'from': _converse.connection.jid, + 'to': jid, + 'type': 'get' + }).c('query', attrs)); + }, + + /** + * Namespace for methods associated with disco entities + * + * @namespace _converse.api.disco.entities + * @memberOf _converse.api.disco + */ + 'entities': { + /** + * Get the the corresponding `DiscoEntity` instance. + * + * @method _converse.api.disco.entities.get + * @param {string} jid The Jabber ID of the entity + * @param {boolean} [create] Whether the entity should be created if it doesn't exist. + * @example _converse.api.disco.entities.get(jid); + */ + 'get'(jid, create = false) { + return _converse.api.waitUntil('discoInitialized').then(() => { + if (_.isNil(jid)) { + return _converse.disco_entities; + } + + const entity = _converse.disco_entities.get(jid); + + if (entity || !create) { + return entity; + } + + return _converse.disco_entities.create({ + 'jid': jid + }); + }); + } + + }, + + /** + * Used to determine whether an entity supports a given feature. + * + * @method _converse.api.disco.supports + * @param {string} feature The feature that might be + * supported. In the XML stanza, this is the `var` + * attribute of the `` element. For + * example: `http://jabber.org/protocol/muc` + * @param {string} jid The JID of the entity + * (and its associated items) which should be queried + * @returns {promise} A promise which resolves with a list containing + * _converse.Entity instances representing the entity + * itself or those items associated with the entity if + * they support the given feature. + * + * @example + * _converse.api.disco.supports(Strophe.NS.MAM, _converse.bare_jid) + * .then(value => { + * // `value` is a map with two keys, `supported` and `feature`. + * if (value.supported) { + * // The feature is supported + * } else { + * // The feature is not supported + * } + * }).catch(() => { + * _converse.log( + * "Error or timeout while checking for feature support", + * Strophe.LogLevel.ERROR + * ); + * }); + */ + 'supports'(feature, jid) { + if (_.isNil(jid)) { + throw new TypeError('api.disco.supports: You need to provide an entity JID'); + } + + return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => entity.waitUntilFeaturesDiscovered).then(entity => { + const promises = _.concat(entity.items.map(item => item.hasFeature(feature)), entity.hasFeature(feature)); + + return Promise.all(promises); + }).then(result => f.filter(f.isObject, result)); + }, + + /** + * Refresh the features (and fields and identities) associated with a + * disco entity by refetching them from the server + * + * @method _converse.api.disco.refreshFeatures + * @param {string} jid The JID of the entity whose features are refreshed. + * @returns {promise} A promise which resolves once the features have been refreshed + * @example + * await _converse.api.disco.refreshFeatures('room@conference.example.org'); + */ + 'refreshFeatures'(jid) { + if (_.isNil(jid)) { + throw new TypeError('api.disco.refreshFeatures: You need to provide an entity JID'); + } + + return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => { + entity.features.reset(); + entity.fields.reset(); + entity.identities.reset(); + entity.waitUntilFeaturesDiscovered = utils.getResolveablePromise(); + entity.queryInfo(); + return entity.waitUntilFeaturesDiscovered; + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + /** + * Return all the features associated with a disco entity + * + * @method _converse.api.disco.getFeatures + * @param {string} jid The JID of the entity whose features are returned. + * @returns {promise} A promise which resolves with the returned features + * @example + * const features = await _converse.api.disco.getFeatures('room@conference.example.org'); + */ + 'getFeatures'(jid) { + if (_.isNil(jid)) { + throw new TypeError('api.disco.getFeatures: You need to provide an entity JID'); + } + + return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => entity.waitUntilFeaturesDiscovered).then(entity => entity.features).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + /** + * Return all the service discovery extensions fields + * associated with an entity. + * + * See [XEP-0129: Service Discovery Extensions](https://xmpp.org/extensions/xep-0128.html) + * + * @method _converse.api.disco.getFields + * @param {string} jid The JID of the entity whose fields are returned. + * @example + * const fields = await _converse.api.disco.getFields('room@conference.example.org'); + */ + 'getFields'(jid) { + if (_.isNil(jid)) { + throw new TypeError('api.disco.getFields: You need to provide an entity JID'); + } + + return _converse.api.waitUntil('discoInitialized').then(() => _converse.api.disco.entities.get(jid, true)).then(entity => entity.waitUntilFeaturesDiscovered).then(entity => entity.fields).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + /** + * Get the identity (with the given category and type) for a given disco entity. + * + * For example, when determining support for PEP (personal eventing protocol), you + * want to know whether the user's own JID has an identity with + * `category='pubsub'` and `type='pep'` as explained in this section of + * XEP-0163: https://xmpp.org/extensions/xep-0163.html#support + * + * @method _converse.api.disco.getIdentity + * @param {string} The identity category. + * In the XML stanza, this is the `category` + * attribute of the `` element. + * For example: 'pubsub' + * @param {string} type The identity type. + * In the XML stanza, this is the `type` + * attribute of the `` element. + * For example: 'pep' + * @param {string} jid The JID of the entity which might have the identity + * @returns {promise} A promise which resolves with a map indicating + * whether an identity with a given type is provided by the entity. + * @example + * _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid).then( + * function (identity) { + * if (_.isNil(identity)) { + * // The entity DOES NOT have this identity + * } else { + * // The entity DOES have this identity + * } + * } + * ).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + */ + 'getIdentity'(category, type, jid) { + return _converse.api.disco.entities.get(jid, true).then(e => e.getIdentity(category, type)); + } + + } + }); + } - }); }); /***/ }), @@ -74889,10 +75147,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**************************************!*\ !*** ./src/headless/converse-mam.js ***! \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_disco__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./converse-disco */ "./src/headless/converse-disco.js"); +/* harmony import */ var strophejs_plugin_rsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! strophejs-plugin-rsm */ "./node_modules/strophejs-plugin-rsm/lib/strophe.rsm.js"); +/* harmony import */ var strophejs_plugin_rsm__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(strophejs_plugin_rsm__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _converse_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(sizzle__WEBPACK_IMPORTED_MODULE_3__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand @@ -74901,660 +75167,655 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*global define */ // XEP-0059 Result Set Management -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"), __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! ./converse-disco */ "./src/headless/converse-disco.js"), __webpack_require__(/*! strophejs-plugin-rsm */ "./node_modules/strophejs-plugin-rsm/lib/strophe.rsm.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (sizzle, converse) { - "use strict"; - - const CHATROOMS_TYPE = 'chatroom'; - const _converse$env = converse.env, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - $iq = _converse$env.$iq, - _ = _converse$env._, - moment = _converse$env.moment; - const u = converse.env.utils; - const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management - - const MAM_ATTRIBUTES = ['with', 'start', 'end']; - - function getMessageArchiveID(stanza) { - // See https://xmpp.org/extensions/xep-0313.html#results - // - // The result messages MUST contain a element with an 'id' - // attribute that gives the current message's archive UID - const result = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); - - if (!_.isUndefined(result)) { - return result.getAttribute('id'); - } // See: https://xmpp.org/extensions/xep-0313.html#archives_id - const stanza_id = sizzle(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop(); - if (!_.isUndefined(stanza_id)) { - return stanza_id.getAttribute('id'); - } + +const CHATROOMS_TYPE = 'chatroom'; +const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + $iq = _converse$env.$iq, + _ = _converse$env._, + moment = _converse$env.moment; +const u = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.utils; +const RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management + +const MAM_ATTRIBUTES = ['with', 'start', 'end']; + +function getMessageArchiveID(stanza) { + // See https://xmpp.org/extensions/xep-0313.html#results + // + // The result messages MUST contain a element with an 'id' + // attribute that gives the current message's archive UID + const result = sizzle__WEBPACK_IMPORTED_MODULE_3___default()(`result[xmlns="${Strophe.NS.MAM}"]`, stanza).pop(); + + if (!_.isUndefined(result)) { + return result.getAttribute('id'); + } // See: https://xmpp.org/extensions/xep-0313.html#archives_id + + + const stanza_id = sizzle__WEBPACK_IMPORTED_MODULE_3___default()(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza).pop(); + + if (!_.isUndefined(stanza_id)) { + return stanza_id.getAttribute('id'); + } +} + +function queryForArchivedMessages(_converse, options, callback, errback) { + /* Internal function, called by the "archive.query" API method. + */ + let date; + + if (_.isFunction(options)) { + callback = options; + errback = callback; + options = null; } - function queryForArchivedMessages(_converse, options, callback, errback) { - /* Internal function, called by the "archive.query" API method. - */ - let date; + const queryid = _converse.connection.getUniqueId(); - if (_.isFunction(options)) { - callback = options; - errback = callback; - options = null; + const attrs = { + 'type': 'set' + }; + + if (options && options.groupchat) { + if (!options['with']) { + // eslint-disable-line dot-notation + throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.'); } - const queryid = _converse.connection.getUniqueId(); + attrs.to = options['with']; // eslint-disable-line dot-notation + } - const attrs = { - 'type': 'set' - }; + const stanza = $iq(attrs).c('query', { + 'xmlns': Strophe.NS.MAM, + 'queryid': queryid + }); - if (options && options.groupchat) { - if (!options['with']) { - // eslint-disable-line dot-notation - throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.'); + if (options) { + stanza.c('x', { + 'xmlns': Strophe.NS.XFORM, + 'type': 'submit' + }).c('field', { + 'var': 'FORM_TYPE', + 'type': 'hidden' + }).c('value').t(Strophe.NS.MAM).up().up(); + + if (options['with'] && !options.groupchat) { + // eslint-disable-line dot-notation + stanza.c('field', { + 'var': 'with' + }).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation + } + + _.each(['start', 'end'], function (t) { + if (options[t]) { + date = moment(options[t]); + + if (date.isValid()) { + stanza.c('field', { + 'var': t + }).c('value').t(date.format()).up().up(); + } else { + throw new TypeError(`archive.query: invalid date provided for: ${t}`); + } } - - attrs.to = options['with']; // eslint-disable-line dot-notation - } - - const stanza = $iq(attrs).c('query', { - 'xmlns': Strophe.NS.MAM, - 'queryid': queryid }); - if (options) { - stanza.c('x', { - 'xmlns': Strophe.NS.XFORM, - 'type': 'submit' - }).c('field', { - 'var': 'FORM_TYPE', - 'type': 'hidden' - }).c('value').t(Strophe.NS.MAM).up().up(); + stanza.up(); - if (options['with'] && !options.groupchat) { - // eslint-disable-line dot-notation - stanza.c('field', { - 'var': 'with' - }).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation - } - - _.each(['start', 'end'], function (t) { - if (options[t]) { - date = moment(options[t]); - - if (date.isValid()) { - stanza.c('field', { - 'var': t - }).c('value').t(date.format()).up().up(); - } else { - throw new TypeError(`archive.query: invalid date provided for: ${t}`); - } - } - }); - - stanza.up(); - - if (options instanceof Strophe.RSM) { - stanza.cnode(options.toXML()); - } else if (_.intersection(RSM_ATTRIBUTES, _.keys(options)).length) { - stanza.cnode(new Strophe.RSM(options).toXML()); - } + if (options instanceof Strophe.RSM) { + stanza.cnode(options.toXML()); + } else if (_.intersection(RSM_ATTRIBUTES, _.keys(options)).length) { + stanza.cnode(new Strophe.RSM(options).toXML()); } - - const messages = []; - - const message_handler = _converse.connection.addHandler(message => { - if (options.groupchat && message.getAttribute('from') !== options['with']) { - // eslint-disable-line dot-notation - return true; - } - - const result = message.querySelector('result'); - - if (!_.isNull(result) && result.getAttribute('queryid') === queryid) { - messages.push(message); - } - - return true; - }, Strophe.NS.MAM); - - _converse.connection.sendIQ(stanza, function (iq) { - _converse.connection.deleteHandler(message_handler); - - if (_.isFunction(callback)) { - const set = iq.querySelector('set'); - let rsm; - - if (!_.isUndefined(set)) { - rsm = new Strophe.RSM({ - xml: set - }); - - _.extend(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max']))); - } - - callback(messages, rsm); - } - }, function () { - _converse.connection.deleteHandler(message_handler); - - if (_.isFunction(errback)) { - errback.apply(this, arguments); - } - }, _converse.message_archiving_timeout); } - converse.plugins.add('converse-mam', { - dependencies: ['converse-chatview', 'converse-muc', 'converse-muc-views'], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - ChatBox: { - getMessageAttributesFromStanza(message, original_stanza) { - function _process(attrs) { - const archive_id = getMessageArchiveID(original_stanza); + const messages = []; - if (archive_id) { - attrs.archive_id = archive_id; - } + const message_handler = _converse.connection.addHandler(message => { + if (options.groupchat && message.getAttribute('from') !== options['with']) { + // eslint-disable-line dot-notation + return true; + } - return attrs; - } + const result = message.querySelector('result'); - const result = this.__super__.getMessageAttributesFromStanza.apply(this, arguments); + if (!_.isNull(result) && result.getAttribute('queryid') === queryid) { + messages.push(message); + } - if (result instanceof Promise) { - return new Promise((resolve, reject) => result.then(attrs => resolve(_process(attrs))).catch(reject)); - } else { - return _process(result); - } - } + return true; + }, Strophe.NS.MAM); - }, - ChatBoxView: { - render() { - const result = this.__super__.render.apply(this, arguments); + _converse.connection.sendIQ(stanza, function (iq) { + _converse.connection.deleteHandler(message_handler); - if (!this.disable_mam) { - this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100)); - } + if (_.isFunction(callback)) { + const set = iq.querySelector('set'); + let rsm; - return result; - }, + if (!_.isUndefined(set)) { + rsm = new Strophe.RSM({ + xml: set + }); - fetchNewestMessages() { - /* Fetches messages that might have been archived *after* - * the last archived message in our local cache. - */ - if (this.disable_mam) { - return; - } + _.extend(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max']))); + } - const _converse = this.__super__._converse, - most_recent_msg = u.getMostRecentMessage(this.model); + callback(messages, rsm); + } + }, function () { + _converse.connection.deleteHandler(message_handler); - if (_.isNil(most_recent_msg)) { - this.fetchArchivedMessages(); - } else { - const archive_id = most_recent_msg.get('archive_id'); - - if (archive_id) { - this.fetchArchivedMessages({ - 'after': most_recent_msg.get('archive_id') - }); - } else { - this.fetchArchivedMessages({ - 'start': most_recent_msg.get('time') - }); - } - } - }, - - fetchArchivedMessagesIfNecessary() { - /* Check if archived messages should be fetched, and if so, do so. */ - if (this.disable_mam || this.model.get('mam_initialized')) { - return; - } - - const _converse = this.__super__._converse; - - _converse.api.disco.supports(Strophe.NS.MAM, _converse.bare_jid).then(result => { - // Success - if (result.length) { - this.fetchArchivedMessages(); - } - - this.model.save({ - 'mam_initialized': true - }); - }, () => { - // Error - _converse.log("Error or timeout while checking for MAM support", Strophe.LogLevel.ERROR); - }).catch(msg => { - this.clearSpinner(); - - _converse.log(msg, Strophe.LogLevel.FATAL); - }); - }, - - fetchArchivedMessages(options) { - const _converse = this.__super__._converse; - - if (this.disable_mam) { - return; - } - - const is_groupchat = this.model.get('type') === CHATROOMS_TYPE; - let mam_jid, message_handler; - - if (is_groupchat) { - mam_jid = this.model.get('jid'); - message_handler = this.model.onMessage.bind(this.model); - } else { - mam_jid = _converse.bare_jid; - message_handler = _converse.chatboxes.onMessage.bind(_converse.chatboxes); - } - - _converse.api.disco.supports(Strophe.NS.MAM, mam_jid).then(results => { - // Success - if (!results.length) { - return; - } - - this.addSpinner(); - - _converse.api.archive.query(_.extend({ - 'groupchat': is_groupchat, - 'before': '', - // Page backwards from the most recent message - 'max': _converse.archived_messages_page_size, - 'with': this.model.get('jid') - }, options), messages => { - // Success - this.clearSpinner(); - - _.each(messages, message_handler); - }, () => { - // Error - this.clearSpinner(); - - _converse.log("Error or timeout while trying to fetch " + "archived messages", Strophe.LogLevel.ERROR); - }); - }, () => { - // Error - _converse.log("Error or timeout while checking for MAM support", Strophe.LogLevel.ERROR); - }).catch(msg => { - this.clearSpinner(); - - _converse.log(msg, Strophe.LogLevel.FATAL); - }); - }, - - onScroll(ev) { - const _converse = this.__super__._converse; - - if (this.content.scrollTop === 0 && this.model.messages.length) { - const oldest_message = this.model.messages.at(0); - const archive_id = oldest_message.get('archive_id'); - - if (archive_id) { - this.fetchArchivedMessages({ - 'before': archive_id - }); - } else { - this.fetchArchivedMessages({ - 'end': oldest_message.get('time') - }); - } - } - } - - }, - ChatRoom: { - isDuplicate(message, original_stanza) { - const result = this.__super__.isDuplicate.apply(this, arguments); - - if (result) { - return result; - } + if (_.isFunction(errback)) { + errback.apply(this, arguments); + } + }, _converse.message_archiving_timeout); +} +_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-mam', { + dependencies: ['converse-chatview', 'converse-muc', 'converse-muc-views'], + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + ChatBox: { + getMessageAttributesFromStanza(message, original_stanza) { + function _process(attrs) { const archive_id = getMessageArchiveID(original_stanza); if (archive_id) { - return this.messages.filter({ - 'archive_id': archive_id - }).length > 0; + attrs.archive_id = archive_id; } + + return attrs; } + const result = this.__super__.getMessageAttributesFromStanza.apply(this, arguments); + + if (result instanceof Promise) { + return new Promise((resolve, reject) => result.then(attrs => resolve(_process(attrs))).catch(reject)); + } else { + return _process(result); + } + } + + }, + ChatBoxView: { + render() { + const result = this.__super__.render.apply(this, arguments); + + if (!this.disable_mam) { + this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100)); + } + + return result; }, - ChatRoomView: { - initialize() { - const _converse = this.__super__._converse; - this.__super__.initialize.apply(this, arguments); + fetchNewestMessages() { + /* Fetches messages that might have been archived *after* + * the last archived message in our local cache. + */ + if (this.disable_mam) { + return; + } - this.model.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this); - this.model.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this); - }, - - renderChatArea() { - const result = this.__super__.renderChatArea.apply(this, arguments); - - if (!this.disable_mam) { - this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100)); - } - - return result; - }, - - fetchArchivedMessagesIfNecessary() { - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED || !this.model.get('mam_enabled') || this.model.get('mam_initialized')) { - return; - } + const _converse = this.__super__._converse, + most_recent_msg = u.getMostRecentMessage(this.model); + if (_.isNil(most_recent_msg)) { this.fetchArchivedMessages(); + } else { + const archive_id = most_recent_msg.get('archive_id'); + + if (archive_id) { + this.fetchArchivedMessages({ + 'after': most_recent_msg.get('archive_id') + }); + } else { + this.fetchArchivedMessages({ + 'start': most_recent_msg.get('time') + }); + } + } + }, + + fetchArchivedMessagesIfNecessary() { + /* Check if archived messages should be fetched, and if so, do so. */ + if (this.disable_mam || this.model.get('mam_initialized')) { + return; + } + + const _converse = this.__super__._converse; + + _converse.api.disco.supports(Strophe.NS.MAM, _converse.bare_jid).then(result => { + // Success + if (result.length) { + this.fetchArchivedMessages(); + } + this.model.save({ 'mam_initialized': true }); + }, () => { + // Error + _converse.log("Error or timeout while checking for MAM support", Strophe.LogLevel.ERROR); + }).catch(msg => { + this.clearSpinner(); + + _converse.log(msg, Strophe.LogLevel.FATAL); + }); + }, + + fetchArchivedMessages(options) { + const _converse = this.__super__._converse; + + if (this.disable_mam) { + return; } - } - }, + const is_groupchat = this.model.get('type') === CHATROOMS_TYPE; + let mam_jid, message_handler; - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by Converse.js's plugin machinery. - */ - const _converse = this._converse; - - _converse.api.settings.update({ - archived_messages_page_size: '50', - message_archiving: undefined, - // Supported values are 'always', 'never', 'roster' (https://xmpp.org/extensions/xep-0313.html#prefs) - message_archiving_timeout: 8000 // Time (in milliseconds) to wait before aborting MAM request - - }); - - _converse.onMAMError = function (model, iq) { - if (iq.querySelectorAll('feature-not-implemented').length) { - _converse.log("Message Archive Management (XEP-0313) not supported by this server", Strophe.LogLevel.WARN); + if (is_groupchat) { + mam_jid = this.model.get('jid'); + message_handler = this.model.onMessage.bind(this.model); } else { - _converse.log("An error occured while trying to set archiving preferences.", Strophe.LogLevel.ERROR); - - _converse.log(iq); + mam_jid = _converse.bare_jid; + message_handler = _converse.chatboxes.onMessage.bind(_converse.chatboxes); } - }; - _converse.onMAMPreferences = function (feature, iq) { - /* Handle returned IQ stanza containing Message Archive - * Management (XEP-0313) preferences. - * - * XXX: For now we only handle the global default preference. - * The XEP also provides for per-JID preferences, which is - * currently not supported in converse.js. - * - * Per JID preferences will be set in chat boxes, so it'll - * probbaly be handled elsewhere in any case. - */ - const preference = sizzle(`prefs[xmlns="${Strophe.NS.MAM}"]`, iq).pop(); - const default_pref = preference.getAttribute('default'); + _converse.api.disco.supports(Strophe.NS.MAM, mam_jid).then(results => { + // Success + if (!results.length) { + return; + } - if (default_pref !== _converse.message_archiving) { - const stanza = $iq({ - 'type': 'set' - }).c('prefs', { - 'xmlns': Strophe.NS.MAM, - 'default': _converse.message_archiving + this.addSpinner(); + + _converse.api.archive.query(_.extend({ + 'groupchat': is_groupchat, + 'before': '', + // Page backwards from the most recent message + 'max': _converse.archived_messages_page_size, + 'with': this.model.get('jid') + }, options), messages => { + // Success + this.clearSpinner(); + + _.each(messages, message_handler); + }, () => { + // Error + this.clearSpinner(); + + _converse.log("Error or timeout while trying to fetch " + "archived messages", Strophe.LogLevel.ERROR); }); + }, () => { + // Error + _converse.log("Error or timeout while checking for MAM support", Strophe.LogLevel.ERROR); + }).catch(msg => { + this.clearSpinner(); - _.each(preference.children, function (child) { - stanza.cnode(child).up(); - }); + _converse.log(msg, Strophe.LogLevel.FATAL); + }); + }, - _converse.connection.sendIQ(stanza, _.partial(function (feature, iq) { - // XXX: Strictly speaking, the server should respond with the updated prefs - // (see example 18: https://xmpp.org/extensions/xep-0313.html#config) - // but Prosody doesn't do this, so we don't rely on it. - feature.save({ - 'preferences': { - 'default': _converse.message_archiving - } + onScroll(ev) { + const _converse = this.__super__._converse; + + if (this.content.scrollTop === 0 && this.model.messages.length) { + const oldest_message = this.model.messages.at(0); + const archive_id = oldest_message.get('archive_id'); + + if (archive_id) { + this.fetchArchivedMessages({ + 'before': archive_id }); - }, feature), _converse.onMAMError); - } else { + } else { + this.fetchArchivedMessages({ + 'end': oldest_message.get('time') + }); + } + } + } + + }, + ChatRoom: { + isDuplicate(message, original_stanza) { + const result = this.__super__.isDuplicate.apply(this, arguments); + + if (result) { + return result; + } + + const archive_id = getMessageArchiveID(original_stanza); + + if (archive_id) { + return this.messages.filter({ + 'archive_id': archive_id + }).length > 0; + } + } + + }, + ChatRoomView: { + initialize() { + const _converse = this.__super__._converse; + + this.__super__.initialize.apply(this, arguments); + + this.model.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this); + this.model.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this); + }, + + renderChatArea() { + const result = this.__super__.renderChatArea.apply(this, arguments); + + if (!this.disable_mam) { + this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100)); + } + + return result; + }, + + fetchArchivedMessagesIfNecessary() { + if (this.model.get('connection_status') !== _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].ROOMSTATUS.ENTERED || !this.model.get('mam_enabled') || this.model.get('mam_initialized')) { + return; + } + + this.fetchArchivedMessages(); + this.model.save({ + 'mam_initialized': true + }); + } + + } + }, + + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by Converse.js's plugin machinery. + */ + const _converse = this._converse; + + _converse.api.settings.update({ + archived_messages_page_size: '50', + message_archiving: undefined, + // Supported values are 'always', 'never', 'roster' (https://xmpp.org/extensions/xep-0313.html#prefs) + message_archiving_timeout: 8000 // Time (in milliseconds) to wait before aborting MAM request + + }); + + _converse.onMAMError = function (model, iq) { + if (iq.querySelectorAll('feature-not-implemented').length) { + _converse.log("Message Archive Management (XEP-0313) not supported by this server", Strophe.LogLevel.WARN); + } else { + _converse.log("An error occured while trying to set archiving preferences.", Strophe.LogLevel.ERROR); + + _converse.log(iq); + } + }; + + _converse.onMAMPreferences = function (feature, iq) { + /* Handle returned IQ stanza containing Message Archive + * Management (XEP-0313) preferences. + * + * XXX: For now we only handle the global default preference. + * The XEP also provides for per-JID preferences, which is + * currently not supported in converse.js. + * + * Per JID preferences will be set in chat boxes, so it'll + * probbaly be handled elsewhere in any case. + */ + const preference = sizzle__WEBPACK_IMPORTED_MODULE_3___default()(`prefs[xmlns="${Strophe.NS.MAM}"]`, iq).pop(); + const default_pref = preference.getAttribute('default'); + + if (default_pref !== _converse.message_archiving) { + const stanza = $iq({ + 'type': 'set' + }).c('prefs', { + 'xmlns': Strophe.NS.MAM, + 'default': _converse.message_archiving + }); + + _.each(preference.children, function (child) { + stanza.cnode(child).up(); + }); + + _converse.connection.sendIQ(stanza, _.partial(function (feature, iq) { + // XXX: Strictly speaking, the server should respond with the updated prefs + // (see example 18: https://xmpp.org/extensions/xep-0313.html#config) + // but Prosody doesn't do this, so we don't rely on it. feature.save({ 'preferences': { 'default': _converse.message_archiving } }); - } - }; - /* Event handlers */ - - - _converse.on('serviceDiscovered', feature => { - const prefs = feature.get('preferences') || {}; - - if (feature.get('var') === Strophe.NS.MAM && prefs['default'] !== _converse.message_archiving && // eslint-disable-line dot-notation - !_.isUndefined(_converse.message_archiving)) { - // Ask the server for archiving preferences - _converse.connection.sendIQ($iq({ - 'type': 'get' - }).c('prefs', { - 'xmlns': Strophe.NS.MAM - }), _.partial(_converse.onMAMPreferences, feature), _.partial(_converse.onMAMError, feature)); - } - }); - - _converse.on('addClientFeatures', () => { - _converse.api.disco.own.features.add(Strophe.NS.MAM); - }); - - _converse.on('afterMessagesFetched', chatboxview => { - chatboxview.fetchNewestMessages(); - }); - - _converse.on('reconnected', () => { - const private_chats = _converse.chatboxviews.filter(view => _.at(view, 'model.attributes.type')[0] === 'chatbox'); - - _.each(private_chats, view => view.fetchNewestMessages()); - }); - - _.extend(_converse.api, { - /** - * The [XEP-0313](https://xmpp.org/extensions/xep-0313.html) Message Archive Management API - * - * Enables you to query an XMPP server for archived messages. - * - * See also the [message-archiving](/docs/html/configuration.html#message-archiving) - * option in the configuration settings section, which you'll - * usually want to use in conjunction with this API. - * - * @namespace _converse.api.archive - * @memberOf _converse.api - */ - 'archive': { - /** - * Query for archived messages. - * - * The options parameter can also be an instance of - * Strophe.RSM to enable easy querying between results pages. - * - * @method _converse.api.archive.query - * @param {(Object|Strophe.RSM)} options Query parameters, either - * MAM-specific or also for Result Set Management. - * Can be either an object or an instance of Strophe.RSM. - * Valid query parameters are: - * * `with` - * * `start` - * * `end` - * * `first` - * * `last` - * * `after` - * * `before` - * * `index` - * * `count` - * @param {Function} callback A function to call whenever - * we receive query-relevant stanza. - * When the callback is called, a Strophe.RSM object is - * returned on which "next" or "previous" can be called - * before passing it in again to this method, to - * get the next or previous page in the result set. - * @param {Function} errback A function to call when an - * error stanza is received, for example when it - * doesn't support message archiving. - * - * @example - * // Requesting all archived messages - * // ================================ - * // - * // The simplest query that can be made is to simply not pass in any parameters. - * // Such a query will return all archived messages for the current user. - * // - * // Generally, you'll however always want to pass in a callback method, to receive - * // the returned messages. - * - * this._converse.api.archive.query( - * (messages) => { - * // Do something with the messages, like showing them in your webpage. - * }, - * (iq) => { - * // The query was not successful, perhaps inform the user? - * // The IQ stanza returned by the XMPP server is passed in, so that you - * // may inspect it and determine what the problem was. - * } - * ) - * @example - * // Waiting until server support has been determined - * // ================================================ - * // - * // The query method will only work if Converse has been able to determine that - * // the server supports MAM queries, otherwise the following error will be raised: - * // - * // "This server does not support XEP-0313, Message Archive Management" - * // - * // The very first time Converse loads in a browser tab, if you call the query - * // API too quickly, the above error might appear because service discovery has not - * // yet been completed. - * // - * // To work solve this problem, you can first listen for the `serviceDiscovered` event, - * // through which you can be informed once support for MAM has been determined. - * - * _converse.api.listen.on('serviceDiscovered', function (feature) { - * if (feature.get('var') === converse.env.Strophe.NS.MAM) { - * _converse.api.archive.query() - * } - * }); - * - * @example - * // Requesting all archived messages for a particular contact or room - * // ================================================================= - * // - * // To query for messages sent between the current user and another user or room, - * // the query options need to contain the the JID (Jabber ID) of the user or - * // room under the `with` key. - * - * // For a particular user - * this._converse.api.archive.query({'with': 'john@doe.net'}, callback, errback);) - * - * // For a particular room - * this._converse.api.archive.query({'with': 'discuss@conference.doglovers.net'}, callback, errback);) - * - * @example - * // Requesting all archived messages before or after a certain date - * // =============================================================== - * // - * // The `start` and `end` parameters are used to query for messages - * // within a certain timeframe. The passed in date values may either be ISO8601 - * // formatted date strings, or JavaScript Date objects. - * - * const options = { - * 'with': 'john@doe.net', - * 'start': '2010-06-07T00:00:00Z', - * 'end': '2010-07-07T13:23:54Z' - * }; - * this._converse.api.archive.query(options, callback, errback); - * - * @example - * // Limiting the amount of messages returned - * // ======================================== - * // - * // The amount of returned messages may be limited with the `max` parameter. - * // By default, the messages are returned from oldest to newest. - * - * // Return maximum 10 archived messages - * this._converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback); - * - * @example - * // Paging forwards through a set of archived messages - * // ================================================== - * // - * // When limiting the amount of messages returned per query, you might want to - * // repeatedly make a further query to fetch the next batch of messages. - * // - * // To simplify this usecase for you, the callback method receives not only an array - * // with the returned archived messages, but also a special RSM (*Result Set - * // Management*) object which contains the query parameters you passed in, as well - * // as two utility methods `next`, and `previous`. - * // - * // When you call one of these utility methods on the returned RSM object, and then - * // pass the result into a new query, you'll receive the next or previous batch of - * // archived messages. Please note, when calling these methods, pass in an integer - * // to limit your results. - * - * const callback = function (messages, rsm) { - * // Do something with the messages, like showing them in your webpage. - * // ... - * // You can now use the returned "rsm" object, to fetch the next batch of messages: - * _converse.api.archive.query(rsm.next(10), callback, errback)) - * - * } - * _converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback); - * - * @example - * // Paging backwards through a set of archived messages - * // =================================================== - * // - * // To page backwards through the archive, you need to know the UID of the message - * // which you'd like to page backwards from and then pass that as value for the - * // `before` parameter. If you simply want to page backwards from the most recent - * // message, pass in the `before` parameter with an empty string value `''`. - * - * _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) { - * // Do something with the messages, like showing them in your webpage. - * // ... - * // You can now use the returned "rsm" object, to fetch the previous batch of messages: - * rsm.previous(5); // Call previous method, to update the object's parameters, - * // passing in a limit value of 5. - * // Now we query again, to get the previous batch. - * _converse.api.archive.query(rsm, callback, errback); - * } - */ - 'query': function query(options, callback, errback) { - if (!_converse.api.connection.connected()) { - throw new Error('Can\'t call `api.archive.query` before having established an XMPP session'); - } - - return queryForArchivedMessages(_converse, options, callback, errback); + }, feature), _converse.onMAMError); + } else { + feature.save({ + 'preferences': { + 'default': _converse.message_archiving } - } - }); - } + }); + } + }; + /* Event handlers */ + + + _converse.on('serviceDiscovered', feature => { + const prefs = feature.get('preferences') || {}; + + if (feature.get('var') === Strophe.NS.MAM && prefs['default'] !== _converse.message_archiving && // eslint-disable-line dot-notation + !_.isUndefined(_converse.message_archiving)) { + // Ask the server for archiving preferences + _converse.connection.sendIQ($iq({ + 'type': 'get' + }).c('prefs', { + 'xmlns': Strophe.NS.MAM + }), _.partial(_converse.onMAMPreferences, feature), _.partial(_converse.onMAMError, feature)); + } + }); + + _converse.on('addClientFeatures', () => { + _converse.api.disco.own.features.add(Strophe.NS.MAM); + }); + + _converse.on('afterMessagesFetched', chatboxview => { + chatboxview.fetchNewestMessages(); + }); + + _converse.on('reconnected', () => { + const private_chats = _converse.chatboxviews.filter(view => _.at(view, 'model.attributes.type')[0] === 'chatbox'); + + _.each(private_chats, view => view.fetchNewestMessages()); + }); + + _.extend(_converse.api, { + /** + * The [XEP-0313](https://xmpp.org/extensions/xep-0313.html) Message Archive Management API + * + * Enables you to query an XMPP server for archived messages. + * + * See also the [message-archiving](/docs/html/configuration.html#message-archiving) + * option in the configuration settings section, which you'll + * usually want to use in conjunction with this API. + * + * @namespace _converse.api.archive + * @memberOf _converse.api + */ + 'archive': { + /** + * Query for archived messages. + * + * The options parameter can also be an instance of + * Strophe.RSM to enable easy querying between results pages. + * + * @method _converse.api.archive.query + * @param {(Object|Strophe.RSM)} options Query parameters, either + * MAM-specific or also for Result Set Management. + * Can be either an object or an instance of Strophe.RSM. + * Valid query parameters are: + * * `with` + * * `start` + * * `end` + * * `first` + * * `last` + * * `after` + * * `before` + * * `index` + * * `count` + * @param {Function} callback A function to call whenever + * we receive query-relevant stanza. + * When the callback is called, a Strophe.RSM object is + * returned on which "next" or "previous" can be called + * before passing it in again to this method, to + * get the next or previous page in the result set. + * @param {Function} errback A function to call when an + * error stanza is received, for example when it + * doesn't support message archiving. + * + * @example + * // Requesting all archived messages + * // ================================ + * // + * // The simplest query that can be made is to simply not pass in any parameters. + * // Such a query will return all archived messages for the current user. + * // + * // Generally, you'll however always want to pass in a callback method, to receive + * // the returned messages. + * + * this._converse.api.archive.query( + * (messages) => { + * // Do something with the messages, like showing them in your webpage. + * }, + * (iq) => { + * // The query was not successful, perhaps inform the user? + * // The IQ stanza returned by the XMPP server is passed in, so that you + * // may inspect it and determine what the problem was. + * } + * ) + * @example + * // Waiting until server support has been determined + * // ================================================ + * // + * // The query method will only work if Converse has been able to determine that + * // the server supports MAM queries, otherwise the following error will be raised: + * // + * // "This server does not support XEP-0313, Message Archive Management" + * // + * // The very first time Converse loads in a browser tab, if you call the query + * // API too quickly, the above error might appear because service discovery has not + * // yet been completed. + * // + * // To work solve this problem, you can first listen for the `serviceDiscovered` event, + * // through which you can be informed once support for MAM has been determined. + * + * _converse.api.listen.on('serviceDiscovered', function (feature) { + * if (feature.get('var') === converse.env.Strophe.NS.MAM) { + * _converse.api.archive.query() + * } + * }); + * + * @example + * // Requesting all archived messages for a particular contact or room + * // ================================================================= + * // + * // To query for messages sent between the current user and another user or room, + * // the query options need to contain the the JID (Jabber ID) of the user or + * // room under the `with` key. + * + * // For a particular user + * this._converse.api.archive.query({'with': 'john@doe.net'}, callback, errback);) + * + * // For a particular room + * this._converse.api.archive.query({'with': 'discuss@conference.doglovers.net'}, callback, errback);) + * + * @example + * // Requesting all archived messages before or after a certain date + * // =============================================================== + * // + * // The `start` and `end` parameters are used to query for messages + * // within a certain timeframe. The passed in date values may either be ISO8601 + * // formatted date strings, or JavaScript Date objects. + * + * const options = { + * 'with': 'john@doe.net', + * 'start': '2010-06-07T00:00:00Z', + * 'end': '2010-07-07T13:23:54Z' + * }; + * this._converse.api.archive.query(options, callback, errback); + * + * @example + * // Limiting the amount of messages returned + * // ======================================== + * // + * // The amount of returned messages may be limited with the `max` parameter. + * // By default, the messages are returned from oldest to newest. + * + * // Return maximum 10 archived messages + * this._converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback); + * + * @example + * // Paging forwards through a set of archived messages + * // ================================================== + * // + * // When limiting the amount of messages returned per query, you might want to + * // repeatedly make a further query to fetch the next batch of messages. + * // + * // To simplify this usecase for you, the callback method receives not only an array + * // with the returned archived messages, but also a special RSM (*Result Set + * // Management*) object which contains the query parameters you passed in, as well + * // as two utility methods `next`, and `previous`. + * // + * // When you call one of these utility methods on the returned RSM object, and then + * // pass the result into a new query, you'll receive the next or previous batch of + * // archived messages. Please note, when calling these methods, pass in an integer + * // to limit your results. + * + * const callback = function (messages, rsm) { + * // Do something with the messages, like showing them in your webpage. + * // ... + * // You can now use the returned "rsm" object, to fetch the next batch of messages: + * _converse.api.archive.query(rsm.next(10), callback, errback)) + * + * } + * _converse.api.archive.query({'with': 'john@doe.net', 'max':10}, callback, errback); + * + * @example + * // Paging backwards through a set of archived messages + * // =================================================== + * // + * // To page backwards through the archive, you need to know the UID of the message + * // which you'd like to page backwards from and then pass that as value for the + * // `before` parameter. If you simply want to page backwards from the most recent + * // message, pass in the `before` parameter with an empty string value `''`. + * + * _converse.api.archive.query({'before': '', 'max':5}, function (message, rsm) { + * // Do something with the messages, like showing them in your webpage. + * // ... + * // You can now use the returned "rsm" object, to fetch the previous batch of messages: + * rsm.previous(5); // Call previous method, to update the object's parameters, + * // passing in a limit value of 5. + * // Now we query again, to get the previous batch. + * _converse.api.archive.query(rsm, callback, errback); + * } + */ + 'query': function query(options, callback, errback) { + if (!_converse.api.connection.connected()) { + throw new Error('Can\'t call `api.archive.query` before having established an XMPP session'); + } + + return queryForArchivedMessages(_converse, options, callback, errback); + } + } + }); + } - }); }); /***/ }), @@ -75563,10 +75824,23 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!**************************************!*\ !*** ./src/headless/converse-muc.js ***! \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_disco__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./converse-disco */ "./src/headless/converse-disco.js"); +/* harmony import */ var _utils_emoji__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils/emoji */ "./src/headless/utils/emoji.js"); +/* harmony import */ var _utils_muc__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/muc */ "./src/headless/utils/muc.js"); +/* harmony import */ var backbone_overview_backbone_orderedlistview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! backbone.overview/backbone.orderedlistview */ "./node_modules/backbone.overview/backbone.orderedlistview.js"); +/* harmony import */ var backbone_overview_backbone_orderedlistview__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(backbone_overview_backbone_orderedlistview__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var backbone_overview_backbone_overview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! backbone.overview/backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js"); +/* harmony import */ var backbone_overview_backbone_overview__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(backbone_overview_backbone_overview__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var backbone_vdomview__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! backbone.vdomview */ "./node_modules/backbone.vdomview/backbone.vdomview.js"); +/* harmony import */ var backbone_vdomview__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(backbone_vdomview__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var _converse_core__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var _utils_form__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./utils/form */ "./src/headless/utils/form.js"); +function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } @@ -75579,1717 +75853,1714 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ./utils/form */ "./src/headless/utils/form.js"), __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! ./converse-disco */ "./src/headless/converse-disco.js"), __webpack_require__(/*! backbone.overview/backbone.overview */ "./node_modules/backbone.overview/backbone.overview.js"), __webpack_require__(/*! backbone.overview/backbone.orderedlistview */ "./node_modules/backbone.overview/backbone.orderedlistview.js"), __webpack_require__(/*! backbone.vdomview */ "./node_modules/backbone.vdomview/backbone.vdomview.js"), __webpack_require__(/*! ./utils/muc */ "./src/headless/utils/muc.js"), __webpack_require__(/*! ./utils/emoji */ "./src/headless/utils/emoji.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (u, converse) { - "use strict"; - const MUC_ROLE_WEIGHTS = { - 'moderator': 1, - 'participant': 2, - 'visitor': 3, - 'none': 2 - }; - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - $iq = _converse$env.$iq, - $build = _converse$env.$build, - $msg = _converse$env.$msg, - $pres = _converse$env.$pres, - b64_sha1 = _converse$env.b64_sha1, - sizzle = _converse$env.sizzle, - f = _converse$env.f, - moment = _converse$env.moment, - _ = _converse$env._; // Add Strophe Namespaces - Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin"); - Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner"); - Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register"); - Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig"); - Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user"); - converse.MUC_NICK_CHANGED_CODE = "303"; - converse.ROOM_FEATURES = ['passwordprotected', 'unsecured', 'hidden', 'publicroom', 'membersonly', 'open', 'persistent', 'temporary', 'nonanonymous', 'semianonymous', 'moderated', 'unmoderated', 'mam_enabled']; - converse.ROOMSTATUS = { - CONNECTED: 0, - CONNECTING: 1, - NICKNAME_REQUIRED: 2, - PASSWORD_REQUIRED: 3, - DISCONNECTED: 4, - ENTERED: 5 - }; - converse.plugins.add('converse-muc', { - /* Optional dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. They are called "optional" because they might not be - * available, in which case any overrides applicable to them will be - * ignored. - * - * It's possible however to make optional dependencies non-optional. - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"], - overrides: { - tearDown() { - const _converse = this.__super__._converse, - groupchats = this.chatboxes.where({ - 'type': _converse.CHATROOMS_TYPE - }); - _.each(groupchats, gc => u.safeSave(gc, { - 'connection_status': converse.ROOMSTATUS.DISCONNECTED - })); - this.__super__.tearDown.call(this, arguments); - }, - ChatBoxes: { - model(attrs, options) { - const _converse = this.__super__._converse; - if (attrs.type == _converse.CHATROOMS_TYPE) { - return new _converse.ChatRoom(attrs, options); - } else { - return this.__super__.model.apply(this, arguments); - } - } - } - }, - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse, - __ = _converse.__; // Configuration values for this plugin - // ==================================== - // Refer to docs/source/configuration.rst for explanations of these - // configuration settings. +const MUC_ROLE_WEIGHTS = { + 'moderator': 1, + 'participant': 2, + 'visitor': 3, + 'none': 2 +}; +const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].env, + Strophe = _converse$env.Strophe, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + $iq = _converse$env.$iq, + $build = _converse$env.$build, + $msg = _converse$env.$msg, + $pres = _converse$env.$pres, + b64_sha1 = _converse$env.b64_sha1, + sizzle = _converse$env.sizzle, + f = _converse$env.f, + moment = _converse$env.moment, + _ = _converse$env._; // Add Strophe Namespaces - _converse.api.settings.update({ - allow_muc: true, - allow_muc_invitations: true, - auto_join_on_invite: false, - auto_join_rooms: [], - auto_register_muc_nickname: false, - muc_domain: undefined, - muc_history_max_stanzas: undefined, - muc_instant_rooms: true, - muc_nickname_from_jid: false +Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin"); +Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner"); +Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register"); +Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig"); +Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user"); +_converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].MUC_NICK_CHANGED_CODE = "303"; +_converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOM_FEATURES = ['passwordprotected', 'unsecured', 'hidden', 'publicroom', 'membersonly', 'open', 'persistent', 'temporary', 'nonanonymous', 'semianonymous', 'moderated', 'unmoderated', 'mam_enabled']; +_converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS = { + CONNECTED: 0, + CONNECTING: 1, + NICKNAME_REQUIRED: 2, + PASSWORD_REQUIRED: 3, + DISCONNECTED: 4, + ENTERED: 5 +}; +_converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc', { + /* Optional dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. They are called "optional" because they might not be + * available, in which case any overrides applicable to them will be + * ignored. + * + * It's possible however to make optional dependencies non-optional. + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"], + overrides: { + tearDown() { + const _converse = this.__super__._converse, + groupchats = this.chatboxes.where({ + 'type': _converse.CHATROOMS_TYPE }); - _converse.api.promises.add(['roomsAutoJoined']); + _.each(groupchats, gc => _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(gc, { + 'connection_status': _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.DISCONNECTED + })); - function openRoom(jid) { - if (!u.isValidMUCJID(jid)) { - return _converse.log(`Invalid JID "${jid}" provided in URL fragment`, Strophe.LogLevel.WARN); + this.__super__.tearDown.call(this, arguments); + }, + + ChatBoxes: { + model(attrs, options) { + const _converse = this.__super__._converse; + + if (attrs.type == _converse.CHATROOMS_TYPE) { + return new _converse.ChatRoom(attrs, options); + } else { + return this.__super__.model.apply(this, arguments); } - - const promises = [_converse.api.waitUntil('roomsAutoJoined')]; - - if (_converse.allow_bookmarks) { - promises.push(_converse.api.waitUntil('bookmarksInitialized')); - } - - Promise.all(promises).then(() => { - _converse.api.rooms.open(jid); - }); } - _converse.router.route('converse/room?jid=:jid', openRoom); + } + }, - _converse.openChatRoom = function (jid, settings, bring_to_foreground) { - /* Opens a groupchat, making sure that certain attributes - * are correct, for example that the "type" is set to - * "chatroom". + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse, + __ = _converse.__; // Configuration values for this plugin + // ==================================== + // Refer to docs/source/configuration.rst for explanations of these + // configuration settings. + + _converse.api.settings.update({ + allow_muc: true, + allow_muc_invitations: true, + auto_join_on_invite: false, + auto_join_rooms: [], + auto_register_muc_nickname: false, + muc_domain: undefined, + muc_history_max_stanzas: undefined, + muc_instant_rooms: true, + muc_nickname_from_jid: false + }); + + _converse.api.promises.add(['roomsAutoJoined']); + + function openRoom(jid) { + if (!_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isValidMUCJID(jid)) { + return _converse.log(`Invalid JID "${jid}" provided in URL fragment`, Strophe.LogLevel.WARN); + } + + const promises = [_converse.api.waitUntil('roomsAutoJoined')]; + + if (_converse.allow_bookmarks) { + promises.push(_converse.api.waitUntil('bookmarksInitialized')); + } + + Promise.all(promises).then(() => { + _converse.api.rooms.open(jid); + }); + } + + _converse.router.route('converse/room?jid=:jid', openRoom); + + _converse.openChatRoom = function (jid, settings, bring_to_foreground) { + /* Opens a groupchat, making sure that certain attributes + * are correct, for example that the "type" is set to + * "chatroom". + */ + settings.type = _converse.CHATROOMS_TYPE; + settings.id = jid; + settings.box_id = b64_sha1(jid); + + const chatbox = _converse.chatboxes.getChatBox(jid, settings, true); + + chatbox.trigger('show', true); + return chatbox; + }; + + _converse.ChatRoom = _converse.ChatBox.extend({ + defaults() { + return _.assign(_.clone(_converse.ChatBox.prototype.defaults), _.zipObject(_converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOM_FEATURES, _.map(_converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOM_FEATURES, _.stubFalse)), { + // For group chats, we distinguish between generally unread + // messages and those ones that specifically mention the + // user. + // + // To keep things simple, we reuse `num_unread` from + // _converse.ChatBox to indicate unread messages which + // mention the user and `num_unread_general` to indicate + // generally unread messages (which *includes* mentions!). + 'num_unread_general': 0, + 'affiliation': null, + 'connection_status': _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.DISCONNECTED, + 'name': '', + 'nick': _converse.xmppstatus.get('nickname') || _converse.nickname, + 'description': '', + 'features_fetched': false, + 'roomconfig': {}, + 'type': _converse.CHATROOMS_TYPE, + 'message_type': 'groupchat' + }); + }, + + initialize() { + this.constructor.__super__.initialize.apply(this, arguments); + + this.on('change:connection_status', this.onConnectionStatusChanged, this); + this.occupants = new _converse.ChatRoomOccupants(); + this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)); + this.occupants.chatroom = this; + this.registerHandlers(); + }, + + async onConnectionStatusChanged() { + if (this.get('connection_status') === _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.ENTERED && _converse.auto_register_muc_nickname && !this.get('reserved_nick')) { + const result = await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid')); + + if (result.length) { + this.registerNickname(); + } + } + }, + + registerHandlers() { + /* Register presence and message handlers for this chat + * groupchat */ - settings.type = _converse.CHATROOMS_TYPE; - settings.id = jid; - settings.box_id = b64_sha1(jid); + const room_jid = this.get('jid'); + this.removeHandlers(); + this.presence_handler = _converse.connection.addHandler(stanza => { + _.each(_.values(this.handlers.presence), callback => callback(stanza)); - const chatbox = _converse.chatboxes.getChatBox(jid, settings, true); + this.onPresence(stanza); + return true; + }, null, 'presence', null, null, room_jid, { + 'ignoreNamespaceFragment': true, + 'matchBareFromJid': true + }); + this.message_handler = _converse.connection.addHandler(stanza => { + _.each(_.values(this.handlers.message), callback => callback(stanza)); - chatbox.trigger('show', true); - return chatbox; - }; + this.onMessage(stanza); + return true; + }, null, 'message', 'groupchat', null, room_jid, { + 'matchBareFromJid': true + }); + }, - _converse.ChatRoom = _converse.ChatBox.extend({ - defaults() { - return _.assign(_.clone(_converse.ChatBox.prototype.defaults), _.zipObject(converse.ROOM_FEATURES, _.map(converse.ROOM_FEATURES, _.stubFalse)), { - // For group chats, we distinguish between generally unread - // messages and those ones that specifically mention the - // user. - // - // To keep things simple, we reuse `num_unread` from - // _converse.ChatBox to indicate unread messages which - // mention the user and `num_unread_general` to indicate - // generally unread messages (which *includes* mentions!). - 'num_unread_general': 0, - 'affiliation': null, - 'connection_status': converse.ROOMSTATUS.DISCONNECTED, - 'name': '', - 'nick': _converse.xmppstatus.get('nickname') || _converse.nickname, - 'description': '', - 'features_fetched': false, - 'roomconfig': {}, - 'type': _converse.CHATROOMS_TYPE, - 'message_type': 'groupchat' - }); - }, + removeHandlers() { + /* Remove the presence and message handlers that were + * registered for this groupchat. + */ + if (this.message_handler) { + _converse.connection.deleteHandler(this.message_handler); - initialize() { - this.constructor.__super__.initialize.apply(this, arguments); + delete this.message_handler; + } - this.on('change:connection_status', this.onConnectionStatusChanged, this); - this.occupants = new _converse.ChatRoomOccupants(); - this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)); - this.occupants.chatroom = this; - this.registerHandlers(); - }, + if (this.presence_handler) { + _converse.connection.deleteHandler(this.presence_handler); - async onConnectionStatusChanged() { - if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED && _converse.auto_register_muc_nickname && !this.get('reserved_nick')) { - const result = await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid')); + delete this.presence_handler; + } - if (result.length) { - this.registerNickname(); - } - } - }, + return this; + }, - registerHandlers() { - /* Register presence and message handlers for this chat - * groupchat - */ - const room_jid = this.get('jid'); - this.removeHandlers(); - this.presence_handler = _converse.connection.addHandler(stanza => { - _.each(_.values(this.handlers.presence), callback => callback(stanza)); + addHandler(type, name, callback) { + /* Allows 'presence' and 'message' handlers to be + * registered. These will be executed once presence or + * message stanzas are received, and *before* this model's + * own handlers are executed. + */ + if (_.isNil(this.handlers)) { + this.handlers = {}; + } - this.onPresence(stanza); - return true; - }, null, 'presence', null, null, room_jid, { - 'ignoreNamespaceFragment': true, - 'matchBareFromJid': true - }); - this.message_handler = _converse.connection.addHandler(stanza => { - _.each(_.values(this.handlers.message), callback => callback(stanza)); + if (_.isNil(this.handlers[type])) { + this.handlers[type] = {}; + } - this.onMessage(stanza); - return true; - }, null, 'message', 'groupchat', null, room_jid, { - 'matchBareFromJid': true - }); - }, + this.handlers[type][name] = callback; + }, - removeHandlers() { - /* Remove the presence and message handlers that were - * registered for this groupchat. - */ - if (this.message_handler) { - _converse.connection.deleteHandler(this.message_handler); + getDisplayName() { + return this.get('name') || this.get('jid'); + }, - delete this.message_handler; - } + join(nick, password) { + /* Join the groupchat. + * + * Parameters: + * (String) nick: The user's nickname + * (String) password: Optional password, if required by + * the groupchat. + */ + nick = nick ? nick : this.get('nick'); - if (this.presence_handler) { - _converse.connection.deleteHandler(this.presence_handler); - - delete this.presence_handler; - } + if (!nick) { + throw new TypeError('join: You need to provide a valid nickname'); + } + if (this.get('connection_status') === _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.ENTERED) { + // We have restored a groupchat from session storage, + // so we don't send out a presence stanza again. return this; - }, + } - addHandler(type, name, callback) { - /* Allows 'presence' and 'message' handlers to be - * registered. These will be executed once presence or - * message stanzas are received, and *before* this model's - * own handlers are executed. - */ - if (_.isNil(this.handlers)) { - this.handlers = {}; - } + const stanza = $pres({ + 'from': _converse.connection.jid, + 'to': this.getRoomJIDAndNick(nick) + }).c("x", { + 'xmlns': Strophe.NS.MUC + }).c("history", { + 'maxstanzas': this.get('mam_enabled') ? 0 : _converse.muc_history_max_stanzas + }).up(); - if (_.isNil(this.handlers[type])) { - this.handlers[type] = {}; - } + if (password) { + stanza.cnode(Strophe.xmlElement("password", [], password)); + } - this.handlers[type][name] = callback; - }, + this.save('connection_status', _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.CONNECTING); - getDisplayName() { - return this.get('name') || this.get('jid'); - }, + _converse.connection.send(stanza); - join(nick, password) { - /* Join the groupchat. - * - * Parameters: - * (String) nick: The user's nickname - * (String) password: Optional password, if required by - * the groupchat. - */ - nick = nick ? nick : this.get('nick'); + return this; + }, - if (!nick) { - throw new TypeError('join: You need to provide a valid nickname'); - } + leave(exit_msg) { + /* Leave the groupchat. + * + * Parameters: + * (String) exit_msg: Optional message to indicate your + * reason for leaving. + */ + this.occupants.browserStorage._clear(); - if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) { - // We have restored a groupchat from session storage, - // so we don't send out a presence stanza again. - return this; - } + this.occupants.reset(); - const stanza = $pres({ - 'from': _converse.connection.jid, - 'to': this.getRoomJIDAndNick(nick) - }).c("x", { - 'xmlns': Strophe.NS.MUC - }).c("history", { - 'maxstanzas': this.get('mam_enabled') ? 0 : _converse.muc_history_max_stanzas - }).up(); + const disco_entity = _converse.disco_entities.get(this.get('jid')); - if (password) { - stanza.cnode(Strophe.xmlElement("password", [], password)); - } + if (disco_entity) { + disco_entity.destroy(); + } - this.save('connection_status', converse.ROOMSTATUS.CONNECTING); + if (_converse.connection.connected) { + this.sendUnavailablePresence(exit_msg); + } - _converse.connection.send(stanza); + _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(this, { + 'connection_status': _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.DISCONNECTED + }); + this.removeHandlers(); + }, - return this; - }, + sendUnavailablePresence(exit_msg) { + const presence = $pres({ + type: "unavailable", + from: _converse.connection.jid, + to: this.getRoomJIDAndNick() + }); - leave(exit_msg) { - /* Leave the groupchat. - * - * Parameters: - * (String) exit_msg: Optional message to indicate your - * reason for leaving. - */ - this.occupants.browserStorage._clear(); + if (exit_msg !== null) { + presence.c("status", exit_msg); + } - this.occupants.reset(); + _converse.connection.sendPresence(presence); + }, - const disco_entity = _converse.disco_entities.get(this.get('jid')); + getReferenceForMention(mention, index) { + const longest_match = _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].getLongestSubstring(mention, this.occupants.map(o => o.getDisplayName())); - if (disco_entity) { - disco_entity.destroy(); - } + if (!longest_match) { + return null; + } - if (_converse.connection.connected) { - this.sendUnavailablePresence(exit_msg); - } + if ((mention[longest_match.length] || '').match(/[A-Za-zäëïöüâêîôûáéíóúàèìòùÄËÏÖÜÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙ]/i)) { + // avoid false positives, i.e. mentions that have + // further alphabetical characters than our longest + // match. + return null; + } - u.safeSave(this, { - 'connection_status': converse.ROOMSTATUS.DISCONNECTED - }); - this.removeHandlers(); - }, + const occupant = this.occupants.findOccupant({ + 'nick': longest_match + }) || this.occupants.findOccupant({ + 'jid': longest_match + }); - sendUnavailablePresence(exit_msg) { - const presence = $pres({ - type: "unavailable", - from: _converse.connection.jid, - to: this.getRoomJIDAndNick() - }); + if (!occupant) { + return null; + } - if (exit_msg !== null) { - presence.c("status", exit_msg); - } + const obj = { + 'begin': index, + 'end': index + longest_match.length, + 'value': longest_match, + 'type': 'mention' + }; - _converse.connection.sendPresence(presence); - }, + if (occupant.get('jid')) { + obj.uri = `xmpp:${occupant.get('jid')}`; + } - getReferenceForMention(mention, index) { - const longest_match = u.getLongestSubstring(mention, this.occupants.map(o => o.getDisplayName())); + return obj; + }, - if (!longest_match) { - return null; - } - - if ((mention[longest_match.length] || '').match(/[A-Za-zäëïöüâêîôûáéíóúàèìòùÄËÏÖÜÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙ]/i)) { - // avoid false positives, i.e. mentions that have - // further alphabetical characters than our longest - // match. - return null; - } - - const occupant = this.occupants.findOccupant({ - 'nick': longest_match - }) || this.occupants.findOccupant({ - 'jid': longest_match - }); - - if (!occupant) { - return null; - } - - const obj = { - 'begin': index, - 'end': index + longest_match.length, - 'value': longest_match, - 'type': 'mention' - }; - - if (occupant.get('jid')) { - obj.uri = `xmpp:${occupant.get('jid')}`; - } - - return obj; - }, - - extractReference(text, index) { - for (let i = index; i < text.length; i++) { - if (text[i] !== '@') { - continue; - } else { - const match = text.slice(i + 1), - ref = this.getReferenceForMention(match, i); - - if (ref) { - return [text.slice(0, i) + match, ref, i]; - } - } - } - - return; - }, - - parseTextForReferences(text) { - const refs = []; - let index = 0; - - while (index < (text || '').length) { - const result = this.extractReference(text, index); - - if (result) { - text = result[0]; // @ gets filtered out - - refs.push(result[1]); - index = result[2]; - } else { - break; - } - } - - return [text, refs]; - }, - - getOutgoingMessageAttributes(text, spoiler_hint) { - const is_spoiler = this.get('composing_spoiler'); - var references; - - var _this$parseTextForRef = this.parseTextForReferences(text); - - var _this$parseTextForRef2 = _slicedToArray(_this$parseTextForRef, 2); - - text = _this$parseTextForRef2[0]; - references = _this$parseTextForRef2[1]; - return { - 'from': `${this.get('jid')}/${this.get('nick')}`, - 'fullname': this.get('nick'), - 'is_spoiler': is_spoiler, - 'message': text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined, - 'nick': this.get('nick'), - 'references': references, - 'sender': 'me', - 'spoiler_hint': is_spoiler ? spoiler_hint : undefined, - 'type': 'groupchat' - }; - }, - - getRoomJIDAndNick(nick) { - /* Utility method to construct the JID for the current user - * as occupant of the groupchat. - * - * This is the groupchat JID, with the user's nick added at the - * end. - * - * For example: groupchat@conference.example.org/nickname - */ - if (nick) { - this.save({ - 'nick': nick - }); + extractReference(text, index) { + for (let i = index; i < text.length; i++) { + if (text[i] !== '@') { + continue; } else { - nick = this.get('nick'); + const match = text.slice(i + 1), + ref = this.getReferenceForMention(match, i); + + if (ref) { + return [text.slice(0, i) + match, ref, i]; + } } + } - const groupchat = this.get('jid'); - const jid = Strophe.getBareJidFromJid(groupchat); - return jid + (nick !== null ? `/${nick}` : ""); - }, + return; + }, - sendChatState() { - /* Sends a message with the status of the user in this chat session - * as taken from the 'chat_state' attribute of the chat box. - * See XEP-0085 Chat State Notifications. - */ - if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - return; + parseTextForReferences(text) { + const refs = []; + let index = 0; + + while (index < (text || '').length) { + const result = this.extractReference(text, index); + + if (result) { + text = result[0]; // @ gets filtered out + + refs.push(result[1]); + index = result[2]; + } else { + break; } + } - const chat_state = this.get('chat_state'); + return [text, refs]; + }, - if (chat_state === _converse.GONE) { - // is not applicable within MUC context - return; - } + getOutgoingMessageAttributes(text, spoiler_hint) { + const is_spoiler = this.get('composing_spoiler'); + var references; - _converse.connection.send($msg({ - 'to': this.get('jid'), - 'type': 'groupchat' - }).c(chat_state, { - 'xmlns': Strophe.NS.CHATSTATES - }).up().c('no-store', { - 'xmlns': Strophe.NS.HINTS - }).up().c('no-permanent-store', { - 'xmlns': Strophe.NS.HINTS - })); - }, + var _this$parseTextForRef = this.parseTextForReferences(text); - directInvite(recipient, reason) { - /* Send a direct invitation as per XEP-0249 - * - * Parameters: - * (String) recipient - JID of the person being invited - * (String) reason - Optional reason for the invitation - */ - if (this.get('membersonly')) { - // When inviting to a members-only groupchat, we first add - // the person to the member list by giving them an - // affiliation of 'member' (if they're not affiliated - // already), otherwise they won't be able to join. - const map = {}; - map[recipient] = 'member'; + var _this$parseTextForRef2 = _slicedToArray(_this$parseTextForRef, 2); - const deltaFunc = _.partial(u.computeAffiliationsDelta, true, false); + text = _this$parseTextForRef2[0]; + references = _this$parseTextForRef2[1]; + return { + 'from': `${this.get('jid')}/${this.get('nick')}`, + 'fullname': this.get('nick'), + 'is_spoiler': is_spoiler, + 'message': text ? _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].httpToGeoUri(_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].shortnameToUnicode(text), _converse) : undefined, + 'nick': this.get('nick'), + 'references': references, + 'sender': 'me', + 'spoiler_hint': is_spoiler ? spoiler_hint : undefined, + 'type': 'groupchat' + }; + }, - this.updateMemberLists([{ - 'jid': recipient, - 'affiliation': 'member', - 'reason': reason - }], ['member', 'owner', 'admin'], deltaFunc); - } - - const attrs = { - 'xmlns': 'jabber:x:conference', - 'jid': this.get('jid') - }; - - if (reason !== null) { - attrs.reason = reason; - } - - if (this.get('password')) { - attrs.password = this.get('password'); - } - - const invitation = $msg({ - from: _converse.connection.jid, - to: recipient, - id: _converse.connection.getUniqueId() - }).c('x', attrs); - - _converse.connection.send(invitation); - - _converse.emit('roomInviteSent', { - 'room': this, - 'recipient': recipient, - 'reason': reason + getRoomJIDAndNick(nick) { + /* Utility method to construct the JID for the current user + * as occupant of the groupchat. + * + * This is the groupchat JID, with the user's nick added at the + * end. + * + * For example: groupchat@conference.example.org/nickname + */ + if (nick) { + this.save({ + 'nick': nick }); - }, + } else { + nick = this.get('nick'); + } - async refreshRoomFeatures() { - await _converse.api.disco.refreshFeatures(this.get('jid')); - return this.getRoomFeatures(); - }, + const groupchat = this.get('jid'); + const jid = Strophe.getBareJidFromJid(groupchat); + return jid + (nick !== null ? `/${nick}` : ""); + }, - async getRoomFeatures() { - const features = await _converse.api.disco.getFeatures(this.get('jid')), - fields = await _converse.api.disco.getFields(this.get('jid')), - identity = await _converse.api.disco.getIdentity('conference', 'text', this.get('jid')), - attrs = { - 'features_fetched': moment().format(), - 'name': identity && identity.get('name') - }; - features.each(feature => { - const fieldname = feature.get('var'); + sendChatState() { + /* Sends a message with the status of the user in this chat session + * as taken from the 'chat_state' attribute of the chat box. + * See XEP-0085 Chat State Notifications. + */ + if (this.get('connection_status') !== _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.ENTERED) { + return; + } - if (!fieldname.startsWith('muc_')) { - if (fieldname === Strophe.NS.MAM) { - attrs.mam_enabled = true; - } + const chat_state = this.get('chat_state'); - return; + if (chat_state === _converse.GONE) { + // is not applicable within MUC context + return; + } + + _converse.connection.send($msg({ + 'to': this.get('jid'), + 'type': 'groupchat' + }).c(chat_state, { + 'xmlns': Strophe.NS.CHATSTATES + }).up().c('no-store', { + 'xmlns': Strophe.NS.HINTS + }).up().c('no-permanent-store', { + 'xmlns': Strophe.NS.HINTS + })); + }, + + directInvite(recipient, reason) { + /* Send a direct invitation as per XEP-0249 + * + * Parameters: + * (String) recipient - JID of the person being invited + * (String) reason - Optional reason for the invitation + */ + if (this.get('membersonly')) { + // When inviting to a members-only groupchat, we first add + // the person to the member list by giving them an + // affiliation of 'member' (if they're not affiliated + // already), otherwise they won't be able to join. + const map = {}; + map[recipient] = 'member'; + + const deltaFunc = _.partial(_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].computeAffiliationsDelta, true, false); + + this.updateMemberLists([{ + 'jid': recipient, + 'affiliation': 'member', + 'reason': reason + }], ['member', 'owner', 'admin'], deltaFunc); + } + + const attrs = { + 'xmlns': 'jabber:x:conference', + 'jid': this.get('jid') + }; + + if (reason !== null) { + attrs.reason = reason; + } + + if (this.get('password')) { + attrs.password = this.get('password'); + } + + const invitation = $msg({ + from: _converse.connection.jid, + to: recipient, + id: _converse.connection.getUniqueId() + }).c('x', attrs); + + _converse.connection.send(invitation); + + _converse.emit('roomInviteSent', { + 'room': this, + 'recipient': recipient, + 'reason': reason + }); + }, + + async refreshRoomFeatures() { + await _converse.api.disco.refreshFeatures(this.get('jid')); + return this.getRoomFeatures(); + }, + + async getRoomFeatures() { + const features = await _converse.api.disco.getFeatures(this.get('jid')), + fields = await _converse.api.disco.getFields(this.get('jid')), + identity = await _converse.api.disco.getIdentity('conference', 'text', this.get('jid')), + attrs = { + 'features_fetched': moment().format(), + 'name': identity && identity.get('name') + }; + features.each(feature => { + const fieldname = feature.get('var'); + + if (!fieldname.startsWith('muc_')) { + if (fieldname === Strophe.NS.MAM) { + attrs.mam_enabled = true; } - attrs[fieldname.replace('muc_', '')] = true; - }); - attrs.description = _.get(fields.findWhere({ - 'var': "muc#roominfo_description" - }), 'attributes.value'); - this.save(attrs); - }, + return; + } - requestMemberList(affiliation) { - /* Send an IQ stanza to the server, asking it for the - * member-list of this groupchat. - * - * See: http://xmpp.org/extensions/xep-0045.html#modifymember - * - * Parameters: - * (String) affiliation: The specific member list to - * fetch. 'admin', 'owner' or 'member'. - * - * Returns: - * A promise which resolves once the list has been - * retrieved. - */ - affiliation = affiliation || 'member'; - const iq = $iq({ - to: this.get('jid'), - type: "get" - }).c("query", { - xmlns: Strophe.NS.MUC_ADMIN - }).c("item", { - 'affiliation': affiliation - }); - return _converse.api.sendIQ(iq); - }, + attrs[fieldname.replace('muc_', '')] = true; + }); + attrs.description = _.get(fields.findWhere({ + 'var': "muc#roominfo_description" + }), 'attributes.value'); + this.save(attrs); + }, - setAffiliation(affiliation, members) { - /* Send IQ stanzas to the server to set an affiliation for - * the provided JIDs. - * - * See: http://xmpp.org/extensions/xep-0045.html#modifymember - * - * XXX: Prosody doesn't accept multiple JIDs' affiliations - * being set in one IQ stanza, so as a workaround we send - * a separate stanza for each JID. - * Related ticket: https://issues.prosody.im/345 - * - * Parameters: - * (String) affiliation: The affiliation - * (Object) members: A map of jids, affiliations and - * optionally reasons. Only those entries with the - * same affiliation as being currently set will be - * considered. - * - * Returns: - * A promise which resolves and fails depending on the - * XMPP server response. - */ - members = _.filter(members, member => // We only want those members who have the right - // affiliation (or none, which implies the provided one). - _.isUndefined(member.affiliation) || member.affiliation === affiliation); + requestMemberList(affiliation) { + /* Send an IQ stanza to the server, asking it for the + * member-list of this groupchat. + * + * See: http://xmpp.org/extensions/xep-0045.html#modifymember + * + * Parameters: + * (String) affiliation: The specific member list to + * fetch. 'admin', 'owner' or 'member'. + * + * Returns: + * A promise which resolves once the list has been + * retrieved. + */ + affiliation = affiliation || 'member'; + const iq = $iq({ + to: this.get('jid'), + type: "get" + }).c("query", { + xmlns: Strophe.NS.MUC_ADMIN + }).c("item", { + 'affiliation': affiliation + }); + return _converse.api.sendIQ(iq); + }, - const promises = _.map(members, _.bind(this.sendAffiliationIQ, this, affiliation)); + setAffiliation(affiliation, members) { + /* Send IQ stanzas to the server to set an affiliation for + * the provided JIDs. + * + * See: http://xmpp.org/extensions/xep-0045.html#modifymember + * + * XXX: Prosody doesn't accept multiple JIDs' affiliations + * being set in one IQ stanza, so as a workaround we send + * a separate stanza for each JID. + * Related ticket: https://issues.prosody.im/345 + * + * Parameters: + * (String) affiliation: The affiliation + * (Object) members: A map of jids, affiliations and + * optionally reasons. Only those entries with the + * same affiliation as being currently set will be + * considered. + * + * Returns: + * A promise which resolves and fails depending on the + * XMPP server response. + */ + members = _.filter(members, member => // We only want those members who have the right + // affiliation (or none, which implies the provided one). + _.isUndefined(member.affiliation) || member.affiliation === affiliation); - return Promise.all(promises); - }, + const promises = _.map(members, _.bind(this.sendAffiliationIQ, this, affiliation)); - saveConfiguration(form) { - /* Submit the groupchat configuration form by sending an IQ - * stanza to the server. - * - * Returns a promise which resolves once the XMPP server - * has return a response IQ. - * - * Parameters: - * (HTMLElement) form: The configuration form DOM element. - * If no form is provided, the default configuration - * values will be used. - */ - return new Promise((resolve, reject) => { - const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [], - configArray = _.map(inputs, u.webForm2xForm); + return Promise.all(promises); + }, - this.sendConfiguration(configArray, resolve, reject); - }); - }, + saveConfiguration(form) { + /* Submit the groupchat configuration form by sending an IQ + * stanza to the server. + * + * Returns a promise which resolves once the XMPP server + * has return a response IQ. + * + * Parameters: + * (HTMLElement) form: The configuration form DOM element. + * If no form is provided, the default configuration + * values will be used. + */ + return new Promise((resolve, reject) => { + const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [], + configArray = _.map(inputs, _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].webForm2xForm); - autoConfigureChatRoom() { - /* Automatically configure groupchat based on this model's - * 'roomconfig' data. - * - * Returns a promise which resolves once a response IQ has - * been received. - */ - return new Promise((resolve, reject) => { - this.fetchRoomConfiguration().then(stanza => { - const configArray = [], - fields = stanza.querySelectorAll('field'), - config = this.get('roomconfig'); - let count = fields.length; + this.sendConfiguration(configArray, resolve, reject); + }); + }, - _.each(fields, field => { - const fieldname = field.getAttribute('var').replace('muc#roomconfig_', ''), - type = field.getAttribute('type'); - let value; + autoConfigureChatRoom() { + /* Automatically configure groupchat based on this model's + * 'roomconfig' data. + * + * Returns a promise which resolves once a response IQ has + * been received. + */ + return new Promise((resolve, reject) => { + this.fetchRoomConfiguration().then(stanza => { + const configArray = [], + fields = stanza.querySelectorAll('field'), + config = this.get('roomconfig'); + let count = fields.length; - if (fieldname in config) { - switch (type) { - case 'boolean': - value = config[fieldname] ? 1 : 0; - break; + _.each(fields, field => { + const fieldname = field.getAttribute('var').replace('muc#roomconfig_', ''), + type = field.getAttribute('type'); + let value; - case 'list-multi': - // TODO: we don't yet handle "list-multi" types - value = field.innerHTML; - break; + if (fieldname in config) { + switch (type) { + case 'boolean': + value = config[fieldname] ? 1 : 0; + break; - default: - value = config[fieldname]; - } + case 'list-multi': + // TODO: we don't yet handle "list-multi" types + value = field.innerHTML; + break; - field.innerHTML = $build('value').t(value); + default: + value = config[fieldname]; } - configArray.push(field); + field.innerHTML = $build('value').t(value); + } - if (! --count) { - this.sendConfiguration(configArray, resolve, reject); - } - }); + configArray.push(field); + + if (! --count) { + this.sendConfiguration(configArray, resolve, reject); + } }); }); - }, + }); + }, - fetchRoomConfiguration() { - /* Send an IQ stanza to fetch the groupchat configuration data. - * Returns a promise which resolves once the response IQ - * has been received. - */ - return new Promise((resolve, reject) => { - _converse.connection.sendIQ($iq({ - 'to': this.get('jid'), - 'type': "get" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }), resolve, reject); - }); - }, + fetchRoomConfiguration() { + /* Send an IQ stanza to fetch the groupchat configuration data. + * Returns a promise which resolves once the response IQ + * has been received. + */ + return new Promise((resolve, reject) => { + _converse.connection.sendIQ($iq({ + 'to': this.get('jid'), + 'type': "get" + }).c("query", { + xmlns: Strophe.NS.MUC_OWNER + }), resolve, reject); + }); + }, - sendConfiguration(config, callback, errback) { - /* Send an IQ stanza with the groupchat configuration. - * - * Parameters: - * (Array) config: The groupchat configuration - * (Function) callback: Callback upon succesful IQ response - * The first parameter passed in is IQ containing the - * groupchat configuration. - * The second is the response IQ from the server. - * (Function) errback: Callback upon error IQ response - * The first parameter passed in is IQ containing the - * groupchat configuration. - * The second is the response IQ from the server. - */ + sendConfiguration(config, callback, errback) { + /* Send an IQ stanza with the groupchat configuration. + * + * Parameters: + * (Array) config: The groupchat configuration + * (Function) callback: Callback upon succesful IQ response + * The first parameter passed in is IQ containing the + * groupchat configuration. + * The second is the response IQ from the server. + * (Function) errback: Callback upon error IQ response + * The first parameter passed in is IQ containing the + * groupchat configuration. + * The second is the response IQ from the server. + */ + const iq = $iq({ + to: this.get('jid'), + type: "set" + }).c("query", { + xmlns: Strophe.NS.MUC_OWNER + }).c("x", { + xmlns: Strophe.NS.XFORM, + type: "submit" + }); + + _.each(config || [], function (node) { + iq.cnode(node).up(); + }); + + callback = _.isUndefined(callback) ? _.noop : _.partial(callback, iq.nodeTree); + errback = _.isUndefined(errback) ? _.noop : _.partial(errback, iq.nodeTree); + return _converse.connection.sendIQ(iq, callback, errback); + }, + + saveAffiliationAndRole(pres) { + /* Parse the presence stanza for the current user's + * affiliation. + * + * Parameters: + * (XMLElement) pres: A stanza. + */ + const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop(); + const is_self = pres.querySelector("status[code='110']"); + + if (is_self && !_.isNil(item)) { + const affiliation = item.getAttribute('affiliation'); + const role = item.getAttribute('role'); + + if (affiliation) { + this.save({ + 'affiliation': affiliation + }); + } + + if (role) { + this.save({ + 'role': role + }); + } + } + }, + + sendAffiliationIQ(affiliation, member) { + /* Send an IQ stanza specifying an affiliation change. + * + * Paremeters: + * (String) affiliation: affiliation (could also be stored + * on the member object). + * (Object) member: Map containing the member's jid and + * optionally a reason and affiliation. + */ + return new Promise((resolve, reject) => { const iq = $iq({ to: this.get('jid'), type: "set" }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: Strophe.NS.XFORM, - type: "submit" + xmlns: Strophe.NS.MUC_ADMIN + }).c("item", { + 'affiliation': member.affiliation || affiliation, + 'nick': member.nick, + 'jid': member.jid }); - _.each(config || [], function (node) { - iq.cnode(node).up(); - }); - - callback = _.isUndefined(callback) ? _.noop : _.partial(callback, iq.nodeTree); - errback = _.isUndefined(errback) ? _.noop : _.partial(errback, iq.nodeTree); - return _converse.connection.sendIQ(iq, callback, errback); - }, - - saveAffiliationAndRole(pres) { - /* Parse the presence stanza for the current user's - * affiliation. - * - * Parameters: - * (XMLElement) pres: A stanza. - */ - const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop(); - const is_self = pres.querySelector("status[code='110']"); - - if (is_self && !_.isNil(item)) { - const affiliation = item.getAttribute('affiliation'); - const role = item.getAttribute('role'); - - if (affiliation) { - this.save({ - 'affiliation': affiliation - }); - } - - if (role) { - this.save({ - 'role': role - }); - } - } - }, - - sendAffiliationIQ(affiliation, member) { - /* Send an IQ stanza specifying an affiliation change. - * - * Paremeters: - * (String) affiliation: affiliation (could also be stored - * on the member object). - * (Object) member: Map containing the member's jid and - * optionally a reason and affiliation. - */ - return new Promise((resolve, reject) => { - const iq = $iq({ - to: this.get('jid'), - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_ADMIN - }).c("item", { - 'affiliation': member.affiliation || affiliation, - 'nick': member.nick, - 'jid': member.jid - }); - - if (!_.isUndefined(member.reason)) { - iq.c("reason", member.reason); - } - - _converse.connection.sendIQ(iq, resolve, reject); - }); - }, - - setAffiliations(members) { - /* Send IQ stanzas to the server to modify the - * affiliations in this groupchat. - * - * See: http://xmpp.org/extensions/xep-0045.html#modifymember - * - * Parameters: - * (Object) members: A map of jids, affiliations and optionally reasons - * (Function) onSuccess: callback for a succesful response - * (Function) onError: callback for an error response - */ - const affiliations = _.uniq(_.map(members, 'affiliation')); - - return Promise.all(_.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members))); - }, - - async getJidsWithAffiliations(affiliations) { - /* Returns a map of JIDs that have the affiliations - * as provided. - */ - if (_.isString(affiliations)) { - affiliations = [affiliations]; + if (!_.isUndefined(member.reason)) { + iq.c("reason", member.reason); } - const result = await Promise.all(affiliations.map(a => this.requestMemberList(a).then(iq => u.parseMemberListIQ(iq)).catch(iq => { - _converse.log(iq, Strophe.LogLevel.ERROR); - }))); - return [].concat.apply([], result).filter(p => p); - }, - - updateMemberLists(members, affiliations, deltaFunc) { - /* Fetch the lists of users with the given affiliations. - * Then compute the delta between those users and - * the passed in members, and if it exists, send the delta - * to the XMPP server to update the member list. - * - * Parameters: - * (Object) members: Map of member jids and affiliations. - * (String|Array) affiliation: An array of affiliations or - * a string if only one affiliation. - * (Function) deltaFunc: The function to compute the delta - * between old and new member lists. - * - * Returns: - * A promise which is resolved once the list has been - * updated or once it's been established there's no need - * to update the list. - */ - this.getJidsWithAffiliations(affiliations).then(old_members => this.setAffiliations(deltaFunc(members, old_members))).then(() => this.occupants.fetchMembers()).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - }, - - getDefaultNick() { - /* The default nickname (used when muc_nickname_from_jid is true) - * is the node part of the user's JID. - * We put this in a separate method so that it can be - * overridden by plugins. - */ - const nick = _converse.xmppstatus.vcard.get('nickname'); - - if (nick) { - return nick; - } else if (_converse.muc_nickname_from_jid) { - return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid)); - } - }, - - checkForReservedNick() { - /* Use service-discovery to ask the XMPP server whether - * this user has a reserved nickname for this groupchat. - * If so, we'll use that, otherwise we render the nickname form. - * - * Parameters: - * (Function) callback: Callback upon succesful IQ response - * (Function) errback: Callback upon error IQ response - */ - return _converse.api.sendIQ($iq({ - 'to': this.get('jid'), - 'from': _converse.connection.jid, - 'type': "get" - }).c("query", { - 'xmlns': Strophe.NS.DISCO_INFO, - 'node': 'x-roomuser-item' - })).then(iq => { - const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'), - nick = identity_el ? identity_el.getAttribute('name') : null; - this.save({ - 'reserved_nick': nick, - 'nick': nick - }, { - 'silent': true - }); - return iq; - }); - }, - - async registerNickname() { - // See https://xmpp.org/extensions/xep-0045.html#register - const nick = this.get('nick'), - jid = this.get('jid'); - let iq, err_msg; - - try { - iq = await _converse.api.sendIQ($iq({ - 'to': jid, - 'from': _converse.connection.jid, - 'type': 'get' - }).c('query', { - 'xmlns': Strophe.NS.MUC_REGISTER - })); - } catch (e) { - if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { - err_msg = __("You're not allowed to register yourself in this groupchat."); - } else if (sizzle('registration-required[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { - err_msg = __("You're not allowed to register in this groupchat because it's members-only."); - } - - _converse.log(e, Strophe.LogLevel.ERROR); - - return err_msg; - } - - const required_fields = sizzle('field required', iq).map(f => f.parentElement); - - if (required_fields.length > 1 && required_fields[0].getAttribute('var') !== 'muc#register_roomnick') { - return _converse.log(`Can't register the user register in the groupchat ${jid} due to the required fields`); - } - - try { - await _converse.api.sendIQ($iq({ - 'to': jid, - 'from': _converse.connection.jid, - 'type': 'set' - }).c('query', { - 'xmlns': Strophe.NS.MUC_REGISTER - }).c('x', { - 'xmlns': Strophe.NS.XFORM, - 'type': 'submit' - }).c('field', { - 'var': 'FORM_TYPE' - }).c('value').t('http://jabber.org/protocol/muc#register').up().up().c('field', { - 'var': 'muc#register_roomnick' - }).c('value').t(nick)); - } catch (e) { - if (sizzle('service-unavailable[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { - err_msg = __("Can't register your nickname in this groupchat, it doesn't support registration."); - } else if (sizzle('bad-request[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { - err_msg = __("Can't register your nickname in this groupchat, invalid data form supplied."); - } - - _converse.log(err_msg); - - _converse.log(e, Strophe.LogLevel.ERROR); - - return err_msg; - } - }, - - updateOccupantsOnPresence(pres) { - /* Given a presence stanza, update the occupant model - * based on its contents. - * - * Parameters: - * (XMLElement) pres: The presence stanza - */ - const data = this.parsePresence(pres); - - if (data.type === 'error' || !data.jid && !data.nick) { - return true; - } - - const occupant = this.occupants.findOccupant(data); - - if (data.type === 'unavailable' && occupant) { - if (!_.includes(data.states, converse.MUC_NICK_CHANGED_CODE) && !occupant.isMember()) { - // We only destroy the occupant if this is not a nickname change operation. - // and if they're not on the member lists. - // Before destroying we set the new data, so - // that we can show the disconnection message. - occupant.set(data); - occupant.destroy(); - return; - } - } - - const jid = Strophe.getBareJidFromJid(data.jid); - - const attributes = _.extend(data, { - 'jid': jid ? jid : undefined, - 'resource': data.jid ? Strophe.getResourceFromJid(data.jid) : undefined - }); - - if (occupant) { - occupant.save(attributes); - } else { - this.occupants.create(attributes); - } - }, - - parsePresence(pres) { - const from = pres.getAttribute("from"), - type = pres.getAttribute("type"), - data = { - 'from': from, - 'nick': Strophe.getResourceFromJid(from), - 'type': type, - 'states': [], - 'show': type !== 'unavailable' ? 'online' : 'offline' - }; - - _.each(pres.childNodes, function (child) { - switch (child.nodeName) { - case "status": - data.status = child.textContent || null; - break; - - case "show": - data.show = child.textContent || 'online'; - break; - - case "x": - if (child.getAttribute("xmlns") === Strophe.NS.MUC_USER) { - _.each(child.childNodes, function (item) { - switch (item.nodeName) { - case "item": - data.affiliation = item.getAttribute("affiliation"); - data.role = item.getAttribute("role"); - data.jid = item.getAttribute("jid"); - data.nick = item.getAttribute("nick") || data.nick; - break; - - case "status": - if (item.getAttribute("code")) { - data.states.push(item.getAttribute("code")); - } - - } - }); - } else if (child.getAttribute("xmlns") === Strophe.NS.VCARDUPDATE) { - data.image_hash = _.get(child.querySelector('photo'), 'textContent'); - } - - } - }); - - return data; - }, - - isDuplicate(message, original_stanza) { - const msgid = message.getAttribute('id'), - jid = message.getAttribute('from'); - - if (msgid) { - return this.messages.where({ - 'msgid': msgid, - 'from': jid - }).length; - } - - return false; - }, - - fetchFeaturesIfConfigurationChanged(stanza) { - const configuration_changed = stanza.querySelector("status[code='104']"), - logging_enabled = stanza.querySelector("status[code='170']"), - logging_disabled = stanza.querySelector("status[code='171']"), - room_no_longer_anon = stanza.querySelector("status[code='172']"), - room_now_semi_anon = stanza.querySelector("status[code='173']"), - room_now_fully_anon = stanza.querySelector("status[code='173']"); - - if (configuration_changed || logging_enabled || logging_disabled || room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) { - this.refreshRoomFeatures(); - } - }, - - onMessage(stanza) { - /* Handler for all MUC messages sent to this groupchat. - * - * Parameters: - * (XMLElement) stanza: The message stanza. - */ - this.fetchFeaturesIfConfigurationChanged(stanza); - const original_stanza = stanza, - forwarded = stanza.querySelector('forwarded'); - - if (!_.isNull(forwarded)) { - stanza = forwarded.querySelector('message'); - } - - if (this.isDuplicate(stanza, original_stanza)) { - return; - } - - const jid = stanza.getAttribute('from'), - resource = Strophe.getResourceFromJid(jid), - sender = resource && Strophe.unescapeNode(resource) || ''; - - if (!this.handleMessageCorrection(stanza)) { - if (sender === '') { - return; - } - - const subject_el = stanza.querySelector('subject'); - - if (subject_el) { - const subject = _.propertyOf(subject_el)('textContent') || ''; - u.safeSave(this, { - 'subject': { - 'author': sender, - 'text': subject - } - }); - } - - this.createMessage(stanza, original_stanza).then(msg => this.incrementUnreadMsgCounter(msg)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - - if (sender !== this.get('nick')) { - // We only emit an event if it's not our own message - _converse.emit('message', { - 'stanza': original_stanza, - 'chatbox': this - }); - } - }, - - onPresence(pres) { - /* Handles all MUC presence stanzas. - * - * Parameters: - * (XMLElement) pres: The stanza - */ - if (pres.getAttribute('type') === 'error') { - this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); - return; - } - - const is_self = pres.querySelector("status[code='110']"); - - if (is_self && pres.getAttribute('type') !== 'unavailable') { - this.onOwnPresence(pres); - } - - this.updateOccupantsOnPresence(pres); - - if (this.get('role') !== 'none' && this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) { - this.save('connection_status', converse.ROOMSTATUS.CONNECTED); - } - }, - - onOwnPresence(pres) { - /* Handles a received presence relating to the current - * user. - * - * For locked groupchats (which are by definition "new"), the - * groupchat will either be auto-configured or created instantly - * (with default config) or a configuration groupchat will be - * rendered. - * - * If the groupchat is not locked, then the groupchat will be - * auto-configured only if applicable and if the current - * user is the groupchat's owner. - * - * Parameters: - * (XMLElement) pres: The stanza - */ - this.saveAffiliationAndRole(pres); - const locked_room = pres.querySelector("status[code='201']"); - - if (locked_room) { - if (this.get('auto_configure')) { - this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures()); - } else if (_converse.muc_instant_rooms) { - // Accept default configuration - this.saveConfiguration().then(() => this.getRoomFeatures()); - } else { - this.trigger('configurationNeeded'); - return; // We haven't yet entered the groupchat, so bail here. - } - } else if (!this.get('features_fetched')) { - // The features for this groupchat weren't fetched. - // That must mean it's a new groupchat without locking - // (in which case Prosody doesn't send a 201 status), - // otherwise the features would have been fetched in - // the "initialize" method already. - if (this.get('affiliation') === 'owner' && this.get('auto_configure')) { - this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures()); - } else { - this.getRoomFeatures(); - } - } - - this.save('connection_status', converse.ROOMSTATUS.ENTERED); - }, - - isUserMentioned(message) { - /* Returns a boolean to indicate whether the current user - * was mentioned in a message. - * - * Parameters: - * (String): The text message - */ - const nick = this.get('nick'); - - if (message.get('references').length) { - const mentions = message.get('references').filter(ref => ref.type === 'mention').map(ref => ref.value); - return _.includes(mentions, nick); - } else { - return new RegExp(`\\b${nick}\\b`).test(message.get('message')); - } - }, - - incrementUnreadMsgCounter(message) { - /* Given a newly received message, update the unread counter if - * necessary. - * - * Parameters: - * (XMLElement): The stanza - */ - if (!message) { - return; - } - - const body = message.get('message'); - - if (_.isNil(body)) { - return; - } - - if (u.isNewMessage(message) && this.isHidden()) { - const settings = { - 'num_unread_general': this.get('num_unread_general') + 1 - }; - - if (this.isUserMentioned(message)) { - settings.num_unread = this.get('num_unread') + 1; - - _converse.incrementMsgCounter(); - } - - this.save(settings); - } - }, - - clearUnreadMsgCounter() { - u.safeSave(this, { - 'num_unread': 0, - 'num_unread_general': 0 - }); - } - - }); - _converse.ChatRoomOccupant = Backbone.Model.extend({ - defaults: { - 'show': 'offline' - }, - - initialize(attributes) { - this.set(_.extend({ - 'id': _converse.connection.getUniqueId() - }, attributes)); - this.on('change:image_hash', this.onAvatarChanged, this); - }, - - onAvatarChanged() { - const hash = this.get('image_hash'); - const vcards = []; - - if (this.get('jid')) { - vcards.push(_converse.vcards.findWhere({ - 'jid': this.get('jid') - })); - } - - vcards.push(_converse.vcards.findWhere({ - 'jid': this.get('from') - })); - - _.forEach(_.filter(vcards, undefined), vcard => { - if (hash && vcard.get('image_hash') !== hash) { - _converse.api.vcard.update(vcard); - } - }); - }, - - getDisplayName() { - return this.get('nick') || this.get('jid'); - }, - - isMember() { - return _.includes(['admin', 'owner', 'member'], this.get('affiliation')); - } - - }); - _converse.ChatRoomOccupants = Backbone.Collection.extend({ - model: _converse.ChatRoomOccupant, - - comparator(occupant1, occupant2) { - const role1 = occupant1.get('role') || 'none'; - const role2 = occupant2.get('role') || 'none'; - - if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) { - const nick1 = occupant1.getDisplayName().toLowerCase(); - const nick2 = occupant2.getDisplayName().toLowerCase(); - return nick1 < nick2 ? -1 : nick1 > nick2 ? 1 : 0; - } else { - return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1; - } - }, - - fetchMembers() { - this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin']).then(new_members => { - const new_jids = new_members.map(m => m.jid).filter(m => !_.isUndefined(m)), - new_nicks = new_members.map(m => !m.jid && m.nick || undefined).filter(m => !_.isUndefined(m)), - removed_members = this.filter(m => { - return f.includes(m.get('affiliation'), ['admin', 'member', 'owner']) && !f.includes(m.get('nick'), new_nicks) && !f.includes(m.get('jid'), new_jids); - }); - - _.each(removed_members, occupant => { - if (occupant.get('jid') === _converse.bare_jid) { - return; - } - - if (occupant.get('show') === 'offline') { - occupant.destroy(); - } - }); - - _.each(new_members, attrs => { - let occupant; - - if (attrs.jid) { - occupant = this.findOccupant({ - 'jid': attrs.jid - }); - } else { - occupant = this.findOccupant({ - 'nick': attrs.nick - }); - } - - if (occupant) { - occupant.save(attrs); - } else { - this.create(attrs); - } - }); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - }, - - findOccupant(data) { - /* Try to find an existing occupant based on the passed in - * data object. - * - * If we have a JID, we use that as lookup variable, - * otherwise we use the nick. We don't always have both, - * but should have at least one or the other. - */ - const jid = Strophe.getBareJidFromJid(data.jid); - - if (jid !== null) { - return this.where({ - 'jid': jid - }).pop(); - } else { - return this.where({ - 'nick': data.nick - }).pop(); - } - } - - }); - _converse.RoomsPanelModel = Backbone.Model.extend({ - defaults: { - 'muc_domain': '' - } - }); - - _converse.onDirectMUCInvitation = function (message) { - /* A direct MUC invitation to join a groupchat has been received - * See XEP-0249: Direct MUC invitations. + _converse.connection.sendIQ(iq, resolve, reject); + }); + }, + + setAffiliations(members) { + /* Send IQ stanzas to the server to modify the + * affiliations in this groupchat. + * + * See: http://xmpp.org/extensions/xep-0045.html#modifymember * * Parameters: - * (XMLElement) message: The message stanza containing the - * invitation. + * (Object) members: A map of jids, affiliations and optionally reasons + * (Function) onSuccess: callback for a succesful response + * (Function) onError: callback for an error response */ - const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(), - from = Strophe.getBareJidFromJid(message.getAttribute('from')), - room_jid = x_el.getAttribute('jid'), - reason = x_el.getAttribute('reason'); + const affiliations = _.uniq(_.map(members, 'affiliation')); - let contact = _converse.roster.get(from), - result; + return Promise.all(_.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members))); + }, - if (_converse.auto_join_on_invite) { - result = true; - } else { - // Invite request might come from someone not your roster list - contact = contact ? contact.get('fullname') : Strophe.getNodeFromJid(from); - - if (!reason) { - result = confirm(__("%1$s has invited you to join a groupchat: %2$s", contact, room_jid)); - } else { - result = confirm(__('%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"', contact, room_jid, reason)); - } + async getJidsWithAffiliations(affiliations) { + /* Returns a map of JIDs that have the affiliations + * as provided. + */ + if (_.isString(affiliations)) { + affiliations = [affiliations]; } - if (result === true) { - const chatroom = _converse.openChatRoom(room_jid, { - 'password': x_el.getAttribute('password') + const result = await Promise.all(affiliations.map(a => this.requestMemberList(a).then(iq => _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].parseMemberListIQ(iq)).catch(iq => { + _converse.log(iq, Strophe.LogLevel.ERROR); + }))); + return [].concat.apply([], result).filter(p => p); + }, + + updateMemberLists(members, affiliations, deltaFunc) { + /* Fetch the lists of users with the given affiliations. + * Then compute the delta between those users and + * the passed in members, and if it exists, send the delta + * to the XMPP server to update the member list. + * + * Parameters: + * (Object) members: Map of member jids and affiliations. + * (String|Array) affiliation: An array of affiliations or + * a string if only one affiliation. + * (Function) deltaFunc: The function to compute the delta + * between old and new member lists. + * + * Returns: + * A promise which is resolved once the list has been + * updated or once it's been established there's no need + * to update the list. + */ + this.getJidsWithAffiliations(affiliations).then(old_members => this.setAffiliations(deltaFunc(members, old_members))).then(() => this.occupants.fetchMembers()).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }, + + getDefaultNick() { + /* The default nickname (used when muc_nickname_from_jid is true) + * is the node part of the user's JID. + * We put this in a separate method so that it can be + * overridden by plugins. + */ + const nick = _converse.xmppstatus.vcard.get('nickname'); + + if (nick) { + return nick; + } else if (_converse.muc_nickname_from_jid) { + return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid)); + } + }, + + checkForReservedNick() { + /* Use service-discovery to ask the XMPP server whether + * this user has a reserved nickname for this groupchat. + * If so, we'll use that, otherwise we render the nickname form. + * + * Parameters: + * (Function) callback: Callback upon succesful IQ response + * (Function) errback: Callback upon error IQ response + */ + return _converse.api.sendIQ($iq({ + 'to': this.get('jid'), + 'from': _converse.connection.jid, + 'type': "get" + }).c("query", { + 'xmlns': Strophe.NS.DISCO_INFO, + 'node': 'x-roomuser-item' + })).then(iq => { + const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'), + nick = identity_el ? identity_el.getAttribute('name') : null; + this.save({ + 'reserved_nick': nick, + 'nick': nick + }, { + 'silent': true }); + return iq; + }); + }, - if (chatroom.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) { - _converse.chatboxviews.get(room_jid).join(); + async registerNickname() { + // See https://xmpp.org/extensions/xep-0045.html#register + const nick = this.get('nick'), + jid = this.get('jid'); + let iq, err_msg; + + try { + iq = await _converse.api.sendIQ($iq({ + 'to': jid, + 'from': _converse.connection.jid, + 'type': 'get' + }).c('query', { + 'xmlns': Strophe.NS.MUC_REGISTER + })); + } catch (e) { + if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { + err_msg = __("You're not allowed to register yourself in this groupchat."); + } else if (sizzle('registration-required[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { + err_msg = __("You're not allowed to register in this groupchat because it's members-only."); + } + + _converse.log(e, Strophe.LogLevel.ERROR); + + return err_msg; + } + + const required_fields = sizzle('field required', iq).map(f => f.parentElement); + + if (required_fields.length > 1 && required_fields[0].getAttribute('var') !== 'muc#register_roomnick') { + return _converse.log(`Can't register the user register in the groupchat ${jid} due to the required fields`); + } + + try { + await _converse.api.sendIQ($iq({ + 'to': jid, + 'from': _converse.connection.jid, + 'type': 'set' + }).c('query', { + 'xmlns': Strophe.NS.MUC_REGISTER + }).c('x', { + 'xmlns': Strophe.NS.XFORM, + 'type': 'submit' + }).c('field', { + 'var': 'FORM_TYPE' + }).c('value').t('http://jabber.org/protocol/muc#register').up().up().c('field', { + 'var': 'muc#register_roomnick' + }).c('value').t(nick)); + } catch (e) { + if (sizzle('service-unavailable[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { + err_msg = __("Can't register your nickname in this groupchat, it doesn't support registration."); + } else if (sizzle('bad-request[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) { + err_msg = __("Can't register your nickname in this groupchat, invalid data form supplied."); + } + + _converse.log(err_msg); + + _converse.log(e, Strophe.LogLevel.ERROR); + + return err_msg; + } + }, + + updateOccupantsOnPresence(pres) { + /* Given a presence stanza, update the occupant model + * based on its contents. + * + * Parameters: + * (XMLElement) pres: The presence stanza + */ + const data = this.parsePresence(pres); + + if (data.type === 'error' || !data.jid && !data.nick) { + return true; + } + + const occupant = this.occupants.findOccupant(data); + + if (data.type === 'unavailable' && occupant) { + if (!_.includes(data.states, _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].MUC_NICK_CHANGED_CODE) && !occupant.isMember()) { + // We only destroy the occupant if this is not a nickname change operation. + // and if they're not on the member lists. + // Before destroying we set the new data, so + // that we can show the disconnection message. + occupant.set(data); + occupant.destroy(); + return; } } - }; - if (_converse.allow_muc_invitations) { - const registerDirectInvitationHandler = function registerDirectInvitationHandler() { - _converse.connection.addHandler(message => { - _converse.onDirectMUCInvitation(message); + const jid = Strophe.getBareJidFromJid(data.jid); - return true; - }, 'jabber:x:conference', 'message'); + const attributes = _.extend(data, { + 'jid': jid ? jid : undefined, + 'resource': data.jid ? Strophe.getResourceFromJid(data.jid) : undefined + }); + + if (occupant) { + occupant.save(attributes); + } else { + this.occupants.create(attributes); + } + }, + + parsePresence(pres) { + const from = pres.getAttribute("from"), + type = pres.getAttribute("type"), + data = { + 'from': from, + 'nick': Strophe.getResourceFromJid(from), + 'type': type, + 'states': [], + 'show': type !== 'unavailable' ? 'online' : 'offline' }; - _converse.on('connected', registerDirectInvitationHandler); + _.each(pres.childNodes, function (child) { + switch (child.nodeName) { + case "status": + data.status = child.textContent || null; + break; - _converse.on('reconnected', registerDirectInvitationHandler); - } + case "show": + data.show = child.textContent || 'online'; + break; - const getChatRoom = function getChatRoom(jid, attrs, create) { - jid = jid.toLowerCase(); - attrs.type = _converse.CHATROOMS_TYPE; - attrs.id = jid; - attrs.box_id = b64_sha1(jid); - return _converse.chatboxes.getChatBox(jid, attrs, create); - }; + case "x": + if (child.getAttribute("xmlns") === Strophe.NS.MUC_USER) { + _.each(child.childNodes, function (item) { + switch (item.nodeName) { + case "item": + data.affiliation = item.getAttribute("affiliation"); + data.role = item.getAttribute("role"); + data.jid = item.getAttribute("jid"); + data.nick = item.getAttribute("nick") || data.nick; + break; - const createChatRoom = function createChatRoom(jid, attrs) { - if (jid.startsWith('xmpp:') && jid.endsWith('?join')) { - jid = jid.replace(/^xmpp:/, '').replace(/\?join$/, ''); + case "status": + if (item.getAttribute("code")) { + data.states.push(item.getAttribute("code")); + } + + } + }); + } else if (child.getAttribute("xmlns") === Strophe.NS.VCARDUPDATE) { + data.image_hash = _.get(child.querySelector('photo'), 'textContent'); + } + + } + }); + + return data; + }, + + isDuplicate(message, original_stanza) { + const msgid = message.getAttribute('id'), + jid = message.getAttribute('from'); + + if (msgid) { + return this.messages.where({ + 'msgid': msgid, + 'from': jid + }).length; } - return getChatRoom(jid, attrs, true); - }; + return false; + }, - function autoJoinRooms() { - /* Automatically join groupchats, based on the - * "auto_join_rooms" configuration setting, which is an array - * of strings (groupchat JIDs) or objects (with groupchat JID and other - * settings). + fetchFeaturesIfConfigurationChanged(stanza) { + const configuration_changed = stanza.querySelector("status[code='104']"), + logging_enabled = stanza.querySelector("status[code='170']"), + logging_disabled = stanza.querySelector("status[code='171']"), + room_no_longer_anon = stanza.querySelector("status[code='172']"), + room_now_semi_anon = stanza.querySelector("status[code='173']"), + room_now_fully_anon = stanza.querySelector("status[code='173']"); + + if (configuration_changed || logging_enabled || logging_disabled || room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) { + this.refreshRoomFeatures(); + } + }, + + onMessage(stanza) { + /* Handler for all MUC messages sent to this groupchat. + * + * Parameters: + * (XMLElement) stanza: The message stanza. */ - _.each(_converse.auto_join_rooms, function (groupchat) { - if (_converse.chatboxes.where({ - 'jid': groupchat - }).length) { + this.fetchFeaturesIfConfigurationChanged(stanza); + const original_stanza = stanza, + forwarded = stanza.querySelector('forwarded'); + + if (!_.isNull(forwarded)) { + stanza = forwarded.querySelector('message'); + } + + if (this.isDuplicate(stanza, original_stanza)) { + return; + } + + const jid = stanza.getAttribute('from'), + resource = Strophe.getResourceFromJid(jid), + sender = resource && Strophe.unescapeNode(resource) || ''; + + if (!this.handleMessageCorrection(stanza)) { + if (sender === '') { return; } - if (_.isString(groupchat)) { - _converse.api.rooms.open(groupchat); - } else if (_.isObject(groupchat)) { - _converse.api.rooms.open(groupchat.jid, groupchat.nick); - } else { - _converse.log('Invalid groupchat criteria specified for "auto_join_rooms"', Strophe.LogLevel.ERROR); - } - }); + const subject_el = stanza.querySelector('subject'); - _converse.emit('roomsAutoJoined'); - } - - function disconnectChatRooms() { - /* When disconnecting, mark all groupchats as - * disconnected, so that they will be properly entered again - * when fetched from session storage. - */ - _converse.chatboxes.each(function (model) { - if (model.get('type') === _converse.CHATROOMS_TYPE) { - model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); - } - }); - } - - function fetchRegistrationForm(room_jid, user_jid) { - _converse.api.sendIQ($iq({ - 'from': user_jid, - 'to': room_jid, - 'type': 'get' - }).c('query', { - 'xmlns': Strophe.NS.REGISTER - })).then(iq => {}).catch(iq => { - if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', iq).length) { - this.feedback.set('error', __(`Error: the groupchat ${this.model.getDisplayName()} does not exist.`)); - } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { - this.feedback.set('error', __(`Sorry, you're not allowed to register in this groupchat`)); - } - }); - } - /************************ BEGIN Event Handlers ************************/ - - - _converse.on('addClientFeatures', () => { - if (_converse.allow_muc) { - _converse.api.disco.own.features.add(Strophe.NS.MUC); - } - - if (_converse.allow_muc_invitations) { - _converse.api.disco.own.features.add('jabber:x:conference'); // Invites - - } - }); - - _converse.api.listen.on('chatBoxesFetched', autoJoinRooms); - - _converse.api.listen.on('disconnecting', disconnectChatRooms); - - _converse.api.listen.on('statusInitialized', () => { - // XXX: For websocket connections, we disconnect from all - // chatrooms when the page reloads. This is a workaround for - // issue #1111 and should be removed once we support XEP-0198 - const options = { - 'once': true, - 'passive': true - }; - window.addEventListener(_converse.unloadevent, () => { - if (_converse.connection._proto instanceof Strophe.Websocket) { - disconnectChatRooms(); - } - }); - }); - /************************ END Event Handlers ************************/ - - /************************ BEGIN API ************************/ - // We extend the default converse.js API to add methods specific to MUC groupchats. - - - _.extend(_converse.api, { - /** - * The "rooms" namespace groups methods relevant to chatrooms - * (aka groupchats). - * - * @namespace _converse.api.rooms - * @memberOf _converse.api - */ - 'rooms': { - /** - * Creates a new MUC chatroom (aka groupchat) - * - * Similar to {@link _converse.api.rooms.open}, but creates - * the chatroom in the background (i.e. doesn't cause a - * view to open). - * - * @method _converse.api.rooms.create - * @param {(string[]|string)} jid|jids The JID or array of - * JIDs of the chatroom(s) to create - * @param {object} [attrs] attrs The room attributes - */ - 'create'(jids, attrs) { - if (_.isString(attrs)) { - attrs = { - 'nick': attrs - }; - } else if (_.isUndefined(attrs)) { - attrs = {}; - } - - if (_.isUndefined(attrs.maximize)) { - attrs.maximize = false; - } - - if (!attrs.nick && _converse.muc_nickname_from_jid) { - attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid); - } - - if (_.isUndefined(jids)) { - throw new TypeError('rooms.create: You need to provide at least one JID'); - } else if (_.isString(jids)) { - return createChatRoom(jids, attrs); - } - - return _.map(jids, _.partial(createChatRoom, _, attrs)); - }, - - /** - * Opens a MUC chatroom (aka groupchat) - * - * Similar to {@link _converse.api.chats.open}, but for groupchats. - * - * @method _converse.api.rooms.open - * @param {string} jid The room JID or JIDs (if not specified, all - * currently open rooms will be returned). - * @param {string} attrs A map containing any extra room attributes. - * @param {string} [attrs.nick] The current user's nickname for the MUC - * @param {boolean} [attrs.auto_configure] A boolean, indicating - * whether the room should be configured automatically or not. - * If set to `true`, then it makes sense to pass in configuration settings. - * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets - * configured automatically. Currently it doesn't make sense to specify - * `roomconfig` values if `auto_configure` is set to `false`. - * For a list of configuration values that can be passed in, refer to these values - * in the [XEP-0045 MUC specification](http://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner). - * The values should be named without the `muc#roomconfig_` prefix. - * @param {boolean} [attrs.maximize] A boolean, indicating whether minimized rooms should also be - * maximized, when opened. Set to `false` by default. - * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be - * brought to the foreground and therefore replace the currently shown chat. - * If there is no chat currently open, then this option is ineffective. - * - * @example - * this._converse.api.rooms.open('group@muc.example.com') - * - * @example - * // To return an array of rooms, provide an array of room JIDs: - * _converse.api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com']) - * - * @example - * // To setup a custom nickname when joining the room, provide the optional nick argument: - * _converse.api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'}) - * - * @example - * // For example, opening a room with a specific default configuration: - * _converse.api.rooms.open( - * 'myroom@conference.example.org', - * { 'nick': 'coolguy69', - * 'auto_configure': true, - * 'roomconfig': { - * 'changesubject': false, - * 'membersonly': true, - * 'persistentroom': true, - * 'publicroom': true, - * 'roomdesc': 'Comfy room for hanging out', - * 'whois': 'anyone' - * } - * }, - * true - * ); - */ - 'open'(jids, attrs) { - return new Promise((resolve, reject) => { - _converse.api.waitUntil('chatBoxesFetched').then(() => { - if (_.isUndefined(jids)) { - const err_msg = 'rooms.open: You need to provide at least one JID'; - - _converse.log(err_msg, Strophe.LogLevel.ERROR); - - reject(new TypeError(err_msg)); - } else if (_.isString(jids)) { - resolve(_converse.api.rooms.create(jids, attrs).trigger('show')); - } else { - resolve(_.map(jids, jid => _converse.api.rooms.create(jid, attrs).trigger('show'))); - } - }); + if (subject_el) { + const subject = _.propertyOf(subject_el)('textContent') || ''; + _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(this, { + 'subject': { + 'author': sender, + 'text': subject + } }); - }, - - /** - * Returns an object representing a MUC chatroom (aka groupchat) - * - * @method _converse.api.rooms.get - * @param {string} [jid] The room JID (if not specified, all rooms will be returned). - * @param {object} attrs A map containing any extra room attributes For example, if you want - * to specify the nickname, use `{'nick': 'bloodninja'}`. Previously (before - * version 1.0.7, the second parameter only accepted the nickname (as a string - * value). This is currently still accepted, but then you can't pass in any - * other room attributes. If the nickname is not specified then the node part of - * the user's JID will be used. - * @param {boolean} create A boolean indicating whether the room should be created - * if not found (default: `false`) - * @example - * _converse.api.waitUntil('roomsAutoJoined').then(() => { - * const create_if_not_found = true; - * _converse.api.rooms.get( - * 'group@muc.example.com', - * {'nick': 'dread-pirate-roberts'}, - * create_if_not_found - * ) - * }); - */ - 'get'(jids, attrs, create) { - if (_.isString(attrs)) { - attrs = { - 'nick': attrs - }; - } else if (_.isUndefined(attrs)) { - attrs = {}; - } - - if (_.isUndefined(jids)) { - const result = []; - - _converse.chatboxes.each(function (chatbox) { - if (chatbox.get('type') === _converse.CHATROOMS_TYPE) { - result.push(chatbox); - } - }); - - return result; - } - - if (!attrs.nick) { - attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid); - } - - if (_.isString(jids)) { - return getChatRoom(jids, attrs); - } - - return _.map(jids, _.partial(getChatRoom, _, attrs)); } + this.createMessage(stanza, original_stanza).then(msg => this.incrementUnreadMsgCounter(msg)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); } - }); - /************************ END API ************************/ + if (sender !== this.get('nick')) { + // We only emit an event if it's not our own message + _converse.emit('message', { + 'stanza': original_stanza, + 'chatbox': this + }); + } + }, + + onPresence(pres) { + /* Handles all MUC presence stanzas. + * + * Parameters: + * (XMLElement) pres: The stanza + */ + if (pres.getAttribute('type') === 'error') { + this.save('connection_status', _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.DISCONNECTED); + return; + } + + const is_self = pres.querySelector("status[code='110']"); + + if (is_self && pres.getAttribute('type') !== 'unavailable') { + this.onOwnPresence(pres); + } + + this.updateOccupantsOnPresence(pres); + + if (this.get('role') !== 'none' && this.get('connection_status') === _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.CONNECTING) { + this.save('connection_status', _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.CONNECTED); + } + }, + + onOwnPresence(pres) { + /* Handles a received presence relating to the current + * user. + * + * For locked groupchats (which are by definition "new"), the + * groupchat will either be auto-configured or created instantly + * (with default config) or a configuration groupchat will be + * rendered. + * + * If the groupchat is not locked, then the groupchat will be + * auto-configured only if applicable and if the current + * user is the groupchat's owner. + * + * Parameters: + * (XMLElement) pres: The stanza + */ + this.saveAffiliationAndRole(pres); + const locked_room = pres.querySelector("status[code='201']"); + + if (locked_room) { + if (this.get('auto_configure')) { + this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures()); + } else if (_converse.muc_instant_rooms) { + // Accept default configuration + this.saveConfiguration().then(() => this.getRoomFeatures()); + } else { + this.trigger('configurationNeeded'); + return; // We haven't yet entered the groupchat, so bail here. + } + } else if (!this.get('features_fetched')) { + // The features for this groupchat weren't fetched. + // That must mean it's a new groupchat without locking + // (in which case Prosody doesn't send a 201 status), + // otherwise the features would have been fetched in + // the "initialize" method already. + if (this.get('affiliation') === 'owner' && this.get('auto_configure')) { + this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures()); + } else { + this.getRoomFeatures(); + } + } + + this.save('connection_status', _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.ENTERED); + }, + + isUserMentioned(message) { + /* Returns a boolean to indicate whether the current user + * was mentioned in a message. + * + * Parameters: + * (String): The text message + */ + const nick = this.get('nick'); + + if (message.get('references').length) { + const mentions = message.get('references').filter(ref => ref.type === 'mention').map(ref => ref.value); + return _.includes(mentions, nick); + } else { + return new RegExp(`\\b${nick}\\b`).test(message.get('message')); + } + }, + + incrementUnreadMsgCounter(message) { + /* Given a newly received message, update the unread counter if + * necessary. + * + * Parameters: + * (XMLElement): The stanza + */ + if (!message) { + return; + } + + const body = message.get('message'); + + if (_.isNil(body)) { + return; + } + + if (_utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].isNewMessage(message) && this.isHidden()) { + const settings = { + 'num_unread_general': this.get('num_unread_general') + 1 + }; + + if (this.isUserMentioned(message)) { + settings.num_unread = this.get('num_unread') + 1; + + _converse.incrementMsgCounter(); + } + + this.save(settings); + } + }, + + clearUnreadMsgCounter() { + _utils_form__WEBPACK_IMPORTED_MODULE_7__["default"].safeSave(this, { + 'num_unread': 0, + 'num_unread_general': 0 + }); + } + + }); + _converse.ChatRoomOccupant = Backbone.Model.extend({ + defaults: { + 'show': 'offline' + }, + + initialize(attributes) { + this.set(_.extend({ + 'id': _converse.connection.getUniqueId() + }, attributes)); + this.on('change:image_hash', this.onAvatarChanged, this); + }, + + onAvatarChanged() { + const hash = this.get('image_hash'); + const vcards = []; + + if (this.get('jid')) { + vcards.push(_converse.vcards.findWhere({ + 'jid': this.get('jid') + })); + } + + vcards.push(_converse.vcards.findWhere({ + 'jid': this.get('from') + })); + + _.forEach(_.filter(vcards, undefined), vcard => { + if (hash && vcard.get('image_hash') !== hash) { + _converse.api.vcard.update(vcard); + } + }); + }, + + getDisplayName() { + return this.get('nick') || this.get('jid'); + }, + + isMember() { + return _.includes(['admin', 'owner', 'member'], this.get('affiliation')); + } + + }); + _converse.ChatRoomOccupants = Backbone.Collection.extend({ + model: _converse.ChatRoomOccupant, + + comparator(occupant1, occupant2) { + const role1 = occupant1.get('role') || 'none'; + const role2 = occupant2.get('role') || 'none'; + + if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) { + const nick1 = occupant1.getDisplayName().toLowerCase(); + const nick2 = occupant2.getDisplayName().toLowerCase(); + return nick1 < nick2 ? -1 : nick1 > nick2 ? 1 : 0; + } else { + return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1; + } + }, + + fetchMembers() { + this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin']).then(new_members => { + const new_jids = new_members.map(m => m.jid).filter(m => !_.isUndefined(m)), + new_nicks = new_members.map(m => !m.jid && m.nick || undefined).filter(m => !_.isUndefined(m)), + removed_members = this.filter(m => { + return f.includes(m.get('affiliation'), ['admin', 'member', 'owner']) && !f.includes(m.get('nick'), new_nicks) && !f.includes(m.get('jid'), new_jids); + }); + + _.each(removed_members, occupant => { + if (occupant.get('jid') === _converse.bare_jid) { + return; + } + + if (occupant.get('show') === 'offline') { + occupant.destroy(); + } + }); + + _.each(new_members, attrs => { + let occupant; + + if (attrs.jid) { + occupant = this.findOccupant({ + 'jid': attrs.jid + }); + } else { + occupant = this.findOccupant({ + 'nick': attrs.nick + }); + } + + if (occupant) { + occupant.save(attrs); + } else { + this.create(attrs); + } + }); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }, + + findOccupant(data) { + /* Try to find an existing occupant based on the passed in + * data object. + * + * If we have a JID, we use that as lookup variable, + * otherwise we use the nick. We don't always have both, + * but should have at least one or the other. + */ + const jid = Strophe.getBareJidFromJid(data.jid); + + if (jid !== null) { + return this.where({ + 'jid': jid + }).pop(); + } else { + return this.where({ + 'nick': data.nick + }).pop(); + } + } + + }); + _converse.RoomsPanelModel = Backbone.Model.extend({ + defaults: { + 'muc_domain': '' + } + }); + + _converse.onDirectMUCInvitation = function (message) { + /* A direct MUC invitation to join a groupchat has been received + * See XEP-0249: Direct MUC invitations. + * + * Parameters: + * (XMLElement) message: The message stanza containing the + * invitation. + */ + const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(), + from = Strophe.getBareJidFromJid(message.getAttribute('from')), + room_jid = x_el.getAttribute('jid'), + reason = x_el.getAttribute('reason'); + + let contact = _converse.roster.get(from), + result; + + if (_converse.auto_join_on_invite) { + result = true; + } else { + // Invite request might come from someone not your roster list + contact = contact ? contact.get('fullname') : Strophe.getNodeFromJid(from); + + if (!reason) { + result = confirm(__("%1$s has invited you to join a groupchat: %2$s", contact, room_jid)); + } else { + result = confirm(__('%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"', contact, room_jid, reason)); + } + } + + if (result === true) { + const chatroom = _converse.openChatRoom(room_jid, { + 'password': x_el.getAttribute('password') + }); + + if (chatroom.get('connection_status') === _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.DISCONNECTED) { + _converse.chatboxviews.get(room_jid).join(); + } + } + }; + + if (_converse.allow_muc_invitations) { + const registerDirectInvitationHandler = function registerDirectInvitationHandler() { + _converse.connection.addHandler(message => { + _converse.onDirectMUCInvitation(message); + + return true; + }, 'jabber:x:conference', 'message'); + }; + + _converse.on('connected', registerDirectInvitationHandler); + + _converse.on('reconnected', registerDirectInvitationHandler); } - }); + const getChatRoom = function getChatRoom(jid, attrs, create) { + jid = jid.toLowerCase(); + attrs.type = _converse.CHATROOMS_TYPE; + attrs.id = jid; + attrs.box_id = b64_sha1(jid); + return _converse.chatboxes.getChatBox(jid, attrs, create); + }; + + const createChatRoom = function createChatRoom(jid, attrs) { + if (jid.startsWith('xmpp:') && jid.endsWith('?join')) { + jid = jid.replace(/^xmpp:/, '').replace(/\?join$/, ''); + } + + return getChatRoom(jid, attrs, true); + }; + + function autoJoinRooms() { + /* Automatically join groupchats, based on the + * "auto_join_rooms" configuration setting, which is an array + * of strings (groupchat JIDs) or objects (with groupchat JID and other + * settings). + */ + _.each(_converse.auto_join_rooms, function (groupchat) { + if (_converse.chatboxes.where({ + 'jid': groupchat + }).length) { + return; + } + + if (_.isString(groupchat)) { + _converse.api.rooms.open(groupchat); + } else if (_.isObject(groupchat)) { + _converse.api.rooms.open(groupchat.jid, groupchat.nick); + } else { + _converse.log('Invalid groupchat criteria specified for "auto_join_rooms"', Strophe.LogLevel.ERROR); + } + }); + + _converse.emit('roomsAutoJoined'); + } + + function disconnectChatRooms() { + /* When disconnecting, mark all groupchats as + * disconnected, so that they will be properly entered again + * when fetched from session storage. + */ + _converse.chatboxes.each(function (model) { + if (model.get('type') === _converse.CHATROOMS_TYPE) { + model.save('connection_status', _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].ROOMSTATUS.DISCONNECTED); + } + }); + } + + function fetchRegistrationForm(room_jid, user_jid) { + _converse.api.sendIQ($iq({ + 'from': user_jid, + 'to': room_jid, + 'type': 'get' + }).c('query', { + 'xmlns': Strophe.NS.REGISTER + })).then(iq => {}).catch(iq => { + if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', iq).length) { + this.feedback.set('error', __(`Error: the groupchat ${this.model.getDisplayName()} does not exist.`)); + } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { + this.feedback.set('error', __(`Sorry, you're not allowed to register in this groupchat`)); + } + }); + } + /************************ BEGIN Event Handlers ************************/ + + + _converse.on('addClientFeatures', () => { + if (_converse.allow_muc) { + _converse.api.disco.own.features.add(Strophe.NS.MUC); + } + + if (_converse.allow_muc_invitations) { + _converse.api.disco.own.features.add('jabber:x:conference'); // Invites + + } + }); + + _converse.api.listen.on('chatBoxesFetched', autoJoinRooms); + + _converse.api.listen.on('disconnecting', disconnectChatRooms); + + _converse.api.listen.on('statusInitialized', () => { + // XXX: For websocket connections, we disconnect from all + // chatrooms when the page reloads. This is a workaround for + // issue #1111 and should be removed once we support XEP-0198 + const options = { + 'once': true, + 'passive': true + }; + window.addEventListener(_converse.unloadevent, () => { + if (_converse.connection._proto instanceof Strophe.Websocket) { + disconnectChatRooms(); + } + }); + }); + /************************ END Event Handlers ************************/ + + /************************ BEGIN API ************************/ + // We extend the default converse.js API to add methods specific to MUC groupchats. + + + _.extend(_converse.api, { + /** + * The "rooms" namespace groups methods relevant to chatrooms + * (aka groupchats). + * + * @namespace _converse.api.rooms + * @memberOf _converse.api + */ + 'rooms': { + /** + * Creates a new MUC chatroom (aka groupchat) + * + * Similar to {@link _converse.api.rooms.open}, but creates + * the chatroom in the background (i.e. doesn't cause a + * view to open). + * + * @method _converse.api.rooms.create + * @param {(string[]|string)} jid|jids The JID or array of + * JIDs of the chatroom(s) to create + * @param {object} [attrs] attrs The room attributes + */ + 'create'(jids, attrs) { + if (_.isString(attrs)) { + attrs = { + 'nick': attrs + }; + } else if (_.isUndefined(attrs)) { + attrs = {}; + } + + if (_.isUndefined(attrs.maximize)) { + attrs.maximize = false; + } + + if (!attrs.nick && _converse.muc_nickname_from_jid) { + attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid); + } + + if (_.isUndefined(jids)) { + throw new TypeError('rooms.create: You need to provide at least one JID'); + } else if (_.isString(jids)) { + return createChatRoom(jids, attrs); + } + + return _.map(jids, _.partial(createChatRoom, _, attrs)); + }, + + /** + * Opens a MUC chatroom (aka groupchat) + * + * Similar to {@link _converse.api.chats.open}, but for groupchats. + * + * @method _converse.api.rooms.open + * @param {string} jid The room JID or JIDs (if not specified, all + * currently open rooms will be returned). + * @param {string} attrs A map containing any extra room attributes. + * @param {string} [attrs.nick] The current user's nickname for the MUC + * @param {boolean} [attrs.auto_configure] A boolean, indicating + * whether the room should be configured automatically or not. + * If set to `true`, then it makes sense to pass in configuration settings. + * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets + * configured automatically. Currently it doesn't make sense to specify + * `roomconfig` values if `auto_configure` is set to `false`. + * For a list of configuration values that can be passed in, refer to these values + * in the [XEP-0045 MUC specification](http://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner). + * The values should be named without the `muc#roomconfig_` prefix. + * @param {boolean} [attrs.maximize] A boolean, indicating whether minimized rooms should also be + * maximized, when opened. Set to `false` by default. + * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be + * brought to the foreground and therefore replace the currently shown chat. + * If there is no chat currently open, then this option is ineffective. + * + * @example + * this._converse.api.rooms.open('group@muc.example.com') + * + * @example + * // To return an array of rooms, provide an array of room JIDs: + * _converse.api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com']) + * + * @example + * // To setup a custom nickname when joining the room, provide the optional nick argument: + * _converse.api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'}) + * + * @example + * // For example, opening a room with a specific default configuration: + * _converse.api.rooms.open( + * 'myroom@conference.example.org', + * { 'nick': 'coolguy69', + * 'auto_configure': true, + * 'roomconfig': { + * 'changesubject': false, + * 'membersonly': true, + * 'persistentroom': true, + * 'publicroom': true, + * 'roomdesc': 'Comfy room for hanging out', + * 'whois': 'anyone' + * } + * }, + * true + * ); + */ + 'open': async function open(jids, attrs) { + await _converse.api.waitUntil('chatBoxesFetched'); + + if (_.isUndefined(jids)) { + const err_msg = 'rooms.open: You need to provide at least one JID'; + + _converse.log(err_msg, Strophe.LogLevel.ERROR); + + throw new TypeError(err_msg); + } else if (_.isString(jids)) { + return _converse.api.rooms.create(jids, attrs).trigger('show'); + } else { + return _.map(jids, jid => _converse.api.rooms.create(jid, attrs).trigger('show')); + } + }, + + /** + * Returns an object representing a MUC chatroom (aka groupchat) + * + * @method _converse.api.rooms.get + * @param {string} [jid] The room JID (if not specified, all rooms will be returned). + * @param {object} attrs A map containing any extra room attributes For example, if you want + * to specify the nickname, use `{'nick': 'bloodninja'}`. Previously (before + * version 1.0.7, the second parameter only accepted the nickname (as a string + * value). This is currently still accepted, but then you can't pass in any + * other room attributes. If the nickname is not specified then the node part of + * the user's JID will be used. + * @param {boolean} create A boolean indicating whether the room should be created + * if not found (default: `false`) + * @example + * _converse.api.waitUntil('roomsAutoJoined').then(() => { + * const create_if_not_found = true; + * _converse.api.rooms.get( + * 'group@muc.example.com', + * {'nick': 'dread-pirate-roberts'}, + * create_if_not_found + * ) + * }); + */ + 'get'(jids, attrs, create) { + if (_.isString(attrs)) { + attrs = { + 'nick': attrs + }; + } else if (_.isUndefined(attrs)) { + attrs = {}; + } + + if (_.isUndefined(jids)) { + const result = []; + + _converse.chatboxes.each(function (chatbox) { + if (chatbox.get('type') === _converse.CHATROOMS_TYPE) { + result.push(chatbox); + } + }); + + return result; + } + + if (!attrs.nick) { + attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid); + } + + if (_.isString(jids)) { + return getChatRoom(jids, attrs); + } + + return _.map(jids, _.partial(getChatRoom, _, attrs)); + } + + } + }); + /************************ END API ************************/ + + } + }); /***/ }), @@ -77298,10 +77569,15 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } /*!***************************************!*\ !*** ./src/headless/converse-ping.js ***! \***************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var strophejs_plugin_ping__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! strophejs-plugin-ping */ "./node_modules/strophejs-plugin-ping/strophe.ping.js"); +/* harmony import */ var strophejs_plugin_ping__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(strophejs_plugin_ping__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _converse_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"); +// Converse.js // https://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers @@ -77310,118 +77586,112 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /* This is a Converse.js plugin which add support for application-level pings * as specified in XEP-0199 XMPP Ping. */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! strophejs-plugin-ping */ "./node_modules/strophejs-plugin-ping/strophe.ping.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse) { - "use strict"; // Strophe methods for building stanzas - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - _ = _converse$env._; - converse.plugins.add('converse-ping', { - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse; + // Strophe methods for building stanzas - _converse.api.settings.update({ - ping_interval: 180 //in seconds +const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].env, + Strophe = _converse$env.Strophe, + _ = _converse$env._; +_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins.add('converse-ping', { + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse; - }); + _converse.api.settings.update({ + ping_interval: 180 //in seconds - _converse.ping = function (jid, success, error, timeout) { - // XXX: We could first check here if the server advertised that - // it supports PING. - // However, some servers don't advertise while still keeping the - // connection option due to pings. - // - // var feature = _converse.disco_entities[_converse.domain].features.findWhere({'var': Strophe.NS.PING}); - _converse.lastStanzaDate = new Date(); + }); - if (_.isNil(jid)) { - jid = Strophe.getDomainFromJid(_converse.bare_jid); - } + _converse.ping = function (jid, success, error, timeout) { + // XXX: We could first check here if the server advertised that + // it supports PING. + // However, some servers don't advertise while still keeping the + // connection option due to pings. + // + // var feature = _converse.disco_entities[_converse.domain].features.findWhere({'var': Strophe.NS.PING}); + _converse.lastStanzaDate = new Date(); - if (_.isUndefined(timeout)) { - timeout = null; - } + if (_.isNil(jid)) { + jid = Strophe.getDomainFromJid(_converse.bare_jid); + } - if (_.isUndefined(success)) { - success = null; - } + if (_.isUndefined(timeout)) { + timeout = null; + } - if (_.isUndefined(error)) { - error = null; - } + if (_.isUndefined(success)) { + success = null; + } - if (_converse.connection) { - _converse.connection.ping.ping(jid, success, error, timeout); + if (_.isUndefined(error)) { + error = null; + } - return true; - } - - return false; - }; - - _converse.pong = function (ping) { - _converse.lastStanzaDate = new Date(); - - _converse.connection.ping.pong(ping); + if (_converse.connection) { + _converse.connection.ping.ping(jid, success, error, timeout); return true; - }; + } - _converse.registerPongHandler = function () { - if (!_.isUndefined(_converse.connection.disco)) { - _converse.api.disco.own.features.add(Strophe.NS.PING); - } + return false; + }; - _converse.connection.ping.addPingHandler(_converse.pong); - }; + _converse.pong = function (ping) { + _converse.lastStanzaDate = new Date(); - _converse.registerPingHandler = function () { - _converse.registerPongHandler(); + _converse.connection.ping.pong(ping); - if (_converse.ping_interval > 0) { - _converse.connection.addHandler(function () { - /* Handler on each stanza, saves the received date - * in order to ping only when needed. - */ - _converse.lastStanzaDate = new Date(); - return true; - }); + return true; + }; - _converse.connection.addTimedHandler(1000, function () { - const now = new Date(); + _converse.registerPongHandler = function () { + if (!_.isUndefined(_converse.connection.disco)) { + _converse.api.disco.own.features.add(Strophe.NS.PING); + } - if (!_converse.lastStanzaDate) { - _converse.lastStanzaDate = now; - } + _converse.connection.ping.addPingHandler(_converse.pong); + }; - if ((now - _converse.lastStanzaDate) / 1000 > _converse.ping_interval) { - return _converse.ping(); - } + _converse.registerPingHandler = function () { + _converse.registerPongHandler(); - return true; - }); - } - }; + if (_converse.ping_interval > 0) { + _converse.connection.addHandler(function () { + /* Handler on each stanza, saves the received date + * in order to ping only when needed. + */ + _converse.lastStanzaDate = new Date(); + return true; + }); - const onConnected = function onConnected() { - // Wrapper so that we can spy on registerPingHandler in tests - _converse.registerPingHandler(); - }; + _converse.connection.addTimedHandler(1000, function () { + const now = new Date(); - _converse.on('connected', onConnected); + if (!_converse.lastStanzaDate) { + _converse.lastStanzaDate = now; + } - _converse.on('reconnected', onConnected); - } + if ((now - _converse.lastStanzaDate) / 1000 > _converse.ping_interval) { + return _converse.ping(); + } + + return true; + }); + } + }; + + const onConnected = function onConnected() { + // Wrapper so that we can spy on registerPingHandler in tests + _converse.registerPingHandler(); + }; + + _converse.on('connected', onConnected); + + _converse.on('reconnected', onConnected); + } - }); }); /***/ }), @@ -77430,268 +77700,266 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!****************************************!*\ !*** ./src/headless/converse-vcard.js ***! \****************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var _templates_vcard_html__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./templates/vcard.html */ "./src/headless/templates/vcard.html"); +/* harmony import */ var _templates_vcard_html__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_templates_vcard_html__WEBPACK_IMPORTED_MODULE_1__); +// Converse.js // http://conversejs.org // // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ./converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! ./templates/vcard.html */ "./src/headless/templates/vcard.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, tpl_vcard) { - "use strict"; - const _converse$env = converse.env, - Backbone = _converse$env.Backbone, - Promise = _converse$env.Promise, - Strophe = _converse$env.Strophe, - _ = _converse$env._, - $iq = _converse$env.$iq, - $build = _converse$env.$build, - b64_sha1 = _converse$env.b64_sha1, - moment = _converse$env.moment, - sizzle = _converse$env.sizzle; - const u = converse.env.utils; - converse.plugins.add('converse-vcard', { - initialize() { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const _converse = this._converse; - _converse.VCard = Backbone.Model.extend({ - defaults: { - 'image': _converse.DEFAULT_IMAGE, - 'image_type': _converse.DEFAULT_IMAGE_TYPE - }, - set(key, val, options) { - // Override Backbone.Model.prototype.set to make sure that the - // default `image` and `image_type` values are maintained. - let attrs; +const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Backbone = _converse$env.Backbone, + Promise = _converse$env.Promise, + Strophe = _converse$env.Strophe, + _ = _converse$env._, + $iq = _converse$env.$iq, + $build = _converse$env.$build, + b64_sha1 = _converse$env.b64_sha1, + moment = _converse$env.moment, + sizzle = _converse$env.sizzle; +const u = _converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env.utils; +_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-vcard', { + initialize() { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const _converse = this._converse; + _converse.VCard = Backbone.Model.extend({ + defaults: { + 'image': _converse.DEFAULT_IMAGE, + 'image_type': _converse.DEFAULT_IMAGE_TYPE + }, - if (typeof key === 'object') { - attrs = key; - options = val; - } else { - (attrs = {})[key] = val; - } + set(key, val, options) { + // Override Backbone.Model.prototype.set to make sure that the + // default `image` and `image_type` values are maintained. + let attrs; - if (_.has(attrs, 'image') && !attrs['image']) { - attrs['image'] = _converse.DEFAULT_IMAGE; - attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE; - return Backbone.Model.prototype.set.call(this, attrs, options); - } else { - return Backbone.Model.prototype.set.apply(this, arguments); - } - } - - }); - _converse.VCards = Backbone.Collection.extend({ - model: _converse.VCard, - - initialize() { - this.on('add', vcard => _converse.api.vcard.update(vcard)); - } - - }); - - function onVCardData(jid, iq, callback) { - const vcard = iq.querySelector('vCard'); - let result = {}; - - if (!_.isNull(vcard)) { - result = { - 'stanza': iq, - 'fullname': _.get(vcard.querySelector('FN'), 'textContent'), - 'nickname': _.get(vcard.querySelector('NICKNAME'), 'textContent'), - 'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'), - 'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'), - 'url': _.get(vcard.querySelector('URL'), 'textContent'), - 'role': _.get(vcard.querySelector('ROLE'), 'textContent'), - 'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent'), - 'vcard_updated': moment().format(), - 'vcard_error': undefined - }; - } - - if (result.image) { - const buffer = u.base64ToArrayBuffer(result['image']); - crypto.subtle.digest('SHA-1', buffer).then(ab => { - result['image_hash'] = u.arrayBufferToHex(ab); - if (callback) callback(result); - }); + if (typeof key === 'object') { + attrs = key; + options = val; } else { + (attrs = {})[key] = val; + } + + if (_.has(attrs, 'image') && !attrs['image']) { + attrs['image'] = _converse.DEFAULT_IMAGE; + attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE; + return Backbone.Model.prototype.set.call(this, attrs, options); + } else { + return Backbone.Model.prototype.set.apply(this, arguments); + } + } + + }); + _converse.VCards = Backbone.Collection.extend({ + model: _converse.VCard, + + initialize() { + this.on('add', vcard => _converse.api.vcard.update(vcard)); + } + + }); + + function onVCardData(jid, iq, callback) { + const vcard = iq.querySelector('vCard'); + let result = {}; + + if (!_.isNull(vcard)) { + result = { + 'stanza': iq, + 'fullname': _.get(vcard.querySelector('FN'), 'textContent'), + 'nickname': _.get(vcard.querySelector('NICKNAME'), 'textContent'), + 'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'), + 'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'), + 'url': _.get(vcard.querySelector('URL'), 'textContent'), + 'role': _.get(vcard.querySelector('ROLE'), 'textContent'), + 'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent'), + 'vcard_updated': moment().format(), + 'vcard_error': undefined + }; + } + + if (result.image) { + const buffer = u.base64ToArrayBuffer(result['image']); + crypto.subtle.digest('SHA-1', buffer).then(ab => { + result['image_hash'] = u.arrayBufferToHex(ab); if (callback) callback(result); - } - } - - function onVCardError(jid, iq, errback) { - if (errback) { - errback({ - 'stanza': iq, - 'jid': jid, - 'vcard_error': moment().format() - }); - } - } - - function createStanza(type, jid, vcard_el) { - const iq = $iq(jid ? { - 'type': type, - 'to': jid - } : { - 'type': type }); - - if (!vcard_el) { - iq.c("vCard", { - 'xmlns': Strophe.NS.VCARD - }); - } else { - iq.cnode(vcard_el); - } - - return iq; + } else { + if (callback) callback(result); } - - function setVCard(jid, data) { - if (!jid) { - throw Error("No jid provided for the VCard data"); - } - - const vcard_el = Strophe.xmlHtmlNode(tpl_vcard(data)).firstElementChild; - return _converse.api.sendIQ(createStanza("set", jid, vcard_el)); - } - - function getVCard(_converse, jid) { - /* Request the VCard of another user. Returns a promise. - * - * Parameters: - * (String) jid - The Jabber ID of the user whose VCard - * is being requested. - */ - const to = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid; - return new Promise((resolve, reject) => { - _converse.connection.sendIQ(createStanza("get", to), _.partial(onVCardData, jid, _, resolve), _.partial(onVCardError, jid, _, resolve), _converse.IQ_TIMEOUT); - }); - } - /* Event handlers */ - - - _converse.initVCardCollection = function () { - _converse.vcards = new _converse.VCards(); - const id = b64_sha1(`converse.vcards`); - _converse.vcards.browserStorage = new Backbone.BrowserStorage[_converse.config.get('storage')](id); - - _converse.vcards.fetch(); - }; - - _converse.api.listen.on('sessionInitialized', _converse.initVCardCollection); - - _converse.on('addClientFeatures', () => { - _converse.api.disco.own.features.add(Strophe.NS.VCARD); - }); - - _.extend(_converse.api, { - /** - * The XEP-0054 VCard API - * - * This API lets you access and update user VCards - * - * @namespace _converse.api.vcard - * @memberOf _converse.api - */ - 'vcard': { - /** - * Enables setting new values for a VCard. - * - * @method _converse.api.vcard.set - * @param {string} jid The JID for which the VCard should be set - * @param {object} data A map of VCard keys and values - * @example - * _converse.api.vcard.set({ - * 'jid': _converse.bare_jid, - * 'fn': 'John Doe', - * 'nickname': 'jdoe' - * }).then(() => { - * // Succes - * }).catch(() => { - * // Failure - * }). - */ - 'set'(jid, data) { - return setVCard(jid, data); - }, - - /** - * @method _converse.api.vcard.get - * @param {Backbone.Model|string} model Either a `Backbone.Model` instance, or a string JID. - * If a `Backbone.Model` instance is passed in, then it must have either a `jid` - * attribute or a `muc_jid` attribute. - * @param {boolean} [force] A boolean indicating whether the vcard should be - * fetched even if it's been fetched before. - * @returns {promise} A Promise which resolves with the VCard data for a particular JID or for - * a `Backbone.Model` instance which represents an entity with a JID (such as a roster contact, - * chat or chatroom occupant). - * - * @example - * _converse.api.waitUntil('rosterContactsFetched').then(() => { - * _converse.api.vcard.get('someone@example.org').then( - * (vcard) => { - * // Do something with the vcard... - * } - * ); - * }); - */ - 'get'(model, force) { - if (_.isString(model)) { - return getVCard(_converse, model); - } else if (force || !model.get('vcard_updated') || !moment(model.get('vcard_error')).isSame(new Date(), "day")) { - const jid = model.get('jid'); - - if (!jid) { - throw new Error("No JID to get vcard for!"); - } - - return getVCard(_converse, jid); - } else { - return Promise.resolve({}); - } - }, - - /** - * Fetches the VCard associated with a particular `Backbone.Model` instance - * (by using its `jid` or `muc_jid` attribute) and then updates the model with the - * returned VCard data. - * - * @method _converse.api.vcard.update - * @param {Backbone.Model} model A `Backbone.Model` instance - * @param {boolean} [force] A boolean indicating whether the vcard should be - * fetched again even if it's been fetched before. - * @returns {promise} A promise which resolves once the update has completed. - * @example - * _converse.api.waitUntil('rosterContactsFetched').then(() => { - * const chatbox = _converse.chatboxes.getChatBox('someone@example.org'); - * _converse.api.vcard.update(chatbox); - * }); - */ - 'update'(model, force) { - return this.get(model, force).then(vcard => { - delete vcard['stanza']; - model.save(vcard); - }); - } - - } - }); } - }); + function onVCardError(jid, iq, errback) { + if (errback) { + errback({ + 'stanza': iq, + 'jid': jid, + 'vcard_error': moment().format() + }); + } + } + + function createStanza(type, jid, vcard_el) { + const iq = $iq(jid ? { + 'type': type, + 'to': jid + } : { + 'type': type + }); + + if (!vcard_el) { + iq.c("vCard", { + 'xmlns': Strophe.NS.VCARD + }); + } else { + iq.cnode(vcard_el); + } + + return iq; + } + + function setVCard(jid, data) { + if (!jid) { + throw Error("No jid provided for the VCard data"); + } + + const vcard_el = Strophe.xmlHtmlNode(_templates_vcard_html__WEBPACK_IMPORTED_MODULE_1___default()(data)).firstElementChild; + return _converse.api.sendIQ(createStanza("set", jid, vcard_el)); + } + + function getVCard(_converse, jid) { + /* Request the VCard of another user. Returns a promise. + * + * Parameters: + * (String) jid - The Jabber ID of the user whose VCard + * is being requested. + */ + const to = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid; + return new Promise((resolve, reject) => { + _converse.connection.sendIQ(createStanza("get", to), _.partial(onVCardData, jid, _, resolve), _.partial(onVCardError, jid, _, resolve), _converse.IQ_TIMEOUT); + }); + } + /* Event handlers */ + + + _converse.initVCardCollection = function () { + _converse.vcards = new _converse.VCards(); + const id = b64_sha1(`converse.vcards`); + _converse.vcards.browserStorage = new Backbone.BrowserStorage[_converse.config.get('storage')](id); + + _converse.vcards.fetch(); + }; + + _converse.api.listen.on('sessionInitialized', _converse.initVCardCollection); + + _converse.on('addClientFeatures', () => { + _converse.api.disco.own.features.add(Strophe.NS.VCARD); + }); + + _.extend(_converse.api, { + /** + * The XEP-0054 VCard API + * + * This API lets you access and update user VCards + * + * @namespace _converse.api.vcard + * @memberOf _converse.api + */ + 'vcard': { + /** + * Enables setting new values for a VCard. + * + * @method _converse.api.vcard.set + * @param {string} jid The JID for which the VCard should be set + * @param {object} data A map of VCard keys and values + * @example + * _converse.api.vcard.set({ + * 'jid': _converse.bare_jid, + * 'fn': 'John Doe', + * 'nickname': 'jdoe' + * }).then(() => { + * // Succes + * }).catch(() => { + * // Failure + * }). + */ + 'set'(jid, data) { + return setVCard(jid, data); + }, + + /** + * @method _converse.api.vcard.get + * @param {Backbone.Model|string} model Either a `Backbone.Model` instance, or a string JID. + * If a `Backbone.Model` instance is passed in, then it must have either a `jid` + * attribute or a `muc_jid` attribute. + * @param {boolean} [force] A boolean indicating whether the vcard should be + * fetched even if it's been fetched before. + * @returns {promise} A Promise which resolves with the VCard data for a particular JID or for + * a `Backbone.Model` instance which represents an entity with a JID (such as a roster contact, + * chat or chatroom occupant). + * + * @example + * _converse.api.waitUntil('rosterContactsFetched').then(() => { + * _converse.api.vcard.get('someone@example.org').then( + * (vcard) => { + * // Do something with the vcard... + * } + * ); + * }); + */ + 'get'(model, force) { + if (_.isString(model)) { + return getVCard(_converse, model); + } else if (force || !model.get('vcard_updated') || !moment(model.get('vcard_error')).isSame(new Date(), "day")) { + const jid = model.get('jid'); + + if (!jid) { + throw new Error("No JID to get vcard for!"); + } + + return getVCard(_converse, jid); + } else { + return Promise.resolve({}); + } + }, + + /** + * Fetches the VCard associated with a particular `Backbone.Model` instance + * (by using its `jid` or `muc_jid` attribute) and then updates the model with the + * returned VCard data. + * + * @method _converse.api.vcard.update + * @param {Backbone.Model} model A `Backbone.Model` instance + * @param {boolean} [force] A boolean indicating whether the vcard should be + * fetched again even if it's been fetched before. + * @returns {promise} A promise which resolves once the update has completed. + * @example + * _converse.api.waitUntil('rosterContactsFetched').then(() => { + * const chatbox = _converse.chatboxes.getChatBox('someone@example.org'); + * _converse.api.vcard.update(chatbox); + * }); + */ + 'update'(model, force) { + return this.get(model, force).then(vcard => { + delete vcard['stanza']; + model.save(vcard); + }); + } + + } + }); + } + }); /***/ }), @@ -78031,21986 +78299,22 @@ return __p /*!************************************!*\ !*** ./src/headless/utils/core.js ***! \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) -// http://conversejs.org -// -// This is the utilities module. -// -// Copyright (c) 2012-2017, Jan-Carel Brand -// Licensed under the Mozilla Public License (MPLv2) -// - -/*global define, escape, window, Uint8Array */ -(function (root, factory) { - if (true) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"), __webpack_require__(/*! es6-promise/dist/es6-promise.auto */ "./node_modules/es6-promise/dist/es6-promise.auto.js"), __webpack_require__(/*! ../lodash.noconflict */ "./src/headless/lodash.noconflict.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"), __webpack_require__(/*! strophe.js */ "./node_modules/strophe.js/dist/strophe.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else {} -})(this, function (sizzle, Promise, _, Backbone, Strophe) { - "use strict"; - - Strophe = Strophe.Strophe; - const u = {}; - - u.getLongestSubstring = function (string, candidates) { - function reducer(accumulator, current_value) { - if (string.startsWith(current_value)) { - if (current_value.length > accumulator.length) { - return current_value; - } else { - return accumulator; - } - } else { - return accumulator; - } - } - - return candidates.reduce(reducer, ''); - }; - - u.prefixMentions = function (message) { - /* Given a message object, return its text with @ chars - * inserted before the mentioned nicknames. - */ - let text = message.get('message'); - (message.get('references') || []).sort((a, b) => b.begin - a.begin).forEach(ref => { - text = `${text.slice(0, ref.begin)}@${text.slice(ref.begin)}`; - }); - return text; - }; - - u.isValidJID = function (jid) { - return _.compact(jid.split('@')).length === 2 && !jid.startsWith('@') && !jid.endsWith('@'); - }; - - u.isValidMUCJID = function (jid) { - return !jid.startsWith('@') && !jid.endsWith('@'); - }; - - u.isSameBareJID = function (jid1, jid2) { - return Strophe.getBareJidFromJid(jid1).toLowerCase() === Strophe.getBareJidFromJid(jid2).toLowerCase(); - }; - - u.getMostRecentMessage = function (model) { - const messages = model.messages.filter('message'); - return messages[messages.length - 1]; - }; - - u.isNewMessage = function (message) { - /* Given a stanza, determine whether it's a new - * message, i.e. not a MAM archived one. - */ - if (message instanceof Element) { - return !(sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, message).length && sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, message).length); - } else { - return !(message.get('is_delayed') && message.get('is_archived')); - } - }; - - u.isOnlyChatStateNotification = function (attrs) { - if (attrs instanceof Backbone.Model) { - attrs = attrs.attributes; - } - - return attrs['chat_state'] && !attrs['oob_url'] && !attrs['file'] && !(attrs['is_encrypted'] && attrs['plaintext']) && !attrs['message']; - }; - - u.isHeadlineMessage = function (_converse, message) { - var from_jid = message.getAttribute('from'); - - if (message.getAttribute('type') === 'headline') { - return true; - } - - const chatbox = _converse.chatboxes.get(Strophe.getBareJidFromJid(from_jid)); - - if (chatbox && chatbox.get('type') === 'chatroom') { - return false; - } - - if (message.getAttribute('type') !== 'error' && !_.isNil(from_jid) && !_.includes(from_jid, '@')) { - // Some servers (I'm looking at you Prosody) don't set the message - // type to "headline" when sending server messages. For now we - // check if an @ signal is included, and if not, we assume it's - // a headline message. - return true; - } - - return false; - }; - - u.merge = function merge(first, second) { - /* Merge the second object into the first one. - */ - for (var k in second) { - if (_.isObject(first[k])) { - merge(first[k], second[k]); - } else { - first[k] = second[k]; - } - } - }; - - u.applyUserSettings = function applyUserSettings(context, settings, user_settings) { - /* Configuration settings might be nested objects. We only want to - * add settings which are whitelisted. - */ - for (var k in settings) { - if (_.isUndefined(user_settings[k])) { - continue; - } - - if (_.isObject(settings[k]) && !_.isArray(settings[k])) { - applyUserSettings(context[k], settings[k], user_settings[k]); - } else { - context[k] = user_settings[k]; - } - } - }; - - u.stringToNode = function (s) { - /* Converts an HTML string into a DOM Node. - * Expects that the HTML string has only one top-level element, - * i.e. not multiple ones. - * - * Parameters: - * (String) s - The HTML string - */ - var div = document.createElement('div'); - div.innerHTML = s; - return div.firstElementChild; - }; - - u.getOuterWidth = function (el, include_margin = false) { - var width = el.offsetWidth; - - if (!include_margin) { - return width; - } - - var style = window.getComputedStyle(el); - width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); - return width; - }; - - u.stringToElement = function (s) { - /* Converts an HTML string into a DOM element. - * Expects that the HTML string has only one top-level element, - * i.e. not multiple ones. - * - * Parameters: - * (String) s - The HTML string - */ - var div = document.createElement('div'); - div.innerHTML = s; - return div.firstElementChild; - }; - - u.matchesSelector = function (el, selector) { - /* Checks whether the DOM element matches the given selector. - * - * Parameters: - * (DOMElement) el - The DOM element - * (String) selector - The selector - */ - return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); - }; - - u.queryChildren = function (el, selector) { - /* Returns a list of children of the DOM element that match the - * selector. - * - * Parameters: - * (DOMElement) el - the DOM element - * (String) selector - the selector they should be matched - * against. - */ - return _.filter(el.childNodes, _.partial(u.matchesSelector, _, selector)); - }; - - u.contains = function (attr, query) { - return function (item) { - if (typeof attr === 'object') { - var value = false; - - _.forEach(attr, function (a) { - value = value || _.includes(item.get(a).toLowerCase(), query.toLowerCase()); - }); - - return value; - } else if (typeof attr === 'string') { - return _.includes(item.get(attr).toLowerCase(), query.toLowerCase()); - } else { - throw new TypeError('contains: wrong attribute type. Must be string or array.'); - } - }; - }; - - u.isOfType = function (type, item) { - return item.get('type') == type; - }; - - u.isInstance = function (type, item) { - return item instanceof type; - }; - - u.getAttribute = function (key, item) { - return item.get(key); - }; - - u.contains.not = function (attr, query) { - return function (item) { - return !u.contains(attr, query)(item); - }; - }; - - u.rootContains = function (root, el) { - // The document element does not have the contains method in IE. - if (root === document && !root.contains) { - return document.head.contains(el) || document.body.contains(el); - } - - return root.contains ? root.contains(el) : window.HTMLElement.prototype.contains.call(root, el); - }; - - u.createFragmentFromText = function (markup) { - /* Returns a DocumentFragment containing DOM nodes based on the - * passed-in markup text. - */ - // http://stackoverflow.com/questions/9334645/create-node-from-markup-string - var frag = document.createDocumentFragment(), - tmp = document.createElement('body'), - child; - tmp.innerHTML = markup; // Append elements in a loop to a DocumentFragment, so that the - // browser does not re-render the document for each node. - - while (child = tmp.firstChild) { - // eslint-disable-line no-cond-assign - frag.appendChild(child); - } - - return frag; - }; - - u.isPersistableModel = function (model) { - return model.collection && model.collection.browserStorage; - }; - - u.getResolveablePromise = function () { - /* Returns a promise object on which `resolve` or `reject` can be - * called. - */ - const wrapper = {}; - const promise = new Promise((resolve, reject) => { - wrapper.resolve = resolve; - wrapper.reject = reject; - }); - - _.assign(promise, wrapper); - - return promise; - }; - - u.interpolate = function (string, o) { - return string.replace(/{{{([^{}]*)}}}/g, (a, b) => { - var r = o[b]; - return typeof r === 'string' || typeof r === 'number' ? r : a; - }); - }; - - u.onMultipleEvents = function (events = [], callback) { - /* Call the callback once all the events have been triggered - * - * Parameters: - * (Array) events: An array of objects, with keys `object` and - * `event`, representing the event name and the object it's - * triggered upon. - * (Function) callback: The function to call once all events have - * been triggered. - */ - let triggered = []; - - function handler(result) { - triggered.push(result); - - if (events.length === triggered.length) { - callback(triggered); - triggered = []; - } - } - - _.each(events, map => map.object.on(map.event, handler)); - }; - - u.safeSave = function (model, attributes) { - if (u.isPersistableModel(model)) { - model.save(attributes); - } else { - model.set(attributes); - } - }; - - u.siblingIndex = function (el) { - /* eslint-disable no-cond-assign */ - for (var i = 0; el = el.previousElementSibling; i++); - - return i; - }; - - u.getCurrentWord = function (input) { - const cursor = input.selectionEnd || undefined; - return _.last(input.value.slice(0, cursor).split(' ')); - }; - - u.replaceCurrentWord = function (input, new_value) { - const cursor = input.selectionEnd || undefined, - current_word = _.last(input.value.slice(0, cursor).split(' ')), - value = input.value; - - input.value = value.slice(0, cursor - current_word.length) + `${new_value} ` + value.slice(cursor); - input.selectionEnd = cursor - current_word.length + new_value.length + 1; - }; - - u.isVisible = function (el) { - if (u.hasClass('hidden', el)) { - return false; - } // XXX: Taken from jQuery's "visible" implementation - - - return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0; - }; - - u.triggerEvent = function (el, name, type = "Event", bubbles = true, cancelable = true) { - const evt = document.createEvent(type); - evt.initEvent(name, bubbles, cancelable); - el.dispatchEvent(evt); - }; - - u.geoUriToHttp = function (text, geouri_replacement) { - const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g; - return text.replace(regex, geouri_replacement); - }; - - u.httpToGeoUri = function (text, _converse) { - const replacement = 'geo:$1,$2'; - return text.replace(_converse.geouri_regex, replacement); - }; - - u.getSelectValues = function (select) { - const result = []; - const options = select && select.options; - - for (var i = 0, iLen = options.length; i < iLen; i++) { - const opt = options[i]; - - if (opt.selected) { - result.push(opt.value || opt.text); - } - } - - return result; - }; - - u.formatFingerprint = function (fp) { - fp = fp.replace(/^05/, ''); - const arr = []; - - for (let i = 1; i < 8; i++) { - const idx = i * 8 + i - 1; - fp = fp.slice(0, idx) + ' ' + fp.slice(idx); - } - - return fp; - }; - - u.appendArrayBuffer = function (buffer1, buffer2) { - const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); - tmp.set(new Uint8Array(buffer1), 0); - tmp.set(new Uint8Array(buffer2), buffer1.byteLength); - return tmp.buffer; - }; - - u.arrayBufferToHex = function (ab) { - // https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex#40031979 - return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join(''); - }; - - u.arrayBufferToString = function (ab) { - return new TextDecoder("utf-8").decode(ab); - }; - - u.stringToArrayBuffer = function (string) { - const bytes = new TextEncoder("utf-8").encode(string); - return bytes.buffer; - }; - - u.arrayBufferToBase64 = function (ab) { - return btoa(new Uint8Array(ab).reduce((data, byte) => data + String.fromCharCode(byte), '')); - }; - - u.base64ToArrayBuffer = function (b64) { - const binary_string = window.atob(b64), - len = binary_string.length, - bytes = new Uint8Array(len); - - for (let i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - - return bytes.buffer; - }; - - u.getRandomInt = function (max) { - return Math.floor(Math.random() * Math.floor(max)); - }; - - u.putCurserAtEnd = function (textarea) { - if (textarea !== document.activeElement) { - textarea.focus(); - } // Double the length because Opera is inconsistent about whether a carriage return is one character or two. - - - const len = textarea.value.length * 2; // Timeout seems to be required for Blink - - setTimeout(() => textarea.setSelectionRange(len, len), 1); // Scroll to the bottom, in case we're in a tall textarea - // (Necessary for Firefox and Chrome) - - this.scrollTop = 999999; - }; - - u.getUniqueId = function () { - return 'xxxxxxxx-xxxx'.replace(/[x]/g, function (c) { - var r = Math.random() * 16 | 0, - v = c === 'x' ? r : r & 0x3 | 0x8; - return v.toString(16); - }); - }; - - return u; -}); - -/***/ }), - -/***/ "./src/headless/utils/emoji.js": -/*!*************************************!*\ - !*** ./src/headless/utils/emoji.js ***! - \*************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ../lodash.noconflict */ "./src/headless/lodash.noconflict.js"), __webpack_require__(/*! ./core */ "./src/headless/utils/core.js"), __webpack_require__(/*! twemoji */ "./node_modules/twemoji/2/esm.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (_, u, twemoji) { - "use strict"; - - const emoji_list = { - ":kiss_mm:": { - "uc_base": "1f468-2764-1f48b-1f468", - "uc_output": "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", - "uc_match": "1f468-2764-fe0f-1f48b-1f468", - "uc_greedy": "1f468-2764-1f48b-1f468", - "shortnames": [":couplekiss_mm:"], - "category": "people" - }, - ":kiss_woman_man:": { - "uc_base": "1f469-2764-1f48b-1f468", - "uc_output": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f468", - "uc_match": "1f469-2764-fe0f-1f48b-1f468", - "uc_greedy": "1f469-2764-1f48b-1f468", - "shortnames": [], - "category": "people" - }, - ":kiss_ww:": { - "uc_base": "1f469-2764-1f48b-1f469", - "uc_output": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", - "uc_match": "1f469-2764-fe0f-1f48b-1f469", - "uc_greedy": "1f469-2764-1f48b-1f469", - "shortnames": [":couplekiss_ww:"], - "category": "people" - }, - ":england:": { - "uc_base": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", - "uc_output": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", - "uc_match": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", - "uc_greedy": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", - "shortnames": [], - "category": "flags" - }, - ":scotland:": { - "uc_base": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", - "uc_output": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", - "uc_match": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", - "uc_greedy": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", - "shortnames": [], - "category": "flags" - }, - ":wales:": { - "uc_base": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", - "uc_output": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", - "uc_match": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", - "uc_greedy": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", - "shortnames": [], - "category": "flags" - }, - ":family_mmbb:": { - "uc_base": "1f468-1f468-1f466-1f466", - "uc_output": "1f468-200d-1f468-200d-1f466-200d-1f466", - "uc_match": "1f468-1f468-1f466-1f466", - "uc_greedy": "1f468-1f468-1f466-1f466", - "shortnames": [], - "category": "people" - }, - ":family_mmgb:": { - "uc_base": "1f468-1f468-1f467-1f466", - "uc_output": "1f468-200d-1f468-200d-1f467-200d-1f466", - "uc_match": "1f468-1f468-1f467-1f466", - "uc_greedy": "1f468-1f468-1f467-1f466", - "shortnames": [], - "category": "people" - }, - ":family_mmgg:": { - "uc_base": "1f468-1f468-1f467-1f467", - "uc_output": "1f468-200d-1f468-200d-1f467-200d-1f467", - "uc_match": "1f468-1f468-1f467-1f467", - "uc_greedy": "1f468-1f468-1f467-1f467", - "shortnames": [], - "category": "people" - }, - ":family_mwbb:": { - "uc_base": "1f468-1f469-1f466-1f466", - "uc_output": "1f468-200d-1f469-200d-1f466-200d-1f466", - "uc_match": "1f468-1f469-1f466-1f466", - "uc_greedy": "1f468-1f469-1f466-1f466", - "shortnames": [], - "category": "people" - }, - ":family_mwgb:": { - "uc_base": "1f468-1f469-1f467-1f466", - "uc_output": "1f468-200d-1f469-200d-1f467-200d-1f466", - "uc_match": "1f468-1f469-1f467-1f466", - "uc_greedy": "1f468-1f469-1f467-1f466", - "shortnames": [], - "category": "people" - }, - ":family_mwgg:": { - "uc_base": "1f468-1f469-1f467-1f467", - "uc_output": "1f468-200d-1f469-200d-1f467-200d-1f467", - "uc_match": "1f468-1f469-1f467-1f467", - "uc_greedy": "1f468-1f469-1f467-1f467", - "shortnames": [], - "category": "people" - }, - ":family_wwbb:": { - "uc_base": "1f469-1f469-1f466-1f466", - "uc_output": "1f469-200d-1f469-200d-1f466-200d-1f466", - "uc_match": "1f469-1f469-1f466-1f466", - "uc_greedy": "1f469-1f469-1f466-1f466", - "shortnames": [], - "category": "people" - }, - ":family_wwgb:": { - "uc_base": "1f469-1f469-1f467-1f466", - "uc_output": "1f469-200d-1f469-200d-1f467-200d-1f466", - "uc_match": "1f469-1f469-1f467-1f466", - "uc_greedy": "1f469-1f469-1f467-1f466", - "shortnames": [], - "category": "people" - }, - ":family_wwgg:": { - "uc_base": "1f469-1f469-1f467-1f467", - "uc_output": "1f469-200d-1f469-200d-1f467-200d-1f467", - "uc_match": "1f469-1f469-1f467-1f467", - "uc_greedy": "1f469-1f469-1f467-1f467", - "shortnames": [], - "category": "people" - }, - ":couple_mm:": { - "uc_base": "1f468-2764-1f468", - "uc_output": "1f468-200d-2764-fe0f-200d-1f468", - "uc_match": "1f468-2764-fe0f-1f468", - "uc_greedy": "1f468-2764-1f468", - "shortnames": [":couple_with_heart_mm:"], - "category": "people" - }, - ":couple_with_heart_woman_man:": { - "uc_base": "1f469-2764-1f468", - "uc_output": "1f469-200d-2764-fe0f-200d-1f468", - "uc_match": "1f469-2764-fe0f-1f468", - "uc_greedy": "1f469-2764-1f468", - "shortnames": [], - "category": "people" - }, - ":couple_ww:": { - "uc_base": "1f469-2764-1f469", - "uc_output": "1f469-200d-2764-fe0f-200d-1f469", - "uc_match": "1f469-2764-fe0f-1f469", - "uc_greedy": "1f469-2764-1f469", - "shortnames": [":couple_with_heart_ww:"], - "category": "people" - }, - ":family_man_boy_boy:": { - "uc_base": "1f468-1f466-1f466", - "uc_output": "1f468-200d-1f466-200d-1f466", - "uc_match": "1f468-1f466-1f466", - "uc_greedy": "1f468-1f466-1f466", - "shortnames": [], - "category": "people" - }, - ":family_man_girl_boy:": { - "uc_base": "1f468-1f467-1f466", - "uc_output": "1f468-200d-1f467-200d-1f466", - "uc_match": "1f468-1f467-1f466", - "uc_greedy": "1f468-1f467-1f466", - "shortnames": [], - "category": "people" - }, - ":family_man_girl_girl:": { - "uc_base": "1f468-1f467-1f467", - "uc_output": "1f468-200d-1f467-200d-1f467", - "uc_match": "1f468-1f467-1f467", - "uc_greedy": "1f468-1f467-1f467", - "shortnames": [], - "category": "people" - }, - ":family_man_woman_boy:": { - "uc_base": "1f468-1f469-1f466", - "uc_output": "1f468-200d-1f469-200d-1f466", - "uc_match": "1f468-1f469-1f466", - "uc_greedy": "1f468-1f469-1f466", - "shortnames": [], - "category": "people" - }, - ":family_mmb:": { - "uc_base": "1f468-1f468-1f466", - "uc_output": "1f468-200d-1f468-200d-1f466", - "uc_match": "1f468-1f468-1f466", - "uc_greedy": "1f468-1f468-1f466", - "shortnames": [], - "category": "people" - }, - ":family_mmg:": { - "uc_base": "1f468-1f468-1f467", - "uc_output": "1f468-200d-1f468-200d-1f467", - "uc_match": "1f468-1f468-1f467", - "uc_greedy": "1f468-1f468-1f467", - "shortnames": [], - "category": "people" - }, - ":family_mwg:": { - "uc_base": "1f468-1f469-1f467", - "uc_output": "1f468-200d-1f469-200d-1f467", - "uc_match": "1f468-1f469-1f467", - "uc_greedy": "1f468-1f469-1f467", - "shortnames": [], - "category": "people" - }, - ":family_woman_boy_boy:": { - "uc_base": "1f469-1f466-1f466", - "uc_output": "1f469-200d-1f466-200d-1f466", - "uc_match": "1f469-1f466-1f466", - "uc_greedy": "1f469-1f466-1f466", - "shortnames": [], - "category": "people" - }, - ":family_woman_girl_boy:": { - "uc_base": "1f469-1f467-1f466", - "uc_output": "1f469-200d-1f467-200d-1f466", - "uc_match": "1f469-1f467-1f466", - "uc_greedy": "1f469-1f467-1f466", - "shortnames": [], - "category": "people" - }, - ":family_woman_girl_girl:": { - "uc_base": "1f469-1f467-1f467", - "uc_output": "1f469-200d-1f467-200d-1f467", - "uc_match": "1f469-1f467-1f467", - "uc_greedy": "1f469-1f467-1f467", - "shortnames": [], - "category": "people" - }, - ":family_wwb:": { - "uc_base": "1f469-1f469-1f466", - "uc_output": "1f469-200d-1f469-200d-1f466", - "uc_match": "1f469-1f469-1f466", - "uc_greedy": "1f469-1f469-1f466", - "shortnames": [], - "category": "people" - }, - ":family_wwg:": { - "uc_base": "1f469-1f469-1f467", - "uc_output": "1f469-200d-1f469-200d-1f467", - "uc_match": "1f469-1f469-1f467", - "uc_greedy": "1f469-1f469-1f467", - "shortnames": [], - "category": "people" - }, - ":blond-haired_man_tone1:": { - "uc_base": "1f471-1f3fb-2642", - "uc_output": "1f471-1f3fb-200d-2642-fe0f", - "uc_match": "1f471-1f3fb-2642-fe0f", - "uc_greedy": "1f471-1f3fb-2642", - "shortnames": [":blond-haired_man_light_skin_tone:"], - "category": "people" - }, - ":blond-haired_man_tone2:": { - "uc_base": "1f471-1f3fc-2642", - "uc_output": "1f471-1f3fc-200d-2642-fe0f", - "uc_match": "1f471-1f3fc-2642-fe0f", - "uc_greedy": "1f471-1f3fc-2642", - "shortnames": [":blond-haired_man_medium_light_skin_tone:"], - "category": "people" - }, - ":blond-haired_man_tone3:": { - "uc_base": "1f471-1f3fd-2642", - "uc_output": "1f471-1f3fd-200d-2642-fe0f", - "uc_match": "1f471-1f3fd-2642-fe0f", - "uc_greedy": "1f471-1f3fd-2642", - "shortnames": [":blond-haired_man_medium_skin_tone:"], - "category": "people" - }, - ":blond-haired_man_tone4:": { - "uc_base": "1f471-1f3fe-2642", - "uc_output": "1f471-1f3fe-200d-2642-fe0f", - "uc_match": "1f471-1f3fe-2642-fe0f", - "uc_greedy": "1f471-1f3fe-2642", - "shortnames": [":blond-haired_man_medium_dark_skin_tone:"], - "category": "people" - }, - ":blond-haired_man_tone5:": { - "uc_base": "1f471-1f3ff-2642", - "uc_output": "1f471-1f3ff-200d-2642-fe0f", - "uc_match": "1f471-1f3ff-2642-fe0f", - "uc_greedy": "1f471-1f3ff-2642", - "shortnames": [":blond-haired_man_dark_skin_tone:"], - "category": "people" - }, - ":blond-haired_woman_tone1:": { - "uc_base": "1f471-1f3fb-2640", - "uc_output": "1f471-1f3fb-200d-2640-fe0f", - "uc_match": "1f471-1f3fb-2640-fe0f", - "uc_greedy": "1f471-1f3fb-2640", - "shortnames": [":blond-haired_woman_light_skin_tone:"], - "category": "people" - }, - ":blond-haired_woman_tone2:": { - "uc_base": "1f471-1f3fc-2640", - "uc_output": "1f471-1f3fc-200d-2640-fe0f", - "uc_match": "1f471-1f3fc-2640-fe0f", - "uc_greedy": "1f471-1f3fc-2640", - "shortnames": [":blond-haired_woman_medium_light_skin_tone:"], - "category": "people" - }, - ":blond-haired_woman_tone3:": { - "uc_base": "1f471-1f3fd-2640", - "uc_output": "1f471-1f3fd-200d-2640-fe0f", - "uc_match": "1f471-1f3fd-2640-fe0f", - "uc_greedy": "1f471-1f3fd-2640", - "shortnames": [":blond-haired_woman_medium_skin_tone:"], - "category": "people" - }, - ":blond-haired_woman_tone4:": { - "uc_base": "1f471-1f3fe-2640", - "uc_output": "1f471-1f3fe-200d-2640-fe0f", - "uc_match": "1f471-1f3fe-2640-fe0f", - "uc_greedy": "1f471-1f3fe-2640", - "shortnames": [":blond-haired_woman_medium_dark_skin_tone:"], - "category": "people" - }, - ":blond-haired_woman_tone5:": { - "uc_base": "1f471-1f3ff-2640", - "uc_output": "1f471-1f3ff-200d-2640-fe0f", - "uc_match": "1f471-1f3ff-2640-fe0f", - "uc_greedy": "1f471-1f3ff-2640", - "shortnames": [":blond-haired_woman_dark_skin_tone:"], - "category": "people" - }, - ":eye_in_speech_bubble:": { - "uc_base": "1f441-1f5e8", - "uc_output": "1f441-fe0f-200d-1f5e8-fe0f", - "uc_match": "1f441-fe0f-200d-1f5e8", - "uc_greedy": "1f441-1f5e8", - "shortnames": [], - "category": "symbols" - }, - ":man_biking_tone1:": { - "uc_base": "1f6b4-1f3fb-2642", - "uc_output": "1f6b4-1f3fb-200d-2642-fe0f", - "uc_match": "1f6b4-1f3fb-2642-fe0f", - "uc_greedy": "1f6b4-1f3fb-2642", - "shortnames": [":man_biking_light_skin_tone:"], - "category": "activity" - }, - ":man_biking_tone2:": { - "uc_base": "1f6b4-1f3fc-2642", - "uc_output": "1f6b4-1f3fc-200d-2642-fe0f", - "uc_match": "1f6b4-1f3fc-2642-fe0f", - "uc_greedy": "1f6b4-1f3fc-2642", - "shortnames": [":man_biking_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_biking_tone3:": { - "uc_base": "1f6b4-1f3fd-2642", - "uc_output": "1f6b4-1f3fd-200d-2642-fe0f", - "uc_match": "1f6b4-1f3fd-2642-fe0f", - "uc_greedy": "1f6b4-1f3fd-2642", - "shortnames": [":man_biking_medium_skin_tone:"], - "category": "activity" - }, - ":man_biking_tone4:": { - "uc_base": "1f6b4-1f3fe-2642", - "uc_output": "1f6b4-1f3fe-200d-2642-fe0f", - "uc_match": "1f6b4-1f3fe-2642-fe0f", - "uc_greedy": "1f6b4-1f3fe-2642", - "shortnames": [":man_biking_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_biking_tone5:": { - "uc_base": "1f6b4-1f3ff-2642", - "uc_output": "1f6b4-1f3ff-200d-2642-fe0f", - "uc_match": "1f6b4-1f3ff-2642-fe0f", - "uc_greedy": "1f6b4-1f3ff-2642", - "shortnames": [":man_biking_dark_skin_tone:"], - "category": "activity" - }, - ":man_bowing_tone1:": { - "uc_base": "1f647-1f3fb-2642", - "uc_output": "1f647-1f3fb-200d-2642-fe0f", - "uc_match": "1f647-1f3fb-2642-fe0f", - "uc_greedy": "1f647-1f3fb-2642", - "shortnames": [":man_bowing_light_skin_tone:"], - "category": "people" - }, - ":man_bowing_tone2:": { - "uc_base": "1f647-1f3fc-2642", - "uc_output": "1f647-1f3fc-200d-2642-fe0f", - "uc_match": "1f647-1f3fc-2642-fe0f", - "uc_greedy": "1f647-1f3fc-2642", - "shortnames": [":man_bowing_medium_light_skin_tone:"], - "category": "people" - }, - ":man_bowing_tone3:": { - "uc_base": "1f647-1f3fd-2642", - "uc_output": "1f647-1f3fd-200d-2642-fe0f", - "uc_match": "1f647-1f3fd-2642-fe0f", - "uc_greedy": "1f647-1f3fd-2642", - "shortnames": [":man_bowing_medium_skin_tone:"], - "category": "people" - }, - ":man_bowing_tone4:": { - "uc_base": "1f647-1f3fe-2642", - "uc_output": "1f647-1f3fe-200d-2642-fe0f", - "uc_match": "1f647-1f3fe-2642-fe0f", - "uc_greedy": "1f647-1f3fe-2642", - "shortnames": [":man_bowing_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_bowing_tone5:": { - "uc_base": "1f647-1f3ff-2642", - "uc_output": "1f647-1f3ff-200d-2642-fe0f", - "uc_match": "1f647-1f3ff-2642-fe0f", - "uc_greedy": "1f647-1f3ff-2642", - "shortnames": [":man_bowing_dark_skin_tone:"], - "category": "people" - }, - ":man_cartwheeling_tone1:": { - "uc_base": "1f938-1f3fb-2642", - "uc_output": "1f938-1f3fb-200d-2642-fe0f", - "uc_match": "1f938-1f3fb-2642-fe0f", - "uc_greedy": "1f938-1f3fb-2642", - "shortnames": [":man_cartwheeling_light_skin_tone:"], - "category": "activity" - }, - ":man_cartwheeling_tone2:": { - "uc_base": "1f938-1f3fc-2642", - "uc_output": "1f938-1f3fc-200d-2642-fe0f", - "uc_match": "1f938-1f3fc-2642-fe0f", - "uc_greedy": "1f938-1f3fc-2642", - "shortnames": [":man_cartwheeling_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_cartwheeling_tone3:": { - "uc_base": "1f938-1f3fd-2642", - "uc_output": "1f938-1f3fd-200d-2642-fe0f", - "uc_match": "1f938-1f3fd-2642-fe0f", - "uc_greedy": "1f938-1f3fd-2642", - "shortnames": [":man_cartwheeling_medium_skin_tone:"], - "category": "activity" - }, - ":man_cartwheeling_tone4:": { - "uc_base": "1f938-1f3fe-2642", - "uc_output": "1f938-1f3fe-200d-2642-fe0f", - "uc_match": "1f938-1f3fe-2642-fe0f", - "uc_greedy": "1f938-1f3fe-2642", - "shortnames": [":man_cartwheeling_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_cartwheeling_tone5:": { - "uc_base": "1f938-1f3ff-2642", - "uc_output": "1f938-1f3ff-200d-2642-fe0f", - "uc_match": "1f938-1f3ff-2642-fe0f", - "uc_greedy": "1f938-1f3ff-2642", - "shortnames": [":man_cartwheeling_dark_skin_tone:"], - "category": "activity" - }, - ":man_climbing_tone1:": { - "uc_base": "1f9d7-1f3fb-2642", - "uc_output": "1f9d7-1f3fb-200d-2642-fe0f", - "uc_match": "1f9d7-1f3fb-2642-fe0f", - "uc_greedy": "1f9d7-1f3fb-2642", - "shortnames": [":man_climbing_light_skin_tone:"], - "category": "activity" - }, - ":man_climbing_tone2:": { - "uc_base": "1f9d7-1f3fc-2642", - "uc_output": "1f9d7-1f3fc-200d-2642-fe0f", - "uc_match": "1f9d7-1f3fc-2642-fe0f", - "uc_greedy": "1f9d7-1f3fc-2642", - "shortnames": [":man_climbing_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_climbing_tone3:": { - "uc_base": "1f9d7-1f3fd-2642", - "uc_output": "1f9d7-1f3fd-200d-2642-fe0f", - "uc_match": "1f9d7-1f3fd-2642-fe0f", - "uc_greedy": "1f9d7-1f3fd-2642", - "shortnames": [":man_climbing_medium_skin_tone:"], - "category": "activity" - }, - ":man_climbing_tone4:": { - "uc_base": "1f9d7-1f3fe-2642", - "uc_output": "1f9d7-1f3fe-200d-2642-fe0f", - "uc_match": "1f9d7-1f3fe-2642-fe0f", - "uc_greedy": "1f9d7-1f3fe-2642", - "shortnames": [":man_climbing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_climbing_tone5:": { - "uc_base": "1f9d7-1f3ff-2642", - "uc_output": "1f9d7-1f3ff-200d-2642-fe0f", - "uc_match": "1f9d7-1f3ff-2642-fe0f", - "uc_greedy": "1f9d7-1f3ff-2642", - "shortnames": [":man_climbing_dark_skin_tone:"], - "category": "activity" - }, - ":man_construction_worker_tone1:": { - "uc_base": "1f477-1f3fb-2642", - "uc_output": "1f477-1f3fb-200d-2642-fe0f", - "uc_match": "1f477-1f3fb-2642-fe0f", - "uc_greedy": "1f477-1f3fb-2642", - "shortnames": [":man_construction_worker_light_skin_tone:"], - "category": "people" - }, - ":man_construction_worker_tone2:": { - "uc_base": "1f477-1f3fc-2642", - "uc_output": "1f477-1f3fc-200d-2642-fe0f", - "uc_match": "1f477-1f3fc-2642-fe0f", - "uc_greedy": "1f477-1f3fc-2642", - "shortnames": [":man_construction_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":man_construction_worker_tone3:": { - "uc_base": "1f477-1f3fd-2642", - "uc_output": "1f477-1f3fd-200d-2642-fe0f", - "uc_match": "1f477-1f3fd-2642-fe0f", - "uc_greedy": "1f477-1f3fd-2642", - "shortnames": [":man_construction_worker_medium_skin_tone:"], - "category": "people" - }, - ":man_construction_worker_tone4:": { - "uc_base": "1f477-1f3fe-2642", - "uc_output": "1f477-1f3fe-200d-2642-fe0f", - "uc_match": "1f477-1f3fe-2642-fe0f", - "uc_greedy": "1f477-1f3fe-2642", - "shortnames": [":man_construction_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_construction_worker_tone5:": { - "uc_base": "1f477-1f3ff-2642", - "uc_output": "1f477-1f3ff-200d-2642-fe0f", - "uc_match": "1f477-1f3ff-2642-fe0f", - "uc_greedy": "1f477-1f3ff-2642", - "shortnames": [":man_construction_worker_dark_skin_tone:"], - "category": "people" - }, - ":man_detective_tone1:": { - "uc_base": "1f575-1f3fb-2642", - "uc_output": "1f575-1f3fb-200d-2642-fe0f", - "uc_match": "1f575-fe0f-1f3fb-2642-fe0f", - "uc_greedy": "1f575-1f3fb-2642", - "shortnames": [":man_detective_light_skin_tone:"], - "category": "people" - }, - ":man_detective_tone2:": { - "uc_base": "1f575-1f3fc-2642", - "uc_output": "1f575-1f3fc-200d-2642-fe0f", - "uc_match": "1f575-fe0f-1f3fc-2642-fe0f", - "uc_greedy": "1f575-1f3fc-2642", - "shortnames": [":man_detective_medium_light_skin_tone:"], - "category": "people" - }, - ":man_detective_tone3:": { - "uc_base": "1f575-1f3fd-2642", - "uc_output": "1f575-1f3fd-200d-2642-fe0f", - "uc_match": "1f575-fe0f-1f3fd-2642-fe0f", - "uc_greedy": "1f575-1f3fd-2642", - "shortnames": [":man_detective_medium_skin_tone:"], - "category": "people" - }, - ":man_detective_tone4:": { - "uc_base": "1f575-1f3fe-2642", - "uc_output": "1f575-1f3fe-200d-2642-fe0f", - "uc_match": "1f575-fe0f-1f3fe-2642-fe0f", - "uc_greedy": "1f575-1f3fe-2642", - "shortnames": [":man_detective_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_detective_tone5:": { - "uc_base": "1f575-1f3ff-2642", - "uc_output": "1f575-1f3ff-200d-2642-fe0f", - "uc_match": "1f575-fe0f-1f3ff-2642-fe0f", - "uc_greedy": "1f575-1f3ff-2642", - "shortnames": [":man_detective_dark_skin_tone:"], - "category": "people" - }, - ":man_elf_tone1:": { - "uc_base": "1f9dd-1f3fb-2642", - "uc_output": "1f9dd-1f3fb-200d-2642-fe0f", - "uc_match": "1f9dd-1f3fb-2642-fe0f", - "uc_greedy": "1f9dd-1f3fb-2642", - "shortnames": [":man_elf_light_skin_tone:"], - "category": "people" - }, - ":man_elf_tone2:": { - "uc_base": "1f9dd-1f3fc-2642", - "uc_output": "1f9dd-1f3fc-200d-2642-fe0f", - "uc_match": "1f9dd-1f3fc-2642-fe0f", - "uc_greedy": "1f9dd-1f3fc-2642", - "shortnames": [":man_elf_medium_light_skin_tone:"], - "category": "people" - }, - ":man_elf_tone3:": { - "uc_base": "1f9dd-1f3fd-2642", - "uc_output": "1f9dd-1f3fd-200d-2642-fe0f", - "uc_match": "1f9dd-1f3fd-2642-fe0f", - "uc_greedy": "1f9dd-1f3fd-2642", - "shortnames": [":man_elf_medium_skin_tone:"], - "category": "people" - }, - ":man_elf_tone4:": { - "uc_base": "1f9dd-1f3fe-2642", - "uc_output": "1f9dd-1f3fe-200d-2642-fe0f", - "uc_match": "1f9dd-1f3fe-2642-fe0f", - "uc_greedy": "1f9dd-1f3fe-2642", - "shortnames": [":man_elf_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_elf_tone5:": { - "uc_base": "1f9dd-1f3ff-2642", - "uc_output": "1f9dd-1f3ff-200d-2642-fe0f", - "uc_match": "1f9dd-1f3ff-2642-fe0f", - "uc_greedy": "1f9dd-1f3ff-2642", - "shortnames": [":man_elf_dark_skin_tone:"], - "category": "people" - }, - ":man_facepalming_tone1:": { - "uc_base": "1f926-1f3fb-2642", - "uc_output": "1f926-1f3fb-200d-2642-fe0f", - "uc_match": "1f926-1f3fb-2642-fe0f", - "uc_greedy": "1f926-1f3fb-2642", - "shortnames": [":man_facepalming_light_skin_tone:"], - "category": "people" - }, - ":man_facepalming_tone2:": { - "uc_base": "1f926-1f3fc-2642", - "uc_output": "1f926-1f3fc-200d-2642-fe0f", - "uc_match": "1f926-1f3fc-2642-fe0f", - "uc_greedy": "1f926-1f3fc-2642", - "shortnames": [":man_facepalming_medium_light_skin_tone:"], - "category": "people" - }, - ":man_facepalming_tone3:": { - "uc_base": "1f926-1f3fd-2642", - "uc_output": "1f926-1f3fd-200d-2642-fe0f", - "uc_match": "1f926-1f3fd-2642-fe0f", - "uc_greedy": "1f926-1f3fd-2642", - "shortnames": [":man_facepalming_medium_skin_tone:"], - "category": "people" - }, - ":man_facepalming_tone4:": { - "uc_base": "1f926-1f3fe-2642", - "uc_output": "1f926-1f3fe-200d-2642-fe0f", - "uc_match": "1f926-1f3fe-2642-fe0f", - "uc_greedy": "1f926-1f3fe-2642", - "shortnames": [":man_facepalming_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_facepalming_tone5:": { - "uc_base": "1f926-1f3ff-2642", - "uc_output": "1f926-1f3ff-200d-2642-fe0f", - "uc_match": "1f926-1f3ff-2642-fe0f", - "uc_greedy": "1f926-1f3ff-2642", - "shortnames": [":man_facepalming_dark_skin_tone:"], - "category": "people" - }, - ":man_fairy_tone1:": { - "uc_base": "1f9da-1f3fb-2642", - "uc_output": "1f9da-1f3fb-200d-2642-fe0f", - "uc_match": "1f9da-1f3fb-2642-fe0f", - "uc_greedy": "1f9da-1f3fb-2642", - "shortnames": [":man_fairy_light_skin_tone:"], - "category": "people" - }, - ":man_fairy_tone2:": { - "uc_base": "1f9da-1f3fc-2642", - "uc_output": "1f9da-1f3fc-200d-2642-fe0f", - "uc_match": "1f9da-1f3fc-2642-fe0f", - "uc_greedy": "1f9da-1f3fc-2642", - "shortnames": [":man_fairy_medium_light_skin_tone:"], - "category": "people" - }, - ":man_fairy_tone3:": { - "uc_base": "1f9da-1f3fd-2642", - "uc_output": "1f9da-1f3fd-200d-2642-fe0f", - "uc_match": "1f9da-1f3fd-2642-fe0f", - "uc_greedy": "1f9da-1f3fd-2642", - "shortnames": [":man_fairy_medium_skin_tone:"], - "category": "people" - }, - ":man_fairy_tone4:": { - "uc_base": "1f9da-1f3fe-2642", - "uc_output": "1f9da-1f3fe-200d-2642-fe0f", - "uc_match": "1f9da-1f3fe-2642-fe0f", - "uc_greedy": "1f9da-1f3fe-2642", - "shortnames": [":man_fairy_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_fairy_tone5:": { - "uc_base": "1f9da-1f3ff-2642", - "uc_output": "1f9da-1f3ff-200d-2642-fe0f", - "uc_match": "1f9da-1f3ff-2642-fe0f", - "uc_greedy": "1f9da-1f3ff-2642", - "shortnames": [":man_fairy_dark_skin_tone:"], - "category": "people" - }, - ":man_frowning_tone1:": { - "uc_base": "1f64d-1f3fb-2642", - "uc_output": "1f64d-1f3fb-200d-2642-fe0f", - "uc_match": "1f64d-1f3fb-2642-fe0f", - "uc_greedy": "1f64d-1f3fb-2642", - "shortnames": [":man_frowning_light_skin_tone:"], - "category": "people" - }, - ":man_frowning_tone2:": { - "uc_base": "1f64d-1f3fc-2642", - "uc_output": "1f64d-1f3fc-200d-2642-fe0f", - "uc_match": "1f64d-1f3fc-2642-fe0f", - "uc_greedy": "1f64d-1f3fc-2642", - "shortnames": [":man_frowning_medium_light_skin_tone:"], - "category": "people" - }, - ":man_frowning_tone3:": { - "uc_base": "1f64d-1f3fd-2642", - "uc_output": "1f64d-1f3fd-200d-2642-fe0f", - "uc_match": "1f64d-1f3fd-2642-fe0f", - "uc_greedy": "1f64d-1f3fd-2642", - "shortnames": [":man_frowning_medium_skin_tone:"], - "category": "people" - }, - ":man_frowning_tone4:": { - "uc_base": "1f64d-1f3fe-2642", - "uc_output": "1f64d-1f3fe-200d-2642-fe0f", - "uc_match": "1f64d-1f3fe-2642-fe0f", - "uc_greedy": "1f64d-1f3fe-2642", - "shortnames": [":man_frowning_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_frowning_tone5:": { - "uc_base": "1f64d-1f3ff-2642", - "uc_output": "1f64d-1f3ff-200d-2642-fe0f", - "uc_match": "1f64d-1f3ff-2642-fe0f", - "uc_greedy": "1f64d-1f3ff-2642", - "shortnames": [":man_frowning_dark_skin_tone:"], - "category": "people" - }, - ":man_gesturing_no_tone1:": { - "uc_base": "1f645-1f3fb-2642", - "uc_output": "1f645-1f3fb-200d-2642-fe0f", - "uc_match": "1f645-1f3fb-2642-fe0f", - "uc_greedy": "1f645-1f3fb-2642", - "shortnames": [":man_gesturing_no_light_skin_tone:"], - "category": "people" - }, - ":man_gesturing_no_tone2:": { - "uc_base": "1f645-1f3fc-2642", - "uc_output": "1f645-1f3fc-200d-2642-fe0f", - "uc_match": "1f645-1f3fc-2642-fe0f", - "uc_greedy": "1f645-1f3fc-2642", - "shortnames": [":man_gesturing_no_medium_light_skin_tone:"], - "category": "people" - }, - ":man_gesturing_no_tone3:": { - "uc_base": "1f645-1f3fd-2642", - "uc_output": "1f645-1f3fd-200d-2642-fe0f", - "uc_match": "1f645-1f3fd-2642-fe0f", - "uc_greedy": "1f645-1f3fd-2642", - "shortnames": [":man_gesturing_no_medium_skin_tone:"], - "category": "people" - }, - ":man_gesturing_no_tone4:": { - "uc_base": "1f645-1f3fe-2642", - "uc_output": "1f645-1f3fe-200d-2642-fe0f", - "uc_match": "1f645-1f3fe-2642-fe0f", - "uc_greedy": "1f645-1f3fe-2642", - "shortnames": [":man_gesturing_no_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_gesturing_no_tone5:": { - "uc_base": "1f645-1f3ff-2642", - "uc_output": "1f645-1f3ff-200d-2642-fe0f", - "uc_match": "1f645-1f3ff-2642-fe0f", - "uc_greedy": "1f645-1f3ff-2642", - "shortnames": [":man_gesturing_no_dark_skin_tone:"], - "category": "people" - }, - ":man_gesturing_ok_tone1:": { - "uc_base": "1f646-1f3fb-2642", - "uc_output": "1f646-1f3fb-200d-2642-fe0f", - "uc_match": "1f646-1f3fb-2642-fe0f", - "uc_greedy": "1f646-1f3fb-2642", - "shortnames": [":man_gesturing_ok_light_skin_tone:"], - "category": "people" - }, - ":man_gesturing_ok_tone2:": { - "uc_base": "1f646-1f3fc-2642", - "uc_output": "1f646-1f3fc-200d-2642-fe0f", - "uc_match": "1f646-1f3fc-2642-fe0f", - "uc_greedy": "1f646-1f3fc-2642", - "shortnames": [":man_gesturing_ok_medium_light_skin_tone:"], - "category": "people" - }, - ":man_gesturing_ok_tone3:": { - "uc_base": "1f646-1f3fd-2642", - "uc_output": "1f646-1f3fd-200d-2642-fe0f", - "uc_match": "1f646-1f3fd-2642-fe0f", - "uc_greedy": "1f646-1f3fd-2642", - "shortnames": [":man_gesturing_ok_medium_skin_tone:"], - "category": "people" - }, - ":man_gesturing_ok_tone4:": { - "uc_base": "1f646-1f3fe-2642", - "uc_output": "1f646-1f3fe-200d-2642-fe0f", - "uc_match": "1f646-1f3fe-2642-fe0f", - "uc_greedy": "1f646-1f3fe-2642", - "shortnames": [":man_gesturing_ok_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_gesturing_ok_tone5:": { - "uc_base": "1f646-1f3ff-2642", - "uc_output": "1f646-1f3ff-200d-2642-fe0f", - "uc_match": "1f646-1f3ff-2642-fe0f", - "uc_greedy": "1f646-1f3ff-2642", - "shortnames": [":man_gesturing_ok_dark_skin_tone:"], - "category": "people" - }, - ":man_getting_face_massage_tone1:": { - "uc_base": "1f486-1f3fb-2642", - "uc_output": "1f486-1f3fb-200d-2642-fe0f", - "uc_match": "1f486-1f3fb-2642-fe0f", - "uc_greedy": "1f486-1f3fb-2642", - "shortnames": [":man_getting_face_massage_light_skin_tone:"], - "category": "people" - }, - ":man_getting_face_massage_tone2:": { - "uc_base": "1f486-1f3fc-2642", - "uc_output": "1f486-1f3fc-200d-2642-fe0f", - "uc_match": "1f486-1f3fc-2642-fe0f", - "uc_greedy": "1f486-1f3fc-2642", - "shortnames": [":man_getting_face_massage_medium_light_skin_tone:"], - "category": "people" - }, - ":man_getting_face_massage_tone3:": { - "uc_base": "1f486-1f3fd-2642", - "uc_output": "1f486-1f3fd-200d-2642-fe0f", - "uc_match": "1f486-1f3fd-2642-fe0f", - "uc_greedy": "1f486-1f3fd-2642", - "shortnames": [":man_getting_face_massage_medium_skin_tone:"], - "category": "people" - }, - ":man_getting_face_massage_tone4:": { - "uc_base": "1f486-1f3fe-2642", - "uc_output": "1f486-1f3fe-200d-2642-fe0f", - "uc_match": "1f486-1f3fe-2642-fe0f", - "uc_greedy": "1f486-1f3fe-2642", - "shortnames": [":man_getting_face_massage_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_getting_face_massage_tone5:": { - "uc_base": "1f486-1f3ff-2642", - "uc_output": "1f486-1f3ff-200d-2642-fe0f", - "uc_match": "1f486-1f3ff-2642-fe0f", - "uc_greedy": "1f486-1f3ff-2642", - "shortnames": [":man_getting_face_massage_dark_skin_tone:"], - "category": "people" - }, - ":man_getting_haircut_tone1:": { - "uc_base": "1f487-1f3fb-2642", - "uc_output": "1f487-1f3fb-200d-2642-fe0f", - "uc_match": "1f487-1f3fb-2642-fe0f", - "uc_greedy": "1f487-1f3fb-2642", - "shortnames": [":man_getting_haircut_light_skin_tone:"], - "category": "people" - }, - ":man_getting_haircut_tone2:": { - "uc_base": "1f487-1f3fc-2642", - "uc_output": "1f487-1f3fc-200d-2642-fe0f", - "uc_match": "1f487-1f3fc-2642-fe0f", - "uc_greedy": "1f487-1f3fc-2642", - "shortnames": [":man_getting_haircut_medium_light_skin_tone:"], - "category": "people" - }, - ":man_getting_haircut_tone3:": { - "uc_base": "1f487-1f3fd-2642", - "uc_output": "1f487-1f3fd-200d-2642-fe0f", - "uc_match": "1f487-1f3fd-2642-fe0f", - "uc_greedy": "1f487-1f3fd-2642", - "shortnames": [":man_getting_haircut_medium_skin_tone:"], - "category": "people" - }, - ":man_getting_haircut_tone4:": { - "uc_base": "1f487-1f3fe-2642", - "uc_output": "1f487-1f3fe-200d-2642-fe0f", - "uc_match": "1f487-1f3fe-2642-fe0f", - "uc_greedy": "1f487-1f3fe-2642", - "shortnames": [":man_getting_haircut_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_getting_haircut_tone5:": { - "uc_base": "1f487-1f3ff-2642", - "uc_output": "1f487-1f3ff-200d-2642-fe0f", - "uc_match": "1f487-1f3ff-2642-fe0f", - "uc_greedy": "1f487-1f3ff-2642", - "shortnames": [":man_getting_haircut_dark_skin_tone:"], - "category": "people" - }, - ":man_golfing_tone1:": { - "uc_base": "1f3cc-1f3fb-2642", - "uc_output": "1f3cc-1f3fb-200d-2642-fe0f", - "uc_match": "1f3cc-fe0f-1f3fb-2642-fe0f", - "uc_greedy": "1f3cc-1f3fb-2642", - "shortnames": [":man_golfing_light_skin_tone:"], - "category": "activity" - }, - ":man_golfing_tone2:": { - "uc_base": "1f3cc-1f3fc-2642", - "uc_output": "1f3cc-1f3fc-200d-2642-fe0f", - "uc_match": "1f3cc-fe0f-1f3fc-2642-fe0f", - "uc_greedy": "1f3cc-1f3fc-2642", - "shortnames": [":man_golfing_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_golfing_tone3:": { - "uc_base": "1f3cc-1f3fd-2642", - "uc_output": "1f3cc-1f3fd-200d-2642-fe0f", - "uc_match": "1f3cc-fe0f-1f3fd-2642-fe0f", - "uc_greedy": "1f3cc-1f3fd-2642", - "shortnames": [":man_golfing_medium_skin_tone:"], - "category": "activity" - }, - ":man_golfing_tone4:": { - "uc_base": "1f3cc-1f3fe-2642", - "uc_output": "1f3cc-1f3fe-200d-2642-fe0f", - "uc_match": "1f3cc-fe0f-1f3fe-2642-fe0f", - "uc_greedy": "1f3cc-1f3fe-2642", - "shortnames": [":man_golfing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_golfing_tone5:": { - "uc_base": "1f3cc-1f3ff-2642", - "uc_output": "1f3cc-1f3ff-200d-2642-fe0f", - "uc_match": "1f3cc-fe0f-1f3ff-2642-fe0f", - "uc_greedy": "1f3cc-1f3ff-2642", - "shortnames": [":man_golfing_dark_skin_tone:"], - "category": "activity" - }, - ":man_guard_tone1:": { - "uc_base": "1f482-1f3fb-2642", - "uc_output": "1f482-1f3fb-200d-2642-fe0f", - "uc_match": "1f482-1f3fb-2642-fe0f", - "uc_greedy": "1f482-1f3fb-2642", - "shortnames": [":man_guard_light_skin_tone:"], - "category": "people" - }, - ":man_guard_tone2:": { - "uc_base": "1f482-1f3fc-2642", - "uc_output": "1f482-1f3fc-200d-2642-fe0f", - "uc_match": "1f482-1f3fc-2642-fe0f", - "uc_greedy": "1f482-1f3fc-2642", - "shortnames": [":man_guard_medium_light_skin_tone:"], - "category": "people" - }, - ":man_guard_tone3:": { - "uc_base": "1f482-1f3fd-2642", - "uc_output": "1f482-1f3fd-200d-2642-fe0f", - "uc_match": "1f482-1f3fd-2642-fe0f", - "uc_greedy": "1f482-1f3fd-2642", - "shortnames": [":man_guard_medium_skin_tone:"], - "category": "people" - }, - ":man_guard_tone4:": { - "uc_base": "1f482-1f3fe-2642", - "uc_output": "1f482-1f3fe-200d-2642-fe0f", - "uc_match": "1f482-1f3fe-2642-fe0f", - "uc_greedy": "1f482-1f3fe-2642", - "shortnames": [":man_guard_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_guard_tone5:": { - "uc_base": "1f482-1f3ff-2642", - "uc_output": "1f482-1f3ff-200d-2642-fe0f", - "uc_match": "1f482-1f3ff-2642-fe0f", - "uc_greedy": "1f482-1f3ff-2642", - "shortnames": [":man_guard_dark_skin_tone:"], - "category": "people" - }, - ":man_health_worker_tone1:": { - "uc_base": "1f468-1f3fb-2695", - "uc_output": "1f468-1f3fb-200d-2695-fe0f", - "uc_match": "1f468-1f3fb-2695-fe0f", - "uc_greedy": "1f468-1f3fb-2695", - "shortnames": [":man_health_worker_light_skin_tone:"], - "category": "people" - }, - ":man_health_worker_tone2:": { - "uc_base": "1f468-1f3fc-2695", - "uc_output": "1f468-1f3fc-200d-2695-fe0f", - "uc_match": "1f468-1f3fc-2695-fe0f", - "uc_greedy": "1f468-1f3fc-2695", - "shortnames": [":man_health_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":man_health_worker_tone3:": { - "uc_base": "1f468-1f3fd-2695", - "uc_output": "1f468-1f3fd-200d-2695-fe0f", - "uc_match": "1f468-1f3fd-2695-fe0f", - "uc_greedy": "1f468-1f3fd-2695", - "shortnames": [":man_health_worker_medium_skin_tone:"], - "category": "people" - }, - ":man_health_worker_tone4:": { - "uc_base": "1f468-1f3fe-2695", - "uc_output": "1f468-1f3fe-200d-2695-fe0f", - "uc_match": "1f468-1f3fe-2695-fe0f", - "uc_greedy": "1f468-1f3fe-2695", - "shortnames": [":man_health_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_health_worker_tone5:": { - "uc_base": "1f468-1f3ff-2695", - "uc_output": "1f468-1f3ff-200d-2695-fe0f", - "uc_match": "1f468-1f3ff-2695-fe0f", - "uc_greedy": "1f468-1f3ff-2695", - "shortnames": [":man_health_worker_dark_skin_tone:"], - "category": "people" - }, - ":man_in_lotus_position_tone1:": { - "uc_base": "1f9d8-1f3fb-2642", - "uc_output": "1f9d8-1f3fb-200d-2642-fe0f", - "uc_match": "1f9d8-1f3fb-2642-fe0f", - "uc_greedy": "1f9d8-1f3fb-2642", - "shortnames": [":man_in_lotus_position_light_skin_tone:"], - "category": "activity" - }, - ":man_in_lotus_position_tone2:": { - "uc_base": "1f9d8-1f3fc-2642", - "uc_output": "1f9d8-1f3fc-200d-2642-fe0f", - "uc_match": "1f9d8-1f3fc-2642-fe0f", - "uc_greedy": "1f9d8-1f3fc-2642", - "shortnames": [":man_in_lotus_position_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_in_lotus_position_tone3:": { - "uc_base": "1f9d8-1f3fd-2642", - "uc_output": "1f9d8-1f3fd-200d-2642-fe0f", - "uc_match": "1f9d8-1f3fd-2642-fe0f", - "uc_greedy": "1f9d8-1f3fd-2642", - "shortnames": [":man_in_lotus_position_medium_skin_tone:"], - "category": "activity" - }, - ":man_in_lotus_position_tone4:": { - "uc_base": "1f9d8-1f3fe-2642", - "uc_output": "1f9d8-1f3fe-200d-2642-fe0f", - "uc_match": "1f9d8-1f3fe-2642-fe0f", - "uc_greedy": "1f9d8-1f3fe-2642", - "shortnames": [":man_in_lotus_position_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_in_lotus_position_tone5:": { - "uc_base": "1f9d8-1f3ff-2642", - "uc_output": "1f9d8-1f3ff-200d-2642-fe0f", - "uc_match": "1f9d8-1f3ff-2642-fe0f", - "uc_greedy": "1f9d8-1f3ff-2642", - "shortnames": [":man_in_lotus_position_dark_skin_tone:"], - "category": "activity" - }, - ":man_in_steamy_room_tone1:": { - "uc_base": "1f9d6-1f3fb-2642", - "uc_output": "1f9d6-1f3fb-200d-2642-fe0f", - "uc_match": "1f9d6-1f3fb-2642-fe0f", - "uc_greedy": "1f9d6-1f3fb-2642", - "shortnames": [":man_in_steamy_room_light_skin_tone:"], - "category": "people" - }, - ":man_in_steamy_room_tone2:": { - "uc_base": "1f9d6-1f3fc-2642", - "uc_output": "1f9d6-1f3fc-200d-2642-fe0f", - "uc_match": "1f9d6-1f3fc-2642-fe0f", - "uc_greedy": "1f9d6-1f3fc-2642", - "shortnames": [":man_in_steamy_room_medium_light_skin_tone:"], - "category": "people" - }, - ":man_in_steamy_room_tone3:": { - "uc_base": "1f9d6-1f3fd-2642", - "uc_output": "1f9d6-1f3fd-200d-2642-fe0f", - "uc_match": "1f9d6-1f3fd-2642-fe0f", - "uc_greedy": "1f9d6-1f3fd-2642", - "shortnames": [":man_in_steamy_room_medium_skin_tone:"], - "category": "people" - }, - ":man_in_steamy_room_tone4:": { - "uc_base": "1f9d6-1f3fe-2642", - "uc_output": "1f9d6-1f3fe-200d-2642-fe0f", - "uc_match": "1f9d6-1f3fe-2642-fe0f", - "uc_greedy": "1f9d6-1f3fe-2642", - "shortnames": [":man_in_steamy_room_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_in_steamy_room_tone5:": { - "uc_base": "1f9d6-1f3ff-2642", - "uc_output": "1f9d6-1f3ff-200d-2642-fe0f", - "uc_match": "1f9d6-1f3ff-2642-fe0f", - "uc_greedy": "1f9d6-1f3ff-2642", - "shortnames": [":man_in_steamy_room_dark_skin_tone:"], - "category": "people" - }, - ":man_judge_tone1:": { - "uc_base": "1f468-1f3fb-2696", - "uc_output": "1f468-1f3fb-200d-2696-fe0f", - "uc_match": "1f468-1f3fb-2696-fe0f", - "uc_greedy": "1f468-1f3fb-2696", - "shortnames": [":man_judge_light_skin_tone:"], - "category": "people" - }, - ":man_judge_tone2:": { - "uc_base": "1f468-1f3fc-2696", - "uc_output": "1f468-1f3fc-200d-2696-fe0f", - "uc_match": "1f468-1f3fc-2696-fe0f", - "uc_greedy": "1f468-1f3fc-2696", - "shortnames": [":man_judge_medium_light_skin_tone:"], - "category": "people" - }, - ":man_judge_tone3:": { - "uc_base": "1f468-1f3fd-2696", - "uc_output": "1f468-1f3fd-200d-2696-fe0f", - "uc_match": "1f468-1f3fd-2696-fe0f", - "uc_greedy": "1f468-1f3fd-2696", - "shortnames": [":man_judge_medium_skin_tone:"], - "category": "people" - }, - ":man_judge_tone4:": { - "uc_base": "1f468-1f3fe-2696", - "uc_output": "1f468-1f3fe-200d-2696-fe0f", - "uc_match": "1f468-1f3fe-2696-fe0f", - "uc_greedy": "1f468-1f3fe-2696", - "shortnames": [":man_judge_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_judge_tone5:": { - "uc_base": "1f468-1f3ff-2696", - "uc_output": "1f468-1f3ff-200d-2696-fe0f", - "uc_match": "1f468-1f3ff-2696-fe0f", - "uc_greedy": "1f468-1f3ff-2696", - "shortnames": [":man_judge_dark_skin_tone:"], - "category": "people" - }, - ":man_juggling_tone1:": { - "uc_base": "1f939-1f3fb-2642", - "uc_output": "1f939-1f3fb-200d-2642-fe0f", - "uc_match": "1f939-1f3fb-2642-fe0f", - "uc_greedy": "1f939-1f3fb-2642", - "shortnames": [":man_juggling_light_skin_tone:"], - "category": "activity" - }, - ":man_juggling_tone2:": { - "uc_base": "1f939-1f3fc-2642", - "uc_output": "1f939-1f3fc-200d-2642-fe0f", - "uc_match": "1f939-1f3fc-2642-fe0f", - "uc_greedy": "1f939-1f3fc-2642", - "shortnames": [":man_juggling_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_juggling_tone3:": { - "uc_base": "1f939-1f3fd-2642", - "uc_output": "1f939-1f3fd-200d-2642-fe0f", - "uc_match": "1f939-1f3fd-2642-fe0f", - "uc_greedy": "1f939-1f3fd-2642", - "shortnames": [":man_juggling_medium_skin_tone:"], - "category": "activity" - }, - ":man_juggling_tone4:": { - "uc_base": "1f939-1f3fe-2642", - "uc_output": "1f939-1f3fe-200d-2642-fe0f", - "uc_match": "1f939-1f3fe-2642-fe0f", - "uc_greedy": "1f939-1f3fe-2642", - "shortnames": [":man_juggling_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_juggling_tone5:": { - "uc_base": "1f939-1f3ff-2642", - "uc_output": "1f939-1f3ff-200d-2642-fe0f", - "uc_match": "1f939-1f3ff-2642-fe0f", - "uc_greedy": "1f939-1f3ff-2642", - "shortnames": [":man_juggling_dark_skin_tone:"], - "category": "activity" - }, - ":man_lifting_weights_tone1:": { - "uc_base": "1f3cb-1f3fb-2642", - "uc_output": "1f3cb-1f3fb-200d-2642-fe0f", - "uc_match": "1f3cb-fe0f-1f3fb-2642-fe0f", - "uc_greedy": "1f3cb-1f3fb-2642", - "shortnames": [":man_lifting_weights_light_skin_tone:"], - "category": "activity" - }, - ":man_lifting_weights_tone2:": { - "uc_base": "1f3cb-1f3fc-2642", - "uc_output": "1f3cb-1f3fc-200d-2642-fe0f", - "uc_match": "1f3cb-fe0f-1f3fc-2642-fe0f", - "uc_greedy": "1f3cb-1f3fc-2642", - "shortnames": [":man_lifting_weights_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_lifting_weights_tone3:": { - "uc_base": "1f3cb-1f3fd-2642", - "uc_output": "1f3cb-1f3fd-200d-2642-fe0f", - "uc_match": "1f3cb-fe0f-1f3fd-2642-fe0f", - "uc_greedy": "1f3cb-1f3fd-2642", - "shortnames": [":man_lifting_weights_medium_skin_tone:"], - "category": "activity" - }, - ":man_lifting_weights_tone4:": { - "uc_base": "1f3cb-1f3fe-2642", - "uc_output": "1f3cb-1f3fe-200d-2642-fe0f", - "uc_match": "1f3cb-fe0f-1f3fe-2642-fe0f", - "uc_greedy": "1f3cb-1f3fe-2642", - "shortnames": [":man_lifting_weights_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_lifting_weights_tone5:": { - "uc_base": "1f3cb-1f3ff-2642", - "uc_output": "1f3cb-1f3ff-200d-2642-fe0f", - "uc_match": "1f3cb-fe0f-1f3ff-2642-fe0f", - "uc_greedy": "1f3cb-1f3ff-2642", - "shortnames": [":man_lifting_weights_dark_skin_tone:"], - "category": "activity" - }, - ":man_mage_tone1:": { - "uc_base": "1f9d9-1f3fb-2642", - "uc_output": "1f9d9-1f3fb-200d-2642-fe0f", - "uc_match": "1f9d9-1f3fb-2642-fe0f", - "uc_greedy": "1f9d9-1f3fb-2642", - "shortnames": [":man_mage_light_skin_tone:"], - "category": "people" - }, - ":man_mage_tone2:": { - "uc_base": "1f9d9-1f3fc-2642", - "uc_output": "1f9d9-1f3fc-200d-2642-fe0f", - "uc_match": "1f9d9-1f3fc-2642-fe0f", - "uc_greedy": "1f9d9-1f3fc-2642", - "shortnames": [":man_mage_medium_light_skin_tone:"], - "category": "people" - }, - ":man_mage_tone3:": { - "uc_base": "1f9d9-1f3fd-2642", - "uc_output": "1f9d9-1f3fd-200d-2642-fe0f", - "uc_match": "1f9d9-1f3fd-2642-fe0f", - "uc_greedy": "1f9d9-1f3fd-2642", - "shortnames": [":man_mage_medium_skin_tone:"], - "category": "people" - }, - ":man_mage_tone4:": { - "uc_base": "1f9d9-1f3fe-2642", - "uc_output": "1f9d9-1f3fe-200d-2642-fe0f", - "uc_match": "1f9d9-1f3fe-2642-fe0f", - "uc_greedy": "1f9d9-1f3fe-2642", - "shortnames": [":man_mage_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_mage_tone5:": { - "uc_base": "1f9d9-1f3ff-2642", - "uc_output": "1f9d9-1f3ff-200d-2642-fe0f", - "uc_match": "1f9d9-1f3ff-2642-fe0f", - "uc_greedy": "1f9d9-1f3ff-2642", - "shortnames": [":man_mage_dark_skin_tone:"], - "category": "people" - }, - ":man_mountain_biking_tone1:": { - "uc_base": "1f6b5-1f3fb-2642", - "uc_output": "1f6b5-1f3fb-200d-2642-fe0f", - "uc_match": "1f6b5-1f3fb-2642-fe0f", - "uc_greedy": "1f6b5-1f3fb-2642", - "shortnames": [":man_mountain_biking_light_skin_tone:"], - "category": "activity" - }, - ":man_mountain_biking_tone2:": { - "uc_base": "1f6b5-1f3fc-2642", - "uc_output": "1f6b5-1f3fc-200d-2642-fe0f", - "uc_match": "1f6b5-1f3fc-2642-fe0f", - "uc_greedy": "1f6b5-1f3fc-2642", - "shortnames": [":man_mountain_biking_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_mountain_biking_tone3:": { - "uc_base": "1f6b5-1f3fd-2642", - "uc_output": "1f6b5-1f3fd-200d-2642-fe0f", - "uc_match": "1f6b5-1f3fd-2642-fe0f", - "uc_greedy": "1f6b5-1f3fd-2642", - "shortnames": [":man_mountain_biking_medium_skin_tone:"], - "category": "activity" - }, - ":man_mountain_biking_tone4:": { - "uc_base": "1f6b5-1f3fe-2642", - "uc_output": "1f6b5-1f3fe-200d-2642-fe0f", - "uc_match": "1f6b5-1f3fe-2642-fe0f", - "uc_greedy": "1f6b5-1f3fe-2642", - "shortnames": [":man_mountain_biking_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_mountain_biking_tone5:": { - "uc_base": "1f6b5-1f3ff-2642", - "uc_output": "1f6b5-1f3ff-200d-2642-fe0f", - "uc_match": "1f6b5-1f3ff-2642-fe0f", - "uc_greedy": "1f6b5-1f3ff-2642", - "shortnames": [":man_mountain_biking_dark_skin_tone:"], - "category": "activity" - }, - ":man_pilot_tone1:": { - "uc_base": "1f468-1f3fb-2708", - "uc_output": "1f468-1f3fb-200d-2708-fe0f", - "uc_match": "1f468-1f3fb-2708-fe0f", - "uc_greedy": "1f468-1f3fb-2708", - "shortnames": [":man_pilot_light_skin_tone:"], - "category": "people" - }, - ":man_pilot_tone2:": { - "uc_base": "1f468-1f3fc-2708", - "uc_output": "1f468-1f3fc-200d-2708-fe0f", - "uc_match": "1f468-1f3fc-2708-fe0f", - "uc_greedy": "1f468-1f3fc-2708", - "shortnames": [":man_pilot_medium_light_skin_tone:"], - "category": "people" - }, - ":man_pilot_tone3:": { - "uc_base": "1f468-1f3fd-2708", - "uc_output": "1f468-1f3fd-200d-2708-fe0f", - "uc_match": "1f468-1f3fd-2708-fe0f", - "uc_greedy": "1f468-1f3fd-2708", - "shortnames": [":man_pilot_medium_skin_tone:"], - "category": "people" - }, - ":man_pilot_tone4:": { - "uc_base": "1f468-1f3fe-2708", - "uc_output": "1f468-1f3fe-200d-2708-fe0f", - "uc_match": "1f468-1f3fe-2708-fe0f", - "uc_greedy": "1f468-1f3fe-2708", - "shortnames": [":man_pilot_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_pilot_tone5:": { - "uc_base": "1f468-1f3ff-2708", - "uc_output": "1f468-1f3ff-200d-2708-fe0f", - "uc_match": "1f468-1f3ff-2708-fe0f", - "uc_greedy": "1f468-1f3ff-2708", - "shortnames": [":man_pilot_dark_skin_tone:"], - "category": "people" - }, - ":man_playing_handball_tone1:": { - "uc_base": "1f93e-1f3fb-2642", - "uc_output": "1f93e-1f3fb-200d-2642-fe0f", - "uc_match": "1f93e-1f3fb-2642-fe0f", - "uc_greedy": "1f93e-1f3fb-2642", - "shortnames": [":man_playing_handball_light_skin_tone:"], - "category": "activity" - }, - ":man_playing_handball_tone2:": { - "uc_base": "1f93e-1f3fc-2642", - "uc_output": "1f93e-1f3fc-200d-2642-fe0f", - "uc_match": "1f93e-1f3fc-2642-fe0f", - "uc_greedy": "1f93e-1f3fc-2642", - "shortnames": [":man_playing_handball_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_playing_handball_tone3:": { - "uc_base": "1f93e-1f3fd-2642", - "uc_output": "1f93e-1f3fd-200d-2642-fe0f", - "uc_match": "1f93e-1f3fd-2642-fe0f", - "uc_greedy": "1f93e-1f3fd-2642", - "shortnames": [":man_playing_handball_medium_skin_tone:"], - "category": "activity" - }, - ":man_playing_handball_tone4:": { - "uc_base": "1f93e-1f3fe-2642", - "uc_output": "1f93e-1f3fe-200d-2642-fe0f", - "uc_match": "1f93e-1f3fe-2642-fe0f", - "uc_greedy": "1f93e-1f3fe-2642", - "shortnames": [":man_playing_handball_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_playing_handball_tone5:": { - "uc_base": "1f93e-1f3ff-2642", - "uc_output": "1f93e-1f3ff-200d-2642-fe0f", - "uc_match": "1f93e-1f3ff-2642-fe0f", - "uc_greedy": "1f93e-1f3ff-2642", - "shortnames": [":man_playing_handball_dark_skin_tone:"], - "category": "activity" - }, - ":man_playing_water_polo_tone1:": { - "uc_base": "1f93d-1f3fb-2642", - "uc_output": "1f93d-1f3fb-200d-2642-fe0f", - "uc_match": "1f93d-1f3fb-2642-fe0f", - "uc_greedy": "1f93d-1f3fb-2642", - "shortnames": [":man_playing_water_polo_light_skin_tone:"], - "category": "activity" - }, - ":man_playing_water_polo_tone2:": { - "uc_base": "1f93d-1f3fc-2642", - "uc_output": "1f93d-1f3fc-200d-2642-fe0f", - "uc_match": "1f93d-1f3fc-2642-fe0f", - "uc_greedy": "1f93d-1f3fc-2642", - "shortnames": [":man_playing_water_polo_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_playing_water_polo_tone3:": { - "uc_base": "1f93d-1f3fd-2642", - "uc_output": "1f93d-1f3fd-200d-2642-fe0f", - "uc_match": "1f93d-1f3fd-2642-fe0f", - "uc_greedy": "1f93d-1f3fd-2642", - "shortnames": [":man_playing_water_polo_medium_skin_tone:"], - "category": "activity" - }, - ":man_playing_water_polo_tone4:": { - "uc_base": "1f93d-1f3fe-2642", - "uc_output": "1f93d-1f3fe-200d-2642-fe0f", - "uc_match": "1f93d-1f3fe-2642-fe0f", - "uc_greedy": "1f93d-1f3fe-2642", - "shortnames": [":man_playing_water_polo_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_playing_water_polo_tone5:": { - "uc_base": "1f93d-1f3ff-2642", - "uc_output": "1f93d-1f3ff-200d-2642-fe0f", - "uc_match": "1f93d-1f3ff-2642-fe0f", - "uc_greedy": "1f93d-1f3ff-2642", - "shortnames": [":man_playing_water_polo_dark_skin_tone:"], - "category": "activity" - }, - ":man_police_officer_tone1:": { - "uc_base": "1f46e-1f3fb-2642", - "uc_output": "1f46e-1f3fb-200d-2642-fe0f", - "uc_match": "1f46e-1f3fb-2642-fe0f", - "uc_greedy": "1f46e-1f3fb-2642", - "shortnames": [":man_police_officer_light_skin_tone:"], - "category": "people" - }, - ":man_police_officer_tone2:": { - "uc_base": "1f46e-1f3fc-2642", - "uc_output": "1f46e-1f3fc-200d-2642-fe0f", - "uc_match": "1f46e-1f3fc-2642-fe0f", - "uc_greedy": "1f46e-1f3fc-2642", - "shortnames": [":man_police_officer_medium_light_skin_tone:"], - "category": "people" - }, - ":man_police_officer_tone3:": { - "uc_base": "1f46e-1f3fd-2642", - "uc_output": "1f46e-1f3fd-200d-2642-fe0f", - "uc_match": "1f46e-1f3fd-2642-fe0f", - "uc_greedy": "1f46e-1f3fd-2642", - "shortnames": [":man_police_officer_medium_skin_tone:"], - "category": "people" - }, - ":man_police_officer_tone4:": { - "uc_base": "1f46e-1f3fe-2642", - "uc_output": "1f46e-1f3fe-200d-2642-fe0f", - "uc_match": "1f46e-1f3fe-2642-fe0f", - "uc_greedy": "1f46e-1f3fe-2642", - "shortnames": [":man_police_officer_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_police_officer_tone5:": { - "uc_base": "1f46e-1f3ff-2642", - "uc_output": "1f46e-1f3ff-200d-2642-fe0f", - "uc_match": "1f46e-1f3ff-2642-fe0f", - "uc_greedy": "1f46e-1f3ff-2642", - "shortnames": [":man_police_officer_dark_skin_tone:"], - "category": "people" - }, - ":man_pouting_tone1:": { - "uc_base": "1f64e-1f3fb-2642", - "uc_output": "1f64e-1f3fb-200d-2642-fe0f", - "uc_match": "1f64e-1f3fb-2642-fe0f", - "uc_greedy": "1f64e-1f3fb-2642", - "shortnames": [":man_pouting_light_skin_tone:"], - "category": "people" - }, - ":man_pouting_tone2:": { - "uc_base": "1f64e-1f3fc-2642", - "uc_output": "1f64e-1f3fc-200d-2642-fe0f", - "uc_match": "1f64e-1f3fc-2642-fe0f", - "uc_greedy": "1f64e-1f3fc-2642", - "shortnames": [":man_pouting_medium_light_skin_tone:"], - "category": "people" - }, - ":man_pouting_tone3:": { - "uc_base": "1f64e-1f3fd-2642", - "uc_output": "1f64e-1f3fd-200d-2642-fe0f", - "uc_match": "1f64e-1f3fd-2642-fe0f", - "uc_greedy": "1f64e-1f3fd-2642", - "shortnames": [":man_pouting_medium_skin_tone:"], - "category": "people" - }, - ":man_pouting_tone4:": { - "uc_base": "1f64e-1f3fe-2642", - "uc_output": "1f64e-1f3fe-200d-2642-fe0f", - "uc_match": "1f64e-1f3fe-2642-fe0f", - "uc_greedy": "1f64e-1f3fe-2642", - "shortnames": [":man_pouting_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_pouting_tone5:": { - "uc_base": "1f64e-1f3ff-2642", - "uc_output": "1f64e-1f3ff-200d-2642-fe0f", - "uc_match": "1f64e-1f3ff-2642-fe0f", - "uc_greedy": "1f64e-1f3ff-2642", - "shortnames": [":man_pouting_dark_skin_tone:"], - "category": "people" - }, - ":man_raising_hand_tone1:": { - "uc_base": "1f64b-1f3fb-2642", - "uc_output": "1f64b-1f3fb-200d-2642-fe0f", - "uc_match": "1f64b-1f3fb-2642-fe0f", - "uc_greedy": "1f64b-1f3fb-2642", - "shortnames": [":man_raising_hand_light_skin_tone:"], - "category": "people" - }, - ":man_raising_hand_tone2:": { - "uc_base": "1f64b-1f3fc-2642", - "uc_output": "1f64b-1f3fc-200d-2642-fe0f", - "uc_match": "1f64b-1f3fc-2642-fe0f", - "uc_greedy": "1f64b-1f3fc-2642", - "shortnames": [":man_raising_hand_medium_light_skin_tone:"], - "category": "people" - }, - ":man_raising_hand_tone3:": { - "uc_base": "1f64b-1f3fd-2642", - "uc_output": "1f64b-1f3fd-200d-2642-fe0f", - "uc_match": "1f64b-1f3fd-2642-fe0f", - "uc_greedy": "1f64b-1f3fd-2642", - "shortnames": [":man_raising_hand_medium_skin_tone:"], - "category": "people" - }, - ":man_raising_hand_tone4:": { - "uc_base": "1f64b-1f3fe-2642", - "uc_output": "1f64b-1f3fe-200d-2642-fe0f", - "uc_match": "1f64b-1f3fe-2642-fe0f", - "uc_greedy": "1f64b-1f3fe-2642", - "shortnames": [":man_raising_hand_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_raising_hand_tone5:": { - "uc_base": "1f64b-1f3ff-2642", - "uc_output": "1f64b-1f3ff-200d-2642-fe0f", - "uc_match": "1f64b-1f3ff-2642-fe0f", - "uc_greedy": "1f64b-1f3ff-2642", - "shortnames": [":man_raising_hand_dark_skin_tone:"], - "category": "people" - }, - ":man_rowing_boat_tone1:": { - "uc_base": "1f6a3-1f3fb-2642", - "uc_output": "1f6a3-1f3fb-200d-2642-fe0f", - "uc_match": "1f6a3-1f3fb-2642-fe0f", - "uc_greedy": "1f6a3-1f3fb-2642", - "shortnames": [":man_rowing_boat_light_skin_tone:"], - "category": "activity" - }, - ":man_rowing_boat_tone2:": { - "uc_base": "1f6a3-1f3fc-2642", - "uc_output": "1f6a3-1f3fc-200d-2642-fe0f", - "uc_match": "1f6a3-1f3fc-2642-fe0f", - "uc_greedy": "1f6a3-1f3fc-2642", - "shortnames": [":man_rowing_boat_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_rowing_boat_tone3:": { - "uc_base": "1f6a3-1f3fd-2642", - "uc_output": "1f6a3-1f3fd-200d-2642-fe0f", - "uc_match": "1f6a3-1f3fd-2642-fe0f", - "uc_greedy": "1f6a3-1f3fd-2642", - "shortnames": [":man_rowing_boat_medium_skin_tone:"], - "category": "activity" - }, - ":man_rowing_boat_tone4:": { - "uc_base": "1f6a3-1f3fe-2642", - "uc_output": "1f6a3-1f3fe-200d-2642-fe0f", - "uc_match": "1f6a3-1f3fe-2642-fe0f", - "uc_greedy": "1f6a3-1f3fe-2642", - "shortnames": [":man_rowing_boat_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_rowing_boat_tone5:": { - "uc_base": "1f6a3-1f3ff-2642", - "uc_output": "1f6a3-1f3ff-200d-2642-fe0f", - "uc_match": "1f6a3-1f3ff-2642-fe0f", - "uc_greedy": "1f6a3-1f3ff-2642", - "shortnames": [":man_rowing_boat_dark_skin_tone:"], - "category": "activity" - }, - ":man_running_tone1:": { - "uc_base": "1f3c3-1f3fb-2642", - "uc_output": "1f3c3-1f3fb-200d-2642-fe0f", - "uc_match": "1f3c3-1f3fb-2642-fe0f", - "uc_greedy": "1f3c3-1f3fb-2642", - "shortnames": [":man_running_light_skin_tone:"], - "category": "people" - }, - ":man_running_tone2:": { - "uc_base": "1f3c3-1f3fc-2642", - "uc_output": "1f3c3-1f3fc-200d-2642-fe0f", - "uc_match": "1f3c3-1f3fc-2642-fe0f", - "uc_greedy": "1f3c3-1f3fc-2642", - "shortnames": [":man_running_medium_light_skin_tone:"], - "category": "people" - }, - ":man_running_tone3:": { - "uc_base": "1f3c3-1f3fd-2642", - "uc_output": "1f3c3-1f3fd-200d-2642-fe0f", - "uc_match": "1f3c3-1f3fd-2642-fe0f", - "uc_greedy": "1f3c3-1f3fd-2642", - "shortnames": [":man_running_medium_skin_tone:"], - "category": "people" - }, - ":man_running_tone4:": { - "uc_base": "1f3c3-1f3fe-2642", - "uc_output": "1f3c3-1f3fe-200d-2642-fe0f", - "uc_match": "1f3c3-1f3fe-2642-fe0f", - "uc_greedy": "1f3c3-1f3fe-2642", - "shortnames": [":man_running_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_running_tone5:": { - "uc_base": "1f3c3-1f3ff-2642", - "uc_output": "1f3c3-1f3ff-200d-2642-fe0f", - "uc_match": "1f3c3-1f3ff-2642-fe0f", - "uc_greedy": "1f3c3-1f3ff-2642", - "shortnames": [":man_running_dark_skin_tone:"], - "category": "people" - }, - ":man_shrugging_tone1:": { - "uc_base": "1f937-1f3fb-2642", - "uc_output": "1f937-1f3fb-200d-2642-fe0f", - "uc_match": "1f937-1f3fb-2642-fe0f", - "uc_greedy": "1f937-1f3fb-2642", - "shortnames": [":man_shrugging_light_skin_tone:"], - "category": "people" - }, - ":man_shrugging_tone2:": { - "uc_base": "1f937-1f3fc-2642", - "uc_output": "1f937-1f3fc-200d-2642-fe0f", - "uc_match": "1f937-1f3fc-2642-fe0f", - "uc_greedy": "1f937-1f3fc-2642", - "shortnames": [":man_shrugging_medium_light_skin_tone:"], - "category": "people" - }, - ":man_shrugging_tone3:": { - "uc_base": "1f937-1f3fd-2642", - "uc_output": "1f937-1f3fd-200d-2642-fe0f", - "uc_match": "1f937-1f3fd-2642-fe0f", - "uc_greedy": "1f937-1f3fd-2642", - "shortnames": [":man_shrugging_medium_skin_tone:"], - "category": "people" - }, - ":man_shrugging_tone4:": { - "uc_base": "1f937-1f3fe-2642", - "uc_output": "1f937-1f3fe-200d-2642-fe0f", - "uc_match": "1f937-1f3fe-2642-fe0f", - "uc_greedy": "1f937-1f3fe-2642", - "shortnames": [":man_shrugging_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_shrugging_tone5:": { - "uc_base": "1f937-1f3ff-2642", - "uc_output": "1f937-1f3ff-200d-2642-fe0f", - "uc_match": "1f937-1f3ff-2642-fe0f", - "uc_greedy": "1f937-1f3ff-2642", - "shortnames": [":man_shrugging_dark_skin_tone:"], - "category": "people" - }, - ":man_surfing_tone1:": { - "uc_base": "1f3c4-1f3fb-2642", - "uc_output": "1f3c4-1f3fb-200d-2642-fe0f", - "uc_match": "1f3c4-1f3fb-2642-fe0f", - "uc_greedy": "1f3c4-1f3fb-2642", - "shortnames": [":man_surfing_light_skin_tone:"], - "category": "activity" - }, - ":man_surfing_tone2:": { - "uc_base": "1f3c4-1f3fc-2642", - "uc_output": "1f3c4-1f3fc-200d-2642-fe0f", - "uc_match": "1f3c4-1f3fc-2642-fe0f", - "uc_greedy": "1f3c4-1f3fc-2642", - "shortnames": [":man_surfing_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_surfing_tone3:": { - "uc_base": "1f3c4-1f3fd-2642", - "uc_output": "1f3c4-1f3fd-200d-2642-fe0f", - "uc_match": "1f3c4-1f3fd-2642-fe0f", - "uc_greedy": "1f3c4-1f3fd-2642", - "shortnames": [":man_surfing_medium_skin_tone:"], - "category": "activity" - }, - ":man_surfing_tone4:": { - "uc_base": "1f3c4-1f3fe-2642", - "uc_output": "1f3c4-1f3fe-200d-2642-fe0f", - "uc_match": "1f3c4-1f3fe-2642-fe0f", - "uc_greedy": "1f3c4-1f3fe-2642", - "shortnames": [":man_surfing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_surfing_tone5:": { - "uc_base": "1f3c4-1f3ff-2642", - "uc_output": "1f3c4-1f3ff-200d-2642-fe0f", - "uc_match": "1f3c4-1f3ff-2642-fe0f", - "uc_greedy": "1f3c4-1f3ff-2642", - "shortnames": [":man_surfing_dark_skin_tone:"], - "category": "activity" - }, - ":man_swimming_tone1:": { - "uc_base": "1f3ca-1f3fb-2642", - "uc_output": "1f3ca-1f3fb-200d-2642-fe0f", - "uc_match": "1f3ca-1f3fb-2642-fe0f", - "uc_greedy": "1f3ca-1f3fb-2642", - "shortnames": [":man_swimming_light_skin_tone:"], - "category": "activity" - }, - ":man_swimming_tone2:": { - "uc_base": "1f3ca-1f3fc-2642", - "uc_output": "1f3ca-1f3fc-200d-2642-fe0f", - "uc_match": "1f3ca-1f3fc-2642-fe0f", - "uc_greedy": "1f3ca-1f3fc-2642", - "shortnames": [":man_swimming_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_swimming_tone3:": { - "uc_base": "1f3ca-1f3fd-2642", - "uc_output": "1f3ca-1f3fd-200d-2642-fe0f", - "uc_match": "1f3ca-1f3fd-2642-fe0f", - "uc_greedy": "1f3ca-1f3fd-2642", - "shortnames": [":man_swimming_medium_skin_tone:"], - "category": "activity" - }, - ":man_swimming_tone4:": { - "uc_base": "1f3ca-1f3fe-2642", - "uc_output": "1f3ca-1f3fe-200d-2642-fe0f", - "uc_match": "1f3ca-1f3fe-2642-fe0f", - "uc_greedy": "1f3ca-1f3fe-2642", - "shortnames": [":man_swimming_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_swimming_tone5:": { - "uc_base": "1f3ca-1f3ff-2642", - "uc_output": "1f3ca-1f3ff-200d-2642-fe0f", - "uc_match": "1f3ca-1f3ff-2642-fe0f", - "uc_greedy": "1f3ca-1f3ff-2642", - "shortnames": [":man_swimming_dark_skin_tone:"], - "category": "activity" - }, - ":man_tipping_hand_tone1:": { - "uc_base": "1f481-1f3fb-2642", - "uc_output": "1f481-1f3fb-200d-2642-fe0f", - "uc_match": "1f481-1f3fb-2642-fe0f", - "uc_greedy": "1f481-1f3fb-2642", - "shortnames": [":man_tipping_hand_light_skin_tone:"], - "category": "people" - }, - ":man_tipping_hand_tone2:": { - "uc_base": "1f481-1f3fc-2642", - "uc_output": "1f481-1f3fc-200d-2642-fe0f", - "uc_match": "1f481-1f3fc-2642-fe0f", - "uc_greedy": "1f481-1f3fc-2642", - "shortnames": [":man_tipping_hand_medium_light_skin_tone:"], - "category": "people" - }, - ":man_tipping_hand_tone3:": { - "uc_base": "1f481-1f3fd-2642", - "uc_output": "1f481-1f3fd-200d-2642-fe0f", - "uc_match": "1f481-1f3fd-2642-fe0f", - "uc_greedy": "1f481-1f3fd-2642", - "shortnames": [":man_tipping_hand_medium_skin_tone:"], - "category": "people" - }, - ":man_tipping_hand_tone4:": { - "uc_base": "1f481-1f3fe-2642", - "uc_output": "1f481-1f3fe-200d-2642-fe0f", - "uc_match": "1f481-1f3fe-2642-fe0f", - "uc_greedy": "1f481-1f3fe-2642", - "shortnames": [":man_tipping_hand_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_tipping_hand_tone5:": { - "uc_base": "1f481-1f3ff-2642", - "uc_output": "1f481-1f3ff-200d-2642-fe0f", - "uc_match": "1f481-1f3ff-2642-fe0f", - "uc_greedy": "1f481-1f3ff-2642", - "shortnames": [":man_tipping_hand_dark_skin_tone:"], - "category": "people" - }, - ":man_vampire_tone1:": { - "uc_base": "1f9db-1f3fb-2642", - "uc_output": "1f9db-1f3fb-200d-2642-fe0f", - "uc_match": "1f9db-1f3fb-2642-fe0f", - "uc_greedy": "1f9db-1f3fb-2642", - "shortnames": [":man_vampire_light_skin_tone:"], - "category": "people" - }, - ":man_vampire_tone2:": { - "uc_base": "1f9db-1f3fc-2642", - "uc_output": "1f9db-1f3fc-200d-2642-fe0f", - "uc_match": "1f9db-1f3fc-2642-fe0f", - "uc_greedy": "1f9db-1f3fc-2642", - "shortnames": [":man_vampire_medium_light_skin_tone:"], - "category": "people" - }, - ":man_vampire_tone3:": { - "uc_base": "1f9db-1f3fd-2642", - "uc_output": "1f9db-1f3fd-200d-2642-fe0f", - "uc_match": "1f9db-1f3fd-2642-fe0f", - "uc_greedy": "1f9db-1f3fd-2642", - "shortnames": [":man_vampire_medium_skin_tone:"], - "category": "people" - }, - ":man_vampire_tone4:": { - "uc_base": "1f9db-1f3fe-2642", - "uc_output": "1f9db-1f3fe-200d-2642-fe0f", - "uc_match": "1f9db-1f3fe-2642-fe0f", - "uc_greedy": "1f9db-1f3fe-2642", - "shortnames": [":man_vampire_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_vampire_tone5:": { - "uc_base": "1f9db-1f3ff-2642", - "uc_output": "1f9db-1f3ff-200d-2642-fe0f", - "uc_match": "1f9db-1f3ff-2642-fe0f", - "uc_greedy": "1f9db-1f3ff-2642", - "shortnames": [":man_vampire_dark_skin_tone:"], - "category": "people" - }, - ":man_walking_tone1:": { - "uc_base": "1f6b6-1f3fb-2642", - "uc_output": "1f6b6-1f3fb-200d-2642-fe0f", - "uc_match": "1f6b6-1f3fb-2642-fe0f", - "uc_greedy": "1f6b6-1f3fb-2642", - "shortnames": [":man_walking_light_skin_tone:"], - "category": "people" - }, - ":man_walking_tone2:": { - "uc_base": "1f6b6-1f3fc-2642", - "uc_output": "1f6b6-1f3fc-200d-2642-fe0f", - "uc_match": "1f6b6-1f3fc-2642-fe0f", - "uc_greedy": "1f6b6-1f3fc-2642", - "shortnames": [":man_walking_medium_light_skin_tone:"], - "category": "people" - }, - ":man_walking_tone3:": { - "uc_base": "1f6b6-1f3fd-2642", - "uc_output": "1f6b6-1f3fd-200d-2642-fe0f", - "uc_match": "1f6b6-1f3fd-2642-fe0f", - "uc_greedy": "1f6b6-1f3fd-2642", - "shortnames": [":man_walking_medium_skin_tone:"], - "category": "people" - }, - ":man_walking_tone4:": { - "uc_base": "1f6b6-1f3fe-2642", - "uc_output": "1f6b6-1f3fe-200d-2642-fe0f", - "uc_match": "1f6b6-1f3fe-2642-fe0f", - "uc_greedy": "1f6b6-1f3fe-2642", - "shortnames": [":man_walking_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_walking_tone5:": { - "uc_base": "1f6b6-1f3ff-2642", - "uc_output": "1f6b6-1f3ff-200d-2642-fe0f", - "uc_match": "1f6b6-1f3ff-2642-fe0f", - "uc_greedy": "1f6b6-1f3ff-2642", - "shortnames": [":man_walking_dark_skin_tone:"], - "category": "people" - }, - ":man_wearing_turban_tone1:": { - "uc_base": "1f473-1f3fb-2642", - "uc_output": "1f473-1f3fb-200d-2642-fe0f", - "uc_match": "1f473-1f3fb-2642-fe0f", - "uc_greedy": "1f473-1f3fb-2642", - "shortnames": [":man_wearing_turban_light_skin_tone:"], - "category": "people" - }, - ":man_wearing_turban_tone2:": { - "uc_base": "1f473-1f3fc-2642", - "uc_output": "1f473-1f3fc-200d-2642-fe0f", - "uc_match": "1f473-1f3fc-2642-fe0f", - "uc_greedy": "1f473-1f3fc-2642", - "shortnames": [":man_wearing_turban_medium_light_skin_tone:"], - "category": "people" - }, - ":man_wearing_turban_tone3:": { - "uc_base": "1f473-1f3fd-2642", - "uc_output": "1f473-1f3fd-200d-2642-fe0f", - "uc_match": "1f473-1f3fd-2642-fe0f", - "uc_greedy": "1f473-1f3fd-2642", - "shortnames": [":man_wearing_turban_medium_skin_tone:"], - "category": "people" - }, - ":man_wearing_turban_tone4:": { - "uc_base": "1f473-1f3fe-2642", - "uc_output": "1f473-1f3fe-200d-2642-fe0f", - "uc_match": "1f473-1f3fe-2642-fe0f", - "uc_greedy": "1f473-1f3fe-2642", - "shortnames": [":man_wearing_turban_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_wearing_turban_tone5:": { - "uc_base": "1f473-1f3ff-2642", - "uc_output": "1f473-1f3ff-200d-2642-fe0f", - "uc_match": "1f473-1f3ff-2642-fe0f", - "uc_greedy": "1f473-1f3ff-2642", - "shortnames": [":man_wearing_turban_dark_skin_tone:"], - "category": "people" - }, - ":mermaid_tone1:": { - "uc_base": "1f9dc-1f3fb-2640", - "uc_output": "1f9dc-1f3fb-200d-2640-fe0f", - "uc_match": "1f9dc-1f3fb-2640-fe0f", - "uc_greedy": "1f9dc-1f3fb-2640", - "shortnames": [":mermaid_light_skin_tone:"], - "category": "people" - }, - ":mermaid_tone2:": { - "uc_base": "1f9dc-1f3fc-2640", - "uc_output": "1f9dc-1f3fc-200d-2640-fe0f", - "uc_match": "1f9dc-1f3fc-2640-fe0f", - "uc_greedy": "1f9dc-1f3fc-2640", - "shortnames": [":mermaid_medium_light_skin_tone:"], - "category": "people" - }, - ":mermaid_tone3:": { - "uc_base": "1f9dc-1f3fd-2640", - "uc_output": "1f9dc-1f3fd-200d-2640-fe0f", - "uc_match": "1f9dc-1f3fd-2640-fe0f", - "uc_greedy": "1f9dc-1f3fd-2640", - "shortnames": [":mermaid_medium_skin_tone:"], - "category": "people" - }, - ":mermaid_tone4:": { - "uc_base": "1f9dc-1f3fe-2640", - "uc_output": "1f9dc-1f3fe-200d-2640-fe0f", - "uc_match": "1f9dc-1f3fe-2640-fe0f", - "uc_greedy": "1f9dc-1f3fe-2640", - "shortnames": [":mermaid_medium_dark_skin_tone:"], - "category": "people" - }, - ":mermaid_tone5:": { - "uc_base": "1f9dc-1f3ff-2640", - "uc_output": "1f9dc-1f3ff-200d-2640-fe0f", - "uc_match": "1f9dc-1f3ff-2640-fe0f", - "uc_greedy": "1f9dc-1f3ff-2640", - "shortnames": [":mermaid_dark_skin_tone:"], - "category": "people" - }, - ":merman_tone1:": { - "uc_base": "1f9dc-1f3fb-2642", - "uc_output": "1f9dc-1f3fb-200d-2642-fe0f", - "uc_match": "1f9dc-1f3fb-2642-fe0f", - "uc_greedy": "1f9dc-1f3fb-2642", - "shortnames": [":merman_light_skin_tone:"], - "category": "people" - }, - ":merman_tone2:": { - "uc_base": "1f9dc-1f3fc-2642", - "uc_output": "1f9dc-1f3fc-200d-2642-fe0f", - "uc_match": "1f9dc-1f3fc-2642-fe0f", - "uc_greedy": "1f9dc-1f3fc-2642", - "shortnames": [":merman_medium_light_skin_tone:"], - "category": "people" - }, - ":merman_tone3:": { - "uc_base": "1f9dc-1f3fd-2642", - "uc_output": "1f9dc-1f3fd-200d-2642-fe0f", - "uc_match": "1f9dc-1f3fd-2642-fe0f", - "uc_greedy": "1f9dc-1f3fd-2642", - "shortnames": [":merman_medium_skin_tone:"], - "category": "people" - }, - ":merman_tone4:": { - "uc_base": "1f9dc-1f3fe-2642", - "uc_output": "1f9dc-1f3fe-200d-2642-fe0f", - "uc_match": "1f9dc-1f3fe-2642-fe0f", - "uc_greedy": "1f9dc-1f3fe-2642", - "shortnames": [":merman_medium_dark_skin_tone:"], - "category": "people" - }, - ":merman_tone5:": { - "uc_base": "1f9dc-1f3ff-2642", - "uc_output": "1f9dc-1f3ff-200d-2642-fe0f", - "uc_match": "1f9dc-1f3ff-2642-fe0f", - "uc_greedy": "1f9dc-1f3ff-2642", - "shortnames": [":merman_dark_skin_tone:"], - "category": "people" - }, - ":woman_biking_tone1:": { - "uc_base": "1f6b4-1f3fb-2640", - "uc_output": "1f6b4-1f3fb-200d-2640-fe0f", - "uc_match": "1f6b4-1f3fb-2640-fe0f", - "uc_greedy": "1f6b4-1f3fb-2640", - "shortnames": [":woman_biking_light_skin_tone:"], - "category": "activity" - }, - ":woman_biking_tone2:": { - "uc_base": "1f6b4-1f3fc-2640", - "uc_output": "1f6b4-1f3fc-200d-2640-fe0f", - "uc_match": "1f6b4-1f3fc-2640-fe0f", - "uc_greedy": "1f6b4-1f3fc-2640", - "shortnames": [":woman_biking_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_biking_tone3:": { - "uc_base": "1f6b4-1f3fd-2640", - "uc_output": "1f6b4-1f3fd-200d-2640-fe0f", - "uc_match": "1f6b4-1f3fd-2640-fe0f", - "uc_greedy": "1f6b4-1f3fd-2640", - "shortnames": [":woman_biking_medium_skin_tone:"], - "category": "activity" - }, - ":woman_biking_tone4:": { - "uc_base": "1f6b4-1f3fe-2640", - "uc_output": "1f6b4-1f3fe-200d-2640-fe0f", - "uc_match": "1f6b4-1f3fe-2640-fe0f", - "uc_greedy": "1f6b4-1f3fe-2640", - "shortnames": [":woman_biking_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_biking_tone5:": { - "uc_base": "1f6b4-1f3ff-2640", - "uc_output": "1f6b4-1f3ff-200d-2640-fe0f", - "uc_match": "1f6b4-1f3ff-2640-fe0f", - "uc_greedy": "1f6b4-1f3ff-2640", - "shortnames": [":woman_biking_dark_skin_tone:"], - "category": "activity" - }, - ":woman_bowing_tone1:": { - "uc_base": "1f647-1f3fb-2640", - "uc_output": "1f647-1f3fb-200d-2640-fe0f", - "uc_match": "1f647-1f3fb-2640-fe0f", - "uc_greedy": "1f647-1f3fb-2640", - "shortnames": [":woman_bowing_light_skin_tone:"], - "category": "people" - }, - ":woman_bowing_tone2:": { - "uc_base": "1f647-1f3fc-2640", - "uc_output": "1f647-1f3fc-200d-2640-fe0f", - "uc_match": "1f647-1f3fc-2640-fe0f", - "uc_greedy": "1f647-1f3fc-2640", - "shortnames": [":woman_bowing_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_bowing_tone3:": { - "uc_base": "1f647-1f3fd-2640", - "uc_output": "1f647-1f3fd-200d-2640-fe0f", - "uc_match": "1f647-1f3fd-2640-fe0f", - "uc_greedy": "1f647-1f3fd-2640", - "shortnames": [":woman_bowing_medium_skin_tone:"], - "category": "people" - }, - ":woman_bowing_tone4:": { - "uc_base": "1f647-1f3fe-2640", - "uc_output": "1f647-1f3fe-200d-2640-fe0f", - "uc_match": "1f647-1f3fe-2640-fe0f", - "uc_greedy": "1f647-1f3fe-2640", - "shortnames": [":woman_bowing_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_bowing_tone5:": { - "uc_base": "1f647-1f3ff-2640", - "uc_output": "1f647-1f3ff-200d-2640-fe0f", - "uc_match": "1f647-1f3ff-2640-fe0f", - "uc_greedy": "1f647-1f3ff-2640", - "shortnames": [":woman_bowing_dark_skin_tone:"], - "category": "people" - }, - ":woman_cartwheeling_tone1:": { - "uc_base": "1f938-1f3fb-2640", - "uc_output": "1f938-1f3fb-200d-2640-fe0f", - "uc_match": "1f938-1f3fb-2640-fe0f", - "uc_greedy": "1f938-1f3fb-2640", - "shortnames": [":woman_cartwheeling_light_skin_tone:"], - "category": "activity" - }, - ":woman_cartwheeling_tone2:": { - "uc_base": "1f938-1f3fc-2640", - "uc_output": "1f938-1f3fc-200d-2640-fe0f", - "uc_match": "1f938-1f3fc-2640-fe0f", - "uc_greedy": "1f938-1f3fc-2640", - "shortnames": [":woman_cartwheeling_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_cartwheeling_tone3:": { - "uc_base": "1f938-1f3fd-2640", - "uc_output": "1f938-1f3fd-200d-2640-fe0f", - "uc_match": "1f938-1f3fd-2640-fe0f", - "uc_greedy": "1f938-1f3fd-2640", - "shortnames": [":woman_cartwheeling_medium_skin_tone:"], - "category": "activity" - }, - ":woman_cartwheeling_tone4:": { - "uc_base": "1f938-1f3fe-2640", - "uc_output": "1f938-1f3fe-200d-2640-fe0f", - "uc_match": "1f938-1f3fe-2640-fe0f", - "uc_greedy": "1f938-1f3fe-2640", - "shortnames": [":woman_cartwheeling_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_cartwheeling_tone5:": { - "uc_base": "1f938-1f3ff-2640", - "uc_output": "1f938-1f3ff-200d-2640-fe0f", - "uc_match": "1f938-1f3ff-2640-fe0f", - "uc_greedy": "1f938-1f3ff-2640", - "shortnames": [":woman_cartwheeling_dark_skin_tone:"], - "category": "activity" - }, - ":woman_climbing_tone1:": { - "uc_base": "1f9d7-1f3fb-2640", - "uc_output": "1f9d7-1f3fb-200d-2640-fe0f", - "uc_match": "1f9d7-1f3fb-2640-fe0f", - "uc_greedy": "1f9d7-1f3fb-2640", - "shortnames": [":woman_climbing_light_skin_tone:"], - "category": "activity" - }, - ":woman_climbing_tone2:": { - "uc_base": "1f9d7-1f3fc-2640", - "uc_output": "1f9d7-1f3fc-200d-2640-fe0f", - "uc_match": "1f9d7-1f3fc-2640-fe0f", - "uc_greedy": "1f9d7-1f3fc-2640", - "shortnames": [":woman_climbing_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_climbing_tone3:": { - "uc_base": "1f9d7-1f3fd-2640", - "uc_output": "1f9d7-1f3fd-200d-2640-fe0f", - "uc_match": "1f9d7-1f3fd-2640-fe0f", - "uc_greedy": "1f9d7-1f3fd-2640", - "shortnames": [":woman_climbing_medium_skin_tone:"], - "category": "activity" - }, - ":woman_climbing_tone4:": { - "uc_base": "1f9d7-1f3fe-2640", - "uc_output": "1f9d7-1f3fe-200d-2640-fe0f", - "uc_match": "1f9d7-1f3fe-2640-fe0f", - "uc_greedy": "1f9d7-1f3fe-2640", - "shortnames": [":woman_climbing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_climbing_tone5:": { - "uc_base": "1f9d7-1f3ff-2640", - "uc_output": "1f9d7-1f3ff-200d-2640-fe0f", - "uc_match": "1f9d7-1f3ff-2640-fe0f", - "uc_greedy": "1f9d7-1f3ff-2640", - "shortnames": [":woman_climbing_dark_skin_tone:"], - "category": "activity" - }, - ":woman_construction_worker_tone1:": { - "uc_base": "1f477-1f3fb-2640", - "uc_output": "1f477-1f3fb-200d-2640-fe0f", - "uc_match": "1f477-1f3fb-2640-fe0f", - "uc_greedy": "1f477-1f3fb-2640", - "shortnames": [":woman_construction_worker_light_skin_tone:"], - "category": "people" - }, - ":woman_construction_worker_tone2:": { - "uc_base": "1f477-1f3fc-2640", - "uc_output": "1f477-1f3fc-200d-2640-fe0f", - "uc_match": "1f477-1f3fc-2640-fe0f", - "uc_greedy": "1f477-1f3fc-2640", - "shortnames": [":woman_construction_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_construction_worker_tone3:": { - "uc_base": "1f477-1f3fd-2640", - "uc_output": "1f477-1f3fd-200d-2640-fe0f", - "uc_match": "1f477-1f3fd-2640-fe0f", - "uc_greedy": "1f477-1f3fd-2640", - "shortnames": [":woman_construction_worker_medium_skin_tone:"], - "category": "people" - }, - ":woman_construction_worker_tone4:": { - "uc_base": "1f477-1f3fe-2640", - "uc_output": "1f477-1f3fe-200d-2640-fe0f", - "uc_match": "1f477-1f3fe-2640-fe0f", - "uc_greedy": "1f477-1f3fe-2640", - "shortnames": [":woman_construction_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_construction_worker_tone5:": { - "uc_base": "1f477-1f3ff-2640", - "uc_output": "1f477-1f3ff-200d-2640-fe0f", - "uc_match": "1f477-1f3ff-2640-fe0f", - "uc_greedy": "1f477-1f3ff-2640", - "shortnames": [":woman_construction_worker_dark_skin_tone:"], - "category": "people" - }, - ":woman_detective_tone1:": { - "uc_base": "1f575-1f3fb-2640", - "uc_output": "1f575-1f3fb-200d-2640-fe0f", - "uc_match": "1f575-fe0f-1f3fb-2640-fe0f", - "uc_greedy": "1f575-1f3fb-2640", - "shortnames": [":woman_detective_light_skin_tone:"], - "category": "people" - }, - ":woman_detective_tone2:": { - "uc_base": "1f575-1f3fc-2640", - "uc_output": "1f575-1f3fc-200d-2640-fe0f", - "uc_match": "1f575-fe0f-1f3fc-2640-fe0f", - "uc_greedy": "1f575-1f3fc-2640", - "shortnames": [":woman_detective_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_detective_tone3:": { - "uc_base": "1f575-1f3fd-2640", - "uc_output": "1f575-1f3fd-200d-2640-fe0f", - "uc_match": "1f575-fe0f-1f3fd-2640-fe0f", - "uc_greedy": "1f575-1f3fd-2640", - "shortnames": [":woman_detective_medium_skin_tone:"], - "category": "people" - }, - ":woman_detective_tone4:": { - "uc_base": "1f575-1f3fe-2640", - "uc_output": "1f575-1f3fe-200d-2640-fe0f", - "uc_match": "1f575-fe0f-1f3fe-2640-fe0f", - "uc_greedy": "1f575-1f3fe-2640", - "shortnames": [":woman_detective_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_detective_tone5:": { - "uc_base": "1f575-1f3ff-2640", - "uc_output": "1f575-1f3ff-200d-2640-fe0f", - "uc_match": "1f575-fe0f-1f3ff-2640-fe0f", - "uc_greedy": "1f575-1f3ff-2640", - "shortnames": [":woman_detective_dark_skin_tone:"], - "category": "people" - }, - ":woman_elf_tone1:": { - "uc_base": "1f9dd-1f3fb-2640", - "uc_output": "1f9dd-1f3fb-200d-2640-fe0f", - "uc_match": "1f9dd-1f3fb-2640-fe0f", - "uc_greedy": "1f9dd-1f3fb-2640", - "shortnames": [":woman_elf_light_skin_tone:"], - "category": "people" - }, - ":woman_elf_tone2:": { - "uc_base": "1f9dd-1f3fc-2640", - "uc_output": "1f9dd-1f3fc-200d-2640-fe0f", - "uc_match": "1f9dd-1f3fc-2640-fe0f", - "uc_greedy": "1f9dd-1f3fc-2640", - "shortnames": [":woman_elf_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_elf_tone3:": { - "uc_base": "1f9dd-1f3fd-2640", - "uc_output": "1f9dd-1f3fd-200d-2640-fe0f", - "uc_match": "1f9dd-1f3fd-2640-fe0f", - "uc_greedy": "1f9dd-1f3fd-2640", - "shortnames": [":woman_elf_medium_skin_tone:"], - "category": "people" - }, - ":woman_elf_tone4:": { - "uc_base": "1f9dd-1f3fe-2640", - "uc_output": "1f9dd-1f3fe-200d-2640-fe0f", - "uc_match": "1f9dd-1f3fe-2640-fe0f", - "uc_greedy": "1f9dd-1f3fe-2640", - "shortnames": [":woman_elf_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_elf_tone5:": { - "uc_base": "1f9dd-1f3ff-2640", - "uc_output": "1f9dd-1f3ff-200d-2640-fe0f", - "uc_match": "1f9dd-1f3ff-2640-fe0f", - "uc_greedy": "1f9dd-1f3ff-2640", - "shortnames": [":woman_elf_dark_skin_tone:"], - "category": "people" - }, - ":woman_facepalming_tone1:": { - "uc_base": "1f926-1f3fb-2640", - "uc_output": "1f926-1f3fb-200d-2640-fe0f", - "uc_match": "1f926-1f3fb-2640-fe0f", - "uc_greedy": "1f926-1f3fb-2640", - "shortnames": [":woman_facepalming_light_skin_tone:"], - "category": "people" - }, - ":woman_facepalming_tone2:": { - "uc_base": "1f926-1f3fc-2640", - "uc_output": "1f926-1f3fc-200d-2640-fe0f", - "uc_match": "1f926-1f3fc-2640-fe0f", - "uc_greedy": "1f926-1f3fc-2640", - "shortnames": [":woman_facepalming_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_facepalming_tone3:": { - "uc_base": "1f926-1f3fd-2640", - "uc_output": "1f926-1f3fd-200d-2640-fe0f", - "uc_match": "1f926-1f3fd-2640-fe0f", - "uc_greedy": "1f926-1f3fd-2640", - "shortnames": [":woman_facepalming_medium_skin_tone:"], - "category": "people" - }, - ":woman_facepalming_tone4:": { - "uc_base": "1f926-1f3fe-2640", - "uc_output": "1f926-1f3fe-200d-2640-fe0f", - "uc_match": "1f926-1f3fe-2640-fe0f", - "uc_greedy": "1f926-1f3fe-2640", - "shortnames": [":woman_facepalming_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_facepalming_tone5:": { - "uc_base": "1f926-1f3ff-2640", - "uc_output": "1f926-1f3ff-200d-2640-fe0f", - "uc_match": "1f926-1f3ff-2640-fe0f", - "uc_greedy": "1f926-1f3ff-2640", - "shortnames": [":woman_facepalming_dark_skin_tone:"], - "category": "people" - }, - ":woman_fairy_tone1:": { - "uc_base": "1f9da-1f3fb-2640", - "uc_output": "1f9da-1f3fb-200d-2640-fe0f", - "uc_match": "1f9da-1f3fb-2640-fe0f", - "uc_greedy": "1f9da-1f3fb-2640", - "shortnames": [":woman_fairy_light_skin_tone:"], - "category": "people" - }, - ":woman_fairy_tone2:": { - "uc_base": "1f9da-1f3fc-2640", - "uc_output": "1f9da-1f3fc-200d-2640-fe0f", - "uc_match": "1f9da-1f3fc-2640-fe0f", - "uc_greedy": "1f9da-1f3fc-2640", - "shortnames": [":woman_fairy_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_fairy_tone3:": { - "uc_base": "1f9da-1f3fd-2640", - "uc_output": "1f9da-1f3fd-200d-2640-fe0f", - "uc_match": "1f9da-1f3fd-2640-fe0f", - "uc_greedy": "1f9da-1f3fd-2640", - "shortnames": [":woman_fairy_medium_skin_tone:"], - "category": "people" - }, - ":woman_fairy_tone4:": { - "uc_base": "1f9da-1f3fe-2640", - "uc_output": "1f9da-1f3fe-200d-2640-fe0f", - "uc_match": "1f9da-1f3fe-2640-fe0f", - "uc_greedy": "1f9da-1f3fe-2640", - "shortnames": [":woman_fairy_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_fairy_tone5:": { - "uc_base": "1f9da-1f3ff-2640", - "uc_output": "1f9da-1f3ff-200d-2640-fe0f", - "uc_match": "1f9da-1f3ff-2640-fe0f", - "uc_greedy": "1f9da-1f3ff-2640", - "shortnames": [":woman_fairy_dark_skin_tone:"], - "category": "people" - }, - ":woman_frowning_tone1:": { - "uc_base": "1f64d-1f3fb-2640", - "uc_output": "1f64d-1f3fb-200d-2640-fe0f", - "uc_match": "1f64d-1f3fb-2640-fe0f", - "uc_greedy": "1f64d-1f3fb-2640", - "shortnames": [":woman_frowning_light_skin_tone:"], - "category": "people" - }, - ":woman_frowning_tone2:": { - "uc_base": "1f64d-1f3fc-2640", - "uc_output": "1f64d-1f3fc-200d-2640-fe0f", - "uc_match": "1f64d-1f3fc-2640-fe0f", - "uc_greedy": "1f64d-1f3fc-2640", - "shortnames": [":woman_frowning_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_frowning_tone3:": { - "uc_base": "1f64d-1f3fd-2640", - "uc_output": "1f64d-1f3fd-200d-2640-fe0f", - "uc_match": "1f64d-1f3fd-2640-fe0f", - "uc_greedy": "1f64d-1f3fd-2640", - "shortnames": [":woman_frowning_medium_skin_tone:"], - "category": "people" - }, - ":woman_frowning_tone4:": { - "uc_base": "1f64d-1f3fe-2640", - "uc_output": "1f64d-1f3fe-200d-2640-fe0f", - "uc_match": "1f64d-1f3fe-2640-fe0f", - "uc_greedy": "1f64d-1f3fe-2640", - "shortnames": [":woman_frowning_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_frowning_tone5:": { - "uc_base": "1f64d-1f3ff-2640", - "uc_output": "1f64d-1f3ff-200d-2640-fe0f", - "uc_match": "1f64d-1f3ff-2640-fe0f", - "uc_greedy": "1f64d-1f3ff-2640", - "shortnames": [":woman_frowning_dark_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_no_tone1:": { - "uc_base": "1f645-1f3fb-2640", - "uc_output": "1f645-1f3fb-200d-2640-fe0f", - "uc_match": "1f645-1f3fb-2640-fe0f", - "uc_greedy": "1f645-1f3fb-2640", - "shortnames": [":woman_gesturing_no_light_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_no_tone2:": { - "uc_base": "1f645-1f3fc-2640", - "uc_output": "1f645-1f3fc-200d-2640-fe0f", - "uc_match": "1f645-1f3fc-2640-fe0f", - "uc_greedy": "1f645-1f3fc-2640", - "shortnames": [":woman_gesturing_no_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_no_tone3:": { - "uc_base": "1f645-1f3fd-2640", - "uc_output": "1f645-1f3fd-200d-2640-fe0f", - "uc_match": "1f645-1f3fd-2640-fe0f", - "uc_greedy": "1f645-1f3fd-2640", - "shortnames": [":woman_gesturing_no_medium_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_no_tone4:": { - "uc_base": "1f645-1f3fe-2640", - "uc_output": "1f645-1f3fe-200d-2640-fe0f", - "uc_match": "1f645-1f3fe-2640-fe0f", - "uc_greedy": "1f645-1f3fe-2640", - "shortnames": [":woman_gesturing_no_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_no_tone5:": { - "uc_base": "1f645-1f3ff-2640", - "uc_output": "1f645-1f3ff-200d-2640-fe0f", - "uc_match": "1f645-1f3ff-2640-fe0f", - "uc_greedy": "1f645-1f3ff-2640", - "shortnames": [":woman_gesturing_no_dark_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_ok_tone1:": { - "uc_base": "1f646-1f3fb-2640", - "uc_output": "1f646-1f3fb-200d-2640-fe0f", - "uc_match": "1f646-1f3fb-2640-fe0f", - "uc_greedy": "1f646-1f3fb-2640", - "shortnames": [":woman_gesturing_ok_light_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_ok_tone2:": { - "uc_base": "1f646-1f3fc-2640", - "uc_output": "1f646-1f3fc-200d-2640-fe0f", - "uc_match": "1f646-1f3fc-2640-fe0f", - "uc_greedy": "1f646-1f3fc-2640", - "shortnames": [":woman_gesturing_ok_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_ok_tone3:": { - "uc_base": "1f646-1f3fd-2640", - "uc_output": "1f646-1f3fd-200d-2640-fe0f", - "uc_match": "1f646-1f3fd-2640-fe0f", - "uc_greedy": "1f646-1f3fd-2640", - "shortnames": [":woman_gesturing_ok_medium_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_ok_tone4:": { - "uc_base": "1f646-1f3fe-2640", - "uc_output": "1f646-1f3fe-200d-2640-fe0f", - "uc_match": "1f646-1f3fe-2640-fe0f", - "uc_greedy": "1f646-1f3fe-2640", - "shortnames": [":woman_gesturing_ok_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_gesturing_ok_tone5:": { - "uc_base": "1f646-1f3ff-2640", - "uc_output": "1f646-1f3ff-200d-2640-fe0f", - "uc_match": "1f646-1f3ff-2640-fe0f", - "uc_greedy": "1f646-1f3ff-2640", - "shortnames": [":woman_gesturing_ok_dark_skin_tone:"], - "category": "people" - }, - ":woman_getting_face_massage_tone1:": { - "uc_base": "1f486-1f3fb-2640", - "uc_output": "1f486-1f3fb-200d-2640-fe0f", - "uc_match": "1f486-1f3fb-2640-fe0f", - "uc_greedy": "1f486-1f3fb-2640", - "shortnames": [":woman_getting_face_massage_light_skin_tone:"], - "category": "people" - }, - ":woman_getting_face_massage_tone2:": { - "uc_base": "1f486-1f3fc-2640", - "uc_output": "1f486-1f3fc-200d-2640-fe0f", - "uc_match": "1f486-1f3fc-2640-fe0f", - "uc_greedy": "1f486-1f3fc-2640", - "shortnames": [":woman_getting_face_massage_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_getting_face_massage_tone3:": { - "uc_base": "1f486-1f3fd-2640", - "uc_output": "1f486-1f3fd-200d-2640-fe0f", - "uc_match": "1f486-1f3fd-2640-fe0f", - "uc_greedy": "1f486-1f3fd-2640", - "shortnames": [":woman_getting_face_massage_medium_skin_tone:"], - "category": "people" - }, - ":woman_getting_face_massage_tone4:": { - "uc_base": "1f486-1f3fe-2640", - "uc_output": "1f486-1f3fe-200d-2640-fe0f", - "uc_match": "1f486-1f3fe-2640-fe0f", - "uc_greedy": "1f486-1f3fe-2640", - "shortnames": [":woman_getting_face_massage_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_getting_face_massage_tone5:": { - "uc_base": "1f486-1f3ff-2640", - "uc_output": "1f486-1f3ff-200d-2640-fe0f", - "uc_match": "1f486-1f3ff-2640-fe0f", - "uc_greedy": "1f486-1f3ff-2640", - "shortnames": [":woman_getting_face_massage_dark_skin_tone:"], - "category": "people" - }, - ":woman_getting_haircut_tone1:": { - "uc_base": "1f487-1f3fb-2640", - "uc_output": "1f487-1f3fb-200d-2640-fe0f", - "uc_match": "1f487-1f3fb-2640-fe0f", - "uc_greedy": "1f487-1f3fb-2640", - "shortnames": [":woman_getting_haircut_light_skin_tone:"], - "category": "people" - }, - ":woman_getting_haircut_tone2:": { - "uc_base": "1f487-1f3fc-2640", - "uc_output": "1f487-1f3fc-200d-2640-fe0f", - "uc_match": "1f487-1f3fc-2640-fe0f", - "uc_greedy": "1f487-1f3fc-2640", - "shortnames": [":woman_getting_haircut_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_getting_haircut_tone3:": { - "uc_base": "1f487-1f3fd-2640", - "uc_output": "1f487-1f3fd-200d-2640-fe0f", - "uc_match": "1f487-1f3fd-2640-fe0f", - "uc_greedy": "1f487-1f3fd-2640", - "shortnames": [":woman_getting_haircut_medium_skin_tone:"], - "category": "people" - }, - ":woman_getting_haircut_tone4:": { - "uc_base": "1f487-1f3fe-2640", - "uc_output": "1f487-1f3fe-200d-2640-fe0f", - "uc_match": "1f487-1f3fe-2640-fe0f", - "uc_greedy": "1f487-1f3fe-2640", - "shortnames": [":woman_getting_haircut_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_getting_haircut_tone5:": { - "uc_base": "1f487-1f3ff-2640", - "uc_output": "1f487-1f3ff-200d-2640-fe0f", - "uc_match": "1f487-1f3ff-2640-fe0f", - "uc_greedy": "1f487-1f3ff-2640", - "shortnames": [":woman_getting_haircut_dark_skin_tone:"], - "category": "people" - }, - ":woman_golfing_tone1:": { - "uc_base": "1f3cc-1f3fb-2640", - "uc_output": "1f3cc-1f3fb-200d-2640-fe0f", - "uc_match": "1f3cc-fe0f-1f3fb-2640-fe0f", - "uc_greedy": "1f3cc-1f3fb-2640", - "shortnames": [":woman_golfing_light_skin_tone:"], - "category": "activity" - }, - ":woman_golfing_tone2:": { - "uc_base": "1f3cc-1f3fc-2640", - "uc_output": "1f3cc-1f3fc-200d-2640-fe0f", - "uc_match": "1f3cc-fe0f-1f3fc-2640-fe0f", - "uc_greedy": "1f3cc-1f3fc-2640", - "shortnames": [":woman_golfing_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_golfing_tone3:": { - "uc_base": "1f3cc-1f3fd-2640", - "uc_output": "1f3cc-1f3fd-200d-2640-fe0f", - "uc_match": "1f3cc-fe0f-1f3fd-2640-fe0f", - "uc_greedy": "1f3cc-1f3fd-2640", - "shortnames": [":woman_golfing_medium_skin_tone:"], - "category": "activity" - }, - ":woman_golfing_tone4:": { - "uc_base": "1f3cc-1f3fe-2640", - "uc_output": "1f3cc-1f3fe-200d-2640-fe0f", - "uc_match": "1f3cc-fe0f-1f3fe-2640-fe0f", - "uc_greedy": "1f3cc-1f3fe-2640", - "shortnames": [":woman_golfing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_golfing_tone5:": { - "uc_base": "1f3cc-1f3ff-2640", - "uc_output": "1f3cc-1f3ff-200d-2640-fe0f", - "uc_match": "1f3cc-fe0f-1f3ff-2640-fe0f", - "uc_greedy": "1f3cc-1f3ff-2640", - "shortnames": [":woman_golfing_dark_skin_tone:"], - "category": "activity" - }, - ":woman_guard_tone1:": { - "uc_base": "1f482-1f3fb-2640", - "uc_output": "1f482-1f3fb-200d-2640-fe0f", - "uc_match": "1f482-1f3fb-2640-fe0f", - "uc_greedy": "1f482-1f3fb-2640", - "shortnames": [":woman_guard_light_skin_tone:"], - "category": "people" - }, - ":woman_guard_tone2:": { - "uc_base": "1f482-1f3fc-2640", - "uc_output": "1f482-1f3fc-200d-2640-fe0f", - "uc_match": "1f482-1f3fc-2640-fe0f", - "uc_greedy": "1f482-1f3fc-2640", - "shortnames": [":woman_guard_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_guard_tone3:": { - "uc_base": "1f482-1f3fd-2640", - "uc_output": "1f482-1f3fd-200d-2640-fe0f", - "uc_match": "1f482-1f3fd-2640-fe0f", - "uc_greedy": "1f482-1f3fd-2640", - "shortnames": [":woman_guard_medium_skin_tone:"], - "category": "people" - }, - ":woman_guard_tone4:": { - "uc_base": "1f482-1f3fe-2640", - "uc_output": "1f482-1f3fe-200d-2640-fe0f", - "uc_match": "1f482-1f3fe-2640-fe0f", - "uc_greedy": "1f482-1f3fe-2640", - "shortnames": [":woman_guard_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_guard_tone5:": { - "uc_base": "1f482-1f3ff-2640", - "uc_output": "1f482-1f3ff-200d-2640-fe0f", - "uc_match": "1f482-1f3ff-2640-fe0f", - "uc_greedy": "1f482-1f3ff-2640", - "shortnames": [":woman_guard_dark_skin_tone:"], - "category": "people" - }, - ":woman_health_worker_tone1:": { - "uc_base": "1f469-1f3fb-2695", - "uc_output": "1f469-1f3fb-200d-2695-fe0f", - "uc_match": "1f469-1f3fb-2695-fe0f", - "uc_greedy": "1f469-1f3fb-2695", - "shortnames": [":woman_health_worker_light_skin_tone:"], - "category": "people" - }, - ":woman_health_worker_tone2:": { - "uc_base": "1f469-1f3fc-2695", - "uc_output": "1f469-1f3fc-200d-2695-fe0f", - "uc_match": "1f469-1f3fc-2695-fe0f", - "uc_greedy": "1f469-1f3fc-2695", - "shortnames": [":woman_health_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_health_worker_tone3:": { - "uc_base": "1f469-1f3fd-2695", - "uc_output": "1f469-1f3fd-200d-2695-fe0f", - "uc_match": "1f469-1f3fd-2695-fe0f", - "uc_greedy": "1f469-1f3fd-2695", - "shortnames": [":woman_health_worker_medium_skin_tone:"], - "category": "people" - }, - ":woman_health_worker_tone4:": { - "uc_base": "1f469-1f3fe-2695", - "uc_output": "1f469-1f3fe-200d-2695-fe0f", - "uc_match": "1f469-1f3fe-2695-fe0f", - "uc_greedy": "1f469-1f3fe-2695", - "shortnames": [":woman_health_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_health_worker_tone5:": { - "uc_base": "1f469-1f3ff-2695", - "uc_output": "1f469-1f3ff-200d-2695-fe0f", - "uc_match": "1f469-1f3ff-2695-fe0f", - "uc_greedy": "1f469-1f3ff-2695", - "shortnames": [":woman_health_worker_dark_skin_tone:"], - "category": "people" - }, - ":woman_in_lotus_position_tone1:": { - "uc_base": "1f9d8-1f3fb-2640", - "uc_output": "1f9d8-1f3fb-200d-2640-fe0f", - "uc_match": "1f9d8-1f3fb-2640-fe0f", - "uc_greedy": "1f9d8-1f3fb-2640", - "shortnames": [":woman_in_lotus_position_light_skin_tone:"], - "category": "activity" - }, - ":woman_in_lotus_position_tone2:": { - "uc_base": "1f9d8-1f3fc-2640", - "uc_output": "1f9d8-1f3fc-200d-2640-fe0f", - "uc_match": "1f9d8-1f3fc-2640-fe0f", - "uc_greedy": "1f9d8-1f3fc-2640", - "shortnames": [":woman_in_lotus_position_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_in_lotus_position_tone3:": { - "uc_base": "1f9d8-1f3fd-2640", - "uc_output": "1f9d8-1f3fd-200d-2640-fe0f", - "uc_match": "1f9d8-1f3fd-2640-fe0f", - "uc_greedy": "1f9d8-1f3fd-2640", - "shortnames": [":woman_in_lotus_position_medium_skin_tone:"], - "category": "activity" - }, - ":woman_in_lotus_position_tone4:": { - "uc_base": "1f9d8-1f3fe-2640", - "uc_output": "1f9d8-1f3fe-200d-2640-fe0f", - "uc_match": "1f9d8-1f3fe-2640-fe0f", - "uc_greedy": "1f9d8-1f3fe-2640", - "shortnames": [":woman_in_lotus_position_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_in_lotus_position_tone5:": { - "uc_base": "1f9d8-1f3ff-2640", - "uc_output": "1f9d8-1f3ff-200d-2640-fe0f", - "uc_match": "1f9d8-1f3ff-2640-fe0f", - "uc_greedy": "1f9d8-1f3ff-2640", - "shortnames": [":woman_in_lotus_position_dark_skin_tone:"], - "category": "activity" - }, - ":woman_in_steamy_room_tone1:": { - "uc_base": "1f9d6-1f3fb-2640", - "uc_output": "1f9d6-1f3fb-200d-2640-fe0f", - "uc_match": "1f9d6-1f3fb-2640-fe0f", - "uc_greedy": "1f9d6-1f3fb-2640", - "shortnames": [":woman_in_steamy_room_light_skin_tone:"], - "category": "people" - }, - ":woman_in_steamy_room_tone2:": { - "uc_base": "1f9d6-1f3fc-2640", - "uc_output": "1f9d6-1f3fc-200d-2640-fe0f", - "uc_match": "1f9d6-1f3fc-2640-fe0f", - "uc_greedy": "1f9d6-1f3fc-2640", - "shortnames": [":woman_in_steamy_room_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_in_steamy_room_tone3:": { - "uc_base": "1f9d6-1f3fd-2640", - "uc_output": "1f9d6-1f3fd-200d-2640-fe0f", - "uc_match": "1f9d6-1f3fd-2640-fe0f", - "uc_greedy": "1f9d6-1f3fd-2640", - "shortnames": [":woman_in_steamy_room_medium_skin_tone:"], - "category": "people" - }, - ":woman_in_steamy_room_tone4:": { - "uc_base": "1f9d6-1f3fe-2640", - "uc_output": "1f9d6-1f3fe-200d-2640-fe0f", - "uc_match": "1f9d6-1f3fe-2640-fe0f", - "uc_greedy": "1f9d6-1f3fe-2640", - "shortnames": [":woman_in_steamy_room_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_in_steamy_room_tone5:": { - "uc_base": "1f9d6-1f3ff-2640", - "uc_output": "1f9d6-1f3ff-200d-2640-fe0f", - "uc_match": "1f9d6-1f3ff-2640-fe0f", - "uc_greedy": "1f9d6-1f3ff-2640", - "shortnames": [":woman_in_steamy_room_dark_skin_tone:"], - "category": "people" - }, - ":woman_judge_tone1:": { - "uc_base": "1f469-1f3fb-2696", - "uc_output": "1f469-1f3fb-200d-2696-fe0f", - "uc_match": "1f469-1f3fb-2696-fe0f", - "uc_greedy": "1f469-1f3fb-2696", - "shortnames": [":woman_judge_light_skin_tone:"], - "category": "people" - }, - ":woman_judge_tone2:": { - "uc_base": "1f469-1f3fc-2696", - "uc_output": "1f469-1f3fc-200d-2696-fe0f", - "uc_match": "1f469-1f3fc-2696-fe0f", - "uc_greedy": "1f469-1f3fc-2696", - "shortnames": [":woman_judge_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_judge_tone3:": { - "uc_base": "1f469-1f3fd-2696", - "uc_output": "1f469-1f3fd-200d-2696-fe0f", - "uc_match": "1f469-1f3fd-2696-fe0f", - "uc_greedy": "1f469-1f3fd-2696", - "shortnames": [":woman_judge_medium_skin_tone:"], - "category": "people" - }, - ":woman_judge_tone4:": { - "uc_base": "1f469-1f3fe-2696", - "uc_output": "1f469-1f3fe-200d-2696-fe0f", - "uc_match": "1f469-1f3fe-2696-fe0f", - "uc_greedy": "1f469-1f3fe-2696", - "shortnames": [":woman_judge_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_judge_tone5:": { - "uc_base": "1f469-1f3ff-2696", - "uc_output": "1f469-1f3ff-200d-2696-fe0f", - "uc_match": "1f469-1f3ff-2696-fe0f", - "uc_greedy": "1f469-1f3ff-2696", - "shortnames": [":woman_judge_dark_skin_tone:"], - "category": "people" - }, - ":woman_juggling_tone1:": { - "uc_base": "1f939-1f3fb-2640", - "uc_output": "1f939-1f3fb-200d-2640-fe0f", - "uc_match": "1f939-1f3fb-2640-fe0f", - "uc_greedy": "1f939-1f3fb-2640", - "shortnames": [":woman_juggling_light_skin_tone:"], - "category": "activity" - }, - ":woman_juggling_tone2:": { - "uc_base": "1f939-1f3fc-2640", - "uc_output": "1f939-1f3fc-200d-2640-fe0f", - "uc_match": "1f939-1f3fc-2640-fe0f", - "uc_greedy": "1f939-1f3fc-2640", - "shortnames": [":woman_juggling_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_juggling_tone3:": { - "uc_base": "1f939-1f3fd-2640", - "uc_output": "1f939-1f3fd-200d-2640-fe0f", - "uc_match": "1f939-1f3fd-2640-fe0f", - "uc_greedy": "1f939-1f3fd-2640", - "shortnames": [":woman_juggling_medium_skin_tone:"], - "category": "activity" - }, - ":woman_juggling_tone4:": { - "uc_base": "1f939-1f3fe-2640", - "uc_output": "1f939-1f3fe-200d-2640-fe0f", - "uc_match": "1f939-1f3fe-2640-fe0f", - "uc_greedy": "1f939-1f3fe-2640", - "shortnames": [":woman_juggling_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_juggling_tone5:": { - "uc_base": "1f939-1f3ff-2640", - "uc_output": "1f939-1f3ff-200d-2640-fe0f", - "uc_match": "1f939-1f3ff-2640-fe0f", - "uc_greedy": "1f939-1f3ff-2640", - "shortnames": [":woman_juggling_dark_skin_tone:"], - "category": "activity" - }, - ":woman_lifting_weights_tone1:": { - "uc_base": "1f3cb-1f3fb-2640", - "uc_output": "1f3cb-1f3fb-200d-2640-fe0f", - "uc_match": "1f3cb-fe0f-1f3fb-2640-fe0f", - "uc_greedy": "1f3cb-1f3fb-2640", - "shortnames": [":woman_lifting_weights_light_skin_tone:"], - "category": "activity" - }, - ":woman_lifting_weights_tone2:": { - "uc_base": "1f3cb-1f3fc-2640", - "uc_output": "1f3cb-1f3fc-200d-2640-fe0f", - "uc_match": "1f3cb-fe0f-1f3fc-2640-fe0f", - "uc_greedy": "1f3cb-1f3fc-2640", - "shortnames": [":woman_lifting_weights_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_lifting_weights_tone3:": { - "uc_base": "1f3cb-1f3fd-2640", - "uc_output": "1f3cb-1f3fd-200d-2640-fe0f", - "uc_match": "1f3cb-fe0f-1f3fd-2640-fe0f", - "uc_greedy": "1f3cb-1f3fd-2640", - "shortnames": [":woman_lifting_weights_medium_skin_tone:"], - "category": "activity" - }, - ":woman_lifting_weights_tone4:": { - "uc_base": "1f3cb-1f3fe-2640", - "uc_output": "1f3cb-1f3fe-200d-2640-fe0f", - "uc_match": "1f3cb-fe0f-1f3fe-2640-fe0f", - "uc_greedy": "1f3cb-1f3fe-2640", - "shortnames": [":woman_lifting_weights_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_lifting_weights_tone5:": { - "uc_base": "1f3cb-1f3ff-2640", - "uc_output": "1f3cb-1f3ff-200d-2640-fe0f", - "uc_match": "1f3cb-fe0f-1f3ff-2640-fe0f", - "uc_greedy": "1f3cb-1f3ff-2640", - "shortnames": [":woman_lifting_weights_dark_skin_tone:"], - "category": "activity" - }, - ":woman_mage_tone1:": { - "uc_base": "1f9d9-1f3fb-2640", - "uc_output": "1f9d9-1f3fb-200d-2640-fe0f", - "uc_match": "1f9d9-1f3fb-2640-fe0f", - "uc_greedy": "1f9d9-1f3fb-2640", - "shortnames": [":woman_mage_light_skin_tone:"], - "category": "people" - }, - ":woman_mage_tone2:": { - "uc_base": "1f9d9-1f3fc-2640", - "uc_output": "1f9d9-1f3fc-200d-2640-fe0f", - "uc_match": "1f9d9-1f3fc-2640-fe0f", - "uc_greedy": "1f9d9-1f3fc-2640", - "shortnames": [":woman_mage_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_mage_tone3:": { - "uc_base": "1f9d9-1f3fd-2640", - "uc_output": "1f9d9-1f3fd-200d-2640-fe0f", - "uc_match": "1f9d9-1f3fd-2640-fe0f", - "uc_greedy": "1f9d9-1f3fd-2640", - "shortnames": [":woman_mage_medium_skin_tone:"], - "category": "people" - }, - ":woman_mage_tone4:": { - "uc_base": "1f9d9-1f3fe-2640", - "uc_output": "1f9d9-1f3fe-200d-2640-fe0f", - "uc_match": "1f9d9-1f3fe-2640-fe0f", - "uc_greedy": "1f9d9-1f3fe-2640", - "shortnames": [":woman_mage_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_mage_tone5:": { - "uc_base": "1f9d9-1f3ff-2640", - "uc_output": "1f9d9-1f3ff-200d-2640-fe0f", - "uc_match": "1f9d9-1f3ff-2640-fe0f", - "uc_greedy": "1f9d9-1f3ff-2640", - "shortnames": [":woman_mage_dark_skin_tone:"], - "category": "people" - }, - ":woman_mountain_biking_tone1:": { - "uc_base": "1f6b5-1f3fb-2640", - "uc_output": "1f6b5-1f3fb-200d-2640-fe0f", - "uc_match": "1f6b5-1f3fb-2640-fe0f", - "uc_greedy": "1f6b5-1f3fb-2640", - "shortnames": [":woman_mountain_biking_light_skin_tone:"], - "category": "activity" - }, - ":woman_mountain_biking_tone2:": { - "uc_base": "1f6b5-1f3fc-2640", - "uc_output": "1f6b5-1f3fc-200d-2640-fe0f", - "uc_match": "1f6b5-1f3fc-2640-fe0f", - "uc_greedy": "1f6b5-1f3fc-2640", - "shortnames": [":woman_mountain_biking_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_mountain_biking_tone3:": { - "uc_base": "1f6b5-1f3fd-2640", - "uc_output": "1f6b5-1f3fd-200d-2640-fe0f", - "uc_match": "1f6b5-1f3fd-2640-fe0f", - "uc_greedy": "1f6b5-1f3fd-2640", - "shortnames": [":woman_mountain_biking_medium_skin_tone:"], - "category": "activity" - }, - ":woman_mountain_biking_tone4:": { - "uc_base": "1f6b5-1f3fe-2640", - "uc_output": "1f6b5-1f3fe-200d-2640-fe0f", - "uc_match": "1f6b5-1f3fe-2640-fe0f", - "uc_greedy": "1f6b5-1f3fe-2640", - "shortnames": [":woman_mountain_biking_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_mountain_biking_tone5:": { - "uc_base": "1f6b5-1f3ff-2640", - "uc_output": "1f6b5-1f3ff-200d-2640-fe0f", - "uc_match": "1f6b5-1f3ff-2640-fe0f", - "uc_greedy": "1f6b5-1f3ff-2640", - "shortnames": [":woman_mountain_biking_dark_skin_tone:"], - "category": "activity" - }, - ":woman_pilot_tone1:": { - "uc_base": "1f469-1f3fb-2708", - "uc_output": "1f469-1f3fb-200d-2708-fe0f", - "uc_match": "1f469-1f3fb-2708-fe0f", - "uc_greedy": "1f469-1f3fb-2708", - "shortnames": [":woman_pilot_light_skin_tone:"], - "category": "people" - }, - ":woman_pilot_tone2:": { - "uc_base": "1f469-1f3fc-2708", - "uc_output": "1f469-1f3fc-200d-2708-fe0f", - "uc_match": "1f469-1f3fc-2708-fe0f", - "uc_greedy": "1f469-1f3fc-2708", - "shortnames": [":woman_pilot_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_pilot_tone3:": { - "uc_base": "1f469-1f3fd-2708", - "uc_output": "1f469-1f3fd-200d-2708-fe0f", - "uc_match": "1f469-1f3fd-2708-fe0f", - "uc_greedy": "1f469-1f3fd-2708", - "shortnames": [":woman_pilot_medium_skin_tone:"], - "category": "people" - }, - ":woman_pilot_tone4:": { - "uc_base": "1f469-1f3fe-2708", - "uc_output": "1f469-1f3fe-200d-2708-fe0f", - "uc_match": "1f469-1f3fe-2708-fe0f", - "uc_greedy": "1f469-1f3fe-2708", - "shortnames": [":woman_pilot_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_pilot_tone5:": { - "uc_base": "1f469-1f3ff-2708", - "uc_output": "1f469-1f3ff-200d-2708-fe0f", - "uc_match": "1f469-1f3ff-2708-fe0f", - "uc_greedy": "1f469-1f3ff-2708", - "shortnames": [":woman_pilot_dark_skin_tone:"], - "category": "people" - }, - ":woman_playing_handball_tone1:": { - "uc_base": "1f93e-1f3fb-2640", - "uc_output": "1f93e-1f3fb-200d-2640-fe0f", - "uc_match": "1f93e-1f3fb-2640-fe0f", - "uc_greedy": "1f93e-1f3fb-2640", - "shortnames": [":woman_playing_handball_light_skin_tone:"], - "category": "activity" - }, - ":woman_playing_handball_tone2:": { - "uc_base": "1f93e-1f3fc-2640", - "uc_output": "1f93e-1f3fc-200d-2640-fe0f", - "uc_match": "1f93e-1f3fc-2640-fe0f", - "uc_greedy": "1f93e-1f3fc-2640", - "shortnames": [":woman_playing_handball_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_playing_handball_tone3:": { - "uc_base": "1f93e-1f3fd-2640", - "uc_output": "1f93e-1f3fd-200d-2640-fe0f", - "uc_match": "1f93e-1f3fd-2640-fe0f", - "uc_greedy": "1f93e-1f3fd-2640", - "shortnames": [":woman_playing_handball_medium_skin_tone:"], - "category": "activity" - }, - ":woman_playing_handball_tone4:": { - "uc_base": "1f93e-1f3fe-2640", - "uc_output": "1f93e-1f3fe-200d-2640-fe0f", - "uc_match": "1f93e-1f3fe-2640-fe0f", - "uc_greedy": "1f93e-1f3fe-2640", - "shortnames": [":woman_playing_handball_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_playing_handball_tone5:": { - "uc_base": "1f93e-1f3ff-2640", - "uc_output": "1f93e-1f3ff-200d-2640-fe0f", - "uc_match": "1f93e-1f3ff-2640-fe0f", - "uc_greedy": "1f93e-1f3ff-2640", - "shortnames": [":woman_playing_handball_dark_skin_tone:"], - "category": "activity" - }, - ":woman_playing_water_polo_tone1:": { - "uc_base": "1f93d-1f3fb-2640", - "uc_output": "1f93d-1f3fb-200d-2640-fe0f", - "uc_match": "1f93d-1f3fb-2640-fe0f", - "uc_greedy": "1f93d-1f3fb-2640", - "shortnames": [":woman_playing_water_polo_light_skin_tone:"], - "category": "activity" - }, - ":woman_playing_water_polo_tone2:": { - "uc_base": "1f93d-1f3fc-2640", - "uc_output": "1f93d-1f3fc-200d-2640-fe0f", - "uc_match": "1f93d-1f3fc-2640-fe0f", - "uc_greedy": "1f93d-1f3fc-2640", - "shortnames": [":woman_playing_water_polo_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_playing_water_polo_tone3:": { - "uc_base": "1f93d-1f3fd-2640", - "uc_output": "1f93d-1f3fd-200d-2640-fe0f", - "uc_match": "1f93d-1f3fd-2640-fe0f", - "uc_greedy": "1f93d-1f3fd-2640", - "shortnames": [":woman_playing_water_polo_medium_skin_tone:"], - "category": "activity" - }, - ":woman_playing_water_polo_tone4:": { - "uc_base": "1f93d-1f3fe-2640", - "uc_output": "1f93d-1f3fe-200d-2640-fe0f", - "uc_match": "1f93d-1f3fe-2640-fe0f", - "uc_greedy": "1f93d-1f3fe-2640", - "shortnames": [":woman_playing_water_polo_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_playing_water_polo_tone5:": { - "uc_base": "1f93d-1f3ff-2640", - "uc_output": "1f93d-1f3ff-200d-2640-fe0f", - "uc_match": "1f93d-1f3ff-2640-fe0f", - "uc_greedy": "1f93d-1f3ff-2640", - "shortnames": [":woman_playing_water_polo_dark_skin_tone:"], - "category": "activity" - }, - ":woman_police_officer_tone1:": { - "uc_base": "1f46e-1f3fb-2640", - "uc_output": "1f46e-1f3fb-200d-2640-fe0f", - "uc_match": "1f46e-1f3fb-2640-fe0f", - "uc_greedy": "1f46e-1f3fb-2640", - "shortnames": [":woman_police_officer_light_skin_tone:"], - "category": "people" - }, - ":woman_police_officer_tone2:": { - "uc_base": "1f46e-1f3fc-2640", - "uc_output": "1f46e-1f3fc-200d-2640-fe0f", - "uc_match": "1f46e-1f3fc-2640-fe0f", - "uc_greedy": "1f46e-1f3fc-2640", - "shortnames": [":woman_police_officer_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_police_officer_tone3:": { - "uc_base": "1f46e-1f3fd-2640", - "uc_output": "1f46e-1f3fd-200d-2640-fe0f", - "uc_match": "1f46e-1f3fd-2640-fe0f", - "uc_greedy": "1f46e-1f3fd-2640", - "shortnames": [":woman_police_officer_medium_skin_tone:"], - "category": "people" - }, - ":woman_police_officer_tone4:": { - "uc_base": "1f46e-1f3fe-2640", - "uc_output": "1f46e-1f3fe-200d-2640-fe0f", - "uc_match": "1f46e-1f3fe-2640-fe0f", - "uc_greedy": "1f46e-1f3fe-2640", - "shortnames": [":woman_police_officer_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_police_officer_tone5:": { - "uc_base": "1f46e-1f3ff-2640", - "uc_output": "1f46e-1f3ff-200d-2640-fe0f", - "uc_match": "1f46e-1f3ff-2640-fe0f", - "uc_greedy": "1f46e-1f3ff-2640", - "shortnames": [":woman_police_officer_dark_skin_tone:"], - "category": "people" - }, - ":woman_pouting_tone1:": { - "uc_base": "1f64e-1f3fb-2640", - "uc_output": "1f64e-1f3fb-200d-2640-fe0f", - "uc_match": "1f64e-1f3fb-2640-fe0f", - "uc_greedy": "1f64e-1f3fb-2640", - "shortnames": [":woman_pouting_light_skin_tone:"], - "category": "people" - }, - ":woman_pouting_tone2:": { - "uc_base": "1f64e-1f3fc-2640", - "uc_output": "1f64e-1f3fc-200d-2640-fe0f", - "uc_match": "1f64e-1f3fc-2640-fe0f", - "uc_greedy": "1f64e-1f3fc-2640", - "shortnames": [":woman_pouting_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_pouting_tone3:": { - "uc_base": "1f64e-1f3fd-2640", - "uc_output": "1f64e-1f3fd-200d-2640-fe0f", - "uc_match": "1f64e-1f3fd-2640-fe0f", - "uc_greedy": "1f64e-1f3fd-2640", - "shortnames": [":woman_pouting_medium_skin_tone:"], - "category": "people" - }, - ":woman_pouting_tone4:": { - "uc_base": "1f64e-1f3fe-2640", - "uc_output": "1f64e-1f3fe-200d-2640-fe0f", - "uc_match": "1f64e-1f3fe-2640-fe0f", - "uc_greedy": "1f64e-1f3fe-2640", - "shortnames": [":woman_pouting_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_pouting_tone5:": { - "uc_base": "1f64e-1f3ff-2640", - "uc_output": "1f64e-1f3ff-200d-2640-fe0f", - "uc_match": "1f64e-1f3ff-2640-fe0f", - "uc_greedy": "1f64e-1f3ff-2640", - "shortnames": [":woman_pouting_dark_skin_tone:"], - "category": "people" - }, - ":woman_raising_hand_tone1:": { - "uc_base": "1f64b-1f3fb-2640", - "uc_output": "1f64b-1f3fb-200d-2640-fe0f", - "uc_match": "1f64b-1f3fb-2640-fe0f", - "uc_greedy": "1f64b-1f3fb-2640", - "shortnames": [":woman_raising_hand_light_skin_tone:"], - "category": "people" - }, - ":woman_raising_hand_tone2:": { - "uc_base": "1f64b-1f3fc-2640", - "uc_output": "1f64b-1f3fc-200d-2640-fe0f", - "uc_match": "1f64b-1f3fc-2640-fe0f", - "uc_greedy": "1f64b-1f3fc-2640", - "shortnames": [":woman_raising_hand_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_raising_hand_tone3:": { - "uc_base": "1f64b-1f3fd-2640", - "uc_output": "1f64b-1f3fd-200d-2640-fe0f", - "uc_match": "1f64b-1f3fd-2640-fe0f", - "uc_greedy": "1f64b-1f3fd-2640", - "shortnames": [":woman_raising_hand_medium_skin_tone:"], - "category": "people" - }, - ":woman_raising_hand_tone4:": { - "uc_base": "1f64b-1f3fe-2640", - "uc_output": "1f64b-1f3fe-200d-2640-fe0f", - "uc_match": "1f64b-1f3fe-2640-fe0f", - "uc_greedy": "1f64b-1f3fe-2640", - "shortnames": [":woman_raising_hand_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_raising_hand_tone5:": { - "uc_base": "1f64b-1f3ff-2640", - "uc_output": "1f64b-1f3ff-200d-2640-fe0f", - "uc_match": "1f64b-1f3ff-2640-fe0f", - "uc_greedy": "1f64b-1f3ff-2640", - "shortnames": [":woman_raising_hand_dark_skin_tone:"], - "category": "people" - }, - ":woman_rowing_boat_tone1:": { - "uc_base": "1f6a3-1f3fb-2640", - "uc_output": "1f6a3-1f3fb-200d-2640-fe0f", - "uc_match": "1f6a3-1f3fb-2640-fe0f", - "uc_greedy": "1f6a3-1f3fb-2640", - "shortnames": [":woman_rowing_boat_light_skin_tone:"], - "category": "activity" - }, - ":woman_rowing_boat_tone2:": { - "uc_base": "1f6a3-1f3fc-2640", - "uc_output": "1f6a3-1f3fc-200d-2640-fe0f", - "uc_match": "1f6a3-1f3fc-2640-fe0f", - "uc_greedy": "1f6a3-1f3fc-2640", - "shortnames": [":woman_rowing_boat_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_rowing_boat_tone3:": { - "uc_base": "1f6a3-1f3fd-2640", - "uc_output": "1f6a3-1f3fd-200d-2640-fe0f", - "uc_match": "1f6a3-1f3fd-2640-fe0f", - "uc_greedy": "1f6a3-1f3fd-2640", - "shortnames": [":woman_rowing_boat_medium_skin_tone:"], - "category": "activity" - }, - ":woman_rowing_boat_tone4:": { - "uc_base": "1f6a3-1f3fe-2640", - "uc_output": "1f6a3-1f3fe-200d-2640-fe0f", - "uc_match": "1f6a3-1f3fe-2640-fe0f", - "uc_greedy": "1f6a3-1f3fe-2640", - "shortnames": [":woman_rowing_boat_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_rowing_boat_tone5:": { - "uc_base": "1f6a3-1f3ff-2640", - "uc_output": "1f6a3-1f3ff-200d-2640-fe0f", - "uc_match": "1f6a3-1f3ff-2640-fe0f", - "uc_greedy": "1f6a3-1f3ff-2640", - "shortnames": [":woman_rowing_boat_dark_skin_tone:"], - "category": "activity" - }, - ":woman_running_tone1:": { - "uc_base": "1f3c3-1f3fb-2640", - "uc_output": "1f3c3-1f3fb-200d-2640-fe0f", - "uc_match": "1f3c3-1f3fb-2640-fe0f", - "uc_greedy": "1f3c3-1f3fb-2640", - "shortnames": [":woman_running_light_skin_tone:"], - "category": "people" - }, - ":woman_running_tone2:": { - "uc_base": "1f3c3-1f3fc-2640", - "uc_output": "1f3c3-1f3fc-200d-2640-fe0f", - "uc_match": "1f3c3-1f3fc-2640-fe0f", - "uc_greedy": "1f3c3-1f3fc-2640", - "shortnames": [":woman_running_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_running_tone3:": { - "uc_base": "1f3c3-1f3fd-2640", - "uc_output": "1f3c3-1f3fd-200d-2640-fe0f", - "uc_match": "1f3c3-1f3fd-2640-fe0f", - "uc_greedy": "1f3c3-1f3fd-2640", - "shortnames": [":woman_running_medium_skin_tone:"], - "category": "people" - }, - ":woman_running_tone4:": { - "uc_base": "1f3c3-1f3fe-2640", - "uc_output": "1f3c3-1f3fe-200d-2640-fe0f", - "uc_match": "1f3c3-1f3fe-2640-fe0f", - "uc_greedy": "1f3c3-1f3fe-2640", - "shortnames": [":woman_running_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_running_tone5:": { - "uc_base": "1f3c3-1f3ff-2640", - "uc_output": "1f3c3-1f3ff-200d-2640-fe0f", - "uc_match": "1f3c3-1f3ff-2640-fe0f", - "uc_greedy": "1f3c3-1f3ff-2640", - "shortnames": [":woman_running_dark_skin_tone:"], - "category": "people" - }, - ":woman_shrugging_tone1:": { - "uc_base": "1f937-1f3fb-2640", - "uc_output": "1f937-1f3fb-200d-2640-fe0f", - "uc_match": "1f937-1f3fb-2640-fe0f", - "uc_greedy": "1f937-1f3fb-2640", - "shortnames": [":woman_shrugging_light_skin_tone:"], - "category": "people" - }, - ":woman_shrugging_tone2:": { - "uc_base": "1f937-1f3fc-2640", - "uc_output": "1f937-1f3fc-200d-2640-fe0f", - "uc_match": "1f937-1f3fc-2640-fe0f", - "uc_greedy": "1f937-1f3fc-2640", - "shortnames": [":woman_shrugging_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_shrugging_tone3:": { - "uc_base": "1f937-1f3fd-2640", - "uc_output": "1f937-1f3fd-200d-2640-fe0f", - "uc_match": "1f937-1f3fd-2640-fe0f", - "uc_greedy": "1f937-1f3fd-2640", - "shortnames": [":woman_shrugging_medium_skin_tone:"], - "category": "people" - }, - ":woman_shrugging_tone4:": { - "uc_base": "1f937-1f3fe-2640", - "uc_output": "1f937-1f3fe-200d-2640-fe0f", - "uc_match": "1f937-1f3fe-2640-fe0f", - "uc_greedy": "1f937-1f3fe-2640", - "shortnames": [":woman_shrugging_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_shrugging_tone5:": { - "uc_base": "1f937-1f3ff-2640", - "uc_output": "1f937-1f3ff-200d-2640-fe0f", - "uc_match": "1f937-1f3ff-2640-fe0f", - "uc_greedy": "1f937-1f3ff-2640", - "shortnames": [":woman_shrugging_dark_skin_tone:"], - "category": "people" - }, - ":woman_surfing_tone1:": { - "uc_base": "1f3c4-1f3fb-2640", - "uc_output": "1f3c4-1f3fb-200d-2640-fe0f", - "uc_match": "1f3c4-1f3fb-2640-fe0f", - "uc_greedy": "1f3c4-1f3fb-2640", - "shortnames": [":woman_surfing_light_skin_tone:"], - "category": "activity" - }, - ":woman_surfing_tone2:": { - "uc_base": "1f3c4-1f3fc-2640", - "uc_output": "1f3c4-1f3fc-200d-2640-fe0f", - "uc_match": "1f3c4-1f3fc-2640-fe0f", - "uc_greedy": "1f3c4-1f3fc-2640", - "shortnames": [":woman_surfing_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_surfing_tone3:": { - "uc_base": "1f3c4-1f3fd-2640", - "uc_output": "1f3c4-1f3fd-200d-2640-fe0f", - "uc_match": "1f3c4-1f3fd-2640-fe0f", - "uc_greedy": "1f3c4-1f3fd-2640", - "shortnames": [":woman_surfing_medium_skin_tone:"], - "category": "activity" - }, - ":woman_surfing_tone4:": { - "uc_base": "1f3c4-1f3fe-2640", - "uc_output": "1f3c4-1f3fe-200d-2640-fe0f", - "uc_match": "1f3c4-1f3fe-2640-fe0f", - "uc_greedy": "1f3c4-1f3fe-2640", - "shortnames": [":woman_surfing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_surfing_tone5:": { - "uc_base": "1f3c4-1f3ff-2640", - "uc_output": "1f3c4-1f3ff-200d-2640-fe0f", - "uc_match": "1f3c4-1f3ff-2640-fe0f", - "uc_greedy": "1f3c4-1f3ff-2640", - "shortnames": [":woman_surfing_dark_skin_tone:"], - "category": "activity" - }, - ":woman_swimming_tone1:": { - "uc_base": "1f3ca-1f3fb-2640", - "uc_output": "1f3ca-1f3fb-200d-2640-fe0f", - "uc_match": "1f3ca-1f3fb-2640-fe0f", - "uc_greedy": "1f3ca-1f3fb-2640", - "shortnames": [":woman_swimming_light_skin_tone:"], - "category": "activity" - }, - ":woman_swimming_tone2:": { - "uc_base": "1f3ca-1f3fc-2640", - "uc_output": "1f3ca-1f3fc-200d-2640-fe0f", - "uc_match": "1f3ca-1f3fc-2640-fe0f", - "uc_greedy": "1f3ca-1f3fc-2640", - "shortnames": [":woman_swimming_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_swimming_tone3:": { - "uc_base": "1f3ca-1f3fd-2640", - "uc_output": "1f3ca-1f3fd-200d-2640-fe0f", - "uc_match": "1f3ca-1f3fd-2640-fe0f", - "uc_greedy": "1f3ca-1f3fd-2640", - "shortnames": [":woman_swimming_medium_skin_tone:"], - "category": "activity" - }, - ":woman_swimming_tone4:": { - "uc_base": "1f3ca-1f3fe-2640", - "uc_output": "1f3ca-1f3fe-200d-2640-fe0f", - "uc_match": "1f3ca-1f3fe-2640-fe0f", - "uc_greedy": "1f3ca-1f3fe-2640", - "shortnames": [":woman_swimming_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_swimming_tone5:": { - "uc_base": "1f3ca-1f3ff-2640", - "uc_output": "1f3ca-1f3ff-200d-2640-fe0f", - "uc_match": "1f3ca-1f3ff-2640-fe0f", - "uc_greedy": "1f3ca-1f3ff-2640", - "shortnames": [":woman_swimming_dark_skin_tone:"], - "category": "activity" - }, - ":woman_tipping_hand_tone1:": { - "uc_base": "1f481-1f3fb-2640", - "uc_output": "1f481-1f3fb-200d-2640-fe0f", - "uc_match": "1f481-1f3fb-2640-fe0f", - "uc_greedy": "1f481-1f3fb-2640", - "shortnames": [":woman_tipping_hand_light_skin_tone:"], - "category": "people" - }, - ":woman_tipping_hand_tone2:": { - "uc_base": "1f481-1f3fc-2640", - "uc_output": "1f481-1f3fc-200d-2640-fe0f", - "uc_match": "1f481-1f3fc-2640-fe0f", - "uc_greedy": "1f481-1f3fc-2640", - "shortnames": [":woman_tipping_hand_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_tipping_hand_tone3:": { - "uc_base": "1f481-1f3fd-2640", - "uc_output": "1f481-1f3fd-200d-2640-fe0f", - "uc_match": "1f481-1f3fd-2640-fe0f", - "uc_greedy": "1f481-1f3fd-2640", - "shortnames": [":woman_tipping_hand_medium_skin_tone:"], - "category": "people" - }, - ":woman_tipping_hand_tone4:": { - "uc_base": "1f481-1f3fe-2640", - "uc_output": "1f481-1f3fe-200d-2640-fe0f", - "uc_match": "1f481-1f3fe-2640-fe0f", - "uc_greedy": "1f481-1f3fe-2640", - "shortnames": [":woman_tipping_hand_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_tipping_hand_tone5:": { - "uc_base": "1f481-1f3ff-2640", - "uc_output": "1f481-1f3ff-200d-2640-fe0f", - "uc_match": "1f481-1f3ff-2640-fe0f", - "uc_greedy": "1f481-1f3ff-2640", - "shortnames": [":woman_tipping_hand_dark_skin_tone:"], - "category": "people" - }, - ":woman_vampire_tone1:": { - "uc_base": "1f9db-1f3fb-2640", - "uc_output": "1f9db-1f3fb-200d-2640-fe0f", - "uc_match": "1f9db-1f3fb-2640-fe0f", - "uc_greedy": "1f9db-1f3fb-2640", - "shortnames": [":woman_vampire_light_skin_tone:"], - "category": "people" - }, - ":woman_vampire_tone2:": { - "uc_base": "1f9db-1f3fc-2640", - "uc_output": "1f9db-1f3fc-200d-2640-fe0f", - "uc_match": "1f9db-1f3fc-2640-fe0f", - "uc_greedy": "1f9db-1f3fc-2640", - "shortnames": [":woman_vampire_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_vampire_tone3:": { - "uc_base": "1f9db-1f3fd-2640", - "uc_output": "1f9db-1f3fd-200d-2640-fe0f", - "uc_match": "1f9db-1f3fd-2640-fe0f", - "uc_greedy": "1f9db-1f3fd-2640", - "shortnames": [":woman_vampire_medium_skin_tone:"], - "category": "people" - }, - ":woman_vampire_tone4:": { - "uc_base": "1f9db-1f3fe-2640", - "uc_output": "1f9db-1f3fe-200d-2640-fe0f", - "uc_match": "1f9db-1f3fe-2640-fe0f", - "uc_greedy": "1f9db-1f3fe-2640", - "shortnames": [":woman_vampire_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_vampire_tone5:": { - "uc_base": "1f9db-1f3ff-2640", - "uc_output": "1f9db-1f3ff-200d-2640-fe0f", - "uc_match": "1f9db-1f3ff-2640-fe0f", - "uc_greedy": "1f9db-1f3ff-2640", - "shortnames": [":woman_vampire_dark_skin_tone:"], - "category": "people" - }, - ":woman_walking_tone1:": { - "uc_base": "1f6b6-1f3fb-2640", - "uc_output": "1f6b6-1f3fb-200d-2640-fe0f", - "uc_match": "1f6b6-1f3fb-2640-fe0f", - "uc_greedy": "1f6b6-1f3fb-2640", - "shortnames": [":woman_walking_light_skin_tone:"], - "category": "people" - }, - ":woman_walking_tone2:": { - "uc_base": "1f6b6-1f3fc-2640", - "uc_output": "1f6b6-1f3fc-200d-2640-fe0f", - "uc_match": "1f6b6-1f3fc-2640-fe0f", - "uc_greedy": "1f6b6-1f3fc-2640", - "shortnames": [":woman_walking_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_walking_tone3:": { - "uc_base": "1f6b6-1f3fd-2640", - "uc_output": "1f6b6-1f3fd-200d-2640-fe0f", - "uc_match": "1f6b6-1f3fd-2640-fe0f", - "uc_greedy": "1f6b6-1f3fd-2640", - "shortnames": [":woman_walking_medium_skin_tone:"], - "category": "people" - }, - ":woman_walking_tone4:": { - "uc_base": "1f6b6-1f3fe-2640", - "uc_output": "1f6b6-1f3fe-200d-2640-fe0f", - "uc_match": "1f6b6-1f3fe-2640-fe0f", - "uc_greedy": "1f6b6-1f3fe-2640", - "shortnames": [":woman_walking_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_walking_tone5:": { - "uc_base": "1f6b6-1f3ff-2640", - "uc_output": "1f6b6-1f3ff-200d-2640-fe0f", - "uc_match": "1f6b6-1f3ff-2640-fe0f", - "uc_greedy": "1f6b6-1f3ff-2640", - "shortnames": [":woman_walking_dark_skin_tone:"], - "category": "people" - }, - ":woman_wearing_turban_tone1:": { - "uc_base": "1f473-1f3fb-2640", - "uc_output": "1f473-1f3fb-200d-2640-fe0f", - "uc_match": "1f473-1f3fb-2640-fe0f", - "uc_greedy": "1f473-1f3fb-2640", - "shortnames": [":woman_wearing_turban_light_skin_tone:"], - "category": "people" - }, - ":woman_wearing_turban_tone2:": { - "uc_base": "1f473-1f3fc-2640", - "uc_output": "1f473-1f3fc-200d-2640-fe0f", - "uc_match": "1f473-1f3fc-2640-fe0f", - "uc_greedy": "1f473-1f3fc-2640", - "shortnames": [":woman_wearing_turban_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_wearing_turban_tone3:": { - "uc_base": "1f473-1f3fd-2640", - "uc_output": "1f473-1f3fd-200d-2640-fe0f", - "uc_match": "1f473-1f3fd-2640-fe0f", - "uc_greedy": "1f473-1f3fd-2640", - "shortnames": [":woman_wearing_turban_medium_skin_tone:"], - "category": "people" - }, - ":woman_wearing_turban_tone4:": { - "uc_base": "1f473-1f3fe-2640", - "uc_output": "1f473-1f3fe-200d-2640-fe0f", - "uc_match": "1f473-1f3fe-2640-fe0f", - "uc_greedy": "1f473-1f3fe-2640", - "shortnames": [":woman_wearing_turban_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_wearing_turban_tone5:": { - "uc_base": "1f473-1f3ff-2640", - "uc_output": "1f473-1f3ff-200d-2640-fe0f", - "uc_match": "1f473-1f3ff-2640-fe0f", - "uc_greedy": "1f473-1f3ff-2640", - "shortnames": [":woman_wearing_turban_dark_skin_tone:"], - "category": "people" - }, - ":man_bouncing_ball_tone1:": { - "uc_base": "26f9-1f3fb-2642", - "uc_output": "26f9-1f3fb-200d-2642-fe0f", - "uc_match": "26f9-fe0f-1f3fb-2642-fe0f", - "uc_greedy": "26f9-1f3fb-2642", - "shortnames": [":man_bouncing_ball_light_skin_tone:"], - "category": "activity" - }, - ":man_bouncing_ball_tone2:": { - "uc_base": "26f9-1f3fc-2642", - "uc_output": "26f9-1f3fc-200d-2642-fe0f", - "uc_match": "26f9-fe0f-1f3fc-2642-fe0f", - "uc_greedy": "26f9-1f3fc-2642", - "shortnames": [":man_bouncing_ball_medium_light_skin_tone:"], - "category": "activity" - }, - ":man_bouncing_ball_tone3:": { - "uc_base": "26f9-1f3fd-2642", - "uc_output": "26f9-1f3fd-200d-2642-fe0f", - "uc_match": "26f9-fe0f-1f3fd-2642-fe0f", - "uc_greedy": "26f9-1f3fd-2642", - "shortnames": [":man_bouncing_ball_medium_skin_tone:"], - "category": "activity" - }, - ":man_bouncing_ball_tone4:": { - "uc_base": "26f9-1f3fe-2642", - "uc_output": "26f9-1f3fe-200d-2642-fe0f", - "uc_match": "26f9-fe0f-1f3fe-2642-fe0f", - "uc_greedy": "26f9-1f3fe-2642", - "shortnames": [":man_bouncing_ball_medium_dark_skin_tone:"], - "category": "activity" - }, - ":man_bouncing_ball_tone5:": { - "uc_base": "26f9-1f3ff-2642", - "uc_output": "26f9-1f3ff-200d-2642-fe0f", - "uc_match": "26f9-fe0f-1f3ff-2642-fe0f", - "uc_greedy": "26f9-1f3ff-2642", - "shortnames": [":man_bouncing_ball_dark_skin_tone:"], - "category": "activity" - }, - ":man_detective:": { - "uc_base": "1f575-2642", - "uc_output": "1f575-fe0f-200d-2642-fe0f", - "uc_match": "1f575-fe0f-200d-2642", - "uc_greedy": "1f575-2642", - "shortnames": [], - "category": "people" - }, - ":man_golfing:": { - "uc_base": "1f3cc-2642", - "uc_output": "1f3cc-fe0f-200d-2642-fe0f", - "uc_match": "1f3cc-fe0f-200d-2642", - "uc_greedy": "1f3cc-2642", - "shortnames": [], - "category": "activity" - }, - ":man_lifting_weights:": { - "uc_base": "1f3cb-2642", - "uc_output": "1f3cb-fe0f-200d-2642-fe0f", - "uc_match": "1f3cb-fe0f-200d-2642", - "uc_greedy": "1f3cb-2642", - "shortnames": [], - "category": "activity" - }, - ":woman_bouncing_ball_tone1:": { - "uc_base": "26f9-1f3fb-2640", - "uc_output": "26f9-1f3fb-200d-2640-fe0f", - "uc_match": "26f9-fe0f-1f3fb-2640-fe0f", - "uc_greedy": "26f9-1f3fb-2640", - "shortnames": [":woman_bouncing_ball_light_skin_tone:"], - "category": "activity" - }, - ":woman_bouncing_ball_tone2:": { - "uc_base": "26f9-1f3fc-2640", - "uc_output": "26f9-1f3fc-200d-2640-fe0f", - "uc_match": "26f9-fe0f-1f3fc-2640-fe0f", - "uc_greedy": "26f9-1f3fc-2640", - "shortnames": [":woman_bouncing_ball_medium_light_skin_tone:"], - "category": "activity" - }, - ":woman_bouncing_ball_tone3:": { - "uc_base": "26f9-1f3fd-2640", - "uc_output": "26f9-1f3fd-200d-2640-fe0f", - "uc_match": "26f9-fe0f-1f3fd-2640-fe0f", - "uc_greedy": "26f9-1f3fd-2640", - "shortnames": [":woman_bouncing_ball_medium_skin_tone:"], - "category": "activity" - }, - ":woman_bouncing_ball_tone4:": { - "uc_base": "26f9-1f3fe-2640", - "uc_output": "26f9-1f3fe-200d-2640-fe0f", - "uc_match": "26f9-fe0f-1f3fe-2640-fe0f", - "uc_greedy": "26f9-1f3fe-2640", - "shortnames": [":woman_bouncing_ball_medium_dark_skin_tone:"], - "category": "activity" - }, - ":woman_bouncing_ball_tone5:": { - "uc_base": "26f9-1f3ff-2640", - "uc_output": "26f9-1f3ff-200d-2640-fe0f", - "uc_match": "26f9-fe0f-1f3ff-2640-fe0f", - "uc_greedy": "26f9-1f3ff-2640", - "shortnames": [":woman_bouncing_ball_dark_skin_tone:"], - "category": "activity" - }, - ":woman_detective:": { - "uc_base": "1f575-2640", - "uc_output": "1f575-fe0f-200d-2640-fe0f", - "uc_match": "1f575-fe0f-200d-2640", - "uc_greedy": "1f575-2640", - "shortnames": [], - "category": "people" - }, - ":woman_golfing:": { - "uc_base": "1f3cc-2640", - "uc_output": "1f3cc-fe0f-200d-2640-fe0f", - "uc_match": "1f3cc-fe0f-200d-2640", - "uc_greedy": "1f3cc-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_lifting_weights:": { - "uc_base": "1f3cb-2640", - "uc_output": "1f3cb-fe0f-200d-2640-fe0f", - "uc_match": "1f3cb-fe0f-200d-2640", - "uc_greedy": "1f3cb-2640", - "shortnames": [], - "category": "activity" - }, - ":man_bouncing_ball:": { - "uc_base": "26f9-2642", - "uc_output": "26f9-fe0f-200d-2642-fe0f", - "uc_match": "26f9-fe0f-200d-2642", - "uc_greedy": "26f9-2642", - "shortnames": [], - "category": "activity" - }, - ":woman_bouncing_ball:": { - "uc_base": "26f9-2640", - "uc_output": "26f9-fe0f-200d-2640-fe0f", - "uc_match": "26f9-fe0f-200d-2640", - "uc_greedy": "26f9-2640", - "shortnames": [], - "category": "activity" - }, - ":man_artist_tone1:": { - "uc_base": "1f468-1f3fb-1f3a8", - "uc_output": "1f468-1f3fb-200d-1f3a8", - "uc_match": "1f468-1f3fb-1f3a8", - "uc_greedy": "1f468-1f3fb-1f3a8", - "shortnames": [":man_artist_light_skin_tone:"], - "category": "people" - }, - ":man_artist_tone2:": { - "uc_base": "1f468-1f3fc-1f3a8", - "uc_output": "1f468-1f3fc-200d-1f3a8", - "uc_match": "1f468-1f3fc-1f3a8", - "uc_greedy": "1f468-1f3fc-1f3a8", - "shortnames": [":man_artist_medium_light_skin_tone:"], - "category": "people" - }, - ":man_artist_tone3:": { - "uc_base": "1f468-1f3fd-1f3a8", - "uc_output": "1f468-1f3fd-200d-1f3a8", - "uc_match": "1f468-1f3fd-1f3a8", - "uc_greedy": "1f468-1f3fd-1f3a8", - "shortnames": [":man_artist_medium_skin_tone:"], - "category": "people" - }, - ":man_artist_tone4:": { - "uc_base": "1f468-1f3fe-1f3a8", - "uc_output": "1f468-1f3fe-200d-1f3a8", - "uc_match": "1f468-1f3fe-1f3a8", - "uc_greedy": "1f468-1f3fe-1f3a8", - "shortnames": [":man_artist_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_artist_tone5:": { - "uc_base": "1f468-1f3ff-1f3a8", - "uc_output": "1f468-1f3ff-200d-1f3a8", - "uc_match": "1f468-1f3ff-1f3a8", - "uc_greedy": "1f468-1f3ff-1f3a8", - "shortnames": [":man_artist_dark_skin_tone:"], - "category": "people" - }, - ":man_astronaut_tone1:": { - "uc_base": "1f468-1f3fb-1f680", - "uc_output": "1f468-1f3fb-200d-1f680", - "uc_match": "1f468-1f3fb-1f680", - "uc_greedy": "1f468-1f3fb-1f680", - "shortnames": [":man_astronaut_light_skin_tone:"], - "category": "people" - }, - ":man_astronaut_tone2:": { - "uc_base": "1f468-1f3fc-1f680", - "uc_output": "1f468-1f3fc-200d-1f680", - "uc_match": "1f468-1f3fc-1f680", - "uc_greedy": "1f468-1f3fc-1f680", - "shortnames": [":man_astronaut_medium_light_skin_tone:"], - "category": "people" - }, - ":man_astronaut_tone3:": { - "uc_base": "1f468-1f3fd-1f680", - "uc_output": "1f468-1f3fd-200d-1f680", - "uc_match": "1f468-1f3fd-1f680", - "uc_greedy": "1f468-1f3fd-1f680", - "shortnames": [":man_astronaut_medium_skin_tone:"], - "category": "people" - }, - ":man_astronaut_tone4:": { - "uc_base": "1f468-1f3fe-1f680", - "uc_output": "1f468-1f3fe-200d-1f680", - "uc_match": "1f468-1f3fe-1f680", - "uc_greedy": "1f468-1f3fe-1f680", - "shortnames": [":man_astronaut_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_astronaut_tone5:": { - "uc_base": "1f468-1f3ff-1f680", - "uc_output": "1f468-1f3ff-200d-1f680", - "uc_match": "1f468-1f3ff-1f680", - "uc_greedy": "1f468-1f3ff-1f680", - "shortnames": [":man_astronaut_dark_skin_tone:"], - "category": "people" - }, - ":man_cook_tone1:": { - "uc_base": "1f468-1f3fb-1f373", - "uc_output": "1f468-1f3fb-200d-1f373", - "uc_match": "1f468-1f3fb-1f373", - "uc_greedy": "1f468-1f3fb-1f373", - "shortnames": [":man_cook_light_skin_tone:"], - "category": "people" - }, - ":man_cook_tone2:": { - "uc_base": "1f468-1f3fc-1f373", - "uc_output": "1f468-1f3fc-200d-1f373", - "uc_match": "1f468-1f3fc-1f373", - "uc_greedy": "1f468-1f3fc-1f373", - "shortnames": [":man_cook_medium_light_skin_tone:"], - "category": "people" - }, - ":man_cook_tone3:": { - "uc_base": "1f468-1f3fd-1f373", - "uc_output": "1f468-1f3fd-200d-1f373", - "uc_match": "1f468-1f3fd-1f373", - "uc_greedy": "1f468-1f3fd-1f373", - "shortnames": [":man_cook_medium_skin_tone:"], - "category": "people" - }, - ":man_cook_tone4:": { - "uc_base": "1f468-1f3fe-1f373", - "uc_output": "1f468-1f3fe-200d-1f373", - "uc_match": "1f468-1f3fe-1f373", - "uc_greedy": "1f468-1f3fe-1f373", - "shortnames": [":man_cook_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_cook_tone5:": { - "uc_base": "1f468-1f3ff-1f373", - "uc_output": "1f468-1f3ff-200d-1f373", - "uc_match": "1f468-1f3ff-1f373", - "uc_greedy": "1f468-1f3ff-1f373", - "shortnames": [":man_cook_dark_skin_tone:"], - "category": "people" - }, - ":man_factory_worker_tone1:": { - "uc_base": "1f468-1f3fb-1f3ed", - "uc_output": "1f468-1f3fb-200d-1f3ed", - "uc_match": "1f468-1f3fb-1f3ed", - "uc_greedy": "1f468-1f3fb-1f3ed", - "shortnames": [":man_factory_worker_light_skin_tone:"], - "category": "people" - }, - ":man_factory_worker_tone2:": { - "uc_base": "1f468-1f3fc-1f3ed", - "uc_output": "1f468-1f3fc-200d-1f3ed", - "uc_match": "1f468-1f3fc-1f3ed", - "uc_greedy": "1f468-1f3fc-1f3ed", - "shortnames": [":man_factory_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":man_factory_worker_tone3:": { - "uc_base": "1f468-1f3fd-1f3ed", - "uc_output": "1f468-1f3fd-200d-1f3ed", - "uc_match": "1f468-1f3fd-1f3ed", - "uc_greedy": "1f468-1f3fd-1f3ed", - "shortnames": [":man_factory_worker_medium_skin_tone:"], - "category": "people" - }, - ":man_factory_worker_tone4:": { - "uc_base": "1f468-1f3fe-1f3ed", - "uc_output": "1f468-1f3fe-200d-1f3ed", - "uc_match": "1f468-1f3fe-1f3ed", - "uc_greedy": "1f468-1f3fe-1f3ed", - "shortnames": [":man_factory_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_factory_worker_tone5:": { - "uc_base": "1f468-1f3ff-1f3ed", - "uc_output": "1f468-1f3ff-200d-1f3ed", - "uc_match": "1f468-1f3ff-1f3ed", - "uc_greedy": "1f468-1f3ff-1f3ed", - "shortnames": [":man_factory_worker_dark_skin_tone:"], - "category": "people" - }, - ":man_farmer_tone1:": { - "uc_base": "1f468-1f3fb-1f33e", - "uc_output": "1f468-1f3fb-200d-1f33e", - "uc_match": "1f468-1f3fb-1f33e", - "uc_greedy": "1f468-1f3fb-1f33e", - "shortnames": [":man_farmer_light_skin_tone:"], - "category": "people" - }, - ":man_farmer_tone2:": { - "uc_base": "1f468-1f3fc-1f33e", - "uc_output": "1f468-1f3fc-200d-1f33e", - "uc_match": "1f468-1f3fc-1f33e", - "uc_greedy": "1f468-1f3fc-1f33e", - "shortnames": [":man_farmer_medium_light_skin_tone:"], - "category": "people" - }, - ":man_farmer_tone3:": { - "uc_base": "1f468-1f3fd-1f33e", - "uc_output": "1f468-1f3fd-200d-1f33e", - "uc_match": "1f468-1f3fd-1f33e", - "uc_greedy": "1f468-1f3fd-1f33e", - "shortnames": [":man_farmer_medium_skin_tone:"], - "category": "people" - }, - ":man_farmer_tone4:": { - "uc_base": "1f468-1f3fe-1f33e", - "uc_output": "1f468-1f3fe-200d-1f33e", - "uc_match": "1f468-1f3fe-1f33e", - "uc_greedy": "1f468-1f3fe-1f33e", - "shortnames": [":man_farmer_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_farmer_tone5:": { - "uc_base": "1f468-1f3ff-1f33e", - "uc_output": "1f468-1f3ff-200d-1f33e", - "uc_match": "1f468-1f3ff-1f33e", - "uc_greedy": "1f468-1f3ff-1f33e", - "shortnames": [":man_farmer_dark_skin_tone:"], - "category": "people" - }, - ":man_firefighter_tone1:": { - "uc_base": "1f468-1f3fb-1f692", - "uc_output": "1f468-1f3fb-200d-1f692", - "uc_match": "1f468-1f3fb-1f692", - "uc_greedy": "1f468-1f3fb-1f692", - "shortnames": [":man_firefighter_light_skin_tone:"], - "category": "people" - }, - ":man_firefighter_tone2:": { - "uc_base": "1f468-1f3fc-1f692", - "uc_output": "1f468-1f3fc-200d-1f692", - "uc_match": "1f468-1f3fc-1f692", - "uc_greedy": "1f468-1f3fc-1f692", - "shortnames": [":man_firefighter_medium_light_skin_tone:"], - "category": "people" - }, - ":man_firefighter_tone3:": { - "uc_base": "1f468-1f3fd-1f692", - "uc_output": "1f468-1f3fd-200d-1f692", - "uc_match": "1f468-1f3fd-1f692", - "uc_greedy": "1f468-1f3fd-1f692", - "shortnames": [":man_firefighter_medium_skin_tone:"], - "category": "people" - }, - ":man_firefighter_tone4:": { - "uc_base": "1f468-1f3fe-1f692", - "uc_output": "1f468-1f3fe-200d-1f692", - "uc_match": "1f468-1f3fe-1f692", - "uc_greedy": "1f468-1f3fe-1f692", - "shortnames": [":man_firefighter_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_firefighter_tone5:": { - "uc_base": "1f468-1f3ff-1f692", - "uc_output": "1f468-1f3ff-200d-1f692", - "uc_match": "1f468-1f3ff-1f692", - "uc_greedy": "1f468-1f3ff-1f692", - "shortnames": [":man_firefighter_dark_skin_tone:"], - "category": "people" - }, - ":man_mechanic_tone1:": { - "uc_base": "1f468-1f3fb-1f527", - "uc_output": "1f468-1f3fb-200d-1f527", - "uc_match": "1f468-1f3fb-1f527", - "uc_greedy": "1f468-1f3fb-1f527", - "shortnames": [":man_mechanic_light_skin_tone:"], - "category": "people" - }, - ":man_mechanic_tone2:": { - "uc_base": "1f468-1f3fc-1f527", - "uc_output": "1f468-1f3fc-200d-1f527", - "uc_match": "1f468-1f3fc-1f527", - "uc_greedy": "1f468-1f3fc-1f527", - "shortnames": [":man_mechanic_medium_light_skin_tone:"], - "category": "people" - }, - ":man_mechanic_tone3:": { - "uc_base": "1f468-1f3fd-1f527", - "uc_output": "1f468-1f3fd-200d-1f527", - "uc_match": "1f468-1f3fd-1f527", - "uc_greedy": "1f468-1f3fd-1f527", - "shortnames": [":man_mechanic_medium_skin_tone:"], - "category": "people" - }, - ":man_mechanic_tone4:": { - "uc_base": "1f468-1f3fe-1f527", - "uc_output": "1f468-1f3fe-200d-1f527", - "uc_match": "1f468-1f3fe-1f527", - "uc_greedy": "1f468-1f3fe-1f527", - "shortnames": [":man_mechanic_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_mechanic_tone5:": { - "uc_base": "1f468-1f3ff-1f527", - "uc_output": "1f468-1f3ff-200d-1f527", - "uc_match": "1f468-1f3ff-1f527", - "uc_greedy": "1f468-1f3ff-1f527", - "shortnames": [":man_mechanic_dark_skin_tone:"], - "category": "people" - }, - ":man_office_worker_tone1:": { - "uc_base": "1f468-1f3fb-1f4bc", - "uc_output": "1f468-1f3fb-200d-1f4bc", - "uc_match": "1f468-1f3fb-1f4bc", - "uc_greedy": "1f468-1f3fb-1f4bc", - "shortnames": [":man_office_worker_light_skin_tone:"], - "category": "people" - }, - ":man_office_worker_tone2:": { - "uc_base": "1f468-1f3fc-1f4bc", - "uc_output": "1f468-1f3fc-200d-1f4bc", - "uc_match": "1f468-1f3fc-1f4bc", - "uc_greedy": "1f468-1f3fc-1f4bc", - "shortnames": [":man_office_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":man_office_worker_tone3:": { - "uc_base": "1f468-1f3fd-1f4bc", - "uc_output": "1f468-1f3fd-200d-1f4bc", - "uc_match": "1f468-1f3fd-1f4bc", - "uc_greedy": "1f468-1f3fd-1f4bc", - "shortnames": [":man_office_worker_medium_skin_tone:"], - "category": "people" - }, - ":man_office_worker_tone4:": { - "uc_base": "1f468-1f3fe-1f4bc", - "uc_output": "1f468-1f3fe-200d-1f4bc", - "uc_match": "1f468-1f3fe-1f4bc", - "uc_greedy": "1f468-1f3fe-1f4bc", - "shortnames": [":man_office_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_office_worker_tone5:": { - "uc_base": "1f468-1f3ff-1f4bc", - "uc_output": "1f468-1f3ff-200d-1f4bc", - "uc_match": "1f468-1f3ff-1f4bc", - "uc_greedy": "1f468-1f3ff-1f4bc", - "shortnames": [":man_office_worker_dark_skin_tone:"], - "category": "people" - }, - ":man_scientist_tone1:": { - "uc_base": "1f468-1f3fb-1f52c", - "uc_output": "1f468-1f3fb-200d-1f52c", - "uc_match": "1f468-1f3fb-1f52c", - "uc_greedy": "1f468-1f3fb-1f52c", - "shortnames": [":man_scientist_light_skin_tone:"], - "category": "people" - }, - ":man_scientist_tone2:": { - "uc_base": "1f468-1f3fc-1f52c", - "uc_output": "1f468-1f3fc-200d-1f52c", - "uc_match": "1f468-1f3fc-1f52c", - "uc_greedy": "1f468-1f3fc-1f52c", - "shortnames": [":man_scientist_medium_light_skin_tone:"], - "category": "people" - }, - ":man_scientist_tone3:": { - "uc_base": "1f468-1f3fd-1f52c", - "uc_output": "1f468-1f3fd-200d-1f52c", - "uc_match": "1f468-1f3fd-1f52c", - "uc_greedy": "1f468-1f3fd-1f52c", - "shortnames": [":man_scientist_medium_skin_tone:"], - "category": "people" - }, - ":man_scientist_tone4:": { - "uc_base": "1f468-1f3fe-1f52c", - "uc_output": "1f468-1f3fe-200d-1f52c", - "uc_match": "1f468-1f3fe-1f52c", - "uc_greedy": "1f468-1f3fe-1f52c", - "shortnames": [":man_scientist_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_scientist_tone5:": { - "uc_base": "1f468-1f3ff-1f52c", - "uc_output": "1f468-1f3ff-200d-1f52c", - "uc_match": "1f468-1f3ff-1f52c", - "uc_greedy": "1f468-1f3ff-1f52c", - "shortnames": [":man_scientist_dark_skin_tone:"], - "category": "people" - }, - ":man_singer_tone1:": { - "uc_base": "1f468-1f3fb-1f3a4", - "uc_output": "1f468-1f3fb-200d-1f3a4", - "uc_match": "1f468-1f3fb-1f3a4", - "uc_greedy": "1f468-1f3fb-1f3a4", - "shortnames": [":man_singer_light_skin_tone:"], - "category": "people" - }, - ":man_singer_tone2:": { - "uc_base": "1f468-1f3fc-1f3a4", - "uc_output": "1f468-1f3fc-200d-1f3a4", - "uc_match": "1f468-1f3fc-1f3a4", - "uc_greedy": "1f468-1f3fc-1f3a4", - "shortnames": [":man_singer_medium_light_skin_tone:"], - "category": "people" - }, - ":man_singer_tone3:": { - "uc_base": "1f468-1f3fd-1f3a4", - "uc_output": "1f468-1f3fd-200d-1f3a4", - "uc_match": "1f468-1f3fd-1f3a4", - "uc_greedy": "1f468-1f3fd-1f3a4", - "shortnames": [":man_singer_medium_skin_tone:"], - "category": "people" - }, - ":man_singer_tone4:": { - "uc_base": "1f468-1f3fe-1f3a4", - "uc_output": "1f468-1f3fe-200d-1f3a4", - "uc_match": "1f468-1f3fe-1f3a4", - "uc_greedy": "1f468-1f3fe-1f3a4", - "shortnames": [":man_singer_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_singer_tone5:": { - "uc_base": "1f468-1f3ff-1f3a4", - "uc_output": "1f468-1f3ff-200d-1f3a4", - "uc_match": "1f468-1f3ff-1f3a4", - "uc_greedy": "1f468-1f3ff-1f3a4", - "shortnames": [":man_singer_dark_skin_tone:"], - "category": "people" - }, - ":man_student_tone1:": { - "uc_base": "1f468-1f3fb-1f393", - "uc_output": "1f468-1f3fb-200d-1f393", - "uc_match": "1f468-1f3fb-1f393", - "uc_greedy": "1f468-1f3fb-1f393", - "shortnames": [":man_student_light_skin_tone:"], - "category": "people" - }, - ":man_student_tone2:": { - "uc_base": "1f468-1f3fc-1f393", - "uc_output": "1f468-1f3fc-200d-1f393", - "uc_match": "1f468-1f3fc-1f393", - "uc_greedy": "1f468-1f3fc-1f393", - "shortnames": [":man_student_medium_light_skin_tone:"], - "category": "people" - }, - ":man_student_tone3:": { - "uc_base": "1f468-1f3fd-1f393", - "uc_output": "1f468-1f3fd-200d-1f393", - "uc_match": "1f468-1f3fd-1f393", - "uc_greedy": "1f468-1f3fd-1f393", - "shortnames": [":man_student_medium_skin_tone:"], - "category": "people" - }, - ":man_student_tone4:": { - "uc_base": "1f468-1f3fe-1f393", - "uc_output": "1f468-1f3fe-200d-1f393", - "uc_match": "1f468-1f3fe-1f393", - "uc_greedy": "1f468-1f3fe-1f393", - "shortnames": [":man_student_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_student_tone5:": { - "uc_base": "1f468-1f3ff-1f393", - "uc_output": "1f468-1f3ff-200d-1f393", - "uc_match": "1f468-1f3ff-1f393", - "uc_greedy": "1f468-1f3ff-1f393", - "shortnames": [":man_student_dark_skin_tone:"], - "category": "people" - }, - ":man_teacher_tone1:": { - "uc_base": "1f468-1f3fb-1f3eb", - "uc_output": "1f468-1f3fb-200d-1f3eb", - "uc_match": "1f468-1f3fb-1f3eb", - "uc_greedy": "1f468-1f3fb-1f3eb", - "shortnames": [":man_teacher_light_skin_tone:"], - "category": "people" - }, - ":man_teacher_tone2:": { - "uc_base": "1f468-1f3fc-1f3eb", - "uc_output": "1f468-1f3fc-200d-1f3eb", - "uc_match": "1f468-1f3fc-1f3eb", - "uc_greedy": "1f468-1f3fc-1f3eb", - "shortnames": [":man_teacher_medium_light_skin_tone:"], - "category": "people" - }, - ":man_teacher_tone3:": { - "uc_base": "1f468-1f3fd-1f3eb", - "uc_output": "1f468-1f3fd-200d-1f3eb", - "uc_match": "1f468-1f3fd-1f3eb", - "uc_greedy": "1f468-1f3fd-1f3eb", - "shortnames": [":man_teacher_medium_skin_tone:"], - "category": "people" - }, - ":man_teacher_tone4:": { - "uc_base": "1f468-1f3fe-1f3eb", - "uc_output": "1f468-1f3fe-200d-1f3eb", - "uc_match": "1f468-1f3fe-1f3eb", - "uc_greedy": "1f468-1f3fe-1f3eb", - "shortnames": [":man_teacher_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_teacher_tone5:": { - "uc_base": "1f468-1f3ff-1f3eb", - "uc_output": "1f468-1f3ff-200d-1f3eb", - "uc_match": "1f468-1f3ff-1f3eb", - "uc_greedy": "1f468-1f3ff-1f3eb", - "shortnames": [":man_teacher_dark_skin_tone:"], - "category": "people" - }, - ":man_technologist_tone1:": { - "uc_base": "1f468-1f3fb-1f4bb", - "uc_output": "1f468-1f3fb-200d-1f4bb", - "uc_match": "1f468-1f3fb-1f4bb", - "uc_greedy": "1f468-1f3fb-1f4bb", - "shortnames": [":man_technologist_light_skin_tone:"], - "category": "people" - }, - ":man_technologist_tone2:": { - "uc_base": "1f468-1f3fc-1f4bb", - "uc_output": "1f468-1f3fc-200d-1f4bb", - "uc_match": "1f468-1f3fc-1f4bb", - "uc_greedy": "1f468-1f3fc-1f4bb", - "shortnames": [":man_technologist_medium_light_skin_tone:"], - "category": "people" - }, - ":man_technologist_tone3:": { - "uc_base": "1f468-1f3fd-1f4bb", - "uc_output": "1f468-1f3fd-200d-1f4bb", - "uc_match": "1f468-1f3fd-1f4bb", - "uc_greedy": "1f468-1f3fd-1f4bb", - "shortnames": [":man_technologist_medium_skin_tone:"], - "category": "people" - }, - ":man_technologist_tone4:": { - "uc_base": "1f468-1f3fe-1f4bb", - "uc_output": "1f468-1f3fe-200d-1f4bb", - "uc_match": "1f468-1f3fe-1f4bb", - "uc_greedy": "1f468-1f3fe-1f4bb", - "shortnames": [":man_technologist_medium_dark_skin_tone:"], - "category": "people" - }, - ":man_technologist_tone5:": { - "uc_base": "1f468-1f3ff-1f4bb", - "uc_output": "1f468-1f3ff-200d-1f4bb", - "uc_match": "1f468-1f3ff-1f4bb", - "uc_greedy": "1f468-1f3ff-1f4bb", - "shortnames": [":man_technologist_dark_skin_tone:"], - "category": "people" - }, - ":woman_artist_tone1:": { - "uc_base": "1f469-1f3fb-1f3a8", - "uc_output": "1f469-1f3fb-200d-1f3a8", - "uc_match": "1f469-1f3fb-1f3a8", - "uc_greedy": "1f469-1f3fb-1f3a8", - "shortnames": [":woman_artist_light_skin_tone:"], - "category": "people" - }, - ":woman_artist_tone2:": { - "uc_base": "1f469-1f3fc-1f3a8", - "uc_output": "1f469-1f3fc-200d-1f3a8", - "uc_match": "1f469-1f3fc-1f3a8", - "uc_greedy": "1f469-1f3fc-1f3a8", - "shortnames": [":woman_artist_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_artist_tone3:": { - "uc_base": "1f469-1f3fd-1f3a8", - "uc_output": "1f469-1f3fd-200d-1f3a8", - "uc_match": "1f469-1f3fd-1f3a8", - "uc_greedy": "1f469-1f3fd-1f3a8", - "shortnames": [":woman_artist_medium_skin_tone:"], - "category": "people" - }, - ":woman_artist_tone4:": { - "uc_base": "1f469-1f3fe-1f3a8", - "uc_output": "1f469-1f3fe-200d-1f3a8", - "uc_match": "1f469-1f3fe-1f3a8", - "uc_greedy": "1f469-1f3fe-1f3a8", - "shortnames": [":woman_artist_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_artist_tone5:": { - "uc_base": "1f469-1f3ff-1f3a8", - "uc_output": "1f469-1f3ff-200d-1f3a8", - "uc_match": "1f469-1f3ff-1f3a8", - "uc_greedy": "1f469-1f3ff-1f3a8", - "shortnames": [":woman_artist_dark_skin_tone:"], - "category": "people" - }, - ":woman_astronaut_tone1:": { - "uc_base": "1f469-1f3fb-1f680", - "uc_output": "1f469-1f3fb-200d-1f680", - "uc_match": "1f469-1f3fb-1f680", - "uc_greedy": "1f469-1f3fb-1f680", - "shortnames": [":woman_astronaut_light_skin_tone:"], - "category": "people" - }, - ":woman_astronaut_tone2:": { - "uc_base": "1f469-1f3fc-1f680", - "uc_output": "1f469-1f3fc-200d-1f680", - "uc_match": "1f469-1f3fc-1f680", - "uc_greedy": "1f469-1f3fc-1f680", - "shortnames": [":woman_astronaut_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_astronaut_tone3:": { - "uc_base": "1f469-1f3fd-1f680", - "uc_output": "1f469-1f3fd-200d-1f680", - "uc_match": "1f469-1f3fd-1f680", - "uc_greedy": "1f469-1f3fd-1f680", - "shortnames": [":woman_astronaut_medium_skin_tone:"], - "category": "people" - }, - ":woman_astronaut_tone4:": { - "uc_base": "1f469-1f3fe-1f680", - "uc_output": "1f469-1f3fe-200d-1f680", - "uc_match": "1f469-1f3fe-1f680", - "uc_greedy": "1f469-1f3fe-1f680", - "shortnames": [":woman_astronaut_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_astronaut_tone5:": { - "uc_base": "1f469-1f3ff-1f680", - "uc_output": "1f469-1f3ff-200d-1f680", - "uc_match": "1f469-1f3ff-1f680", - "uc_greedy": "1f469-1f3ff-1f680", - "shortnames": [":woman_astronaut_dark_skin_tone:"], - "category": "people" - }, - ":woman_cook_tone1:": { - "uc_base": "1f469-1f3fb-1f373", - "uc_output": "1f469-1f3fb-200d-1f373", - "uc_match": "1f469-1f3fb-1f373", - "uc_greedy": "1f469-1f3fb-1f373", - "shortnames": [":woman_cook_light_skin_tone:"], - "category": "people" - }, - ":woman_cook_tone2:": { - "uc_base": "1f469-1f3fc-1f373", - "uc_output": "1f469-1f3fc-200d-1f373", - "uc_match": "1f469-1f3fc-1f373", - "uc_greedy": "1f469-1f3fc-1f373", - "shortnames": [":woman_cook_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_cook_tone3:": { - "uc_base": "1f469-1f3fd-1f373", - "uc_output": "1f469-1f3fd-200d-1f373", - "uc_match": "1f469-1f3fd-1f373", - "uc_greedy": "1f469-1f3fd-1f373", - "shortnames": [":woman_cook_medium_skin_tone:"], - "category": "people" - }, - ":woman_cook_tone4:": { - "uc_base": "1f469-1f3fe-1f373", - "uc_output": "1f469-1f3fe-200d-1f373", - "uc_match": "1f469-1f3fe-1f373", - "uc_greedy": "1f469-1f3fe-1f373", - "shortnames": [":woman_cook_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_cook_tone5:": { - "uc_base": "1f469-1f3ff-1f373", - "uc_output": "1f469-1f3ff-200d-1f373", - "uc_match": "1f469-1f3ff-1f373", - "uc_greedy": "1f469-1f3ff-1f373", - "shortnames": [":woman_cook_dark_skin_tone:"], - "category": "people" - }, - ":woman_factory_worker_tone1:": { - "uc_base": "1f469-1f3fb-1f3ed", - "uc_output": "1f469-1f3fb-200d-1f3ed", - "uc_match": "1f469-1f3fb-1f3ed", - "uc_greedy": "1f469-1f3fb-1f3ed", - "shortnames": [":woman_factory_worker_light_skin_tone:"], - "category": "people" - }, - ":woman_factory_worker_tone2:": { - "uc_base": "1f469-1f3fc-1f3ed", - "uc_output": "1f469-1f3fc-200d-1f3ed", - "uc_match": "1f469-1f3fc-1f3ed", - "uc_greedy": "1f469-1f3fc-1f3ed", - "shortnames": [":woman_factory_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_factory_worker_tone3:": { - "uc_base": "1f469-1f3fd-1f3ed", - "uc_output": "1f469-1f3fd-200d-1f3ed", - "uc_match": "1f469-1f3fd-1f3ed", - "uc_greedy": "1f469-1f3fd-1f3ed", - "shortnames": [":woman_factory_worker_medium_skin_tone:"], - "category": "people" - }, - ":woman_factory_worker_tone4:": { - "uc_base": "1f469-1f3fe-1f3ed", - "uc_output": "1f469-1f3fe-200d-1f3ed", - "uc_match": "1f469-1f3fe-1f3ed", - "uc_greedy": "1f469-1f3fe-1f3ed", - "shortnames": [":woman_factory_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_factory_worker_tone5:": { - "uc_base": "1f469-1f3ff-1f3ed", - "uc_output": "1f469-1f3ff-200d-1f3ed", - "uc_match": "1f469-1f3ff-1f3ed", - "uc_greedy": "1f469-1f3ff-1f3ed", - "shortnames": [":woman_factory_worker_dark_skin_tone:"], - "category": "people" - }, - ":woman_farmer_tone1:": { - "uc_base": "1f469-1f3fb-1f33e", - "uc_output": "1f469-1f3fb-200d-1f33e", - "uc_match": "1f469-1f3fb-1f33e", - "uc_greedy": "1f469-1f3fb-1f33e", - "shortnames": [":woman_farmer_light_skin_tone:"], - "category": "people" - }, - ":woman_farmer_tone2:": { - "uc_base": "1f469-1f3fc-1f33e", - "uc_output": "1f469-1f3fc-200d-1f33e", - "uc_match": "1f469-1f3fc-1f33e", - "uc_greedy": "1f469-1f3fc-1f33e", - "shortnames": [":woman_farmer_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_farmer_tone3:": { - "uc_base": "1f469-1f3fd-1f33e", - "uc_output": "1f469-1f3fd-200d-1f33e", - "uc_match": "1f469-1f3fd-1f33e", - "uc_greedy": "1f469-1f3fd-1f33e", - "shortnames": [":woman_farmer_medium_skin_tone:"], - "category": "people" - }, - ":woman_farmer_tone4:": { - "uc_base": "1f469-1f3fe-1f33e", - "uc_output": "1f469-1f3fe-200d-1f33e", - "uc_match": "1f469-1f3fe-1f33e", - "uc_greedy": "1f469-1f3fe-1f33e", - "shortnames": [":woman_farmer_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_farmer_tone5:": { - "uc_base": "1f469-1f3ff-1f33e", - "uc_output": "1f469-1f3ff-200d-1f33e", - "uc_match": "1f469-1f3ff-1f33e", - "uc_greedy": "1f469-1f3ff-1f33e", - "shortnames": [":woman_farmer_dark_skin_tone:"], - "category": "people" - }, - ":woman_firefighter_tone1:": { - "uc_base": "1f469-1f3fb-1f692", - "uc_output": "1f469-1f3fb-200d-1f692", - "uc_match": "1f469-1f3fb-1f692", - "uc_greedy": "1f469-1f3fb-1f692", - "shortnames": [":woman_firefighter_light_skin_tone:"], - "category": "people" - }, - ":woman_firefighter_tone2:": { - "uc_base": "1f469-1f3fc-1f692", - "uc_output": "1f469-1f3fc-200d-1f692", - "uc_match": "1f469-1f3fc-1f692", - "uc_greedy": "1f469-1f3fc-1f692", - "shortnames": [":woman_firefighter_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_firefighter_tone3:": { - "uc_base": "1f469-1f3fd-1f692", - "uc_output": "1f469-1f3fd-200d-1f692", - "uc_match": "1f469-1f3fd-1f692", - "uc_greedy": "1f469-1f3fd-1f692", - "shortnames": [":woman_firefighter_medium_skin_tone:"], - "category": "people" - }, - ":woman_firefighter_tone4:": { - "uc_base": "1f469-1f3fe-1f692", - "uc_output": "1f469-1f3fe-200d-1f692", - "uc_match": "1f469-1f3fe-1f692", - "uc_greedy": "1f469-1f3fe-1f692", - "shortnames": [":woman_firefighter_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_firefighter_tone5:": { - "uc_base": "1f469-1f3ff-1f692", - "uc_output": "1f469-1f3ff-200d-1f692", - "uc_match": "1f469-1f3ff-1f692", - "uc_greedy": "1f469-1f3ff-1f692", - "shortnames": [":woman_firefighter_dark_skin_tone:"], - "category": "people" - }, - ":woman_mechanic_tone1:": { - "uc_base": "1f469-1f3fb-1f527", - "uc_output": "1f469-1f3fb-200d-1f527", - "uc_match": "1f469-1f3fb-1f527", - "uc_greedy": "1f469-1f3fb-1f527", - "shortnames": [":woman_mechanic_light_skin_tone:"], - "category": "people" - }, - ":woman_mechanic_tone2:": { - "uc_base": "1f469-1f3fc-1f527", - "uc_output": "1f469-1f3fc-200d-1f527", - "uc_match": "1f469-1f3fc-1f527", - "uc_greedy": "1f469-1f3fc-1f527", - "shortnames": [":woman_mechanic_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_mechanic_tone3:": { - "uc_base": "1f469-1f3fd-1f527", - "uc_output": "1f469-1f3fd-200d-1f527", - "uc_match": "1f469-1f3fd-1f527", - "uc_greedy": "1f469-1f3fd-1f527", - "shortnames": [":woman_mechanic_medium_skin_tone:"], - "category": "people" - }, - ":woman_mechanic_tone4:": { - "uc_base": "1f469-1f3fe-1f527", - "uc_output": "1f469-1f3fe-200d-1f527", - "uc_match": "1f469-1f3fe-1f527", - "uc_greedy": "1f469-1f3fe-1f527", - "shortnames": [":woman_mechanic_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_mechanic_tone5:": { - "uc_base": "1f469-1f3ff-1f527", - "uc_output": "1f469-1f3ff-200d-1f527", - "uc_match": "1f469-1f3ff-1f527", - "uc_greedy": "1f469-1f3ff-1f527", - "shortnames": [":woman_mechanic_dark_skin_tone:"], - "category": "people" - }, - ":woman_office_worker_tone1:": { - "uc_base": "1f469-1f3fb-1f4bc", - "uc_output": "1f469-1f3fb-200d-1f4bc", - "uc_match": "1f469-1f3fb-1f4bc", - "uc_greedy": "1f469-1f3fb-1f4bc", - "shortnames": [":woman_office_worker_light_skin_tone:"], - "category": "people" - }, - ":woman_office_worker_tone2:": { - "uc_base": "1f469-1f3fc-1f4bc", - "uc_output": "1f469-1f3fc-200d-1f4bc", - "uc_match": "1f469-1f3fc-1f4bc", - "uc_greedy": "1f469-1f3fc-1f4bc", - "shortnames": [":woman_office_worker_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_office_worker_tone3:": { - "uc_base": "1f469-1f3fd-1f4bc", - "uc_output": "1f469-1f3fd-200d-1f4bc", - "uc_match": "1f469-1f3fd-1f4bc", - "uc_greedy": "1f469-1f3fd-1f4bc", - "shortnames": [":woman_office_worker_medium_skin_tone:"], - "category": "people" - }, - ":woman_office_worker_tone4:": { - "uc_base": "1f469-1f3fe-1f4bc", - "uc_output": "1f469-1f3fe-200d-1f4bc", - "uc_match": "1f469-1f3fe-1f4bc", - "uc_greedy": "1f469-1f3fe-1f4bc", - "shortnames": [":woman_office_worker_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_office_worker_tone5:": { - "uc_base": "1f469-1f3ff-1f4bc", - "uc_output": "1f469-1f3ff-200d-1f4bc", - "uc_match": "1f469-1f3ff-1f4bc", - "uc_greedy": "1f469-1f3ff-1f4bc", - "shortnames": [":woman_office_worker_dark_skin_tone:"], - "category": "people" - }, - ":woman_scientist_tone1:": { - "uc_base": "1f469-1f3fb-1f52c", - "uc_output": "1f469-1f3fb-200d-1f52c", - "uc_match": "1f469-1f3fb-1f52c", - "uc_greedy": "1f469-1f3fb-1f52c", - "shortnames": [":woman_scientist_light_skin_tone:"], - "category": "people" - }, - ":woman_scientist_tone2:": { - "uc_base": "1f469-1f3fc-1f52c", - "uc_output": "1f469-1f3fc-200d-1f52c", - "uc_match": "1f469-1f3fc-1f52c", - "uc_greedy": "1f469-1f3fc-1f52c", - "shortnames": [":woman_scientist_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_scientist_tone3:": { - "uc_base": "1f469-1f3fd-1f52c", - "uc_output": "1f469-1f3fd-200d-1f52c", - "uc_match": "1f469-1f3fd-1f52c", - "uc_greedy": "1f469-1f3fd-1f52c", - "shortnames": [":woman_scientist_medium_skin_tone:"], - "category": "people" - }, - ":woman_scientist_tone4:": { - "uc_base": "1f469-1f3fe-1f52c", - "uc_output": "1f469-1f3fe-200d-1f52c", - "uc_match": "1f469-1f3fe-1f52c", - "uc_greedy": "1f469-1f3fe-1f52c", - "shortnames": [":woman_scientist_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_scientist_tone5:": { - "uc_base": "1f469-1f3ff-1f52c", - "uc_output": "1f469-1f3ff-200d-1f52c", - "uc_match": "1f469-1f3ff-1f52c", - "uc_greedy": "1f469-1f3ff-1f52c", - "shortnames": [":woman_scientist_dark_skin_tone:"], - "category": "people" - }, - ":woman_singer_tone1:": { - "uc_base": "1f469-1f3fb-1f3a4", - "uc_output": "1f469-1f3fb-200d-1f3a4", - "uc_match": "1f469-1f3fb-1f3a4", - "uc_greedy": "1f469-1f3fb-1f3a4", - "shortnames": [":woman_singer_light_skin_tone:"], - "category": "people" - }, - ":woman_singer_tone2:": { - "uc_base": "1f469-1f3fc-1f3a4", - "uc_output": "1f469-1f3fc-200d-1f3a4", - "uc_match": "1f469-1f3fc-1f3a4", - "uc_greedy": "1f469-1f3fc-1f3a4", - "shortnames": [":woman_singer_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_singer_tone3:": { - "uc_base": "1f469-1f3fd-1f3a4", - "uc_output": "1f469-1f3fd-200d-1f3a4", - "uc_match": "1f469-1f3fd-1f3a4", - "uc_greedy": "1f469-1f3fd-1f3a4", - "shortnames": [":woman_singer_medium_skin_tone:"], - "category": "people" - }, - ":woman_singer_tone4:": { - "uc_base": "1f469-1f3fe-1f3a4", - "uc_output": "1f469-1f3fe-200d-1f3a4", - "uc_match": "1f469-1f3fe-1f3a4", - "uc_greedy": "1f469-1f3fe-1f3a4", - "shortnames": [":woman_singer_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_singer_tone5:": { - "uc_base": "1f469-1f3ff-1f3a4", - "uc_output": "1f469-1f3ff-200d-1f3a4", - "uc_match": "1f469-1f3ff-1f3a4", - "uc_greedy": "1f469-1f3ff-1f3a4", - "shortnames": [":woman_singer_dark_skin_tone:"], - "category": "people" - }, - ":woman_student_tone1:": { - "uc_base": "1f469-1f3fb-1f393", - "uc_output": "1f469-1f3fb-200d-1f393", - "uc_match": "1f469-1f3fb-1f393", - "uc_greedy": "1f469-1f3fb-1f393", - "shortnames": [":woman_student_light_skin_tone:"], - "category": "people" - }, - ":woman_student_tone2:": { - "uc_base": "1f469-1f3fc-1f393", - "uc_output": "1f469-1f3fc-200d-1f393", - "uc_match": "1f469-1f3fc-1f393", - "uc_greedy": "1f469-1f3fc-1f393", - "shortnames": [":woman_student_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_student_tone3:": { - "uc_base": "1f469-1f3fd-1f393", - "uc_output": "1f469-1f3fd-200d-1f393", - "uc_match": "1f469-1f3fd-1f393", - "uc_greedy": "1f469-1f3fd-1f393", - "shortnames": [":woman_student_medium_skin_tone:"], - "category": "people" - }, - ":woman_student_tone4:": { - "uc_base": "1f469-1f3fe-1f393", - "uc_output": "1f469-1f3fe-200d-1f393", - "uc_match": "1f469-1f3fe-1f393", - "uc_greedy": "1f469-1f3fe-1f393", - "shortnames": [":woman_student_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_student_tone5:": { - "uc_base": "1f469-1f3ff-1f393", - "uc_output": "1f469-1f3ff-200d-1f393", - "uc_match": "1f469-1f3ff-1f393", - "uc_greedy": "1f469-1f3ff-1f393", - "shortnames": [":woman_student_dark_skin_tone:"], - "category": "people" - }, - ":woman_teacher_tone1:": { - "uc_base": "1f469-1f3fb-1f3eb", - "uc_output": "1f469-1f3fb-200d-1f3eb", - "uc_match": "1f469-1f3fb-1f3eb", - "uc_greedy": "1f469-1f3fb-1f3eb", - "shortnames": [":woman_teacher_light_skin_tone:"], - "category": "people" - }, - ":woman_teacher_tone2:": { - "uc_base": "1f469-1f3fc-1f3eb", - "uc_output": "1f469-1f3fc-200d-1f3eb", - "uc_match": "1f469-1f3fc-1f3eb", - "uc_greedy": "1f469-1f3fc-1f3eb", - "shortnames": [":woman_teacher_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_teacher_tone3:": { - "uc_base": "1f469-1f3fd-1f3eb", - "uc_output": "1f469-1f3fd-200d-1f3eb", - "uc_match": "1f469-1f3fd-1f3eb", - "uc_greedy": "1f469-1f3fd-1f3eb", - "shortnames": [":woman_teacher_medium_skin_tone:"], - "category": "people" - }, - ":woman_teacher_tone4:": { - "uc_base": "1f469-1f3fe-1f3eb", - "uc_output": "1f469-1f3fe-200d-1f3eb", - "uc_match": "1f469-1f3fe-1f3eb", - "uc_greedy": "1f469-1f3fe-1f3eb", - "shortnames": [":woman_teacher_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_teacher_tone5:": { - "uc_base": "1f469-1f3ff-1f3eb", - "uc_output": "1f469-1f3ff-200d-1f3eb", - "uc_match": "1f469-1f3ff-1f3eb", - "uc_greedy": "1f469-1f3ff-1f3eb", - "shortnames": [":woman_teacher_dark_skin_tone:"], - "category": "people" - }, - ":woman_technologist_tone1:": { - "uc_base": "1f469-1f3fb-1f4bb", - "uc_output": "1f469-1f3fb-200d-1f4bb", - "uc_match": "1f469-1f3fb-1f4bb", - "uc_greedy": "1f469-1f3fb-1f4bb", - "shortnames": [":woman_technologist_light_skin_tone:"], - "category": "people" - }, - ":woman_technologist_tone2:": { - "uc_base": "1f469-1f3fc-1f4bb", - "uc_output": "1f469-1f3fc-200d-1f4bb", - "uc_match": "1f469-1f3fc-1f4bb", - "uc_greedy": "1f469-1f3fc-1f4bb", - "shortnames": [":woman_technologist_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_technologist_tone3:": { - "uc_base": "1f469-1f3fd-1f4bb", - "uc_output": "1f469-1f3fd-200d-1f4bb", - "uc_match": "1f469-1f3fd-1f4bb", - "uc_greedy": "1f469-1f3fd-1f4bb", - "shortnames": [":woman_technologist_medium_skin_tone:"], - "category": "people" - }, - ":woman_technologist_tone4:": { - "uc_base": "1f469-1f3fe-1f4bb", - "uc_output": "1f469-1f3fe-200d-1f4bb", - "uc_match": "1f469-1f3fe-1f4bb", - "uc_greedy": "1f469-1f3fe-1f4bb", - "shortnames": [":woman_technologist_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_technologist_tone5:": { - "uc_base": "1f469-1f3ff-1f4bb", - "uc_output": "1f469-1f3ff-200d-1f4bb", - "uc_match": "1f469-1f3ff-1f4bb", - "uc_greedy": "1f469-1f3ff-1f4bb", - "shortnames": [":woman_technologist_dark_skin_tone:"], - "category": "people" - }, - ":rainbow_flag:": { - "uc_base": "1f3f3-1f308", - "uc_output": "1f3f3-fe0f-200d-1f308", - "uc_match": "1f3f3-fe0f-1f308", - "uc_greedy": "1f3f3-1f308", - "shortnames": [":gay_pride_flag:"], - "category": "flags" - }, - ":blond-haired_man:": { - "uc_base": "1f471-2642", - "uc_output": "1f471-200d-2642-fe0f", - "uc_match": "1f471-2642-fe0f", - "uc_greedy": "1f471-2642", - "shortnames": [], - "category": "people" - }, - ":blond-haired_woman:": { - "uc_base": "1f471-2640", - "uc_output": "1f471-200d-2640-fe0f", - "uc_match": "1f471-2640-fe0f", - "uc_greedy": "1f471-2640", - "shortnames": [], - "category": "people" - }, - ":man_biking:": { - "uc_base": "1f6b4-2642", - "uc_output": "1f6b4-200d-2642-fe0f", - "uc_match": "1f6b4-2642-fe0f", - "uc_greedy": "1f6b4-2642", - "shortnames": [], - "category": "activity" - }, - ":man_bowing:": { - "uc_base": "1f647-2642", - "uc_output": "1f647-200d-2642-fe0f", - "uc_match": "1f647-2642-fe0f", - "uc_greedy": "1f647-2642", - "shortnames": [], - "category": "people" - }, - ":man_cartwheeling:": { - "uc_base": "1f938-2642", - "uc_output": "1f938-200d-2642-fe0f", - "uc_match": "1f938-2642-fe0f", - "uc_greedy": "1f938-2642", - "shortnames": [], - "category": "activity" - }, - ":man_climbing:": { - "uc_base": "1f9d7-2642", - "uc_output": "1f9d7-200d-2642-fe0f", - "uc_match": "1f9d7-2642-fe0f", - "uc_greedy": "1f9d7-2642", - "shortnames": [], - "category": "activity" - }, - ":man_construction_worker:": { - "uc_base": "1f477-2642", - "uc_output": "1f477-200d-2642-fe0f", - "uc_match": "1f477-2642-fe0f", - "uc_greedy": "1f477-2642", - "shortnames": [], - "category": "people" - }, - ":man_elf:": { - "uc_base": "1f9dd-2642", - "uc_output": "1f9dd-200d-2642-fe0f", - "uc_match": "1f9dd-2642-fe0f", - "uc_greedy": "1f9dd-2642", - "shortnames": [], - "category": "people" - }, - ":man_facepalming:": { - "uc_base": "1f926-2642", - "uc_output": "1f926-200d-2642-fe0f", - "uc_match": "1f926-2642-fe0f", - "uc_greedy": "1f926-2642", - "shortnames": [], - "category": "people" - }, - ":man_fairy:": { - "uc_base": "1f9da-2642", - "uc_output": "1f9da-200d-2642-fe0f", - "uc_match": "1f9da-2642-fe0f", - "uc_greedy": "1f9da-2642", - "shortnames": [], - "category": "people" - }, - ":man_frowning:": { - "uc_base": "1f64d-2642", - "uc_output": "1f64d-200d-2642-fe0f", - "uc_match": "1f64d-2642-fe0f", - "uc_greedy": "1f64d-2642", - "shortnames": [], - "category": "people" - }, - ":man_genie:": { - "uc_base": "1f9de-2642", - "uc_output": "1f9de-200d-2642-fe0f", - "uc_match": "1f9de-2642-fe0f", - "uc_greedy": "1f9de-2642", - "shortnames": [], - "category": "people" - }, - ":man_gesturing_no:": { - "uc_base": "1f645-2642", - "uc_output": "1f645-200d-2642-fe0f", - "uc_match": "1f645-2642-fe0f", - "uc_greedy": "1f645-2642", - "shortnames": [], - "category": "people" - }, - ":man_gesturing_ok:": { - "uc_base": "1f646-2642", - "uc_output": "1f646-200d-2642-fe0f", - "uc_match": "1f646-2642-fe0f", - "uc_greedy": "1f646-2642", - "shortnames": [], - "category": "people" - }, - ":man_getting_face_massage:": { - "uc_base": "1f486-2642", - "uc_output": "1f486-200d-2642-fe0f", - "uc_match": "1f486-2642-fe0f", - "uc_greedy": "1f486-2642", - "shortnames": [], - "category": "people" - }, - ":man_getting_haircut:": { - "uc_base": "1f487-2642", - "uc_output": "1f487-200d-2642-fe0f", - "uc_match": "1f487-2642-fe0f", - "uc_greedy": "1f487-2642", - "shortnames": [], - "category": "people" - }, - ":man_guard:": { - "uc_base": "1f482-2642", - "uc_output": "1f482-200d-2642-fe0f", - "uc_match": "1f482-2642-fe0f", - "uc_greedy": "1f482-2642", - "shortnames": [], - "category": "people" - }, - ":man_health_worker:": { - "uc_base": "1f468-2695", - "uc_output": "1f468-200d-2695-fe0f", - "uc_match": "1f468-2695-fe0f", - "uc_greedy": "1f468-2695", - "shortnames": [], - "category": "people" - }, - ":man_in_lotus_position:": { - "uc_base": "1f9d8-2642", - "uc_output": "1f9d8-200d-2642-fe0f", - "uc_match": "1f9d8-2642-fe0f", - "uc_greedy": "1f9d8-2642", - "shortnames": [], - "category": "activity" - }, - ":man_in_steamy_room:": { - "uc_base": "1f9d6-2642", - "uc_output": "1f9d6-200d-2642-fe0f", - "uc_match": "1f9d6-2642-fe0f", - "uc_greedy": "1f9d6-2642", - "shortnames": [], - "category": "people" - }, - ":man_judge:": { - "uc_base": "1f468-2696", - "uc_output": "1f468-200d-2696-fe0f", - "uc_match": "1f468-2696-fe0f", - "uc_greedy": "1f468-2696", - "shortnames": [], - "category": "people" - }, - ":man_juggling:": { - "uc_base": "1f939-2642", - "uc_output": "1f939-200d-2642-fe0f", - "uc_match": "1f939-2642-fe0f", - "uc_greedy": "1f939-2642", - "shortnames": [], - "category": "activity" - }, - ":man_mage:": { - "uc_base": "1f9d9-2642", - "uc_output": "1f9d9-200d-2642-fe0f", - "uc_match": "1f9d9-2642-fe0f", - "uc_greedy": "1f9d9-2642", - "shortnames": [], - "category": "people" - }, - ":man_mountain_biking:": { - "uc_base": "1f6b5-2642", - "uc_output": "1f6b5-200d-2642-fe0f", - "uc_match": "1f6b5-2642-fe0f", - "uc_greedy": "1f6b5-2642", - "shortnames": [], - "category": "activity" - }, - ":man_pilot:": { - "uc_base": "1f468-2708", - "uc_output": "1f468-200d-2708-fe0f", - "uc_match": "1f468-2708-fe0f", - "uc_greedy": "1f468-2708", - "shortnames": [], - "category": "people" - }, - ":man_playing_handball:": { - "uc_base": "1f93e-2642", - "uc_output": "1f93e-200d-2642-fe0f", - "uc_match": "1f93e-2642-fe0f", - "uc_greedy": "1f93e-2642", - "shortnames": [], - "category": "activity" - }, - ":man_playing_water_polo:": { - "uc_base": "1f93d-2642", - "uc_output": "1f93d-200d-2642-fe0f", - "uc_match": "1f93d-2642-fe0f", - "uc_greedy": "1f93d-2642", - "shortnames": [], - "category": "activity" - }, - ":man_police_officer:": { - "uc_base": "1f46e-2642", - "uc_output": "1f46e-200d-2642-fe0f", - "uc_match": "1f46e-2642-fe0f", - "uc_greedy": "1f46e-2642", - "shortnames": [], - "category": "people" - }, - ":man_pouting:": { - "uc_base": "1f64e-2642", - "uc_output": "1f64e-200d-2642-fe0f", - "uc_match": "1f64e-2642-fe0f", - "uc_greedy": "1f64e-2642", - "shortnames": [], - "category": "people" - }, - ":man_raising_hand:": { - "uc_base": "1f64b-2642", - "uc_output": "1f64b-200d-2642-fe0f", - "uc_match": "1f64b-2642-fe0f", - "uc_greedy": "1f64b-2642", - "shortnames": [], - "category": "people" - }, - ":man_rowing_boat:": { - "uc_base": "1f6a3-2642", - "uc_output": "1f6a3-200d-2642-fe0f", - "uc_match": "1f6a3-2642-fe0f", - "uc_greedy": "1f6a3-2642", - "shortnames": [], - "category": "activity" - }, - ":man_running:": { - "uc_base": "1f3c3-2642", - "uc_output": "1f3c3-200d-2642-fe0f", - "uc_match": "1f3c3-2642-fe0f", - "uc_greedy": "1f3c3-2642", - "shortnames": [], - "category": "people" - }, - ":man_shrugging:": { - "uc_base": "1f937-2642", - "uc_output": "1f937-200d-2642-fe0f", - "uc_match": "1f937-2642-fe0f", - "uc_greedy": "1f937-2642", - "shortnames": [], - "category": "people" - }, - ":man_surfing:": { - "uc_base": "1f3c4-2642", - "uc_output": "1f3c4-200d-2642-fe0f", - "uc_match": "1f3c4-2642-fe0f", - "uc_greedy": "1f3c4-2642", - "shortnames": [], - "category": "activity" - }, - ":man_swimming:": { - "uc_base": "1f3ca-2642", - "uc_output": "1f3ca-200d-2642-fe0f", - "uc_match": "1f3ca-2642-fe0f", - "uc_greedy": "1f3ca-2642", - "shortnames": [], - "category": "activity" - }, - ":man_tipping_hand:": { - "uc_base": "1f481-2642", - "uc_output": "1f481-200d-2642-fe0f", - "uc_match": "1f481-2642-fe0f", - "uc_greedy": "1f481-2642", - "shortnames": [], - "category": "people" - }, - ":man_vampire:": { - "uc_base": "1f9db-2642", - "uc_output": "1f9db-200d-2642-fe0f", - "uc_match": "1f9db-2642-fe0f", - "uc_greedy": "1f9db-2642", - "shortnames": [], - "category": "people" - }, - ":man_walking:": { - "uc_base": "1f6b6-2642", - "uc_output": "1f6b6-200d-2642-fe0f", - "uc_match": "1f6b6-2642-fe0f", - "uc_greedy": "1f6b6-2642", - "shortnames": [], - "category": "people" - }, - ":man_wearing_turban:": { - "uc_base": "1f473-2642", - "uc_output": "1f473-200d-2642-fe0f", - "uc_match": "1f473-2642-fe0f", - "uc_greedy": "1f473-2642", - "shortnames": [], - "category": "people" - }, - ":man_zombie:": { - "uc_base": "1f9df-2642", - "uc_output": "1f9df-200d-2642-fe0f", - "uc_match": "1f9df-2642-fe0f", - "uc_greedy": "1f9df-2642", - "shortnames": [], - "category": "people" - }, - ":men_with_bunny_ears_partying:": { - "uc_base": "1f46f-2642", - "uc_output": "1f46f-200d-2642-fe0f", - "uc_match": "1f46f-2642-fe0f", - "uc_greedy": "1f46f-2642", - "shortnames": [], - "category": "people" - }, - ":men_wrestling:": { - "uc_base": "1f93c-2642", - "uc_output": "1f93c-200d-2642-fe0f", - "uc_match": "1f93c-2642-fe0f", - "uc_greedy": "1f93c-2642", - "shortnames": [], - "category": "activity" - }, - ":mermaid:": { - "uc_base": "1f9dc-2640", - "uc_output": "1f9dc-200d-2640-fe0f", - "uc_match": "1f9dc-2640-fe0f", - "uc_greedy": "1f9dc-2640", - "shortnames": [], - "category": "people" - }, - ":merman:": { - "uc_base": "1f9dc-2642", - "uc_output": "1f9dc-200d-2642-fe0f", - "uc_match": "1f9dc-2642-fe0f", - "uc_greedy": "1f9dc-2642", - "shortnames": [], - "category": "people" - }, - ":woman_biking:": { - "uc_base": "1f6b4-2640", - "uc_output": "1f6b4-200d-2640-fe0f", - "uc_match": "1f6b4-2640-fe0f", - "uc_greedy": "1f6b4-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_bowing:": { - "uc_base": "1f647-2640", - "uc_output": "1f647-200d-2640-fe0f", - "uc_match": "1f647-2640-fe0f", - "uc_greedy": "1f647-2640", - "shortnames": [], - "category": "people" - }, - ":woman_cartwheeling:": { - "uc_base": "1f938-2640", - "uc_output": "1f938-200d-2640-fe0f", - "uc_match": "1f938-2640-fe0f", - "uc_greedy": "1f938-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_climbing:": { - "uc_base": "1f9d7-2640", - "uc_output": "1f9d7-200d-2640-fe0f", - "uc_match": "1f9d7-2640-fe0f", - "uc_greedy": "1f9d7-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_construction_worker:": { - "uc_base": "1f477-2640", - "uc_output": "1f477-200d-2640-fe0f", - "uc_match": "1f477-2640-fe0f", - "uc_greedy": "1f477-2640", - "shortnames": [], - "category": "people" - }, - ":woman_elf:": { - "uc_base": "1f9dd-2640", - "uc_output": "1f9dd-200d-2640-fe0f", - "uc_match": "1f9dd-2640-fe0f", - "uc_greedy": "1f9dd-2640", - "shortnames": [], - "category": "people" - }, - ":woman_facepalming:": { - "uc_base": "1f926-2640", - "uc_output": "1f926-200d-2640-fe0f", - "uc_match": "1f926-2640-fe0f", - "uc_greedy": "1f926-2640", - "shortnames": [], - "category": "people" - }, - ":woman_fairy:": { - "uc_base": "1f9da-2640", - "uc_output": "1f9da-200d-2640-fe0f", - "uc_match": "1f9da-2640-fe0f", - "uc_greedy": "1f9da-2640", - "shortnames": [], - "category": "people" - }, - ":woman_frowning:": { - "uc_base": "1f64d-2640", - "uc_output": "1f64d-200d-2640-fe0f", - "uc_match": "1f64d-2640-fe0f", - "uc_greedy": "1f64d-2640", - "shortnames": [], - "category": "people" - }, - ":woman_genie:": { - "uc_base": "1f9de-2640", - "uc_output": "1f9de-200d-2640-fe0f", - "uc_match": "1f9de-2640-fe0f", - "uc_greedy": "1f9de-2640", - "shortnames": [], - "category": "people" - }, - ":woman_gesturing_no:": { - "uc_base": "1f645-2640", - "uc_output": "1f645-200d-2640-fe0f", - "uc_match": "1f645-2640-fe0f", - "uc_greedy": "1f645-2640", - "shortnames": [], - "category": "people" - }, - ":woman_gesturing_ok:": { - "uc_base": "1f646-2640", - "uc_output": "1f646-200d-2640-fe0f", - "uc_match": "1f646-2640-fe0f", - "uc_greedy": "1f646-2640", - "shortnames": [], - "category": "people" - }, - ":woman_getting_face_massage:": { - "uc_base": "1f486-2640", - "uc_output": "1f486-200d-2640-fe0f", - "uc_match": "1f486-2640-fe0f", - "uc_greedy": "1f486-2640", - "shortnames": [], - "category": "people" - }, - ":woman_getting_haircut:": { - "uc_base": "1f487-2640", - "uc_output": "1f487-200d-2640-fe0f", - "uc_match": "1f487-2640-fe0f", - "uc_greedy": "1f487-2640", - "shortnames": [], - "category": "people" - }, - ":woman_guard:": { - "uc_base": "1f482-2640", - "uc_output": "1f482-200d-2640-fe0f", - "uc_match": "1f482-2640-fe0f", - "uc_greedy": "1f482-2640", - "shortnames": [], - "category": "people" - }, - ":woman_health_worker:": { - "uc_base": "1f469-2695", - "uc_output": "1f469-200d-2695-fe0f", - "uc_match": "1f469-2695-fe0f", - "uc_greedy": "1f469-2695", - "shortnames": [], - "category": "people" - }, - ":woman_in_lotus_position:": { - "uc_base": "1f9d8-2640", - "uc_output": "1f9d8-200d-2640-fe0f", - "uc_match": "1f9d8-2640-fe0f", - "uc_greedy": "1f9d8-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_in_steamy_room:": { - "uc_base": "1f9d6-2640", - "uc_output": "1f9d6-200d-2640-fe0f", - "uc_match": "1f9d6-2640-fe0f", - "uc_greedy": "1f9d6-2640", - "shortnames": [], - "category": "people" - }, - ":woman_judge:": { - "uc_base": "1f469-2696", - "uc_output": "1f469-200d-2696-fe0f", - "uc_match": "1f469-2696-fe0f", - "uc_greedy": "1f469-2696", - "shortnames": [], - "category": "people" - }, - ":woman_juggling:": { - "uc_base": "1f939-2640", - "uc_output": "1f939-200d-2640-fe0f", - "uc_match": "1f939-2640-fe0f", - "uc_greedy": "1f939-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_mage:": { - "uc_base": "1f9d9-2640", - "uc_output": "1f9d9-200d-2640-fe0f", - "uc_match": "1f9d9-2640-fe0f", - "uc_greedy": "1f9d9-2640", - "shortnames": [], - "category": "people" - }, - ":woman_mountain_biking:": { - "uc_base": "1f6b5-2640", - "uc_output": "1f6b5-200d-2640-fe0f", - "uc_match": "1f6b5-2640-fe0f", - "uc_greedy": "1f6b5-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_pilot:": { - "uc_base": "1f469-2708", - "uc_output": "1f469-200d-2708-fe0f", - "uc_match": "1f469-2708-fe0f", - "uc_greedy": "1f469-2708", - "shortnames": [], - "category": "people" - }, - ":woman_playing_handball:": { - "uc_base": "1f93e-2640", - "uc_output": "1f93e-200d-2640-fe0f", - "uc_match": "1f93e-2640-fe0f", - "uc_greedy": "1f93e-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_playing_water_polo:": { - "uc_base": "1f93d-2640", - "uc_output": "1f93d-200d-2640-fe0f", - "uc_match": "1f93d-2640-fe0f", - "uc_greedy": "1f93d-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_police_officer:": { - "uc_base": "1f46e-2640", - "uc_output": "1f46e-200d-2640-fe0f", - "uc_match": "1f46e-2640-fe0f", - "uc_greedy": "1f46e-2640", - "shortnames": [], - "category": "people" - }, - ":woman_pouting:": { - "uc_base": "1f64e-2640", - "uc_output": "1f64e-200d-2640-fe0f", - "uc_match": "1f64e-2640-fe0f", - "uc_greedy": "1f64e-2640", - "shortnames": [], - "category": "people" - }, - ":woman_raising_hand:": { - "uc_base": "1f64b-2640", - "uc_output": "1f64b-200d-2640-fe0f", - "uc_match": "1f64b-2640-fe0f", - "uc_greedy": "1f64b-2640", - "shortnames": [], - "category": "people" - }, - ":woman_rowing_boat:": { - "uc_base": "1f6a3-2640", - "uc_output": "1f6a3-200d-2640-fe0f", - "uc_match": "1f6a3-2640-fe0f", - "uc_greedy": "1f6a3-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_running:": { - "uc_base": "1f3c3-2640", - "uc_output": "1f3c3-200d-2640-fe0f", - "uc_match": "1f3c3-2640-fe0f", - "uc_greedy": "1f3c3-2640", - "shortnames": [], - "category": "people" - }, - ":woman_shrugging:": { - "uc_base": "1f937-2640", - "uc_output": "1f937-200d-2640-fe0f", - "uc_match": "1f937-2640-fe0f", - "uc_greedy": "1f937-2640", - "shortnames": [], - "category": "people" - }, - ":woman_surfing:": { - "uc_base": "1f3c4-2640", - "uc_output": "1f3c4-200d-2640-fe0f", - "uc_match": "1f3c4-2640-fe0f", - "uc_greedy": "1f3c4-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_swimming:": { - "uc_base": "1f3ca-2640", - "uc_output": "1f3ca-200d-2640-fe0f", - "uc_match": "1f3ca-2640-fe0f", - "uc_greedy": "1f3ca-2640", - "shortnames": [], - "category": "activity" - }, - ":woman_tipping_hand:": { - "uc_base": "1f481-2640", - "uc_output": "1f481-200d-2640-fe0f", - "uc_match": "1f481-2640-fe0f", - "uc_greedy": "1f481-2640", - "shortnames": [], - "category": "people" - }, - ":woman_vampire:": { - "uc_base": "1f9db-2640", - "uc_output": "1f9db-200d-2640-fe0f", - "uc_match": "1f9db-2640-fe0f", - "uc_greedy": "1f9db-2640", - "shortnames": [], - "category": "people" - }, - ":woman_walking:": { - "uc_base": "1f6b6-2640", - "uc_output": "1f6b6-200d-2640-fe0f", - "uc_match": "1f6b6-2640-fe0f", - "uc_greedy": "1f6b6-2640", - "shortnames": [], - "category": "people" - }, - ":woman_wearing_turban:": { - "uc_base": "1f473-2640", - "uc_output": "1f473-200d-2640-fe0f", - "uc_match": "1f473-2640-fe0f", - "uc_greedy": "1f473-2640", - "shortnames": [], - "category": "people" - }, - ":woman_zombie:": { - "uc_base": "1f9df-2640", - "uc_output": "1f9df-200d-2640-fe0f", - "uc_match": "1f9df-2640-fe0f", - "uc_greedy": "1f9df-2640", - "shortnames": [], - "category": "people" - }, - ":women_with_bunny_ears_partying:": { - "uc_base": "1f46f-2640", - "uc_output": "1f46f-200d-2640-fe0f", - "uc_match": "1f46f-2640-fe0f", - "uc_greedy": "1f46f-2640", - "shortnames": [], - "category": "people" - }, - ":women_wrestling:": { - "uc_base": "1f93c-2640", - "uc_output": "1f93c-200d-2640-fe0f", - "uc_match": "1f93c-2640-fe0f", - "uc_greedy": "1f93c-2640", - "shortnames": [], - "category": "activity" - }, - ":family_man_boy:": { - "uc_base": "1f468-1f466", - "uc_output": "1f468-200d-1f466", - "uc_match": "1f468-1f466", - "uc_greedy": "1f468-1f466", - "shortnames": [], - "category": "people" - }, - ":family_man_girl:": { - "uc_base": "1f468-1f467", - "uc_output": "1f468-200d-1f467", - "uc_match": "1f468-1f467", - "uc_greedy": "1f468-1f467", - "shortnames": [], - "category": "people" - }, - ":family_woman_boy:": { - "uc_base": "1f469-1f466", - "uc_output": "1f469-200d-1f466", - "uc_match": "1f469-1f466", - "uc_greedy": "1f469-1f466", - "shortnames": [], - "category": "people" - }, - ":family_woman_girl:": { - "uc_base": "1f469-1f467", - "uc_output": "1f469-200d-1f467", - "uc_match": "1f469-1f467", - "uc_greedy": "1f469-1f467", - "shortnames": [], - "category": "people" - }, - ":man_artist:": { - "uc_base": "1f468-1f3a8", - "uc_output": "1f468-200d-1f3a8", - "uc_match": "1f468-1f3a8", - "uc_greedy": "1f468-1f3a8", - "shortnames": [], - "category": "people" - }, - ":man_astronaut:": { - "uc_base": "1f468-1f680", - "uc_output": "1f468-200d-1f680", - "uc_match": "1f468-1f680", - "uc_greedy": "1f468-1f680", - "shortnames": [], - "category": "people" - }, - ":man_cook:": { - "uc_base": "1f468-1f373", - "uc_output": "1f468-200d-1f373", - "uc_match": "1f468-1f373", - "uc_greedy": "1f468-1f373", - "shortnames": [], - "category": "people" - }, - ":man_factory_worker:": { - "uc_base": "1f468-1f3ed", - "uc_output": "1f468-200d-1f3ed", - "uc_match": "1f468-1f3ed", - "uc_greedy": "1f468-1f3ed", - "shortnames": [], - "category": "people" - }, - ":man_farmer:": { - "uc_base": "1f468-1f33e", - "uc_output": "1f468-200d-1f33e", - "uc_match": "1f468-1f33e", - "uc_greedy": "1f468-1f33e", - "shortnames": [], - "category": "people" - }, - ":man_firefighter:": { - "uc_base": "1f468-1f692", - "uc_output": "1f468-200d-1f692", - "uc_match": "1f468-1f692", - "uc_greedy": "1f468-1f692", - "shortnames": [], - "category": "people" - }, - ":man_mechanic:": { - "uc_base": "1f468-1f527", - "uc_output": "1f468-200d-1f527", - "uc_match": "1f468-1f527", - "uc_greedy": "1f468-1f527", - "shortnames": [], - "category": "people" - }, - ":man_office_worker:": { - "uc_base": "1f468-1f4bc", - "uc_output": "1f468-200d-1f4bc", - "uc_match": "1f468-1f4bc", - "uc_greedy": "1f468-1f4bc", - "shortnames": [], - "category": "people" - }, - ":man_scientist:": { - "uc_base": "1f468-1f52c", - "uc_output": "1f468-200d-1f52c", - "uc_match": "1f468-1f52c", - "uc_greedy": "1f468-1f52c", - "shortnames": [], - "category": "people" - }, - ":man_singer:": { - "uc_base": "1f468-1f3a4", - "uc_output": "1f468-200d-1f3a4", - "uc_match": "1f468-1f3a4", - "uc_greedy": "1f468-1f3a4", - "shortnames": [], - "category": "people" - }, - ":man_student:": { - "uc_base": "1f468-1f393", - "uc_output": "1f468-200d-1f393", - "uc_match": "1f468-1f393", - "uc_greedy": "1f468-1f393", - "shortnames": [], - "category": "people" - }, - ":man_teacher:": { - "uc_base": "1f468-1f3eb", - "uc_output": "1f468-200d-1f3eb", - "uc_match": "1f468-1f3eb", - "uc_greedy": "1f468-1f3eb", - "shortnames": [], - "category": "people" - }, - ":man_technologist:": { - "uc_base": "1f468-1f4bb", - "uc_output": "1f468-200d-1f4bb", - "uc_match": "1f468-1f4bb", - "uc_greedy": "1f468-1f4bb", - "shortnames": [], - "category": "people" - }, - ":woman_artist:": { - "uc_base": "1f469-1f3a8", - "uc_output": "1f469-200d-1f3a8", - "uc_match": "1f469-1f3a8", - "uc_greedy": "1f469-1f3a8", - "shortnames": [], - "category": "people" - }, - ":woman_astronaut:": { - "uc_base": "1f469-1f680", - "uc_output": "1f469-200d-1f680", - "uc_match": "1f469-1f680", - "uc_greedy": "1f469-1f680", - "shortnames": [], - "category": "people" - }, - ":woman_cook:": { - "uc_base": "1f469-1f373", - "uc_output": "1f469-200d-1f373", - "uc_match": "1f469-1f373", - "uc_greedy": "1f469-1f373", - "shortnames": [], - "category": "people" - }, - ":woman_factory_worker:": { - "uc_base": "1f469-1f3ed", - "uc_output": "1f469-200d-1f3ed", - "uc_match": "1f469-1f3ed", - "uc_greedy": "1f469-1f3ed", - "shortnames": [], - "category": "people" - }, - ":woman_farmer:": { - "uc_base": "1f469-1f33e", - "uc_output": "1f469-200d-1f33e", - "uc_match": "1f469-1f33e", - "uc_greedy": "1f469-1f33e", - "shortnames": [], - "category": "people" - }, - ":woman_firefighter:": { - "uc_base": "1f469-1f692", - "uc_output": "1f469-200d-1f692", - "uc_match": "1f469-1f692", - "uc_greedy": "1f469-1f692", - "shortnames": [], - "category": "people" - }, - ":woman_mechanic:": { - "uc_base": "1f469-1f527", - "uc_output": "1f469-200d-1f527", - "uc_match": "1f469-1f527", - "uc_greedy": "1f469-1f527", - "shortnames": [], - "category": "people" - }, - ":woman_office_worker:": { - "uc_base": "1f469-1f4bc", - "uc_output": "1f469-200d-1f4bc", - "uc_match": "1f469-1f4bc", - "uc_greedy": "1f469-1f4bc", - "shortnames": [], - "category": "people" - }, - ":woman_scientist:": { - "uc_base": "1f469-1f52c", - "uc_output": "1f469-200d-1f52c", - "uc_match": "1f469-1f52c", - "uc_greedy": "1f469-1f52c", - "shortnames": [], - "category": "people" - }, - ":woman_singer:": { - "uc_base": "1f469-1f3a4", - "uc_output": "1f469-200d-1f3a4", - "uc_match": "1f469-1f3a4", - "uc_greedy": "1f469-1f3a4", - "shortnames": [], - "category": "people" - }, - ":woman_student:": { - "uc_base": "1f469-1f393", - "uc_output": "1f469-200d-1f393", - "uc_match": "1f469-1f393", - "uc_greedy": "1f469-1f393", - "shortnames": [], - "category": "people" - }, - ":woman_teacher:": { - "uc_base": "1f469-1f3eb", - "uc_output": "1f469-200d-1f3eb", - "uc_match": "1f469-1f3eb", - "uc_greedy": "1f469-1f3eb", - "shortnames": [], - "category": "people" - }, - ":woman_technologist:": { - "uc_base": "1f469-1f4bb", - "uc_output": "1f469-200d-1f4bb", - "uc_match": "1f469-1f4bb", - "uc_greedy": "1f469-1f4bb", - "shortnames": [], - "category": "people" - }, - ":asterisk:": { - "uc_base": "002a-20e3", - "uc_output": "002a-fe0f-20e3", - "uc_match": "002a-20e3", - "uc_greedy": "002a-20e3", - "shortnames": [":keycap_asterisk:"], - "category": "symbols" - }, - ":eight:": { - "uc_base": "0038-20e3", - "uc_output": "0038-fe0f-20e3", - "uc_match": "0038-20e3", - "uc_greedy": "0038-20e3", - "shortnames": [], - "category": "symbols" - }, - ":five:": { - "uc_base": "0035-20e3", - "uc_output": "0035-fe0f-20e3", - "uc_match": "0035-20e3", - "uc_greedy": "0035-20e3", - "shortnames": [], - "category": "symbols" - }, - ":four:": { - "uc_base": "0034-20e3", - "uc_output": "0034-fe0f-20e3", - "uc_match": "0034-20e3", - "uc_greedy": "0034-20e3", - "shortnames": [], - "category": "symbols" - }, - ":hash:": { - "uc_base": "0023-20e3", - "uc_output": "0023-fe0f-20e3", - "uc_match": "0023-20e3", - "uc_greedy": "0023-20e3", - "shortnames": [], - "category": "symbols" - }, - ":nine:": { - "uc_base": "0039-20e3", - "uc_output": "0039-fe0f-20e3", - "uc_match": "0039-20e3", - "uc_greedy": "0039-20e3", - "shortnames": [], - "category": "symbols" - }, - ":one:": { - "uc_base": "0031-20e3", - "uc_output": "0031-fe0f-20e3", - "uc_match": "0031-20e3", - "uc_greedy": "0031-20e3", - "shortnames": [], - "category": "symbols" - }, - ":seven:": { - "uc_base": "0037-20e3", - "uc_output": "0037-fe0f-20e3", - "uc_match": "0037-20e3", - "uc_greedy": "0037-20e3", - "shortnames": [], - "category": "symbols" - }, - ":six:": { - "uc_base": "0036-20e3", - "uc_output": "0036-fe0f-20e3", - "uc_match": "0036-20e3", - "uc_greedy": "0036-20e3", - "shortnames": [], - "category": "symbols" - }, - ":three:": { - "uc_base": "0033-20e3", - "uc_output": "0033-fe0f-20e3", - "uc_match": "0033-20e3", - "uc_greedy": "0033-20e3", - "shortnames": [], - "category": "symbols" - }, - ":two:": { - "uc_base": "0032-20e3", - "uc_output": "0032-fe0f-20e3", - "uc_match": "0032-20e3", - "uc_greedy": "0032-20e3", - "shortnames": [], - "category": "symbols" - }, - ":zero:": { - "uc_base": "0030-20e3", - "uc_output": "0030-fe0f-20e3", - "uc_match": "0030-20e3", - "uc_greedy": "0030-20e3", - "shortnames": [], - "category": "symbols" - }, - ":adult_tone1:": { - "uc_base": "1f9d1-1f3fb", - "uc_output": "1f9d1-1f3fb", - "uc_match": "1f9d1-1f3fb", - "uc_greedy": "1f9d1-1f3fb", - "shortnames": [":adult_light_skin_tone:"], - "category": "people" - }, - ":adult_tone2:": { - "uc_base": "1f9d1-1f3fc", - "uc_output": "1f9d1-1f3fc", - "uc_match": "1f9d1-1f3fc", - "uc_greedy": "1f9d1-1f3fc", - "shortnames": [":adult_medium_light_skin_tone:"], - "category": "people" - }, - ":adult_tone3:": { - "uc_base": "1f9d1-1f3fd", - "uc_output": "1f9d1-1f3fd", - "uc_match": "1f9d1-1f3fd", - "uc_greedy": "1f9d1-1f3fd", - "shortnames": [":adult_medium_skin_tone:"], - "category": "people" - }, - ":adult_tone4:": { - "uc_base": "1f9d1-1f3fe", - "uc_output": "1f9d1-1f3fe", - "uc_match": "1f9d1-1f3fe", - "uc_greedy": "1f9d1-1f3fe", - "shortnames": [":adult_medium_dark_skin_tone:"], - "category": "people" - }, - ":adult_tone5:": { - "uc_base": "1f9d1-1f3ff", - "uc_output": "1f9d1-1f3ff", - "uc_match": "1f9d1-1f3ff", - "uc_greedy": "1f9d1-1f3ff", - "shortnames": [":adult_dark_skin_tone:"], - "category": "people" - }, - ":angel_tone1:": { - "uc_base": "1f47c-1f3fb", - "uc_output": "1f47c-1f3fb", - "uc_match": "1f47c-1f3fb", - "uc_greedy": "1f47c-1f3fb", - "shortnames": [], - "category": "people" - }, - ":angel_tone2:": { - "uc_base": "1f47c-1f3fc", - "uc_output": "1f47c-1f3fc", - "uc_match": "1f47c-1f3fc", - "uc_greedy": "1f47c-1f3fc", - "shortnames": [], - "category": "people" - }, - ":angel_tone3:": { - "uc_base": "1f47c-1f3fd", - "uc_output": "1f47c-1f3fd", - "uc_match": "1f47c-1f3fd", - "uc_greedy": "1f47c-1f3fd", - "shortnames": [], - "category": "people" - }, - ":angel_tone4:": { - "uc_base": "1f47c-1f3fe", - "uc_output": "1f47c-1f3fe", - "uc_match": "1f47c-1f3fe", - "uc_greedy": "1f47c-1f3fe", - "shortnames": [], - "category": "people" - }, - ":angel_tone5:": { - "uc_base": "1f47c-1f3ff", - "uc_output": "1f47c-1f3ff", - "uc_match": "1f47c-1f3ff", - "uc_greedy": "1f47c-1f3ff", - "shortnames": [], - "category": "people" - }, - ":baby_tone1:": { - "uc_base": "1f476-1f3fb", - "uc_output": "1f476-1f3fb", - "uc_match": "1f476-1f3fb", - "uc_greedy": "1f476-1f3fb", - "shortnames": [], - "category": "people" - }, - ":baby_tone2:": { - "uc_base": "1f476-1f3fc", - "uc_output": "1f476-1f3fc", - "uc_match": "1f476-1f3fc", - "uc_greedy": "1f476-1f3fc", - "shortnames": [], - "category": "people" - }, - ":baby_tone3:": { - "uc_base": "1f476-1f3fd", - "uc_output": "1f476-1f3fd", - "uc_match": "1f476-1f3fd", - "uc_greedy": "1f476-1f3fd", - "shortnames": [], - "category": "people" - }, - ":baby_tone4:": { - "uc_base": "1f476-1f3fe", - "uc_output": "1f476-1f3fe", - "uc_match": "1f476-1f3fe", - "uc_greedy": "1f476-1f3fe", - "shortnames": [], - "category": "people" - }, - ":baby_tone5:": { - "uc_base": "1f476-1f3ff", - "uc_output": "1f476-1f3ff", - "uc_match": "1f476-1f3ff", - "uc_greedy": "1f476-1f3ff", - "shortnames": [], - "category": "people" - }, - ":bath_tone1:": { - "uc_base": "1f6c0-1f3fb", - "uc_output": "1f6c0-1f3fb", - "uc_match": "1f6c0-1f3fb", - "uc_greedy": "1f6c0-1f3fb", - "shortnames": [], - "category": "objects" - }, - ":bath_tone2:": { - "uc_base": "1f6c0-1f3fc", - "uc_output": "1f6c0-1f3fc", - "uc_match": "1f6c0-1f3fc", - "uc_greedy": "1f6c0-1f3fc", - "shortnames": [], - "category": "objects" - }, - ":bath_tone3:": { - "uc_base": "1f6c0-1f3fd", - "uc_output": "1f6c0-1f3fd", - "uc_match": "1f6c0-1f3fd", - "uc_greedy": "1f6c0-1f3fd", - "shortnames": [], - "category": "objects" - }, - ":bath_tone4:": { - "uc_base": "1f6c0-1f3fe", - "uc_output": "1f6c0-1f3fe", - "uc_match": "1f6c0-1f3fe", - "uc_greedy": "1f6c0-1f3fe", - "shortnames": [], - "category": "objects" - }, - ":bath_tone5:": { - "uc_base": "1f6c0-1f3ff", - "uc_output": "1f6c0-1f3ff", - "uc_match": "1f6c0-1f3ff", - "uc_greedy": "1f6c0-1f3ff", - "shortnames": [], - "category": "objects" - }, - ":bearded_person_tone1:": { - "uc_base": "1f9d4-1f3fb", - "uc_output": "1f9d4-1f3fb", - "uc_match": "1f9d4-1f3fb", - "uc_greedy": "1f9d4-1f3fb", - "shortnames": [":bearded_person_light_skin_tone:"], - "category": "people" - }, - ":bearded_person_tone2:": { - "uc_base": "1f9d4-1f3fc", - "uc_output": "1f9d4-1f3fc", - "uc_match": "1f9d4-1f3fc", - "uc_greedy": "1f9d4-1f3fc", - "shortnames": [":bearded_person_medium_light_skin_tone:"], - "category": "people" - }, - ":bearded_person_tone3:": { - "uc_base": "1f9d4-1f3fd", - "uc_output": "1f9d4-1f3fd", - "uc_match": "1f9d4-1f3fd", - "uc_greedy": "1f9d4-1f3fd", - "shortnames": [":bearded_person_medium_skin_tone:"], - "category": "people" - }, - ":bearded_person_tone4:": { - "uc_base": "1f9d4-1f3fe", - "uc_output": "1f9d4-1f3fe", - "uc_match": "1f9d4-1f3fe", - "uc_greedy": "1f9d4-1f3fe", - "shortnames": [":bearded_person_medium_dark_skin_tone:"], - "category": "people" - }, - ":bearded_person_tone5:": { - "uc_base": "1f9d4-1f3ff", - "uc_output": "1f9d4-1f3ff", - "uc_match": "1f9d4-1f3ff", - "uc_greedy": "1f9d4-1f3ff", - "shortnames": [":bearded_person_dark_skin_tone:"], - "category": "people" - }, - ":blond_haired_person_tone1:": { - "uc_base": "1f471-1f3fb", - "uc_output": "1f471-1f3fb", - "uc_match": "1f471-1f3fb", - "uc_greedy": "1f471-1f3fb", - "shortnames": [":person_with_blond_hair_tone1:"], - "category": "people" - }, - ":blond_haired_person_tone2:": { - "uc_base": "1f471-1f3fc", - "uc_output": "1f471-1f3fc", - "uc_match": "1f471-1f3fc", - "uc_greedy": "1f471-1f3fc", - "shortnames": [":person_with_blond_hair_tone2:"], - "category": "people" - }, - ":blond_haired_person_tone3:": { - "uc_base": "1f471-1f3fd", - "uc_output": "1f471-1f3fd", - "uc_match": "1f471-1f3fd", - "uc_greedy": "1f471-1f3fd", - "shortnames": [":person_with_blond_hair_tone3:"], - "category": "people" - }, - ":blond_haired_person_tone4:": { - "uc_base": "1f471-1f3fe", - "uc_output": "1f471-1f3fe", - "uc_match": "1f471-1f3fe", - "uc_greedy": "1f471-1f3fe", - "shortnames": [":person_with_blond_hair_tone4:"], - "category": "people" - }, - ":blond_haired_person_tone5:": { - "uc_base": "1f471-1f3ff", - "uc_output": "1f471-1f3ff", - "uc_match": "1f471-1f3ff", - "uc_greedy": "1f471-1f3ff", - "shortnames": [":person_with_blond_hair_tone5:"], - "category": "people" - }, - ":boy_tone1:": { - "uc_base": "1f466-1f3fb", - "uc_output": "1f466-1f3fb", - "uc_match": "1f466-1f3fb", - "uc_greedy": "1f466-1f3fb", - "shortnames": [], - "category": "people" - }, - ":boy_tone2:": { - "uc_base": "1f466-1f3fc", - "uc_output": "1f466-1f3fc", - "uc_match": "1f466-1f3fc", - "uc_greedy": "1f466-1f3fc", - "shortnames": [], - "category": "people" - }, - ":boy_tone3:": { - "uc_base": "1f466-1f3fd", - "uc_output": "1f466-1f3fd", - "uc_match": "1f466-1f3fd", - "uc_greedy": "1f466-1f3fd", - "shortnames": [], - "category": "people" - }, - ":boy_tone4:": { - "uc_base": "1f466-1f3fe", - "uc_output": "1f466-1f3fe", - "uc_match": "1f466-1f3fe", - "uc_greedy": "1f466-1f3fe", - "shortnames": [], - "category": "people" - }, - ":boy_tone5:": { - "uc_base": "1f466-1f3ff", - "uc_output": "1f466-1f3ff", - "uc_match": "1f466-1f3ff", - "uc_greedy": "1f466-1f3ff", - "shortnames": [], - "category": "people" - }, - ":breast_feeding_tone1:": { - "uc_base": "1f931-1f3fb", - "uc_output": "1f931-1f3fb", - "uc_match": "1f931-1f3fb", - "uc_greedy": "1f931-1f3fb", - "shortnames": [":breast_feeding_light_skin_tone:"], - "category": "people" - }, - ":breast_feeding_tone2:": { - "uc_base": "1f931-1f3fc", - "uc_output": "1f931-1f3fc", - "uc_match": "1f931-1f3fc", - "uc_greedy": "1f931-1f3fc", - "shortnames": [":breast_feeding_medium_light_skin_tone:"], - "category": "people" - }, - ":breast_feeding_tone3:": { - "uc_base": "1f931-1f3fd", - "uc_output": "1f931-1f3fd", - "uc_match": "1f931-1f3fd", - "uc_greedy": "1f931-1f3fd", - "shortnames": [":breast_feeding_medium_skin_tone:"], - "category": "people" - }, - ":breast_feeding_tone4:": { - "uc_base": "1f931-1f3fe", - "uc_output": "1f931-1f3fe", - "uc_match": "1f931-1f3fe", - "uc_greedy": "1f931-1f3fe", - "shortnames": [":breast_feeding_medium_dark_skin_tone:"], - "category": "people" - }, - ":breast_feeding_tone5:": { - "uc_base": "1f931-1f3ff", - "uc_output": "1f931-1f3ff", - "uc_match": "1f931-1f3ff", - "uc_greedy": "1f931-1f3ff", - "shortnames": [":breast_feeding_dark_skin_tone:"], - "category": "people" - }, - ":bride_with_veil_tone1:": { - "uc_base": "1f470-1f3fb", - "uc_output": "1f470-1f3fb", - "uc_match": "1f470-1f3fb", - "uc_greedy": "1f470-1f3fb", - "shortnames": [], - "category": "people" - }, - ":bride_with_veil_tone2:": { - "uc_base": "1f470-1f3fc", - "uc_output": "1f470-1f3fc", - "uc_match": "1f470-1f3fc", - "uc_greedy": "1f470-1f3fc", - "shortnames": [], - "category": "people" - }, - ":bride_with_veil_tone3:": { - "uc_base": "1f470-1f3fd", - "uc_output": "1f470-1f3fd", - "uc_match": "1f470-1f3fd", - "uc_greedy": "1f470-1f3fd", - "shortnames": [], - "category": "people" - }, - ":bride_with_veil_tone4:": { - "uc_base": "1f470-1f3fe", - "uc_output": "1f470-1f3fe", - "uc_match": "1f470-1f3fe", - "uc_greedy": "1f470-1f3fe", - "shortnames": [], - "category": "people" - }, - ":bride_with_veil_tone5:": { - "uc_base": "1f470-1f3ff", - "uc_output": "1f470-1f3ff", - "uc_match": "1f470-1f3ff", - "uc_greedy": "1f470-1f3ff", - "shortnames": [], - "category": "people" - }, - ":call_me_tone1:": { - "uc_base": "1f919-1f3fb", - "uc_output": "1f919-1f3fb", - "uc_match": "1f919-1f3fb", - "uc_greedy": "1f919-1f3fb", - "shortnames": [":call_me_hand_tone1:"], - "category": "people" - }, - ":call_me_tone2:": { - "uc_base": "1f919-1f3fc", - "uc_output": "1f919-1f3fc", - "uc_match": "1f919-1f3fc", - "uc_greedy": "1f919-1f3fc", - "shortnames": [":call_me_hand_tone2:"], - "category": "people" - }, - ":call_me_tone3:": { - "uc_base": "1f919-1f3fd", - "uc_output": "1f919-1f3fd", - "uc_match": "1f919-1f3fd", - "uc_greedy": "1f919-1f3fd", - "shortnames": [":call_me_hand_tone3:"], - "category": "people" - }, - ":call_me_tone4:": { - "uc_base": "1f919-1f3fe", - "uc_output": "1f919-1f3fe", - "uc_match": "1f919-1f3fe", - "uc_greedy": "1f919-1f3fe", - "shortnames": [":call_me_hand_tone4:"], - "category": "people" - }, - ":call_me_tone5:": { - "uc_base": "1f919-1f3ff", - "uc_output": "1f919-1f3ff", - "uc_match": "1f919-1f3ff", - "uc_greedy": "1f919-1f3ff", - "shortnames": [":call_me_hand_tone5:"], - "category": "people" - }, - ":child_tone1:": { - "uc_base": "1f9d2-1f3fb", - "uc_output": "1f9d2-1f3fb", - "uc_match": "1f9d2-1f3fb", - "uc_greedy": "1f9d2-1f3fb", - "shortnames": [":child_light_skin_tone:"], - "category": "people" - }, - ":child_tone2:": { - "uc_base": "1f9d2-1f3fc", - "uc_output": "1f9d2-1f3fc", - "uc_match": "1f9d2-1f3fc", - "uc_greedy": "1f9d2-1f3fc", - "shortnames": [":child_medium_light_skin_tone:"], - "category": "people" - }, - ":child_tone3:": { - "uc_base": "1f9d2-1f3fd", - "uc_output": "1f9d2-1f3fd", - "uc_match": "1f9d2-1f3fd", - "uc_greedy": "1f9d2-1f3fd", - "shortnames": [":child_medium_skin_tone:"], - "category": "people" - }, - ":child_tone4:": { - "uc_base": "1f9d2-1f3fe", - "uc_output": "1f9d2-1f3fe", - "uc_match": "1f9d2-1f3fe", - "uc_greedy": "1f9d2-1f3fe", - "shortnames": [":child_medium_dark_skin_tone:"], - "category": "people" - }, - ":child_tone5:": { - "uc_base": "1f9d2-1f3ff", - "uc_output": "1f9d2-1f3ff", - "uc_match": "1f9d2-1f3ff", - "uc_greedy": "1f9d2-1f3ff", - "shortnames": [":child_dark_skin_tone:"], - "category": "people" - }, - ":clap_tone1:": { - "uc_base": "1f44f-1f3fb", - "uc_output": "1f44f-1f3fb", - "uc_match": "1f44f-1f3fb", - "uc_greedy": "1f44f-1f3fb", - "shortnames": [], - "category": "people" - }, - ":clap_tone2:": { - "uc_base": "1f44f-1f3fc", - "uc_output": "1f44f-1f3fc", - "uc_match": "1f44f-1f3fc", - "uc_greedy": "1f44f-1f3fc", - "shortnames": [], - "category": "people" - }, - ":clap_tone3:": { - "uc_base": "1f44f-1f3fd", - "uc_output": "1f44f-1f3fd", - "uc_match": "1f44f-1f3fd", - "uc_greedy": "1f44f-1f3fd", - "shortnames": [], - "category": "people" - }, - ":clap_tone4:": { - "uc_base": "1f44f-1f3fe", - "uc_output": "1f44f-1f3fe", - "uc_match": "1f44f-1f3fe", - "uc_greedy": "1f44f-1f3fe", - "shortnames": [], - "category": "people" - }, - ":clap_tone5:": { - "uc_base": "1f44f-1f3ff", - "uc_output": "1f44f-1f3ff", - "uc_match": "1f44f-1f3ff", - "uc_greedy": "1f44f-1f3ff", - "shortnames": [], - "category": "people" - }, - ":construction_worker_tone1:": { - "uc_base": "1f477-1f3fb", - "uc_output": "1f477-1f3fb", - "uc_match": "1f477-1f3fb", - "uc_greedy": "1f477-1f3fb", - "shortnames": [], - "category": "people" - }, - ":construction_worker_tone2:": { - "uc_base": "1f477-1f3fc", - "uc_output": "1f477-1f3fc", - "uc_match": "1f477-1f3fc", - "uc_greedy": "1f477-1f3fc", - "shortnames": [], - "category": "people" - }, - ":construction_worker_tone3:": { - "uc_base": "1f477-1f3fd", - "uc_output": "1f477-1f3fd", - "uc_match": "1f477-1f3fd", - "uc_greedy": "1f477-1f3fd", - "shortnames": [], - "category": "people" - }, - ":construction_worker_tone4:": { - "uc_base": "1f477-1f3fe", - "uc_output": "1f477-1f3fe", - "uc_match": "1f477-1f3fe", - "uc_greedy": "1f477-1f3fe", - "shortnames": [], - "category": "people" - }, - ":construction_worker_tone5:": { - "uc_base": "1f477-1f3ff", - "uc_output": "1f477-1f3ff", - "uc_match": "1f477-1f3ff", - "uc_greedy": "1f477-1f3ff", - "shortnames": [], - "category": "people" - }, - ":dancer_tone1:": { - "uc_base": "1f483-1f3fb", - "uc_output": "1f483-1f3fb", - "uc_match": "1f483-1f3fb", - "uc_greedy": "1f483-1f3fb", - "shortnames": [], - "category": "people" - }, - ":dancer_tone2:": { - "uc_base": "1f483-1f3fc", - "uc_output": "1f483-1f3fc", - "uc_match": "1f483-1f3fc", - "uc_greedy": "1f483-1f3fc", - "shortnames": [], - "category": "people" - }, - ":dancer_tone3:": { - "uc_base": "1f483-1f3fd", - "uc_output": "1f483-1f3fd", - "uc_match": "1f483-1f3fd", - "uc_greedy": "1f483-1f3fd", - "shortnames": [], - "category": "people" - }, - ":dancer_tone4:": { - "uc_base": "1f483-1f3fe", - "uc_output": "1f483-1f3fe", - "uc_match": "1f483-1f3fe", - "uc_greedy": "1f483-1f3fe", - "shortnames": [], - "category": "people" - }, - ":dancer_tone5:": { - "uc_base": "1f483-1f3ff", - "uc_output": "1f483-1f3ff", - "uc_match": "1f483-1f3ff", - "uc_greedy": "1f483-1f3ff", - "shortnames": [], - "category": "people" - }, - ":detective_tone1:": { - "uc_base": "1f575-1f3fb", - "uc_output": "1f575-1f3fb", - "uc_match": "1f575-fe0f-1f3fb", - "uc_greedy": "1f575-fe0f-1f3fb", - "shortnames": [":spy_tone1:", ":sleuth_or_spy_tone1:"], - "category": "people" - }, - ":detective_tone2:": { - "uc_base": "1f575-1f3fc", - "uc_output": "1f575-1f3fc", - "uc_match": "1f575-fe0f-1f3fc", - "uc_greedy": "1f575-fe0f-1f3fc", - "shortnames": [":spy_tone2:", ":sleuth_or_spy_tone2:"], - "category": "people" - }, - ":detective_tone3:": { - "uc_base": "1f575-1f3fd", - "uc_output": "1f575-1f3fd", - "uc_match": "1f575-fe0f-1f3fd", - "uc_greedy": "1f575-fe0f-1f3fd", - "shortnames": [":spy_tone3:", ":sleuth_or_spy_tone3:"], - "category": "people" - }, - ":detective_tone4:": { - "uc_base": "1f575-1f3fe", - "uc_output": "1f575-1f3fe", - "uc_match": "1f575-fe0f-1f3fe", - "uc_greedy": "1f575-fe0f-1f3fe", - "shortnames": [":spy_tone4:", ":sleuth_or_spy_tone4:"], - "category": "people" - }, - ":detective_tone5:": { - "uc_base": "1f575-1f3ff", - "uc_output": "1f575-1f3ff", - "uc_match": "1f575-fe0f-1f3ff", - "uc_greedy": "1f575-fe0f-1f3ff", - "shortnames": [":spy_tone5:", ":sleuth_or_spy_tone5:"], - "category": "people" - }, - ":ear_tone1:": { - "uc_base": "1f442-1f3fb", - "uc_output": "1f442-1f3fb", - "uc_match": "1f442-1f3fb", - "uc_greedy": "1f442-1f3fb", - "shortnames": [], - "category": "people" - }, - ":ear_tone2:": { - "uc_base": "1f442-1f3fc", - "uc_output": "1f442-1f3fc", - "uc_match": "1f442-1f3fc", - "uc_greedy": "1f442-1f3fc", - "shortnames": [], - "category": "people" - }, - ":ear_tone3:": { - "uc_base": "1f442-1f3fd", - "uc_output": "1f442-1f3fd", - "uc_match": "1f442-1f3fd", - "uc_greedy": "1f442-1f3fd", - "shortnames": [], - "category": "people" - }, - ":ear_tone4:": { - "uc_base": "1f442-1f3fe", - "uc_output": "1f442-1f3fe", - "uc_match": "1f442-1f3fe", - "uc_greedy": "1f442-1f3fe", - "shortnames": [], - "category": "people" - }, - ":ear_tone5:": { - "uc_base": "1f442-1f3ff", - "uc_output": "1f442-1f3ff", - "uc_match": "1f442-1f3ff", - "uc_greedy": "1f442-1f3ff", - "shortnames": [], - "category": "people" - }, - ":elf_tone1:": { - "uc_base": "1f9dd-1f3fb", - "uc_output": "1f9dd-1f3fb", - "uc_match": "1f9dd-1f3fb", - "uc_greedy": "1f9dd-1f3fb", - "shortnames": [":elf_light_skin_tone:"], - "category": "people" - }, - ":elf_tone2:": { - "uc_base": "1f9dd-1f3fc", - "uc_output": "1f9dd-1f3fc", - "uc_match": "1f9dd-1f3fc", - "uc_greedy": "1f9dd-1f3fc", - "shortnames": [":elf_medium_light_skin_tone:"], - "category": "people" - }, - ":elf_tone3:": { - "uc_base": "1f9dd-1f3fd", - "uc_output": "1f9dd-1f3fd", - "uc_match": "1f9dd-1f3fd", - "uc_greedy": "1f9dd-1f3fd", - "shortnames": [":elf_medium_skin_tone:"], - "category": "people" - }, - ":elf_tone4:": { - "uc_base": "1f9dd-1f3fe", - "uc_output": "1f9dd-1f3fe", - "uc_match": "1f9dd-1f3fe", - "uc_greedy": "1f9dd-1f3fe", - "shortnames": [":elf_medium_dark_skin_tone:"], - "category": "people" - }, - ":elf_tone5:": { - "uc_base": "1f9dd-1f3ff", - "uc_output": "1f9dd-1f3ff", - "uc_match": "1f9dd-1f3ff", - "uc_greedy": "1f9dd-1f3ff", - "shortnames": [":elf_dark_skin_tone:"], - "category": "people" - }, - ":fairy_tone1:": { - "uc_base": "1f9da-1f3fb", - "uc_output": "1f9da-1f3fb", - "uc_match": "1f9da-1f3fb", - "uc_greedy": "1f9da-1f3fb", - "shortnames": [":fairy_light_skin_tone:"], - "category": "people" - }, - ":fairy_tone2:": { - "uc_base": "1f9da-1f3fc", - "uc_output": "1f9da-1f3fc", - "uc_match": "1f9da-1f3fc", - "uc_greedy": "1f9da-1f3fc", - "shortnames": [":fairy_medium_light_skin_tone:"], - "category": "people" - }, - ":fairy_tone3:": { - "uc_base": "1f9da-1f3fd", - "uc_output": "1f9da-1f3fd", - "uc_match": "1f9da-1f3fd", - "uc_greedy": "1f9da-1f3fd", - "shortnames": [":fairy_medium_skin_tone:"], - "category": "people" - }, - ":fairy_tone4:": { - "uc_base": "1f9da-1f3fe", - "uc_output": "1f9da-1f3fe", - "uc_match": "1f9da-1f3fe", - "uc_greedy": "1f9da-1f3fe", - "shortnames": [":fairy_medium_dark_skin_tone:"], - "category": "people" - }, - ":fairy_tone5:": { - "uc_base": "1f9da-1f3ff", - "uc_output": "1f9da-1f3ff", - "uc_match": "1f9da-1f3ff", - "uc_greedy": "1f9da-1f3ff", - "shortnames": [":fairy_dark_skin_tone:"], - "category": "people" - }, - ":fingers_crossed_tone1:": { - "uc_base": "1f91e-1f3fb", - "uc_output": "1f91e-1f3fb", - "uc_match": "1f91e-1f3fb", - "uc_greedy": "1f91e-1f3fb", - "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone1:"], - "category": "people" - }, - ":fingers_crossed_tone2:": { - "uc_base": "1f91e-1f3fc", - "uc_output": "1f91e-1f3fc", - "uc_match": "1f91e-1f3fc", - "uc_greedy": "1f91e-1f3fc", - "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone2:"], - "category": "people" - }, - ":fingers_crossed_tone3:": { - "uc_base": "1f91e-1f3fd", - "uc_output": "1f91e-1f3fd", - "uc_match": "1f91e-1f3fd", - "uc_greedy": "1f91e-1f3fd", - "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone3:"], - "category": "people" - }, - ":fingers_crossed_tone4:": { - "uc_base": "1f91e-1f3fe", - "uc_output": "1f91e-1f3fe", - "uc_match": "1f91e-1f3fe", - "uc_greedy": "1f91e-1f3fe", - "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone4:"], - "category": "people" - }, - ":fingers_crossed_tone5:": { - "uc_base": "1f91e-1f3ff", - "uc_output": "1f91e-1f3ff", - "uc_match": "1f91e-1f3ff", - "uc_greedy": "1f91e-1f3ff", - "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone5:"], - "category": "people" - }, - ":flag_ac:": { - "uc_base": "1f1e6-1f1e8", - "uc_output": "1f1e6-1f1e8", - "uc_match": "1f1e6-1f1e8", - "uc_greedy": "1f1e6-1f1e8", - "shortnames": [":ac:"], - "category": "flags" - }, - ":flag_ad:": { - "uc_base": "1f1e6-1f1e9", - "uc_output": "1f1e6-1f1e9", - "uc_match": "1f1e6-1f1e9", - "uc_greedy": "1f1e6-1f1e9", - "shortnames": [":ad:"], - "category": "flags" - }, - ":flag_ae:": { - "uc_base": "1f1e6-1f1ea", - "uc_output": "1f1e6-1f1ea", - "uc_match": "1f1e6-1f1ea", - "uc_greedy": "1f1e6-1f1ea", - "shortnames": [":ae:"], - "category": "flags" - }, - ":flag_af:": { - "uc_base": "1f1e6-1f1eb", - "uc_output": "1f1e6-1f1eb", - "uc_match": "1f1e6-1f1eb", - "uc_greedy": "1f1e6-1f1eb", - "shortnames": [":af:"], - "category": "flags" - }, - ":flag_ag:": { - "uc_base": "1f1e6-1f1ec", - "uc_output": "1f1e6-1f1ec", - "uc_match": "1f1e6-1f1ec", - "uc_greedy": "1f1e6-1f1ec", - "shortnames": [":ag:"], - "category": "flags" - }, - ":flag_ai:": { - "uc_base": "1f1e6-1f1ee", - "uc_output": "1f1e6-1f1ee", - "uc_match": "1f1e6-1f1ee", - "uc_greedy": "1f1e6-1f1ee", - "shortnames": [":ai:"], - "category": "flags" - }, - ":flag_al:": { - "uc_base": "1f1e6-1f1f1", - "uc_output": "1f1e6-1f1f1", - "uc_match": "1f1e6-1f1f1", - "uc_greedy": "1f1e6-1f1f1", - "shortnames": [":al:"], - "category": "flags" - }, - ":flag_am:": { - "uc_base": "1f1e6-1f1f2", - "uc_output": "1f1e6-1f1f2", - "uc_match": "1f1e6-1f1f2", - "uc_greedy": "1f1e6-1f1f2", - "shortnames": [":am:"], - "category": "flags" - }, - ":flag_ao:": { - "uc_base": "1f1e6-1f1f4", - "uc_output": "1f1e6-1f1f4", - "uc_match": "1f1e6-1f1f4", - "uc_greedy": "1f1e6-1f1f4", - "shortnames": [":ao:"], - "category": "flags" - }, - ":flag_aq:": { - "uc_base": "1f1e6-1f1f6", - "uc_output": "1f1e6-1f1f6", - "uc_match": "1f1e6-1f1f6", - "uc_greedy": "1f1e6-1f1f6", - "shortnames": [":aq:"], - "category": "flags" - }, - ":flag_ar:": { - "uc_base": "1f1e6-1f1f7", - "uc_output": "1f1e6-1f1f7", - "uc_match": "1f1e6-1f1f7", - "uc_greedy": "1f1e6-1f1f7", - "shortnames": [":ar:"], - "category": "flags" - }, - ":flag_as:": { - "uc_base": "1f1e6-1f1f8", - "uc_output": "1f1e6-1f1f8", - "uc_match": "1f1e6-1f1f8", - "uc_greedy": "1f1e6-1f1f8", - "shortnames": [":as:"], - "category": "flags" - }, - ":flag_at:": { - "uc_base": "1f1e6-1f1f9", - "uc_output": "1f1e6-1f1f9", - "uc_match": "1f1e6-1f1f9", - "uc_greedy": "1f1e6-1f1f9", - "shortnames": [":at:"], - "category": "flags" - }, - ":flag_au:": { - "uc_base": "1f1e6-1f1fa", - "uc_output": "1f1e6-1f1fa", - "uc_match": "1f1e6-1f1fa", - "uc_greedy": "1f1e6-1f1fa", - "shortnames": [":au:"], - "category": "flags" - }, - ":flag_aw:": { - "uc_base": "1f1e6-1f1fc", - "uc_output": "1f1e6-1f1fc", - "uc_match": "1f1e6-1f1fc", - "uc_greedy": "1f1e6-1f1fc", - "shortnames": [":aw:"], - "category": "flags" - }, - ":flag_ax:": { - "uc_base": "1f1e6-1f1fd", - "uc_output": "1f1e6-1f1fd", - "uc_match": "1f1e6-1f1fd", - "uc_greedy": "1f1e6-1f1fd", - "shortnames": [":ax:"], - "category": "flags" - }, - ":flag_az:": { - "uc_base": "1f1e6-1f1ff", - "uc_output": "1f1e6-1f1ff", - "uc_match": "1f1e6-1f1ff", - "uc_greedy": "1f1e6-1f1ff", - "shortnames": [":az:"], - "category": "flags" - }, - ":flag_ba:": { - "uc_base": "1f1e7-1f1e6", - "uc_output": "1f1e7-1f1e6", - "uc_match": "1f1e7-1f1e6", - "uc_greedy": "1f1e7-1f1e6", - "shortnames": [":ba:"], - "category": "flags" - }, - ":flag_bb:": { - "uc_base": "1f1e7-1f1e7", - "uc_output": "1f1e7-1f1e7", - "uc_match": "1f1e7-1f1e7", - "uc_greedy": "1f1e7-1f1e7", - "shortnames": [":bb:"], - "category": "flags" - }, - ":flag_bd:": { - "uc_base": "1f1e7-1f1e9", - "uc_output": "1f1e7-1f1e9", - "uc_match": "1f1e7-1f1e9", - "uc_greedy": "1f1e7-1f1e9", - "shortnames": [":bd:"], - "category": "flags" - }, - ":flag_be:": { - "uc_base": "1f1e7-1f1ea", - "uc_output": "1f1e7-1f1ea", - "uc_match": "1f1e7-1f1ea", - "uc_greedy": "1f1e7-1f1ea", - "shortnames": [":be:"], - "category": "flags" - }, - ":flag_bf:": { - "uc_base": "1f1e7-1f1eb", - "uc_output": "1f1e7-1f1eb", - "uc_match": "1f1e7-1f1eb", - "uc_greedy": "1f1e7-1f1eb", - "shortnames": [":bf:"], - "category": "flags" - }, - ":flag_bg:": { - "uc_base": "1f1e7-1f1ec", - "uc_output": "1f1e7-1f1ec", - "uc_match": "1f1e7-1f1ec", - "uc_greedy": "1f1e7-1f1ec", - "shortnames": [":bg:"], - "category": "flags" - }, - ":flag_bh:": { - "uc_base": "1f1e7-1f1ed", - "uc_output": "1f1e7-1f1ed", - "uc_match": "1f1e7-1f1ed", - "uc_greedy": "1f1e7-1f1ed", - "shortnames": [":bh:"], - "category": "flags" - }, - ":flag_bi:": { - "uc_base": "1f1e7-1f1ee", - "uc_output": "1f1e7-1f1ee", - "uc_match": "1f1e7-1f1ee", - "uc_greedy": "1f1e7-1f1ee", - "shortnames": [":bi:"], - "category": "flags" - }, - ":flag_bj:": { - "uc_base": "1f1e7-1f1ef", - "uc_output": "1f1e7-1f1ef", - "uc_match": "1f1e7-1f1ef", - "uc_greedy": "1f1e7-1f1ef", - "shortnames": [":bj:"], - "category": "flags" - }, - ":flag_bl:": { - "uc_base": "1f1e7-1f1f1", - "uc_output": "1f1e7-1f1f1", - "uc_match": "1f1e7-1f1f1", - "uc_greedy": "1f1e7-1f1f1", - "shortnames": [":bl:"], - "category": "flags" - }, - ":flag_bm:": { - "uc_base": "1f1e7-1f1f2", - "uc_output": "1f1e7-1f1f2", - "uc_match": "1f1e7-1f1f2", - "uc_greedy": "1f1e7-1f1f2", - "shortnames": [":bm:"], - "category": "flags" - }, - ":flag_bn:": { - "uc_base": "1f1e7-1f1f3", - "uc_output": "1f1e7-1f1f3", - "uc_match": "1f1e7-1f1f3", - "uc_greedy": "1f1e7-1f1f3", - "shortnames": [":bn:"], - "category": "flags" - }, - ":flag_bo:": { - "uc_base": "1f1e7-1f1f4", - "uc_output": "1f1e7-1f1f4", - "uc_match": "1f1e7-1f1f4", - "uc_greedy": "1f1e7-1f1f4", - "shortnames": [":bo:"], - "category": "flags" - }, - ":flag_bq:": { - "uc_base": "1f1e7-1f1f6", - "uc_output": "1f1e7-1f1f6", - "uc_match": "1f1e7-1f1f6", - "uc_greedy": "1f1e7-1f1f6", - "shortnames": [":bq:"], - "category": "flags" - }, - ":flag_br:": { - "uc_base": "1f1e7-1f1f7", - "uc_output": "1f1e7-1f1f7", - "uc_match": "1f1e7-1f1f7", - "uc_greedy": "1f1e7-1f1f7", - "shortnames": [":br:"], - "category": "flags" - }, - ":flag_bs:": { - "uc_base": "1f1e7-1f1f8", - "uc_output": "1f1e7-1f1f8", - "uc_match": "1f1e7-1f1f8", - "uc_greedy": "1f1e7-1f1f8", - "shortnames": [":bs:"], - "category": "flags" - }, - ":flag_bt:": { - "uc_base": "1f1e7-1f1f9", - "uc_output": "1f1e7-1f1f9", - "uc_match": "1f1e7-1f1f9", - "uc_greedy": "1f1e7-1f1f9", - "shortnames": [":bt:"], - "category": "flags" - }, - ":flag_bv:": { - "uc_base": "1f1e7-1f1fb", - "uc_output": "1f1e7-1f1fb", - "uc_match": "1f1e7-1f1fb", - "uc_greedy": "1f1e7-1f1fb", - "shortnames": [":bv:"], - "category": "flags" - }, - ":flag_bw:": { - "uc_base": "1f1e7-1f1fc", - "uc_output": "1f1e7-1f1fc", - "uc_match": "1f1e7-1f1fc", - "uc_greedy": "1f1e7-1f1fc", - "shortnames": [":bw:"], - "category": "flags" - }, - ":flag_by:": { - "uc_base": "1f1e7-1f1fe", - "uc_output": "1f1e7-1f1fe", - "uc_match": "1f1e7-1f1fe", - "uc_greedy": "1f1e7-1f1fe", - "shortnames": [":by:"], - "category": "flags" - }, - ":flag_bz:": { - "uc_base": "1f1e7-1f1ff", - "uc_output": "1f1e7-1f1ff", - "uc_match": "1f1e7-1f1ff", - "uc_greedy": "1f1e7-1f1ff", - "shortnames": [":bz:"], - "category": "flags" - }, - ":flag_ca:": { - "uc_base": "1f1e8-1f1e6", - "uc_output": "1f1e8-1f1e6", - "uc_match": "1f1e8-1f1e6", - "uc_greedy": "1f1e8-1f1e6", - "shortnames": [":ca:"], - "category": "flags" - }, - ":flag_cc:": { - "uc_base": "1f1e8-1f1e8", - "uc_output": "1f1e8-1f1e8", - "uc_match": "1f1e8-1f1e8", - "uc_greedy": "1f1e8-1f1e8", - "shortnames": [":cc:"], - "category": "flags" - }, - ":flag_cd:": { - "uc_base": "1f1e8-1f1e9", - "uc_output": "1f1e8-1f1e9", - "uc_match": "1f1e8-1f1e9", - "uc_greedy": "1f1e8-1f1e9", - "shortnames": [":congo:"], - "category": "flags" - }, - ":flag_cf:": { - "uc_base": "1f1e8-1f1eb", - "uc_output": "1f1e8-1f1eb", - "uc_match": "1f1e8-1f1eb", - "uc_greedy": "1f1e8-1f1eb", - "shortnames": [":cf:"], - "category": "flags" - }, - ":flag_cg:": { - "uc_base": "1f1e8-1f1ec", - "uc_output": "1f1e8-1f1ec", - "uc_match": "1f1e8-1f1ec", - "uc_greedy": "1f1e8-1f1ec", - "shortnames": [":cg:"], - "category": "flags" - }, - ":flag_ch:": { - "uc_base": "1f1e8-1f1ed", - "uc_output": "1f1e8-1f1ed", - "uc_match": "1f1e8-1f1ed", - "uc_greedy": "1f1e8-1f1ed", - "shortnames": [":ch:"], - "category": "flags" - }, - ":flag_ci:": { - "uc_base": "1f1e8-1f1ee", - "uc_output": "1f1e8-1f1ee", - "uc_match": "1f1e8-1f1ee", - "uc_greedy": "1f1e8-1f1ee", - "shortnames": [":ci:"], - "category": "flags" - }, - ":flag_ck:": { - "uc_base": "1f1e8-1f1f0", - "uc_output": "1f1e8-1f1f0", - "uc_match": "1f1e8-1f1f0", - "uc_greedy": "1f1e8-1f1f0", - "shortnames": [":ck:"], - "category": "flags" - }, - ":flag_cl:": { - "uc_base": "1f1e8-1f1f1", - "uc_output": "1f1e8-1f1f1", - "uc_match": "1f1e8-1f1f1", - "uc_greedy": "1f1e8-1f1f1", - "shortnames": [":chile:"], - "category": "flags" - }, - ":flag_cm:": { - "uc_base": "1f1e8-1f1f2", - "uc_output": "1f1e8-1f1f2", - "uc_match": "1f1e8-1f1f2", - "uc_greedy": "1f1e8-1f1f2", - "shortnames": [":cm:"], - "category": "flags" - }, - ":flag_cn:": { - "uc_base": "1f1e8-1f1f3", - "uc_output": "1f1e8-1f1f3", - "uc_match": "1f1e8-1f1f3", - "uc_greedy": "1f1e8-1f1f3", - "shortnames": [":cn:"], - "category": "flags" - }, - ":flag_co:": { - "uc_base": "1f1e8-1f1f4", - "uc_output": "1f1e8-1f1f4", - "uc_match": "1f1e8-1f1f4", - "uc_greedy": "1f1e8-1f1f4", - "shortnames": [":co:"], - "category": "flags" - }, - ":flag_cp:": { - "uc_base": "1f1e8-1f1f5", - "uc_output": "1f1e8-1f1f5", - "uc_match": "1f1e8-1f1f5", - "uc_greedy": "1f1e8-1f1f5", - "shortnames": [":cp:"], - "category": "flags" - }, - ":flag_cr:": { - "uc_base": "1f1e8-1f1f7", - "uc_output": "1f1e8-1f1f7", - "uc_match": "1f1e8-1f1f7", - "uc_greedy": "1f1e8-1f1f7", - "shortnames": [":cr:"], - "category": "flags" - }, - ":flag_cu:": { - "uc_base": "1f1e8-1f1fa", - "uc_output": "1f1e8-1f1fa", - "uc_match": "1f1e8-1f1fa", - "uc_greedy": "1f1e8-1f1fa", - "shortnames": [":cu:"], - "category": "flags" - }, - ":flag_cv:": { - "uc_base": "1f1e8-1f1fb", - "uc_output": "1f1e8-1f1fb", - "uc_match": "1f1e8-1f1fb", - "uc_greedy": "1f1e8-1f1fb", - "shortnames": [":cv:"], - "category": "flags" - }, - ":flag_cw:": { - "uc_base": "1f1e8-1f1fc", - "uc_output": "1f1e8-1f1fc", - "uc_match": "1f1e8-1f1fc", - "uc_greedy": "1f1e8-1f1fc", - "shortnames": [":cw:"], - "category": "flags" - }, - ":flag_cx:": { - "uc_base": "1f1e8-1f1fd", - "uc_output": "1f1e8-1f1fd", - "uc_match": "1f1e8-1f1fd", - "uc_greedy": "1f1e8-1f1fd", - "shortnames": [":cx:"], - "category": "flags" - }, - ":flag_cy:": { - "uc_base": "1f1e8-1f1fe", - "uc_output": "1f1e8-1f1fe", - "uc_match": "1f1e8-1f1fe", - "uc_greedy": "1f1e8-1f1fe", - "shortnames": [":cy:"], - "category": "flags" - }, - ":flag_cz:": { - "uc_base": "1f1e8-1f1ff", - "uc_output": "1f1e8-1f1ff", - "uc_match": "1f1e8-1f1ff", - "uc_greedy": "1f1e8-1f1ff", - "shortnames": [":cz:"], - "category": "flags" - }, - ":flag_de:": { - "uc_base": "1f1e9-1f1ea", - "uc_output": "1f1e9-1f1ea", - "uc_match": "1f1e9-1f1ea", - "uc_greedy": "1f1e9-1f1ea", - "shortnames": [":de:"], - "category": "flags" - }, - ":flag_dg:": { - "uc_base": "1f1e9-1f1ec", - "uc_output": "1f1e9-1f1ec", - "uc_match": "1f1e9-1f1ec", - "uc_greedy": "1f1e9-1f1ec", - "shortnames": [":dg:"], - "category": "flags" - }, - ":flag_dj:": { - "uc_base": "1f1e9-1f1ef", - "uc_output": "1f1e9-1f1ef", - "uc_match": "1f1e9-1f1ef", - "uc_greedy": "1f1e9-1f1ef", - "shortnames": [":dj:"], - "category": "flags" - }, - ":flag_dk:": { - "uc_base": "1f1e9-1f1f0", - "uc_output": "1f1e9-1f1f0", - "uc_match": "1f1e9-1f1f0", - "uc_greedy": "1f1e9-1f1f0", - "shortnames": [":dk:"], - "category": "flags" - }, - ":flag_dm:": { - "uc_base": "1f1e9-1f1f2", - "uc_output": "1f1e9-1f1f2", - "uc_match": "1f1e9-1f1f2", - "uc_greedy": "1f1e9-1f1f2", - "shortnames": [":dm:"], - "category": "flags" - }, - ":flag_do:": { - "uc_base": "1f1e9-1f1f4", - "uc_output": "1f1e9-1f1f4", - "uc_match": "1f1e9-1f1f4", - "uc_greedy": "1f1e9-1f1f4", - "shortnames": [":do:"], - "category": "flags" - }, - ":flag_dz:": { - "uc_base": "1f1e9-1f1ff", - "uc_output": "1f1e9-1f1ff", - "uc_match": "1f1e9-1f1ff", - "uc_greedy": "1f1e9-1f1ff", - "shortnames": [":dz:"], - "category": "flags" - }, - ":flag_ea:": { - "uc_base": "1f1ea-1f1e6", - "uc_output": "1f1ea-1f1e6", - "uc_match": "1f1ea-1f1e6", - "uc_greedy": "1f1ea-1f1e6", - "shortnames": [":ea:"], - "category": "flags" - }, - ":flag_ec:": { - "uc_base": "1f1ea-1f1e8", - "uc_output": "1f1ea-1f1e8", - "uc_match": "1f1ea-1f1e8", - "uc_greedy": "1f1ea-1f1e8", - "shortnames": [":ec:"], - "category": "flags" - }, - ":flag_ee:": { - "uc_base": "1f1ea-1f1ea", - "uc_output": "1f1ea-1f1ea", - "uc_match": "1f1ea-1f1ea", - "uc_greedy": "1f1ea-1f1ea", - "shortnames": [":ee:"], - "category": "flags" - }, - ":flag_eg:": { - "uc_base": "1f1ea-1f1ec", - "uc_output": "1f1ea-1f1ec", - "uc_match": "1f1ea-1f1ec", - "uc_greedy": "1f1ea-1f1ec", - "shortnames": [":eg:"], - "category": "flags" - }, - ":flag_eh:": { - "uc_base": "1f1ea-1f1ed", - "uc_output": "1f1ea-1f1ed", - "uc_match": "1f1ea-1f1ed", - "uc_greedy": "1f1ea-1f1ed", - "shortnames": [":eh:"], - "category": "flags" - }, - ":flag_er:": { - "uc_base": "1f1ea-1f1f7", - "uc_output": "1f1ea-1f1f7", - "uc_match": "1f1ea-1f1f7", - "uc_greedy": "1f1ea-1f1f7", - "shortnames": [":er:"], - "category": "flags" - }, - ":flag_es:": { - "uc_base": "1f1ea-1f1f8", - "uc_output": "1f1ea-1f1f8", - "uc_match": "1f1ea-1f1f8", - "uc_greedy": "1f1ea-1f1f8", - "shortnames": [":es:"], - "category": "flags" - }, - ":flag_et:": { - "uc_base": "1f1ea-1f1f9", - "uc_output": "1f1ea-1f1f9", - "uc_match": "1f1ea-1f1f9", - "uc_greedy": "1f1ea-1f1f9", - "shortnames": [":et:"], - "category": "flags" - }, - ":flag_eu:": { - "uc_base": "1f1ea-1f1fa", - "uc_output": "1f1ea-1f1fa", - "uc_match": "1f1ea-1f1fa", - "uc_greedy": "1f1ea-1f1fa", - "shortnames": [":eu:"], - "category": "flags" - }, - ":flag_fi:": { - "uc_base": "1f1eb-1f1ee", - "uc_output": "1f1eb-1f1ee", - "uc_match": "1f1eb-1f1ee", - "uc_greedy": "1f1eb-1f1ee", - "shortnames": [":fi:"], - "category": "flags" - }, - ":flag_fj:": { - "uc_base": "1f1eb-1f1ef", - "uc_output": "1f1eb-1f1ef", - "uc_match": "1f1eb-1f1ef", - "uc_greedy": "1f1eb-1f1ef", - "shortnames": [":fj:"], - "category": "flags" - }, - ":flag_fk:": { - "uc_base": "1f1eb-1f1f0", - "uc_output": "1f1eb-1f1f0", - "uc_match": "1f1eb-1f1f0", - "uc_greedy": "1f1eb-1f1f0", - "shortnames": [":fk:"], - "category": "flags" - }, - ":flag_fm:": { - "uc_base": "1f1eb-1f1f2", - "uc_output": "1f1eb-1f1f2", - "uc_match": "1f1eb-1f1f2", - "uc_greedy": "1f1eb-1f1f2", - "shortnames": [":fm:"], - "category": "flags" - }, - ":flag_fo:": { - "uc_base": "1f1eb-1f1f4", - "uc_output": "1f1eb-1f1f4", - "uc_match": "1f1eb-1f1f4", - "uc_greedy": "1f1eb-1f1f4", - "shortnames": [":fo:"], - "category": "flags" - }, - ":flag_fr:": { - "uc_base": "1f1eb-1f1f7", - "uc_output": "1f1eb-1f1f7", - "uc_match": "1f1eb-1f1f7", - "uc_greedy": "1f1eb-1f1f7", - "shortnames": [":fr:"], - "category": "flags" - }, - ":flag_ga:": { - "uc_base": "1f1ec-1f1e6", - "uc_output": "1f1ec-1f1e6", - "uc_match": "1f1ec-1f1e6", - "uc_greedy": "1f1ec-1f1e6", - "shortnames": [":ga:"], - "category": "flags" - }, - ":flag_gb:": { - "uc_base": "1f1ec-1f1e7", - "uc_output": "1f1ec-1f1e7", - "uc_match": "1f1ec-1f1e7", - "uc_greedy": "1f1ec-1f1e7", - "shortnames": [":gb:"], - "category": "flags" - }, - ":flag_gd:": { - "uc_base": "1f1ec-1f1e9", - "uc_output": "1f1ec-1f1e9", - "uc_match": "1f1ec-1f1e9", - "uc_greedy": "1f1ec-1f1e9", - "shortnames": [":gd:"], - "category": "flags" - }, - ":flag_ge:": { - "uc_base": "1f1ec-1f1ea", - "uc_output": "1f1ec-1f1ea", - "uc_match": "1f1ec-1f1ea", - "uc_greedy": "1f1ec-1f1ea", - "shortnames": [":ge:"], - "category": "flags" - }, - ":flag_gf:": { - "uc_base": "1f1ec-1f1eb", - "uc_output": "1f1ec-1f1eb", - "uc_match": "1f1ec-1f1eb", - "uc_greedy": "1f1ec-1f1eb", - "shortnames": [":gf:"], - "category": "flags" - }, - ":flag_gg:": { - "uc_base": "1f1ec-1f1ec", - "uc_output": "1f1ec-1f1ec", - "uc_match": "1f1ec-1f1ec", - "uc_greedy": "1f1ec-1f1ec", - "shortnames": [":gg:"], - "category": "flags" - }, - ":flag_gh:": { - "uc_base": "1f1ec-1f1ed", - "uc_output": "1f1ec-1f1ed", - "uc_match": "1f1ec-1f1ed", - "uc_greedy": "1f1ec-1f1ed", - "shortnames": [":gh:"], - "category": "flags" - }, - ":flag_gi:": { - "uc_base": "1f1ec-1f1ee", - "uc_output": "1f1ec-1f1ee", - "uc_match": "1f1ec-1f1ee", - "uc_greedy": "1f1ec-1f1ee", - "shortnames": [":gi:"], - "category": "flags" - }, - ":flag_gl:": { - "uc_base": "1f1ec-1f1f1", - "uc_output": "1f1ec-1f1f1", - "uc_match": "1f1ec-1f1f1", - "uc_greedy": "1f1ec-1f1f1", - "shortnames": [":gl:"], - "category": "flags" - }, - ":flag_gm:": { - "uc_base": "1f1ec-1f1f2", - "uc_output": "1f1ec-1f1f2", - "uc_match": "1f1ec-1f1f2", - "uc_greedy": "1f1ec-1f1f2", - "shortnames": [":gm:"], - "category": "flags" - }, - ":flag_gn:": { - "uc_base": "1f1ec-1f1f3", - "uc_output": "1f1ec-1f1f3", - "uc_match": "1f1ec-1f1f3", - "uc_greedy": "1f1ec-1f1f3", - "shortnames": [":gn:"], - "category": "flags" - }, - ":flag_gp:": { - "uc_base": "1f1ec-1f1f5", - "uc_output": "1f1ec-1f1f5", - "uc_match": "1f1ec-1f1f5", - "uc_greedy": "1f1ec-1f1f5", - "shortnames": [":gp:"], - "category": "flags" - }, - ":flag_gq:": { - "uc_base": "1f1ec-1f1f6", - "uc_output": "1f1ec-1f1f6", - "uc_match": "1f1ec-1f1f6", - "uc_greedy": "1f1ec-1f1f6", - "shortnames": [":gq:"], - "category": "flags" - }, - ":flag_gr:": { - "uc_base": "1f1ec-1f1f7", - "uc_output": "1f1ec-1f1f7", - "uc_match": "1f1ec-1f1f7", - "uc_greedy": "1f1ec-1f1f7", - "shortnames": [":gr:"], - "category": "flags" - }, - ":flag_gs:": { - "uc_base": "1f1ec-1f1f8", - "uc_output": "1f1ec-1f1f8", - "uc_match": "1f1ec-1f1f8", - "uc_greedy": "1f1ec-1f1f8", - "shortnames": [":gs:"], - "category": "flags" - }, - ":flag_gt:": { - "uc_base": "1f1ec-1f1f9", - "uc_output": "1f1ec-1f1f9", - "uc_match": "1f1ec-1f1f9", - "uc_greedy": "1f1ec-1f1f9", - "shortnames": [":gt:"], - "category": "flags" - }, - ":flag_gu:": { - "uc_base": "1f1ec-1f1fa", - "uc_output": "1f1ec-1f1fa", - "uc_match": "1f1ec-1f1fa", - "uc_greedy": "1f1ec-1f1fa", - "shortnames": [":gu:"], - "category": "flags" - }, - ":flag_gw:": { - "uc_base": "1f1ec-1f1fc", - "uc_output": "1f1ec-1f1fc", - "uc_match": "1f1ec-1f1fc", - "uc_greedy": "1f1ec-1f1fc", - "shortnames": [":gw:"], - "category": "flags" - }, - ":flag_gy:": { - "uc_base": "1f1ec-1f1fe", - "uc_output": "1f1ec-1f1fe", - "uc_match": "1f1ec-1f1fe", - "uc_greedy": "1f1ec-1f1fe", - "shortnames": [":gy:"], - "category": "flags" - }, - ":flag_hk:": { - "uc_base": "1f1ed-1f1f0", - "uc_output": "1f1ed-1f1f0", - "uc_match": "1f1ed-1f1f0", - "uc_greedy": "1f1ed-1f1f0", - "shortnames": [":hk:"], - "category": "flags" - }, - ":flag_hm:": { - "uc_base": "1f1ed-1f1f2", - "uc_output": "1f1ed-1f1f2", - "uc_match": "1f1ed-1f1f2", - "uc_greedy": "1f1ed-1f1f2", - "shortnames": [":hm:"], - "category": "flags" - }, - ":flag_hn:": { - "uc_base": "1f1ed-1f1f3", - "uc_output": "1f1ed-1f1f3", - "uc_match": "1f1ed-1f1f3", - "uc_greedy": "1f1ed-1f1f3", - "shortnames": [":hn:"], - "category": "flags" - }, - ":flag_hr:": { - "uc_base": "1f1ed-1f1f7", - "uc_output": "1f1ed-1f1f7", - "uc_match": "1f1ed-1f1f7", - "uc_greedy": "1f1ed-1f1f7", - "shortnames": [":hr:"], - "category": "flags" - }, - ":flag_ht:": { - "uc_base": "1f1ed-1f1f9", - "uc_output": "1f1ed-1f1f9", - "uc_match": "1f1ed-1f1f9", - "uc_greedy": "1f1ed-1f1f9", - "shortnames": [":ht:"], - "category": "flags" - }, - ":flag_hu:": { - "uc_base": "1f1ed-1f1fa", - "uc_output": "1f1ed-1f1fa", - "uc_match": "1f1ed-1f1fa", - "uc_greedy": "1f1ed-1f1fa", - "shortnames": [":hu:"], - "category": "flags" - }, - ":flag_ic:": { - "uc_base": "1f1ee-1f1e8", - "uc_output": "1f1ee-1f1e8", - "uc_match": "1f1ee-1f1e8", - "uc_greedy": "1f1ee-1f1e8", - "shortnames": [":ic:"], - "category": "flags" - }, - ":flag_id:": { - "uc_base": "1f1ee-1f1e9", - "uc_output": "1f1ee-1f1e9", - "uc_match": "1f1ee-1f1e9", - "uc_greedy": "1f1ee-1f1e9", - "shortnames": [":indonesia:"], - "category": "flags" - }, - ":flag_ie:": { - "uc_base": "1f1ee-1f1ea", - "uc_output": "1f1ee-1f1ea", - "uc_match": "1f1ee-1f1ea", - "uc_greedy": "1f1ee-1f1ea", - "shortnames": [":ie:"], - "category": "flags" - }, - ":flag_il:": { - "uc_base": "1f1ee-1f1f1", - "uc_output": "1f1ee-1f1f1", - "uc_match": "1f1ee-1f1f1", - "uc_greedy": "1f1ee-1f1f1", - "shortnames": [":il:"], - "category": "flags" - }, - ":flag_im:": { - "uc_base": "1f1ee-1f1f2", - "uc_output": "1f1ee-1f1f2", - "uc_match": "1f1ee-1f1f2", - "uc_greedy": "1f1ee-1f1f2", - "shortnames": [":im:"], - "category": "flags" - }, - ":flag_in:": { - "uc_base": "1f1ee-1f1f3", - "uc_output": "1f1ee-1f1f3", - "uc_match": "1f1ee-1f1f3", - "uc_greedy": "1f1ee-1f1f3", - "shortnames": [":in:"], - "category": "flags" - }, - ":flag_io:": { - "uc_base": "1f1ee-1f1f4", - "uc_output": "1f1ee-1f1f4", - "uc_match": "1f1ee-1f1f4", - "uc_greedy": "1f1ee-1f1f4", - "shortnames": [":io:"], - "category": "flags" - }, - ":flag_iq:": { - "uc_base": "1f1ee-1f1f6", - "uc_output": "1f1ee-1f1f6", - "uc_match": "1f1ee-1f1f6", - "uc_greedy": "1f1ee-1f1f6", - "shortnames": [":iq:"], - "category": "flags" - }, - ":flag_ir:": { - "uc_base": "1f1ee-1f1f7", - "uc_output": "1f1ee-1f1f7", - "uc_match": "1f1ee-1f1f7", - "uc_greedy": "1f1ee-1f1f7", - "shortnames": [":ir:"], - "category": "flags" - }, - ":flag_is:": { - "uc_base": "1f1ee-1f1f8", - "uc_output": "1f1ee-1f1f8", - "uc_match": "1f1ee-1f1f8", - "uc_greedy": "1f1ee-1f1f8", - "shortnames": [":is:"], - "category": "flags" - }, - ":flag_it:": { - "uc_base": "1f1ee-1f1f9", - "uc_output": "1f1ee-1f1f9", - "uc_match": "1f1ee-1f1f9", - "uc_greedy": "1f1ee-1f1f9", - "shortnames": [":it:"], - "category": "flags" - }, - ":flag_je:": { - "uc_base": "1f1ef-1f1ea", - "uc_output": "1f1ef-1f1ea", - "uc_match": "1f1ef-1f1ea", - "uc_greedy": "1f1ef-1f1ea", - "shortnames": [":je:"], - "category": "flags" - }, - ":flag_jm:": { - "uc_base": "1f1ef-1f1f2", - "uc_output": "1f1ef-1f1f2", - "uc_match": "1f1ef-1f1f2", - "uc_greedy": "1f1ef-1f1f2", - "shortnames": [":jm:"], - "category": "flags" - }, - ":flag_jo:": { - "uc_base": "1f1ef-1f1f4", - "uc_output": "1f1ef-1f1f4", - "uc_match": "1f1ef-1f1f4", - "uc_greedy": "1f1ef-1f1f4", - "shortnames": [":jo:"], - "category": "flags" - }, - ":flag_jp:": { - "uc_base": "1f1ef-1f1f5", - "uc_output": "1f1ef-1f1f5", - "uc_match": "1f1ef-1f1f5", - "uc_greedy": "1f1ef-1f1f5", - "shortnames": [":jp:"], - "category": "flags" - }, - ":flag_ke:": { - "uc_base": "1f1f0-1f1ea", - "uc_output": "1f1f0-1f1ea", - "uc_match": "1f1f0-1f1ea", - "uc_greedy": "1f1f0-1f1ea", - "shortnames": [":ke:"], - "category": "flags" - }, - ":flag_kg:": { - "uc_base": "1f1f0-1f1ec", - "uc_output": "1f1f0-1f1ec", - "uc_match": "1f1f0-1f1ec", - "uc_greedy": "1f1f0-1f1ec", - "shortnames": [":kg:"], - "category": "flags" - }, - ":flag_kh:": { - "uc_base": "1f1f0-1f1ed", - "uc_output": "1f1f0-1f1ed", - "uc_match": "1f1f0-1f1ed", - "uc_greedy": "1f1f0-1f1ed", - "shortnames": [":kh:"], - "category": "flags" - }, - ":flag_ki:": { - "uc_base": "1f1f0-1f1ee", - "uc_output": "1f1f0-1f1ee", - "uc_match": "1f1f0-1f1ee", - "uc_greedy": "1f1f0-1f1ee", - "shortnames": [":ki:"], - "category": "flags" - }, - ":flag_km:": { - "uc_base": "1f1f0-1f1f2", - "uc_output": "1f1f0-1f1f2", - "uc_match": "1f1f0-1f1f2", - "uc_greedy": "1f1f0-1f1f2", - "shortnames": [":km:"], - "category": "flags" - }, - ":flag_kn:": { - "uc_base": "1f1f0-1f1f3", - "uc_output": "1f1f0-1f1f3", - "uc_match": "1f1f0-1f1f3", - "uc_greedy": "1f1f0-1f1f3", - "shortnames": [":kn:"], - "category": "flags" - }, - ":flag_kp:": { - "uc_base": "1f1f0-1f1f5", - "uc_output": "1f1f0-1f1f5", - "uc_match": "1f1f0-1f1f5", - "uc_greedy": "1f1f0-1f1f5", - "shortnames": [":kp:"], - "category": "flags" - }, - ":flag_kr:": { - "uc_base": "1f1f0-1f1f7", - "uc_output": "1f1f0-1f1f7", - "uc_match": "1f1f0-1f1f7", - "uc_greedy": "1f1f0-1f1f7", - "shortnames": [":kr:"], - "category": "flags" - }, - ":flag_kw:": { - "uc_base": "1f1f0-1f1fc", - "uc_output": "1f1f0-1f1fc", - "uc_match": "1f1f0-1f1fc", - "uc_greedy": "1f1f0-1f1fc", - "shortnames": [":kw:"], - "category": "flags" - }, - ":flag_ky:": { - "uc_base": "1f1f0-1f1fe", - "uc_output": "1f1f0-1f1fe", - "uc_match": "1f1f0-1f1fe", - "uc_greedy": "1f1f0-1f1fe", - "shortnames": [":ky:"], - "category": "flags" - }, - ":flag_kz:": { - "uc_base": "1f1f0-1f1ff", - "uc_output": "1f1f0-1f1ff", - "uc_match": "1f1f0-1f1ff", - "uc_greedy": "1f1f0-1f1ff", - "shortnames": [":kz:"], - "category": "flags" - }, - ":flag_la:": { - "uc_base": "1f1f1-1f1e6", - "uc_output": "1f1f1-1f1e6", - "uc_match": "1f1f1-1f1e6", - "uc_greedy": "1f1f1-1f1e6", - "shortnames": [":la:"], - "category": "flags" - }, - ":flag_lb:": { - "uc_base": "1f1f1-1f1e7", - "uc_output": "1f1f1-1f1e7", - "uc_match": "1f1f1-1f1e7", - "uc_greedy": "1f1f1-1f1e7", - "shortnames": [":lb:"], - "category": "flags" - }, - ":flag_lc:": { - "uc_base": "1f1f1-1f1e8", - "uc_output": "1f1f1-1f1e8", - "uc_match": "1f1f1-1f1e8", - "uc_greedy": "1f1f1-1f1e8", - "shortnames": [":lc:"], - "category": "flags" - }, - ":flag_li:": { - "uc_base": "1f1f1-1f1ee", - "uc_output": "1f1f1-1f1ee", - "uc_match": "1f1f1-1f1ee", - "uc_greedy": "1f1f1-1f1ee", - "shortnames": [":li:"], - "category": "flags" - }, - ":flag_lk:": { - "uc_base": "1f1f1-1f1f0", - "uc_output": "1f1f1-1f1f0", - "uc_match": "1f1f1-1f1f0", - "uc_greedy": "1f1f1-1f1f0", - "shortnames": [":lk:"], - "category": "flags" - }, - ":flag_lr:": { - "uc_base": "1f1f1-1f1f7", - "uc_output": "1f1f1-1f1f7", - "uc_match": "1f1f1-1f1f7", - "uc_greedy": "1f1f1-1f1f7", - "shortnames": [":lr:"], - "category": "flags" - }, - ":flag_ls:": { - "uc_base": "1f1f1-1f1f8", - "uc_output": "1f1f1-1f1f8", - "uc_match": "1f1f1-1f1f8", - "uc_greedy": "1f1f1-1f1f8", - "shortnames": [":ls:"], - "category": "flags" - }, - ":flag_lt:": { - "uc_base": "1f1f1-1f1f9", - "uc_output": "1f1f1-1f1f9", - "uc_match": "1f1f1-1f1f9", - "uc_greedy": "1f1f1-1f1f9", - "shortnames": [":lt:"], - "category": "flags" - }, - ":flag_lu:": { - "uc_base": "1f1f1-1f1fa", - "uc_output": "1f1f1-1f1fa", - "uc_match": "1f1f1-1f1fa", - "uc_greedy": "1f1f1-1f1fa", - "shortnames": [":lu:"], - "category": "flags" - }, - ":flag_lv:": { - "uc_base": "1f1f1-1f1fb", - "uc_output": "1f1f1-1f1fb", - "uc_match": "1f1f1-1f1fb", - "uc_greedy": "1f1f1-1f1fb", - "shortnames": [":lv:"], - "category": "flags" - }, - ":flag_ly:": { - "uc_base": "1f1f1-1f1fe", - "uc_output": "1f1f1-1f1fe", - "uc_match": "1f1f1-1f1fe", - "uc_greedy": "1f1f1-1f1fe", - "shortnames": [":ly:"], - "category": "flags" - }, - ":flag_ma:": { - "uc_base": "1f1f2-1f1e6", - "uc_output": "1f1f2-1f1e6", - "uc_match": "1f1f2-1f1e6", - "uc_greedy": "1f1f2-1f1e6", - "shortnames": [":ma:"], - "category": "flags" - }, - ":flag_mc:": { - "uc_base": "1f1f2-1f1e8", - "uc_output": "1f1f2-1f1e8", - "uc_match": "1f1f2-1f1e8", - "uc_greedy": "1f1f2-1f1e8", - "shortnames": [":mc:"], - "category": "flags" - }, - ":flag_md:": { - "uc_base": "1f1f2-1f1e9", - "uc_output": "1f1f2-1f1e9", - "uc_match": "1f1f2-1f1e9", - "uc_greedy": "1f1f2-1f1e9", - "shortnames": [":md:"], - "category": "flags" - }, - ":flag_me:": { - "uc_base": "1f1f2-1f1ea", - "uc_output": "1f1f2-1f1ea", - "uc_match": "1f1f2-1f1ea", - "uc_greedy": "1f1f2-1f1ea", - "shortnames": [":me:"], - "category": "flags" - }, - ":flag_mf:": { - "uc_base": "1f1f2-1f1eb", - "uc_output": "1f1f2-1f1eb", - "uc_match": "1f1f2-1f1eb", - "uc_greedy": "1f1f2-1f1eb", - "shortnames": [":mf:"], - "category": "flags" - }, - ":flag_mg:": { - "uc_base": "1f1f2-1f1ec", - "uc_output": "1f1f2-1f1ec", - "uc_match": "1f1f2-1f1ec", - "uc_greedy": "1f1f2-1f1ec", - "shortnames": [":mg:"], - "category": "flags" - }, - ":flag_mh:": { - "uc_base": "1f1f2-1f1ed", - "uc_output": "1f1f2-1f1ed", - "uc_match": "1f1f2-1f1ed", - "uc_greedy": "1f1f2-1f1ed", - "shortnames": [":mh:"], - "category": "flags" - }, - ":flag_mk:": { - "uc_base": "1f1f2-1f1f0", - "uc_output": "1f1f2-1f1f0", - "uc_match": "1f1f2-1f1f0", - "uc_greedy": "1f1f2-1f1f0", - "shortnames": [":mk:"], - "category": "flags" - }, - ":flag_ml:": { - "uc_base": "1f1f2-1f1f1", - "uc_output": "1f1f2-1f1f1", - "uc_match": "1f1f2-1f1f1", - "uc_greedy": "1f1f2-1f1f1", - "shortnames": [":ml:"], - "category": "flags" - }, - ":flag_mm:": { - "uc_base": "1f1f2-1f1f2", - "uc_output": "1f1f2-1f1f2", - "uc_match": "1f1f2-1f1f2", - "uc_greedy": "1f1f2-1f1f2", - "shortnames": [":mm:"], - "category": "flags" - }, - ":flag_mn:": { - "uc_base": "1f1f2-1f1f3", - "uc_output": "1f1f2-1f1f3", - "uc_match": "1f1f2-1f1f3", - "uc_greedy": "1f1f2-1f1f3", - "shortnames": [":mn:"], - "category": "flags" - }, - ":flag_mo:": { - "uc_base": "1f1f2-1f1f4", - "uc_output": "1f1f2-1f1f4", - "uc_match": "1f1f2-1f1f4", - "uc_greedy": "1f1f2-1f1f4", - "shortnames": [":mo:"], - "category": "flags" - }, - ":flag_mp:": { - "uc_base": "1f1f2-1f1f5", - "uc_output": "1f1f2-1f1f5", - "uc_match": "1f1f2-1f1f5", - "uc_greedy": "1f1f2-1f1f5", - "shortnames": [":mp:"], - "category": "flags" - }, - ":flag_mq:": { - "uc_base": "1f1f2-1f1f6", - "uc_output": "1f1f2-1f1f6", - "uc_match": "1f1f2-1f1f6", - "uc_greedy": "1f1f2-1f1f6", - "shortnames": [":mq:"], - "category": "flags" - }, - ":flag_mr:": { - "uc_base": "1f1f2-1f1f7", - "uc_output": "1f1f2-1f1f7", - "uc_match": "1f1f2-1f1f7", - "uc_greedy": "1f1f2-1f1f7", - "shortnames": [":mr:"], - "category": "flags" - }, - ":flag_ms:": { - "uc_base": "1f1f2-1f1f8", - "uc_output": "1f1f2-1f1f8", - "uc_match": "1f1f2-1f1f8", - "uc_greedy": "1f1f2-1f1f8", - "shortnames": [":ms:"], - "category": "flags" - }, - ":flag_mt:": { - "uc_base": "1f1f2-1f1f9", - "uc_output": "1f1f2-1f1f9", - "uc_match": "1f1f2-1f1f9", - "uc_greedy": "1f1f2-1f1f9", - "shortnames": [":mt:"], - "category": "flags" - }, - ":flag_mu:": { - "uc_base": "1f1f2-1f1fa", - "uc_output": "1f1f2-1f1fa", - "uc_match": "1f1f2-1f1fa", - "uc_greedy": "1f1f2-1f1fa", - "shortnames": [":mu:"], - "category": "flags" - }, - ":flag_mv:": { - "uc_base": "1f1f2-1f1fb", - "uc_output": "1f1f2-1f1fb", - "uc_match": "1f1f2-1f1fb", - "uc_greedy": "1f1f2-1f1fb", - "shortnames": [":mv:"], - "category": "flags" - }, - ":flag_mw:": { - "uc_base": "1f1f2-1f1fc", - "uc_output": "1f1f2-1f1fc", - "uc_match": "1f1f2-1f1fc", - "uc_greedy": "1f1f2-1f1fc", - "shortnames": [":mw:"], - "category": "flags" - }, - ":flag_mx:": { - "uc_base": "1f1f2-1f1fd", - "uc_output": "1f1f2-1f1fd", - "uc_match": "1f1f2-1f1fd", - "uc_greedy": "1f1f2-1f1fd", - "shortnames": [":mx:"], - "category": "flags" - }, - ":flag_my:": { - "uc_base": "1f1f2-1f1fe", - "uc_output": "1f1f2-1f1fe", - "uc_match": "1f1f2-1f1fe", - "uc_greedy": "1f1f2-1f1fe", - "shortnames": [":my:"], - "category": "flags" - }, - ":flag_mz:": { - "uc_base": "1f1f2-1f1ff", - "uc_output": "1f1f2-1f1ff", - "uc_match": "1f1f2-1f1ff", - "uc_greedy": "1f1f2-1f1ff", - "shortnames": [":mz:"], - "category": "flags" - }, - ":flag_na:": { - "uc_base": "1f1f3-1f1e6", - "uc_output": "1f1f3-1f1e6", - "uc_match": "1f1f3-1f1e6", - "uc_greedy": "1f1f3-1f1e6", - "shortnames": [":na:"], - "category": "flags" - }, - ":flag_nc:": { - "uc_base": "1f1f3-1f1e8", - "uc_output": "1f1f3-1f1e8", - "uc_match": "1f1f3-1f1e8", - "uc_greedy": "1f1f3-1f1e8", - "shortnames": [":nc:"], - "category": "flags" - }, - ":flag_ne:": { - "uc_base": "1f1f3-1f1ea", - "uc_output": "1f1f3-1f1ea", - "uc_match": "1f1f3-1f1ea", - "uc_greedy": "1f1f3-1f1ea", - "shortnames": [":ne:"], - "category": "flags" - }, - ":flag_nf:": { - "uc_base": "1f1f3-1f1eb", - "uc_output": "1f1f3-1f1eb", - "uc_match": "1f1f3-1f1eb", - "uc_greedy": "1f1f3-1f1eb", - "shortnames": [":nf:"], - "category": "flags" - }, - ":flag_ng:": { - "uc_base": "1f1f3-1f1ec", - "uc_output": "1f1f3-1f1ec", - "uc_match": "1f1f3-1f1ec", - "uc_greedy": "1f1f3-1f1ec", - "shortnames": [":nigeria:"], - "category": "flags" - }, - ":flag_ni:": { - "uc_base": "1f1f3-1f1ee", - "uc_output": "1f1f3-1f1ee", - "uc_match": "1f1f3-1f1ee", - "uc_greedy": "1f1f3-1f1ee", - "shortnames": [":ni:"], - "category": "flags" - }, - ":flag_nl:": { - "uc_base": "1f1f3-1f1f1", - "uc_output": "1f1f3-1f1f1", - "uc_match": "1f1f3-1f1f1", - "uc_greedy": "1f1f3-1f1f1", - "shortnames": [":nl:"], - "category": "flags" - }, - ":flag_no:": { - "uc_base": "1f1f3-1f1f4", - "uc_output": "1f1f3-1f1f4", - "uc_match": "1f1f3-1f1f4", - "uc_greedy": "1f1f3-1f1f4", - "shortnames": [":no:"], - "category": "flags" - }, - ":flag_np:": { - "uc_base": "1f1f3-1f1f5", - "uc_output": "1f1f3-1f1f5", - "uc_match": "1f1f3-1f1f5", - "uc_greedy": "1f1f3-1f1f5", - "shortnames": [":np:"], - "category": "flags" - }, - ":flag_nr:": { - "uc_base": "1f1f3-1f1f7", - "uc_output": "1f1f3-1f1f7", - "uc_match": "1f1f3-1f1f7", - "uc_greedy": "1f1f3-1f1f7", - "shortnames": [":nr:"], - "category": "flags" - }, - ":flag_nu:": { - "uc_base": "1f1f3-1f1fa", - "uc_output": "1f1f3-1f1fa", - "uc_match": "1f1f3-1f1fa", - "uc_greedy": "1f1f3-1f1fa", - "shortnames": [":nu:"], - "category": "flags" - }, - ":flag_nz:": { - "uc_base": "1f1f3-1f1ff", - "uc_output": "1f1f3-1f1ff", - "uc_match": "1f1f3-1f1ff", - "uc_greedy": "1f1f3-1f1ff", - "shortnames": [":nz:"], - "category": "flags" - }, - ":flag_om:": { - "uc_base": "1f1f4-1f1f2", - "uc_output": "1f1f4-1f1f2", - "uc_match": "1f1f4-1f1f2", - "uc_greedy": "1f1f4-1f1f2", - "shortnames": [":om:"], - "category": "flags" - }, - ":flag_pa:": { - "uc_base": "1f1f5-1f1e6", - "uc_output": "1f1f5-1f1e6", - "uc_match": "1f1f5-1f1e6", - "uc_greedy": "1f1f5-1f1e6", - "shortnames": [":pa:"], - "category": "flags" - }, - ":flag_pe:": { - "uc_base": "1f1f5-1f1ea", - "uc_output": "1f1f5-1f1ea", - "uc_match": "1f1f5-1f1ea", - "uc_greedy": "1f1f5-1f1ea", - "shortnames": [":pe:"], - "category": "flags" - }, - ":flag_pf:": { - "uc_base": "1f1f5-1f1eb", - "uc_output": "1f1f5-1f1eb", - "uc_match": "1f1f5-1f1eb", - "uc_greedy": "1f1f5-1f1eb", - "shortnames": [":pf:"], - "category": "flags" - }, - ":flag_pg:": { - "uc_base": "1f1f5-1f1ec", - "uc_output": "1f1f5-1f1ec", - "uc_match": "1f1f5-1f1ec", - "uc_greedy": "1f1f5-1f1ec", - "shortnames": [":pg:"], - "category": "flags" - }, - ":flag_ph:": { - "uc_base": "1f1f5-1f1ed", - "uc_output": "1f1f5-1f1ed", - "uc_match": "1f1f5-1f1ed", - "uc_greedy": "1f1f5-1f1ed", - "shortnames": [":ph:"], - "category": "flags" - }, - ":flag_pk:": { - "uc_base": "1f1f5-1f1f0", - "uc_output": "1f1f5-1f1f0", - "uc_match": "1f1f5-1f1f0", - "uc_greedy": "1f1f5-1f1f0", - "shortnames": [":pk:"], - "category": "flags" - }, - ":flag_pl:": { - "uc_base": "1f1f5-1f1f1", - "uc_output": "1f1f5-1f1f1", - "uc_match": "1f1f5-1f1f1", - "uc_greedy": "1f1f5-1f1f1", - "shortnames": [":pl:"], - "category": "flags" - }, - ":flag_pm:": { - "uc_base": "1f1f5-1f1f2", - "uc_output": "1f1f5-1f1f2", - "uc_match": "1f1f5-1f1f2", - "uc_greedy": "1f1f5-1f1f2", - "shortnames": [":pm:"], - "category": "flags" - }, - ":flag_pn:": { - "uc_base": "1f1f5-1f1f3", - "uc_output": "1f1f5-1f1f3", - "uc_match": "1f1f5-1f1f3", - "uc_greedy": "1f1f5-1f1f3", - "shortnames": [":pn:"], - "category": "flags" - }, - ":flag_pr:": { - "uc_base": "1f1f5-1f1f7", - "uc_output": "1f1f5-1f1f7", - "uc_match": "1f1f5-1f1f7", - "uc_greedy": "1f1f5-1f1f7", - "shortnames": [":pr:"], - "category": "flags" - }, - ":flag_ps:": { - "uc_base": "1f1f5-1f1f8", - "uc_output": "1f1f5-1f1f8", - "uc_match": "1f1f5-1f1f8", - "uc_greedy": "1f1f5-1f1f8", - "shortnames": [":ps:"], - "category": "flags" - }, - ":flag_pt:": { - "uc_base": "1f1f5-1f1f9", - "uc_output": "1f1f5-1f1f9", - "uc_match": "1f1f5-1f1f9", - "uc_greedy": "1f1f5-1f1f9", - "shortnames": [":pt:"], - "category": "flags" - }, - ":flag_pw:": { - "uc_base": "1f1f5-1f1fc", - "uc_output": "1f1f5-1f1fc", - "uc_match": "1f1f5-1f1fc", - "uc_greedy": "1f1f5-1f1fc", - "shortnames": [":pw:"], - "category": "flags" - }, - ":flag_py:": { - "uc_base": "1f1f5-1f1fe", - "uc_output": "1f1f5-1f1fe", - "uc_match": "1f1f5-1f1fe", - "uc_greedy": "1f1f5-1f1fe", - "shortnames": [":py:"], - "category": "flags" - }, - ":flag_qa:": { - "uc_base": "1f1f6-1f1e6", - "uc_output": "1f1f6-1f1e6", - "uc_match": "1f1f6-1f1e6", - "uc_greedy": "1f1f6-1f1e6", - "shortnames": [":qa:"], - "category": "flags" - }, - ":flag_re:": { - "uc_base": "1f1f7-1f1ea", - "uc_output": "1f1f7-1f1ea", - "uc_match": "1f1f7-1f1ea", - "uc_greedy": "1f1f7-1f1ea", - "shortnames": [":re:"], - "category": "flags" - }, - ":flag_ro:": { - "uc_base": "1f1f7-1f1f4", - "uc_output": "1f1f7-1f1f4", - "uc_match": "1f1f7-1f1f4", - "uc_greedy": "1f1f7-1f1f4", - "shortnames": [":ro:"], - "category": "flags" - }, - ":flag_rs:": { - "uc_base": "1f1f7-1f1f8", - "uc_output": "1f1f7-1f1f8", - "uc_match": "1f1f7-1f1f8", - "uc_greedy": "1f1f7-1f1f8", - "shortnames": [":rs:"], - "category": "flags" - }, - ":flag_ru:": { - "uc_base": "1f1f7-1f1fa", - "uc_output": "1f1f7-1f1fa", - "uc_match": "1f1f7-1f1fa", - "uc_greedy": "1f1f7-1f1fa", - "shortnames": [":ru:"], - "category": "flags" - }, - ":flag_rw:": { - "uc_base": "1f1f7-1f1fc", - "uc_output": "1f1f7-1f1fc", - "uc_match": "1f1f7-1f1fc", - "uc_greedy": "1f1f7-1f1fc", - "shortnames": [":rw:"], - "category": "flags" - }, - ":flag_sa:": { - "uc_base": "1f1f8-1f1e6", - "uc_output": "1f1f8-1f1e6", - "uc_match": "1f1f8-1f1e6", - "uc_greedy": "1f1f8-1f1e6", - "shortnames": [":saudiarabia:", ":saudi:"], - "category": "flags" - }, - ":flag_sb:": { - "uc_base": "1f1f8-1f1e7", - "uc_output": "1f1f8-1f1e7", - "uc_match": "1f1f8-1f1e7", - "uc_greedy": "1f1f8-1f1e7", - "shortnames": [":sb:"], - "category": "flags" - }, - ":flag_sc:": { - "uc_base": "1f1f8-1f1e8", - "uc_output": "1f1f8-1f1e8", - "uc_match": "1f1f8-1f1e8", - "uc_greedy": "1f1f8-1f1e8", - "shortnames": [":sc:"], - "category": "flags" - }, - ":flag_sd:": { - "uc_base": "1f1f8-1f1e9", - "uc_output": "1f1f8-1f1e9", - "uc_match": "1f1f8-1f1e9", - "uc_greedy": "1f1f8-1f1e9", - "shortnames": [":sd:"], - "category": "flags" - }, - ":flag_se:": { - "uc_base": "1f1f8-1f1ea", - "uc_output": "1f1f8-1f1ea", - "uc_match": "1f1f8-1f1ea", - "uc_greedy": "1f1f8-1f1ea", - "shortnames": [":se:"], - "category": "flags" - }, - ":flag_sg:": { - "uc_base": "1f1f8-1f1ec", - "uc_output": "1f1f8-1f1ec", - "uc_match": "1f1f8-1f1ec", - "uc_greedy": "1f1f8-1f1ec", - "shortnames": [":sg:"], - "category": "flags" - }, - ":flag_sh:": { - "uc_base": "1f1f8-1f1ed", - "uc_output": "1f1f8-1f1ed", - "uc_match": "1f1f8-1f1ed", - "uc_greedy": "1f1f8-1f1ed", - "shortnames": [":sh:"], - "category": "flags" - }, - ":flag_si:": { - "uc_base": "1f1f8-1f1ee", - "uc_output": "1f1f8-1f1ee", - "uc_match": "1f1f8-1f1ee", - "uc_greedy": "1f1f8-1f1ee", - "shortnames": [":si:"], - "category": "flags" - }, - ":flag_sj:": { - "uc_base": "1f1f8-1f1ef", - "uc_output": "1f1f8-1f1ef", - "uc_match": "1f1f8-1f1ef", - "uc_greedy": "1f1f8-1f1ef", - "shortnames": [":sj:"], - "category": "flags" - }, - ":flag_sk:": { - "uc_base": "1f1f8-1f1f0", - "uc_output": "1f1f8-1f1f0", - "uc_match": "1f1f8-1f1f0", - "uc_greedy": "1f1f8-1f1f0", - "shortnames": [":sk:"], - "category": "flags" - }, - ":flag_sl:": { - "uc_base": "1f1f8-1f1f1", - "uc_output": "1f1f8-1f1f1", - "uc_match": "1f1f8-1f1f1", - "uc_greedy": "1f1f8-1f1f1", - "shortnames": [":sl:"], - "category": "flags" - }, - ":flag_sm:": { - "uc_base": "1f1f8-1f1f2", - "uc_output": "1f1f8-1f1f2", - "uc_match": "1f1f8-1f1f2", - "uc_greedy": "1f1f8-1f1f2", - "shortnames": [":sm:"], - "category": "flags" - }, - ":flag_sn:": { - "uc_base": "1f1f8-1f1f3", - "uc_output": "1f1f8-1f1f3", - "uc_match": "1f1f8-1f1f3", - "uc_greedy": "1f1f8-1f1f3", - "shortnames": [":sn:"], - "category": "flags" - }, - ":flag_so:": { - "uc_base": "1f1f8-1f1f4", - "uc_output": "1f1f8-1f1f4", - "uc_match": "1f1f8-1f1f4", - "uc_greedy": "1f1f8-1f1f4", - "shortnames": [":so:"], - "category": "flags" - }, - ":flag_sr:": { - "uc_base": "1f1f8-1f1f7", - "uc_output": "1f1f8-1f1f7", - "uc_match": "1f1f8-1f1f7", - "uc_greedy": "1f1f8-1f1f7", - "shortnames": [":sr:"], - "category": "flags" - }, - ":flag_ss:": { - "uc_base": "1f1f8-1f1f8", - "uc_output": "1f1f8-1f1f8", - "uc_match": "1f1f8-1f1f8", - "uc_greedy": "1f1f8-1f1f8", - "shortnames": [":ss:"], - "category": "flags" - }, - ":flag_st:": { - "uc_base": "1f1f8-1f1f9", - "uc_output": "1f1f8-1f1f9", - "uc_match": "1f1f8-1f1f9", - "uc_greedy": "1f1f8-1f1f9", - "shortnames": [":st:"], - "category": "flags" - }, - ":flag_sv:": { - "uc_base": "1f1f8-1f1fb", - "uc_output": "1f1f8-1f1fb", - "uc_match": "1f1f8-1f1fb", - "uc_greedy": "1f1f8-1f1fb", - "shortnames": [":sv:"], - "category": "flags" - }, - ":flag_sx:": { - "uc_base": "1f1f8-1f1fd", - "uc_output": "1f1f8-1f1fd", - "uc_match": "1f1f8-1f1fd", - "uc_greedy": "1f1f8-1f1fd", - "shortnames": [":sx:"], - "category": "flags" - }, - ":flag_sy:": { - "uc_base": "1f1f8-1f1fe", - "uc_output": "1f1f8-1f1fe", - "uc_match": "1f1f8-1f1fe", - "uc_greedy": "1f1f8-1f1fe", - "shortnames": [":sy:"], - "category": "flags" - }, - ":flag_sz:": { - "uc_base": "1f1f8-1f1ff", - "uc_output": "1f1f8-1f1ff", - "uc_match": "1f1f8-1f1ff", - "uc_greedy": "1f1f8-1f1ff", - "shortnames": [":sz:"], - "category": "flags" - }, - ":flag_ta:": { - "uc_base": "1f1f9-1f1e6", - "uc_output": "1f1f9-1f1e6", - "uc_match": "1f1f9-1f1e6", - "uc_greedy": "1f1f9-1f1e6", - "shortnames": [":ta:"], - "category": "flags" - }, - ":flag_tc:": { - "uc_base": "1f1f9-1f1e8", - "uc_output": "1f1f9-1f1e8", - "uc_match": "1f1f9-1f1e8", - "uc_greedy": "1f1f9-1f1e8", - "shortnames": [":tc:"], - "category": "flags" - }, - ":flag_td:": { - "uc_base": "1f1f9-1f1e9", - "uc_output": "1f1f9-1f1e9", - "uc_match": "1f1f9-1f1e9", - "uc_greedy": "1f1f9-1f1e9", - "shortnames": [":td:"], - "category": "flags" - }, - ":flag_tf:": { - "uc_base": "1f1f9-1f1eb", - "uc_output": "1f1f9-1f1eb", - "uc_match": "1f1f9-1f1eb", - "uc_greedy": "1f1f9-1f1eb", - "shortnames": [":tf:"], - "category": "flags" - }, - ":flag_tg:": { - "uc_base": "1f1f9-1f1ec", - "uc_output": "1f1f9-1f1ec", - "uc_match": "1f1f9-1f1ec", - "uc_greedy": "1f1f9-1f1ec", - "shortnames": [":tg:"], - "category": "flags" - }, - ":flag_th:": { - "uc_base": "1f1f9-1f1ed", - "uc_output": "1f1f9-1f1ed", - "uc_match": "1f1f9-1f1ed", - "uc_greedy": "1f1f9-1f1ed", - "shortnames": [":th:"], - "category": "flags" - }, - ":flag_tj:": { - "uc_base": "1f1f9-1f1ef", - "uc_output": "1f1f9-1f1ef", - "uc_match": "1f1f9-1f1ef", - "uc_greedy": "1f1f9-1f1ef", - "shortnames": [":tj:"], - "category": "flags" - }, - ":flag_tk:": { - "uc_base": "1f1f9-1f1f0", - "uc_output": "1f1f9-1f1f0", - "uc_match": "1f1f9-1f1f0", - "uc_greedy": "1f1f9-1f1f0", - "shortnames": [":tk:"], - "category": "flags" - }, - ":flag_tl:": { - "uc_base": "1f1f9-1f1f1", - "uc_output": "1f1f9-1f1f1", - "uc_match": "1f1f9-1f1f1", - "uc_greedy": "1f1f9-1f1f1", - "shortnames": [":tl:"], - "category": "flags" - }, - ":flag_tm:": { - "uc_base": "1f1f9-1f1f2", - "uc_output": "1f1f9-1f1f2", - "uc_match": "1f1f9-1f1f2", - "uc_greedy": "1f1f9-1f1f2", - "shortnames": [":turkmenistan:"], - "category": "flags" - }, - ":flag_tn:": { - "uc_base": "1f1f9-1f1f3", - "uc_output": "1f1f9-1f1f3", - "uc_match": "1f1f9-1f1f3", - "uc_greedy": "1f1f9-1f1f3", - "shortnames": [":tn:"], - "category": "flags" - }, - ":flag_to:": { - "uc_base": "1f1f9-1f1f4", - "uc_output": "1f1f9-1f1f4", - "uc_match": "1f1f9-1f1f4", - "uc_greedy": "1f1f9-1f1f4", - "shortnames": [":to:"], - "category": "flags" - }, - ":flag_tr:": { - "uc_base": "1f1f9-1f1f7", - "uc_output": "1f1f9-1f1f7", - "uc_match": "1f1f9-1f1f7", - "uc_greedy": "1f1f9-1f1f7", - "shortnames": [":tr:"], - "category": "flags" - }, - ":flag_tt:": { - "uc_base": "1f1f9-1f1f9", - "uc_output": "1f1f9-1f1f9", - "uc_match": "1f1f9-1f1f9", - "uc_greedy": "1f1f9-1f1f9", - "shortnames": [":tt:"], - "category": "flags" - }, - ":flag_tv:": { - "uc_base": "1f1f9-1f1fb", - "uc_output": "1f1f9-1f1fb", - "uc_match": "1f1f9-1f1fb", - "uc_greedy": "1f1f9-1f1fb", - "shortnames": [":tuvalu:"], - "category": "flags" - }, - ":flag_tw:": { - "uc_base": "1f1f9-1f1fc", - "uc_output": "1f1f9-1f1fc", - "uc_match": "1f1f9-1f1fc", - "uc_greedy": "1f1f9-1f1fc", - "shortnames": [":tw:"], - "category": "flags" - }, - ":flag_tz:": { - "uc_base": "1f1f9-1f1ff", - "uc_output": "1f1f9-1f1ff", - "uc_match": "1f1f9-1f1ff", - "uc_greedy": "1f1f9-1f1ff", - "shortnames": [":tz:"], - "category": "flags" - }, - ":flag_ua:": { - "uc_base": "1f1fa-1f1e6", - "uc_output": "1f1fa-1f1e6", - "uc_match": "1f1fa-1f1e6", - "uc_greedy": "1f1fa-1f1e6", - "shortnames": [":ua:"], - "category": "flags" - }, - ":flag_ug:": { - "uc_base": "1f1fa-1f1ec", - "uc_output": "1f1fa-1f1ec", - "uc_match": "1f1fa-1f1ec", - "uc_greedy": "1f1fa-1f1ec", - "shortnames": [":ug:"], - "category": "flags" - }, - ":flag_um:": { - "uc_base": "1f1fa-1f1f2", - "uc_output": "1f1fa-1f1f2", - "uc_match": "1f1fa-1f1f2", - "uc_greedy": "1f1fa-1f1f2", - "shortnames": [":um:"], - "category": "flags" - }, - ":flag_us:": { - "uc_base": "1f1fa-1f1f8", - "uc_output": "1f1fa-1f1f8", - "uc_match": "1f1fa-1f1f8", - "uc_greedy": "1f1fa-1f1f8", - "shortnames": [":us:"], - "category": "flags" - }, - ":flag_uy:": { - "uc_base": "1f1fa-1f1fe", - "uc_output": "1f1fa-1f1fe", - "uc_match": "1f1fa-1f1fe", - "uc_greedy": "1f1fa-1f1fe", - "shortnames": [":uy:"], - "category": "flags" - }, - ":flag_uz:": { - "uc_base": "1f1fa-1f1ff", - "uc_output": "1f1fa-1f1ff", - "uc_match": "1f1fa-1f1ff", - "uc_greedy": "1f1fa-1f1ff", - "shortnames": [":uz:"], - "category": "flags" - }, - ":flag_va:": { - "uc_base": "1f1fb-1f1e6", - "uc_output": "1f1fb-1f1e6", - "uc_match": "1f1fb-1f1e6", - "uc_greedy": "1f1fb-1f1e6", - "shortnames": [":va:"], - "category": "flags" - }, - ":flag_vc:": { - "uc_base": "1f1fb-1f1e8", - "uc_output": "1f1fb-1f1e8", - "uc_match": "1f1fb-1f1e8", - "uc_greedy": "1f1fb-1f1e8", - "shortnames": [":vc:"], - "category": "flags" - }, - ":flag_ve:": { - "uc_base": "1f1fb-1f1ea", - "uc_output": "1f1fb-1f1ea", - "uc_match": "1f1fb-1f1ea", - "uc_greedy": "1f1fb-1f1ea", - "shortnames": [":ve:"], - "category": "flags" - }, - ":flag_vg:": { - "uc_base": "1f1fb-1f1ec", - "uc_output": "1f1fb-1f1ec", - "uc_match": "1f1fb-1f1ec", - "uc_greedy": "1f1fb-1f1ec", - "shortnames": [":vg:"], - "category": "flags" - }, - ":flag_vi:": { - "uc_base": "1f1fb-1f1ee", - "uc_output": "1f1fb-1f1ee", - "uc_match": "1f1fb-1f1ee", - "uc_greedy": "1f1fb-1f1ee", - "shortnames": [":vi:"], - "category": "flags" - }, - ":flag_vn:": { - "uc_base": "1f1fb-1f1f3", - "uc_output": "1f1fb-1f1f3", - "uc_match": "1f1fb-1f1f3", - "uc_greedy": "1f1fb-1f1f3", - "shortnames": [":vn:"], - "category": "flags" - }, - ":flag_vu:": { - "uc_base": "1f1fb-1f1fa", - "uc_output": "1f1fb-1f1fa", - "uc_match": "1f1fb-1f1fa", - "uc_greedy": "1f1fb-1f1fa", - "shortnames": [":vu:"], - "category": "flags" - }, - ":flag_wf:": { - "uc_base": "1f1fc-1f1eb", - "uc_output": "1f1fc-1f1eb", - "uc_match": "1f1fc-1f1eb", - "uc_greedy": "1f1fc-1f1eb", - "shortnames": [":wf:"], - "category": "flags" - }, - ":flag_ws:": { - "uc_base": "1f1fc-1f1f8", - "uc_output": "1f1fc-1f1f8", - "uc_match": "1f1fc-1f1f8", - "uc_greedy": "1f1fc-1f1f8", - "shortnames": [":ws:"], - "category": "flags" - }, - ":flag_xk:": { - "uc_base": "1f1fd-1f1f0", - "uc_output": "1f1fd-1f1f0", - "uc_match": "1f1fd-1f1f0", - "uc_greedy": "1f1fd-1f1f0", - "shortnames": [":xk:"], - "category": "flags" - }, - ":flag_ye:": { - "uc_base": "1f1fe-1f1ea", - "uc_output": "1f1fe-1f1ea", - "uc_match": "1f1fe-1f1ea", - "uc_greedy": "1f1fe-1f1ea", - "shortnames": [":ye:"], - "category": "flags" - }, - ":flag_yt:": { - "uc_base": "1f1fe-1f1f9", - "uc_output": "1f1fe-1f1f9", - "uc_match": "1f1fe-1f1f9", - "uc_greedy": "1f1fe-1f1f9", - "shortnames": [":yt:"], - "category": "flags" - }, - ":flag_za:": { - "uc_base": "1f1ff-1f1e6", - "uc_output": "1f1ff-1f1e6", - "uc_match": "1f1ff-1f1e6", - "uc_greedy": "1f1ff-1f1e6", - "shortnames": [":za:"], - "category": "flags" - }, - ":flag_zm:": { - "uc_base": "1f1ff-1f1f2", - "uc_output": "1f1ff-1f1f2", - "uc_match": "1f1ff-1f1f2", - "uc_greedy": "1f1ff-1f1f2", - "shortnames": [":zm:"], - "category": "flags" - }, - ":flag_zw:": { - "uc_base": "1f1ff-1f1fc", - "uc_output": "1f1ff-1f1fc", - "uc_match": "1f1ff-1f1fc", - "uc_greedy": "1f1ff-1f1fc", - "shortnames": [":zw:"], - "category": "flags" - }, - ":girl_tone1:": { - "uc_base": "1f467-1f3fb", - "uc_output": "1f467-1f3fb", - "uc_match": "1f467-1f3fb", - "uc_greedy": "1f467-1f3fb", - "shortnames": [], - "category": "people" - }, - ":girl_tone2:": { - "uc_base": "1f467-1f3fc", - "uc_output": "1f467-1f3fc", - "uc_match": "1f467-1f3fc", - "uc_greedy": "1f467-1f3fc", - "shortnames": [], - "category": "people" - }, - ":girl_tone3:": { - "uc_base": "1f467-1f3fd", - "uc_output": "1f467-1f3fd", - "uc_match": "1f467-1f3fd", - "uc_greedy": "1f467-1f3fd", - "shortnames": [], - "category": "people" - }, - ":girl_tone4:": { - "uc_base": "1f467-1f3fe", - "uc_output": "1f467-1f3fe", - "uc_match": "1f467-1f3fe", - "uc_greedy": "1f467-1f3fe", - "shortnames": [], - "category": "people" - }, - ":girl_tone5:": { - "uc_base": "1f467-1f3ff", - "uc_output": "1f467-1f3ff", - "uc_match": "1f467-1f3ff", - "uc_greedy": "1f467-1f3ff", - "shortnames": [], - "category": "people" - }, - ":guard_tone1:": { - "uc_base": "1f482-1f3fb", - "uc_output": "1f482-1f3fb", - "uc_match": "1f482-1f3fb", - "uc_greedy": "1f482-1f3fb", - "shortnames": [":guardsman_tone1:"], - "category": "people" - }, - ":guard_tone2:": { - "uc_base": "1f482-1f3fc", - "uc_output": "1f482-1f3fc", - "uc_match": "1f482-1f3fc", - "uc_greedy": "1f482-1f3fc", - "shortnames": [":guardsman_tone2:"], - "category": "people" - }, - ":guard_tone3:": { - "uc_base": "1f482-1f3fd", - "uc_output": "1f482-1f3fd", - "uc_match": "1f482-1f3fd", - "uc_greedy": "1f482-1f3fd", - "shortnames": [":guardsman_tone3:"], - "category": "people" - }, - ":guard_tone4:": { - "uc_base": "1f482-1f3fe", - "uc_output": "1f482-1f3fe", - "uc_match": "1f482-1f3fe", - "uc_greedy": "1f482-1f3fe", - "shortnames": [":guardsman_tone4:"], - "category": "people" - }, - ":guard_tone5:": { - "uc_base": "1f482-1f3ff", - "uc_output": "1f482-1f3ff", - "uc_match": "1f482-1f3ff", - "uc_greedy": "1f482-1f3ff", - "shortnames": [":guardsman_tone5:"], - "category": "people" - }, - ":hand_splayed_tone1:": { - "uc_base": "1f590-1f3fb", - "uc_output": "1f590-1f3fb", - "uc_match": "1f590-fe0f-1f3fb", - "uc_greedy": "1f590-fe0f-1f3fb", - "shortnames": [":raised_hand_with_fingers_splayed_tone1:"], - "category": "people" - }, - ":hand_splayed_tone2:": { - "uc_base": "1f590-1f3fc", - "uc_output": "1f590-1f3fc", - "uc_match": "1f590-fe0f-1f3fc", - "uc_greedy": "1f590-fe0f-1f3fc", - "shortnames": [":raised_hand_with_fingers_splayed_tone2:"], - "category": "people" - }, - ":hand_splayed_tone3:": { - "uc_base": "1f590-1f3fd", - "uc_output": "1f590-1f3fd", - "uc_match": "1f590-fe0f-1f3fd", - "uc_greedy": "1f590-fe0f-1f3fd", - "shortnames": [":raised_hand_with_fingers_splayed_tone3:"], - "category": "people" - }, - ":hand_splayed_tone4:": { - "uc_base": "1f590-1f3fe", - "uc_output": "1f590-1f3fe", - "uc_match": "1f590-fe0f-1f3fe", - "uc_greedy": "1f590-fe0f-1f3fe", - "shortnames": [":raised_hand_with_fingers_splayed_tone4:"], - "category": "people" - }, - ":hand_splayed_tone5:": { - "uc_base": "1f590-1f3ff", - "uc_output": "1f590-1f3ff", - "uc_match": "1f590-fe0f-1f3ff", - "uc_greedy": "1f590-fe0f-1f3ff", - "shortnames": [":raised_hand_with_fingers_splayed_tone5:"], - "category": "people" - }, - ":horse_racing_tone1:": { - "uc_base": "1f3c7-1f3fb", - "uc_output": "1f3c7-1f3fb", - "uc_match": "1f3c7-1f3fb", - "uc_greedy": "1f3c7-1f3fb", - "shortnames": [], - "category": "activity" - }, - ":horse_racing_tone2:": { - "uc_base": "1f3c7-1f3fc", - "uc_output": "1f3c7-1f3fc", - "uc_match": "1f3c7-1f3fc", - "uc_greedy": "1f3c7-1f3fc", - "shortnames": [], - "category": "activity" - }, - ":horse_racing_tone3:": { - "uc_base": "1f3c7-1f3fd", - "uc_output": "1f3c7-1f3fd", - "uc_match": "1f3c7-1f3fd", - "uc_greedy": "1f3c7-1f3fd", - "shortnames": [], - "category": "activity" - }, - ":horse_racing_tone4:": { - "uc_base": "1f3c7-1f3fe", - "uc_output": "1f3c7-1f3fe", - "uc_match": "1f3c7-1f3fe", - "uc_greedy": "1f3c7-1f3fe", - "shortnames": [], - "category": "activity" - }, - ":horse_racing_tone5:": { - "uc_base": "1f3c7-1f3ff", - "uc_output": "1f3c7-1f3ff", - "uc_match": "1f3c7-1f3ff", - "uc_greedy": "1f3c7-1f3ff", - "shortnames": [], - "category": "activity" - }, - ":left_facing_fist_tone1:": { - "uc_base": "1f91b-1f3fb", - "uc_output": "1f91b-1f3fb", - "uc_match": "1f91b-1f3fb", - "uc_greedy": "1f91b-1f3fb", - "shortnames": [":left_fist_tone1:"], - "category": "people" - }, - ":left_facing_fist_tone2:": { - "uc_base": "1f91b-1f3fc", - "uc_output": "1f91b-1f3fc", - "uc_match": "1f91b-1f3fc", - "uc_greedy": "1f91b-1f3fc", - "shortnames": [":left_fist_tone2:"], - "category": "people" - }, - ":left_facing_fist_tone3:": { - "uc_base": "1f91b-1f3fd", - "uc_output": "1f91b-1f3fd", - "uc_match": "1f91b-1f3fd", - "uc_greedy": "1f91b-1f3fd", - "shortnames": [":left_fist_tone3:"], - "category": "people" - }, - ":left_facing_fist_tone4:": { - "uc_base": "1f91b-1f3fe", - "uc_output": "1f91b-1f3fe", - "uc_match": "1f91b-1f3fe", - "uc_greedy": "1f91b-1f3fe", - "shortnames": [":left_fist_tone4:"], - "category": "people" - }, - ":left_facing_fist_tone5:": { - "uc_base": "1f91b-1f3ff", - "uc_output": "1f91b-1f3ff", - "uc_match": "1f91b-1f3ff", - "uc_greedy": "1f91b-1f3ff", - "shortnames": [":left_fist_tone5:"], - "category": "people" - }, - ":levitate_tone1:": { - "uc_base": "1f574-1f3fb", - "uc_output": "1f574-1f3fb", - "uc_match": "1f574-fe0f-1f3fb", - "uc_greedy": "1f574-fe0f-1f3fb", - "shortnames": [":man_in_business_suit_levitating_tone1:", ":man_in_business_suit_levitating_light_skin_tone:"], - "category": "people" - }, - ":levitate_tone2:": { - "uc_base": "1f574-1f3fc", - "uc_output": "1f574-1f3fc", - "uc_match": "1f574-fe0f-1f3fc", - "uc_greedy": "1f574-fe0f-1f3fc", - "shortnames": [":man_in_business_suit_levitating_tone2:", ":man_in_business_suit_levitating_medium_light_skin_tone:"], - "category": "people" - }, - ":levitate_tone3:": { - "uc_base": "1f574-1f3fd", - "uc_output": "1f574-1f3fd", - "uc_match": "1f574-fe0f-1f3fd", - "uc_greedy": "1f574-fe0f-1f3fd", - "shortnames": [":man_in_business_suit_levitating_tone3:", ":man_in_business_suit_levitating_medium_skin_tone:"], - "category": "people" - }, - ":levitate_tone4:": { - "uc_base": "1f574-1f3fe", - "uc_output": "1f574-1f3fe", - "uc_match": "1f574-fe0f-1f3fe", - "uc_greedy": "1f574-fe0f-1f3fe", - "shortnames": [":man_in_business_suit_levitating_tone4:", ":man_in_business_suit_levitating_medium_dark_skin_tone:"], - "category": "people" - }, - ":levitate_tone5:": { - "uc_base": "1f574-1f3ff", - "uc_output": "1f574-1f3ff", - "uc_match": "1f574-fe0f-1f3ff", - "uc_greedy": "1f574-fe0f-1f3ff", - "shortnames": [":man_in_business_suit_levitating_tone5:", ":man_in_business_suit_levitating_dark_skin_tone:"], - "category": "people" - }, - ":love_you_gesture_tone1:": { - "uc_base": "1f91f-1f3fb", - "uc_output": "1f91f-1f3fb", - "uc_match": "1f91f-1f3fb", - "uc_greedy": "1f91f-1f3fb", - "shortnames": [":love_you_gesture_light_skin_tone:"], - "category": "people" - }, - ":love_you_gesture_tone2:": { - "uc_base": "1f91f-1f3fc", - "uc_output": "1f91f-1f3fc", - "uc_match": "1f91f-1f3fc", - "uc_greedy": "1f91f-1f3fc", - "shortnames": [":love_you_gesture_medium_light_skin_tone:"], - "category": "people" - }, - ":love_you_gesture_tone3:": { - "uc_base": "1f91f-1f3fd", - "uc_output": "1f91f-1f3fd", - "uc_match": "1f91f-1f3fd", - "uc_greedy": "1f91f-1f3fd", - "shortnames": [":love_you_gesture_medium_skin_tone:"], - "category": "people" - }, - ":love_you_gesture_tone4:": { - "uc_base": "1f91f-1f3fe", - "uc_output": "1f91f-1f3fe", - "uc_match": "1f91f-1f3fe", - "uc_greedy": "1f91f-1f3fe", - "shortnames": [":love_you_gesture_medium_dark_skin_tone:"], - "category": "people" - }, - ":love_you_gesture_tone5:": { - "uc_base": "1f91f-1f3ff", - "uc_output": "1f91f-1f3ff", - "uc_match": "1f91f-1f3ff", - "uc_greedy": "1f91f-1f3ff", - "shortnames": [":love_you_gesture_dark_skin_tone:"], - "category": "people" - }, - ":mage_tone1:": { - "uc_base": "1f9d9-1f3fb", - "uc_output": "1f9d9-1f3fb", - "uc_match": "1f9d9-1f3fb", - "uc_greedy": "1f9d9-1f3fb", - "shortnames": [":mage_light_skin_tone:"], - "category": "people" - }, - ":mage_tone2:": { - "uc_base": "1f9d9-1f3fc", - "uc_output": "1f9d9-1f3fc", - "uc_match": "1f9d9-1f3fc", - "uc_greedy": "1f9d9-1f3fc", - "shortnames": [":mage_medium_light_skin_tone:"], - "category": "people" - }, - ":mage_tone3:": { - "uc_base": "1f9d9-1f3fd", - "uc_output": "1f9d9-1f3fd", - "uc_match": "1f9d9-1f3fd", - "uc_greedy": "1f9d9-1f3fd", - "shortnames": [":mage_medium_skin_tone:"], - "category": "people" - }, - ":mage_tone4:": { - "uc_base": "1f9d9-1f3fe", - "uc_output": "1f9d9-1f3fe", - "uc_match": "1f9d9-1f3fe", - "uc_greedy": "1f9d9-1f3fe", - "shortnames": [":mage_medium_dark_skin_tone:"], - "category": "people" - }, - ":mage_tone5:": { - "uc_base": "1f9d9-1f3ff", - "uc_output": "1f9d9-1f3ff", - "uc_match": "1f9d9-1f3ff", - "uc_greedy": "1f9d9-1f3ff", - "shortnames": [":mage_dark_skin_tone:"], - "category": "people" - }, - ":man_dancing_tone1:": { - "uc_base": "1f57a-1f3fb", - "uc_output": "1f57a-1f3fb", - "uc_match": "1f57a-1f3fb", - "uc_greedy": "1f57a-1f3fb", - "shortnames": [":male_dancer_tone1:"], - "category": "people" - }, - ":man_dancing_tone2:": { - "uc_base": "1f57a-1f3fc", - "uc_output": "1f57a-1f3fc", - "uc_match": "1f57a-1f3fc", - "uc_greedy": "1f57a-1f3fc", - "shortnames": [":male_dancer_tone2:"], - "category": "people" - }, - ":man_dancing_tone3:": { - "uc_base": "1f57a-1f3fd", - "uc_output": "1f57a-1f3fd", - "uc_match": "1f57a-1f3fd", - "uc_greedy": "1f57a-1f3fd", - "shortnames": [":male_dancer_tone3:"], - "category": "people" - }, - ":man_dancing_tone4:": { - "uc_base": "1f57a-1f3fe", - "uc_output": "1f57a-1f3fe", - "uc_match": "1f57a-1f3fe", - "uc_greedy": "1f57a-1f3fe", - "shortnames": [":male_dancer_tone4:"], - "category": "people" - }, - ":man_dancing_tone5:": { - "uc_base": "1f57a-1f3ff", - "uc_output": "1f57a-1f3ff", - "uc_match": "1f57a-1f3ff", - "uc_greedy": "1f57a-1f3ff", - "shortnames": [":male_dancer_tone5:"], - "category": "people" - }, - ":man_in_tuxedo_tone1:": { - "uc_base": "1f935-1f3fb", - "uc_output": "1f935-1f3fb", - "uc_match": "1f935-1f3fb", - "uc_greedy": "1f935-1f3fb", - "shortnames": [":tuxedo_tone1:"], - "category": "people" - }, - ":man_in_tuxedo_tone2:": { - "uc_base": "1f935-1f3fc", - "uc_output": "1f935-1f3fc", - "uc_match": "1f935-1f3fc", - "uc_greedy": "1f935-1f3fc", - "shortnames": [":tuxedo_tone2:"], - "category": "people" - }, - ":man_in_tuxedo_tone3:": { - "uc_base": "1f935-1f3fd", - "uc_output": "1f935-1f3fd", - "uc_match": "1f935-1f3fd", - "uc_greedy": "1f935-1f3fd", - "shortnames": [":tuxedo_tone3:"], - "category": "people" - }, - ":man_in_tuxedo_tone4:": { - "uc_base": "1f935-1f3fe", - "uc_output": "1f935-1f3fe", - "uc_match": "1f935-1f3fe", - "uc_greedy": "1f935-1f3fe", - "shortnames": [":tuxedo_tone4:"], - "category": "people" - }, - ":man_in_tuxedo_tone5:": { - "uc_base": "1f935-1f3ff", - "uc_output": "1f935-1f3ff", - "uc_match": "1f935-1f3ff", - "uc_greedy": "1f935-1f3ff", - "shortnames": [":tuxedo_tone5:"], - "category": "people" - }, - ":man_tone1:": { - "uc_base": "1f468-1f3fb", - "uc_output": "1f468-1f3fb", - "uc_match": "1f468-1f3fb", - "uc_greedy": "1f468-1f3fb", - "shortnames": [], - "category": "people" - }, - ":man_tone2:": { - "uc_base": "1f468-1f3fc", - "uc_output": "1f468-1f3fc", - "uc_match": "1f468-1f3fc", - "uc_greedy": "1f468-1f3fc", - "shortnames": [], - "category": "people" - }, - ":man_tone3:": { - "uc_base": "1f468-1f3fd", - "uc_output": "1f468-1f3fd", - "uc_match": "1f468-1f3fd", - "uc_greedy": "1f468-1f3fd", - "shortnames": [], - "category": "people" - }, - ":man_tone4:": { - "uc_base": "1f468-1f3fe", - "uc_output": "1f468-1f3fe", - "uc_match": "1f468-1f3fe", - "uc_greedy": "1f468-1f3fe", - "shortnames": [], - "category": "people" - }, - ":man_tone5:": { - "uc_base": "1f468-1f3ff", - "uc_output": "1f468-1f3ff", - "uc_match": "1f468-1f3ff", - "uc_greedy": "1f468-1f3ff", - "shortnames": [], - "category": "people" - }, - ":man_with_chinese_cap_tone1:": { - "uc_base": "1f472-1f3fb", - "uc_output": "1f472-1f3fb", - "uc_match": "1f472-1f3fb", - "uc_greedy": "1f472-1f3fb", - "shortnames": [":man_with_gua_pi_mao_tone1:"], - "category": "people" - }, - ":man_with_chinese_cap_tone2:": { - "uc_base": "1f472-1f3fc", - "uc_output": "1f472-1f3fc", - "uc_match": "1f472-1f3fc", - "uc_greedy": "1f472-1f3fc", - "shortnames": [":man_with_gua_pi_mao_tone2:"], - "category": "people" - }, - ":man_with_chinese_cap_tone3:": { - "uc_base": "1f472-1f3fd", - "uc_output": "1f472-1f3fd", - "uc_match": "1f472-1f3fd", - "uc_greedy": "1f472-1f3fd", - "shortnames": [":man_with_gua_pi_mao_tone3:"], - "category": "people" - }, - ":man_with_chinese_cap_tone4:": { - "uc_base": "1f472-1f3fe", - "uc_output": "1f472-1f3fe", - "uc_match": "1f472-1f3fe", - "uc_greedy": "1f472-1f3fe", - "shortnames": [":man_with_gua_pi_mao_tone4:"], - "category": "people" - }, - ":man_with_chinese_cap_tone5:": { - "uc_base": "1f472-1f3ff", - "uc_output": "1f472-1f3ff", - "uc_match": "1f472-1f3ff", - "uc_greedy": "1f472-1f3ff", - "shortnames": [":man_with_gua_pi_mao_tone5:"], - "category": "people" - }, - ":merperson_tone1:": { - "uc_base": "1f9dc-1f3fb", - "uc_output": "1f9dc-1f3fb", - "uc_match": "1f9dc-1f3fb", - "uc_greedy": "1f9dc-1f3fb", - "shortnames": [":merperson_light_skin_tone:"], - "category": "people" - }, - ":merperson_tone2:": { - "uc_base": "1f9dc-1f3fc", - "uc_output": "1f9dc-1f3fc", - "uc_match": "1f9dc-1f3fc", - "uc_greedy": "1f9dc-1f3fc", - "shortnames": [":merperson_medium_light_skin_tone:"], - "category": "people" - }, - ":merperson_tone3:": { - "uc_base": "1f9dc-1f3fd", - "uc_output": "1f9dc-1f3fd", - "uc_match": "1f9dc-1f3fd", - "uc_greedy": "1f9dc-1f3fd", - "shortnames": [":merperson_medium_skin_tone:"], - "category": "people" - }, - ":merperson_tone4:": { - "uc_base": "1f9dc-1f3fe", - "uc_output": "1f9dc-1f3fe", - "uc_match": "1f9dc-1f3fe", - "uc_greedy": "1f9dc-1f3fe", - "shortnames": [":merperson_medium_dark_skin_tone:"], - "category": "people" - }, - ":merperson_tone5:": { - "uc_base": "1f9dc-1f3ff", - "uc_output": "1f9dc-1f3ff", - "uc_match": "1f9dc-1f3ff", - "uc_greedy": "1f9dc-1f3ff", - "shortnames": [":merperson_dark_skin_tone:"], - "category": "people" - }, - ":metal_tone1:": { - "uc_base": "1f918-1f3fb", - "uc_output": "1f918-1f3fb", - "uc_match": "1f918-1f3fb", - "uc_greedy": "1f918-1f3fb", - "shortnames": [":sign_of_the_horns_tone1:"], - "category": "people" - }, - ":metal_tone2:": { - "uc_base": "1f918-1f3fc", - "uc_output": "1f918-1f3fc", - "uc_match": "1f918-1f3fc", - "uc_greedy": "1f918-1f3fc", - "shortnames": [":sign_of_the_horns_tone2:"], - "category": "people" - }, - ":metal_tone3:": { - "uc_base": "1f918-1f3fd", - "uc_output": "1f918-1f3fd", - "uc_match": "1f918-1f3fd", - "uc_greedy": "1f918-1f3fd", - "shortnames": [":sign_of_the_horns_tone3:"], - "category": "people" - }, - ":metal_tone4:": { - "uc_base": "1f918-1f3fe", - "uc_output": "1f918-1f3fe", - "uc_match": "1f918-1f3fe", - "uc_greedy": "1f918-1f3fe", - "shortnames": [":sign_of_the_horns_tone4:"], - "category": "people" - }, - ":metal_tone5:": { - "uc_base": "1f918-1f3ff", - "uc_output": "1f918-1f3ff", - "uc_match": "1f918-1f3ff", - "uc_greedy": "1f918-1f3ff", - "shortnames": [":sign_of_the_horns_tone5:"], - "category": "people" - }, - ":middle_finger_tone1:": { - "uc_base": "1f595-1f3fb", - "uc_output": "1f595-1f3fb", - "uc_match": "1f595-1f3fb", - "uc_greedy": "1f595-1f3fb", - "shortnames": [":reversed_hand_with_middle_finger_extended_tone1:"], - "category": "people" - }, - ":middle_finger_tone2:": { - "uc_base": "1f595-1f3fc", - "uc_output": "1f595-1f3fc", - "uc_match": "1f595-1f3fc", - "uc_greedy": "1f595-1f3fc", - "shortnames": [":reversed_hand_with_middle_finger_extended_tone2:"], - "category": "people" - }, - ":middle_finger_tone3:": { - "uc_base": "1f595-1f3fd", - "uc_output": "1f595-1f3fd", - "uc_match": "1f595-1f3fd", - "uc_greedy": "1f595-1f3fd", - "shortnames": [":reversed_hand_with_middle_finger_extended_tone3:"], - "category": "people" - }, - ":middle_finger_tone4:": { - "uc_base": "1f595-1f3fe", - "uc_output": "1f595-1f3fe", - "uc_match": "1f595-1f3fe", - "uc_greedy": "1f595-1f3fe", - "shortnames": [":reversed_hand_with_middle_finger_extended_tone4:"], - "category": "people" - }, - ":middle_finger_tone5:": { - "uc_base": "1f595-1f3ff", - "uc_output": "1f595-1f3ff", - "uc_match": "1f595-1f3ff", - "uc_greedy": "1f595-1f3ff", - "shortnames": [":reversed_hand_with_middle_finger_extended_tone5:"], - "category": "people" - }, - ":mrs_claus_tone1:": { - "uc_base": "1f936-1f3fb", - "uc_output": "1f936-1f3fb", - "uc_match": "1f936-1f3fb", - "uc_greedy": "1f936-1f3fb", - "shortnames": [":mother_christmas_tone1:"], - "category": "people" - }, - ":mrs_claus_tone2:": { - "uc_base": "1f936-1f3fc", - "uc_output": "1f936-1f3fc", - "uc_match": "1f936-1f3fc", - "uc_greedy": "1f936-1f3fc", - "shortnames": [":mother_christmas_tone2:"], - "category": "people" - }, - ":mrs_claus_tone3:": { - "uc_base": "1f936-1f3fd", - "uc_output": "1f936-1f3fd", - "uc_match": "1f936-1f3fd", - "uc_greedy": "1f936-1f3fd", - "shortnames": [":mother_christmas_tone3:"], - "category": "people" - }, - ":mrs_claus_tone4:": { - "uc_base": "1f936-1f3fe", - "uc_output": "1f936-1f3fe", - "uc_match": "1f936-1f3fe", - "uc_greedy": "1f936-1f3fe", - "shortnames": [":mother_christmas_tone4:"], - "category": "people" - }, - ":mrs_claus_tone5:": { - "uc_base": "1f936-1f3ff", - "uc_output": "1f936-1f3ff", - "uc_match": "1f936-1f3ff", - "uc_greedy": "1f936-1f3ff", - "shortnames": [":mother_christmas_tone5:"], - "category": "people" - }, - ":muscle_tone1:": { - "uc_base": "1f4aa-1f3fb", - "uc_output": "1f4aa-1f3fb", - "uc_match": "1f4aa-1f3fb", - "uc_greedy": "1f4aa-1f3fb", - "shortnames": [], - "category": "people" - }, - ":muscle_tone2:": { - "uc_base": "1f4aa-1f3fc", - "uc_output": "1f4aa-1f3fc", - "uc_match": "1f4aa-1f3fc", - "uc_greedy": "1f4aa-1f3fc", - "shortnames": [], - "category": "people" - }, - ":muscle_tone3:": { - "uc_base": "1f4aa-1f3fd", - "uc_output": "1f4aa-1f3fd", - "uc_match": "1f4aa-1f3fd", - "uc_greedy": "1f4aa-1f3fd", - "shortnames": [], - "category": "people" - }, - ":muscle_tone4:": { - "uc_base": "1f4aa-1f3fe", - "uc_output": "1f4aa-1f3fe", - "uc_match": "1f4aa-1f3fe", - "uc_greedy": "1f4aa-1f3fe", - "shortnames": [], - "category": "people" - }, - ":muscle_tone5:": { - "uc_base": "1f4aa-1f3ff", - "uc_output": "1f4aa-1f3ff", - "uc_match": "1f4aa-1f3ff", - "uc_greedy": "1f4aa-1f3ff", - "shortnames": [], - "category": "people" - }, - ":nail_care_tone1:": { - "uc_base": "1f485-1f3fb", - "uc_output": "1f485-1f3fb", - "uc_match": "1f485-1f3fb", - "uc_greedy": "1f485-1f3fb", - "shortnames": [], - "category": "people" - }, - ":nail_care_tone2:": { - "uc_base": "1f485-1f3fc", - "uc_output": "1f485-1f3fc", - "uc_match": "1f485-1f3fc", - "uc_greedy": "1f485-1f3fc", - "shortnames": [], - "category": "people" - }, - ":nail_care_tone3:": { - "uc_base": "1f485-1f3fd", - "uc_output": "1f485-1f3fd", - "uc_match": "1f485-1f3fd", - "uc_greedy": "1f485-1f3fd", - "shortnames": [], - "category": "people" - }, - ":nail_care_tone4:": { - "uc_base": "1f485-1f3fe", - "uc_output": "1f485-1f3fe", - "uc_match": "1f485-1f3fe", - "uc_greedy": "1f485-1f3fe", - "shortnames": [], - "category": "people" - }, - ":nail_care_tone5:": { - "uc_base": "1f485-1f3ff", - "uc_output": "1f485-1f3ff", - "uc_match": "1f485-1f3ff", - "uc_greedy": "1f485-1f3ff", - "shortnames": [], - "category": "people" - }, - ":nose_tone1:": { - "uc_base": "1f443-1f3fb", - "uc_output": "1f443-1f3fb", - "uc_match": "1f443-1f3fb", - "uc_greedy": "1f443-1f3fb", - "shortnames": [], - "category": "people" - }, - ":nose_tone2:": { - "uc_base": "1f443-1f3fc", - "uc_output": "1f443-1f3fc", - "uc_match": "1f443-1f3fc", - "uc_greedy": "1f443-1f3fc", - "shortnames": [], - "category": "people" - }, - ":nose_tone3:": { - "uc_base": "1f443-1f3fd", - "uc_output": "1f443-1f3fd", - "uc_match": "1f443-1f3fd", - "uc_greedy": "1f443-1f3fd", - "shortnames": [], - "category": "people" - }, - ":nose_tone4:": { - "uc_base": "1f443-1f3fe", - "uc_output": "1f443-1f3fe", - "uc_match": "1f443-1f3fe", - "uc_greedy": "1f443-1f3fe", - "shortnames": [], - "category": "people" - }, - ":nose_tone5:": { - "uc_base": "1f443-1f3ff", - "uc_output": "1f443-1f3ff", - "uc_match": "1f443-1f3ff", - "uc_greedy": "1f443-1f3ff", - "shortnames": [], - "category": "people" - }, - ":ok_hand_tone1:": { - "uc_base": "1f44c-1f3fb", - "uc_output": "1f44c-1f3fb", - "uc_match": "1f44c-1f3fb", - "uc_greedy": "1f44c-1f3fb", - "shortnames": [], - "category": "people" - }, - ":ok_hand_tone2:": { - "uc_base": "1f44c-1f3fc", - "uc_output": "1f44c-1f3fc", - "uc_match": "1f44c-1f3fc", - "uc_greedy": "1f44c-1f3fc", - "shortnames": [], - "category": "people" - }, - ":ok_hand_tone3:": { - "uc_base": "1f44c-1f3fd", - "uc_output": "1f44c-1f3fd", - "uc_match": "1f44c-1f3fd", - "uc_greedy": "1f44c-1f3fd", - "shortnames": [], - "category": "people" - }, - ":ok_hand_tone4:": { - "uc_base": "1f44c-1f3fe", - "uc_output": "1f44c-1f3fe", - "uc_match": "1f44c-1f3fe", - "uc_greedy": "1f44c-1f3fe", - "shortnames": [], - "category": "people" - }, - ":ok_hand_tone5:": { - "uc_base": "1f44c-1f3ff", - "uc_output": "1f44c-1f3ff", - "uc_match": "1f44c-1f3ff", - "uc_greedy": "1f44c-1f3ff", - "shortnames": [], - "category": "people" - }, - ":older_adult_tone1:": { - "uc_base": "1f9d3-1f3fb", - "uc_output": "1f9d3-1f3fb", - "uc_match": "1f9d3-1f3fb", - "uc_greedy": "1f9d3-1f3fb", - "shortnames": [":older_adult_light_skin_tone:"], - "category": "people" - }, - ":older_adult_tone2:": { - "uc_base": "1f9d3-1f3fc", - "uc_output": "1f9d3-1f3fc", - "uc_match": "1f9d3-1f3fc", - "uc_greedy": "1f9d3-1f3fc", - "shortnames": [":older_adult_medium_light_skin_tone:"], - "category": "people" - }, - ":older_adult_tone3:": { - "uc_base": "1f9d3-1f3fd", - "uc_output": "1f9d3-1f3fd", - "uc_match": "1f9d3-1f3fd", - "uc_greedy": "1f9d3-1f3fd", - "shortnames": [":older_adult_medium_skin_tone:"], - "category": "people" - }, - ":older_adult_tone4:": { - "uc_base": "1f9d3-1f3fe", - "uc_output": "1f9d3-1f3fe", - "uc_match": "1f9d3-1f3fe", - "uc_greedy": "1f9d3-1f3fe", - "shortnames": [":older_adult_medium_dark_skin_tone:"], - "category": "people" - }, - ":older_adult_tone5:": { - "uc_base": "1f9d3-1f3ff", - "uc_output": "1f9d3-1f3ff", - "uc_match": "1f9d3-1f3ff", - "uc_greedy": "1f9d3-1f3ff", - "shortnames": [":older_adult_dark_skin_tone:"], - "category": "people" - }, - ":older_man_tone1:": { - "uc_base": "1f474-1f3fb", - "uc_output": "1f474-1f3fb", - "uc_match": "1f474-1f3fb", - "uc_greedy": "1f474-1f3fb", - "shortnames": [], - "category": "people" - }, - ":older_man_tone2:": { - "uc_base": "1f474-1f3fc", - "uc_output": "1f474-1f3fc", - "uc_match": "1f474-1f3fc", - "uc_greedy": "1f474-1f3fc", - "shortnames": [], - "category": "people" - }, - ":older_man_tone3:": { - "uc_base": "1f474-1f3fd", - "uc_output": "1f474-1f3fd", - "uc_match": "1f474-1f3fd", - "uc_greedy": "1f474-1f3fd", - "shortnames": [], - "category": "people" - }, - ":older_man_tone4:": { - "uc_base": "1f474-1f3fe", - "uc_output": "1f474-1f3fe", - "uc_match": "1f474-1f3fe", - "uc_greedy": "1f474-1f3fe", - "shortnames": [], - "category": "people" - }, - ":older_man_tone5:": { - "uc_base": "1f474-1f3ff", - "uc_output": "1f474-1f3ff", - "uc_match": "1f474-1f3ff", - "uc_greedy": "1f474-1f3ff", - "shortnames": [], - "category": "people" - }, - ":older_woman_tone1:": { - "uc_base": "1f475-1f3fb", - "uc_output": "1f475-1f3fb", - "uc_match": "1f475-1f3fb", - "uc_greedy": "1f475-1f3fb", - "shortnames": [":grandma_tone1:"], - "category": "people" - }, - ":older_woman_tone2:": { - "uc_base": "1f475-1f3fc", - "uc_output": "1f475-1f3fc", - "uc_match": "1f475-1f3fc", - "uc_greedy": "1f475-1f3fc", - "shortnames": [":grandma_tone2:"], - "category": "people" - }, - ":older_woman_tone3:": { - "uc_base": "1f475-1f3fd", - "uc_output": "1f475-1f3fd", - "uc_match": "1f475-1f3fd", - "uc_greedy": "1f475-1f3fd", - "shortnames": [":grandma_tone3:"], - "category": "people" - }, - ":older_woman_tone4:": { - "uc_base": "1f475-1f3fe", - "uc_output": "1f475-1f3fe", - "uc_match": "1f475-1f3fe", - "uc_greedy": "1f475-1f3fe", - "shortnames": [":grandma_tone4:"], - "category": "people" - }, - ":older_woman_tone5:": { - "uc_base": "1f475-1f3ff", - "uc_output": "1f475-1f3ff", - "uc_match": "1f475-1f3ff", - "uc_greedy": "1f475-1f3ff", - "shortnames": [":grandma_tone5:"], - "category": "people" - }, - ":open_hands_tone1:": { - "uc_base": "1f450-1f3fb", - "uc_output": "1f450-1f3fb", - "uc_match": "1f450-1f3fb", - "uc_greedy": "1f450-1f3fb", - "shortnames": [], - "category": "people" - }, - ":open_hands_tone2:": { - "uc_base": "1f450-1f3fc", - "uc_output": "1f450-1f3fc", - "uc_match": "1f450-1f3fc", - "uc_greedy": "1f450-1f3fc", - "shortnames": [], - "category": "people" - }, - ":open_hands_tone3:": { - "uc_base": "1f450-1f3fd", - "uc_output": "1f450-1f3fd", - "uc_match": "1f450-1f3fd", - "uc_greedy": "1f450-1f3fd", - "shortnames": [], - "category": "people" - }, - ":open_hands_tone4:": { - "uc_base": "1f450-1f3fe", - "uc_output": "1f450-1f3fe", - "uc_match": "1f450-1f3fe", - "uc_greedy": "1f450-1f3fe", - "shortnames": [], - "category": "people" - }, - ":open_hands_tone5:": { - "uc_base": "1f450-1f3ff", - "uc_output": "1f450-1f3ff", - "uc_match": "1f450-1f3ff", - "uc_greedy": "1f450-1f3ff", - "shortnames": [], - "category": "people" - }, - ":palms_up_together_tone1:": { - "uc_base": "1f932-1f3fb", - "uc_output": "1f932-1f3fb", - "uc_match": "1f932-1f3fb", - "uc_greedy": "1f932-1f3fb", - "shortnames": [":palms_up_together_light_skin_tone:"], - "category": "people" - }, - ":palms_up_together_tone2:": { - "uc_base": "1f932-1f3fc", - "uc_output": "1f932-1f3fc", - "uc_match": "1f932-1f3fc", - "uc_greedy": "1f932-1f3fc", - "shortnames": [":palms_up_together_medium_light_skin_tone:"], - "category": "people" - }, - ":palms_up_together_tone3:": { - "uc_base": "1f932-1f3fd", - "uc_output": "1f932-1f3fd", - "uc_match": "1f932-1f3fd", - "uc_greedy": "1f932-1f3fd", - "shortnames": [":palms_up_together_medium_skin_tone:"], - "category": "people" - }, - ":palms_up_together_tone4:": { - "uc_base": "1f932-1f3fe", - "uc_output": "1f932-1f3fe", - "uc_match": "1f932-1f3fe", - "uc_greedy": "1f932-1f3fe", - "shortnames": [":palms_up_together_medium_dark_skin_tone:"], - "category": "people" - }, - ":palms_up_together_tone5:": { - "uc_base": "1f932-1f3ff", - "uc_output": "1f932-1f3ff", - "uc_match": "1f932-1f3ff", - "uc_greedy": "1f932-1f3ff", - "shortnames": [":palms_up_together_dark_skin_tone:"], - "category": "people" - }, - ":person_biking_tone1:": { - "uc_base": "1f6b4-1f3fb", - "uc_output": "1f6b4-1f3fb", - "uc_match": "1f6b4-1f3fb", - "uc_greedy": "1f6b4-1f3fb", - "shortnames": [":bicyclist_tone1:"], - "category": "activity" - }, - ":person_biking_tone2:": { - "uc_base": "1f6b4-1f3fc", - "uc_output": "1f6b4-1f3fc", - "uc_match": "1f6b4-1f3fc", - "uc_greedy": "1f6b4-1f3fc", - "shortnames": [":bicyclist_tone2:"], - "category": "activity" - }, - ":person_biking_tone3:": { - "uc_base": "1f6b4-1f3fd", - "uc_output": "1f6b4-1f3fd", - "uc_match": "1f6b4-1f3fd", - "uc_greedy": "1f6b4-1f3fd", - "shortnames": [":bicyclist_tone3:"], - "category": "activity" - }, - ":person_biking_tone4:": { - "uc_base": "1f6b4-1f3fe", - "uc_output": "1f6b4-1f3fe", - "uc_match": "1f6b4-1f3fe", - "uc_greedy": "1f6b4-1f3fe", - "shortnames": [":bicyclist_tone4:"], - "category": "activity" - }, - ":person_biking_tone5:": { - "uc_base": "1f6b4-1f3ff", - "uc_output": "1f6b4-1f3ff", - "uc_match": "1f6b4-1f3ff", - "uc_greedy": "1f6b4-1f3ff", - "shortnames": [":bicyclist_tone5:"], - "category": "activity" - }, - ":person_bowing_tone1:": { - "uc_base": "1f647-1f3fb", - "uc_output": "1f647-1f3fb", - "uc_match": "1f647-1f3fb", - "uc_greedy": "1f647-1f3fb", - "shortnames": [":bow_tone1:"], - "category": "people" - }, - ":person_bowing_tone2:": { - "uc_base": "1f647-1f3fc", - "uc_output": "1f647-1f3fc", - "uc_match": "1f647-1f3fc", - "uc_greedy": "1f647-1f3fc", - "shortnames": [":bow_tone2:"], - "category": "people" - }, - ":person_bowing_tone3:": { - "uc_base": "1f647-1f3fd", - "uc_output": "1f647-1f3fd", - "uc_match": "1f647-1f3fd", - "uc_greedy": "1f647-1f3fd", - "shortnames": [":bow_tone3:"], - "category": "people" - }, - ":person_bowing_tone4:": { - "uc_base": "1f647-1f3fe", - "uc_output": "1f647-1f3fe", - "uc_match": "1f647-1f3fe", - "uc_greedy": "1f647-1f3fe", - "shortnames": [":bow_tone4:"], - "category": "people" - }, - ":person_bowing_tone5:": { - "uc_base": "1f647-1f3ff", - "uc_output": "1f647-1f3ff", - "uc_match": "1f647-1f3ff", - "uc_greedy": "1f647-1f3ff", - "shortnames": [":bow_tone5:"], - "category": "people" - }, - ":person_climbing_tone1:": { - "uc_base": "1f9d7-1f3fb", - "uc_output": "1f9d7-1f3fb", - "uc_match": "1f9d7-1f3fb", - "uc_greedy": "1f9d7-1f3fb", - "shortnames": [":person_climbing_light_skin_tone:"], - "category": "activity" - }, - ":person_climbing_tone2:": { - "uc_base": "1f9d7-1f3fc", - "uc_output": "1f9d7-1f3fc", - "uc_match": "1f9d7-1f3fc", - "uc_greedy": "1f9d7-1f3fc", - "shortnames": [":person_climbing_medium_light_skin_tone:"], - "category": "activity" - }, - ":person_climbing_tone3:": { - "uc_base": "1f9d7-1f3fd", - "uc_output": "1f9d7-1f3fd", - "uc_match": "1f9d7-1f3fd", - "uc_greedy": "1f9d7-1f3fd", - "shortnames": [":person_climbing_medium_skin_tone:"], - "category": "activity" - }, - ":person_climbing_tone4:": { - "uc_base": "1f9d7-1f3fe", - "uc_output": "1f9d7-1f3fe", - "uc_match": "1f9d7-1f3fe", - "uc_greedy": "1f9d7-1f3fe", - "shortnames": [":person_climbing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":person_climbing_tone5:": { - "uc_base": "1f9d7-1f3ff", - "uc_output": "1f9d7-1f3ff", - "uc_match": "1f9d7-1f3ff", - "uc_greedy": "1f9d7-1f3ff", - "shortnames": [":person_climbing_dark_skin_tone:"], - "category": "activity" - }, - ":person_doing_cartwheel_tone1:": { - "uc_base": "1f938-1f3fb", - "uc_output": "1f938-1f3fb", - "uc_match": "1f938-1f3fb", - "uc_greedy": "1f938-1f3fb", - "shortnames": [":cartwheel_tone1:"], - "category": "activity" - }, - ":person_doing_cartwheel_tone2:": { - "uc_base": "1f938-1f3fc", - "uc_output": "1f938-1f3fc", - "uc_match": "1f938-1f3fc", - "uc_greedy": "1f938-1f3fc", - "shortnames": [":cartwheel_tone2:"], - "category": "activity" - }, - ":person_doing_cartwheel_tone3:": { - "uc_base": "1f938-1f3fd", - "uc_output": "1f938-1f3fd", - "uc_match": "1f938-1f3fd", - "uc_greedy": "1f938-1f3fd", - "shortnames": [":cartwheel_tone3:"], - "category": "activity" - }, - ":person_doing_cartwheel_tone4:": { - "uc_base": "1f938-1f3fe", - "uc_output": "1f938-1f3fe", - "uc_match": "1f938-1f3fe", - "uc_greedy": "1f938-1f3fe", - "shortnames": [":cartwheel_tone4:"], - "category": "activity" - }, - ":person_doing_cartwheel_tone5:": { - "uc_base": "1f938-1f3ff", - "uc_output": "1f938-1f3ff", - "uc_match": "1f938-1f3ff", - "uc_greedy": "1f938-1f3ff", - "shortnames": [":cartwheel_tone5:"], - "category": "activity" - }, - ":person_facepalming_tone1:": { - "uc_base": "1f926-1f3fb", - "uc_output": "1f926-1f3fb", - "uc_match": "1f926-1f3fb", - "uc_greedy": "1f926-1f3fb", - "shortnames": [":face_palm_tone1:", ":facepalm_tone1:"], - "category": "people" - }, - ":person_facepalming_tone2:": { - "uc_base": "1f926-1f3fc", - "uc_output": "1f926-1f3fc", - "uc_match": "1f926-1f3fc", - "uc_greedy": "1f926-1f3fc", - "shortnames": [":face_palm_tone2:", ":facepalm_tone2:"], - "category": "people" - }, - ":person_facepalming_tone3:": { - "uc_base": "1f926-1f3fd", - "uc_output": "1f926-1f3fd", - "uc_match": "1f926-1f3fd", - "uc_greedy": "1f926-1f3fd", - "shortnames": [":face_palm_tone3:", ":facepalm_tone3:"], - "category": "people" - }, - ":person_facepalming_tone4:": { - "uc_base": "1f926-1f3fe", - "uc_output": "1f926-1f3fe", - "uc_match": "1f926-1f3fe", - "uc_greedy": "1f926-1f3fe", - "shortnames": [":face_palm_tone4:", ":facepalm_tone4:"], - "category": "people" - }, - ":person_facepalming_tone5:": { - "uc_base": "1f926-1f3ff", - "uc_output": "1f926-1f3ff", - "uc_match": "1f926-1f3ff", - "uc_greedy": "1f926-1f3ff", - "shortnames": [":face_palm_tone5:", ":facepalm_tone5:"], - "category": "people" - }, - ":person_frowning_tone1:": { - "uc_base": "1f64d-1f3fb", - "uc_output": "1f64d-1f3fb", - "uc_match": "1f64d-1f3fb", - "uc_greedy": "1f64d-1f3fb", - "shortnames": [], - "category": "people" - }, - ":person_frowning_tone2:": { - "uc_base": "1f64d-1f3fc", - "uc_output": "1f64d-1f3fc", - "uc_match": "1f64d-1f3fc", - "uc_greedy": "1f64d-1f3fc", - "shortnames": [], - "category": "people" - }, - ":person_frowning_tone3:": { - "uc_base": "1f64d-1f3fd", - "uc_output": "1f64d-1f3fd", - "uc_match": "1f64d-1f3fd", - "uc_greedy": "1f64d-1f3fd", - "shortnames": [], - "category": "people" - }, - ":person_frowning_tone4:": { - "uc_base": "1f64d-1f3fe", - "uc_output": "1f64d-1f3fe", - "uc_match": "1f64d-1f3fe", - "uc_greedy": "1f64d-1f3fe", - "shortnames": [], - "category": "people" - }, - ":person_frowning_tone5:": { - "uc_base": "1f64d-1f3ff", - "uc_output": "1f64d-1f3ff", - "uc_match": "1f64d-1f3ff", - "uc_greedy": "1f64d-1f3ff", - "shortnames": [], - "category": "people" - }, - ":person_gesturing_no_tone1:": { - "uc_base": "1f645-1f3fb", - "uc_output": "1f645-1f3fb", - "uc_match": "1f645-1f3fb", - "uc_greedy": "1f645-1f3fb", - "shortnames": [":no_good_tone1:"], - "category": "people" - }, - ":person_gesturing_no_tone2:": { - "uc_base": "1f645-1f3fc", - "uc_output": "1f645-1f3fc", - "uc_match": "1f645-1f3fc", - "uc_greedy": "1f645-1f3fc", - "shortnames": [":no_good_tone2:"], - "category": "people" - }, - ":person_gesturing_no_tone3:": { - "uc_base": "1f645-1f3fd", - "uc_output": "1f645-1f3fd", - "uc_match": "1f645-1f3fd", - "uc_greedy": "1f645-1f3fd", - "shortnames": [":no_good_tone3:"], - "category": "people" - }, - ":person_gesturing_no_tone4:": { - "uc_base": "1f645-1f3fe", - "uc_output": "1f645-1f3fe", - "uc_match": "1f645-1f3fe", - "uc_greedy": "1f645-1f3fe", - "shortnames": [":no_good_tone4:"], - "category": "people" - }, - ":person_gesturing_no_tone5:": { - "uc_base": "1f645-1f3ff", - "uc_output": "1f645-1f3ff", - "uc_match": "1f645-1f3ff", - "uc_greedy": "1f645-1f3ff", - "shortnames": [":no_good_tone5:"], - "category": "people" - }, - ":person_gesturing_ok_tone1:": { - "uc_base": "1f646-1f3fb", - "uc_output": "1f646-1f3fb", - "uc_match": "1f646-1f3fb", - "uc_greedy": "1f646-1f3fb", - "shortnames": [":ok_woman_tone1:"], - "category": "people" - }, - ":person_gesturing_ok_tone2:": { - "uc_base": "1f646-1f3fc", - "uc_output": "1f646-1f3fc", - "uc_match": "1f646-1f3fc", - "uc_greedy": "1f646-1f3fc", - "shortnames": [":ok_woman_tone2:"], - "category": "people" - }, - ":person_gesturing_ok_tone3:": { - "uc_base": "1f646-1f3fd", - "uc_output": "1f646-1f3fd", - "uc_match": "1f646-1f3fd", - "uc_greedy": "1f646-1f3fd", - "shortnames": [":ok_woman_tone3:"], - "category": "people" - }, - ":person_gesturing_ok_tone4:": { - "uc_base": "1f646-1f3fe", - "uc_output": "1f646-1f3fe", - "uc_match": "1f646-1f3fe", - "uc_greedy": "1f646-1f3fe", - "shortnames": [":ok_woman_tone4:"], - "category": "people" - }, - ":person_gesturing_ok_tone5:": { - "uc_base": "1f646-1f3ff", - "uc_output": "1f646-1f3ff", - "uc_match": "1f646-1f3ff", - "uc_greedy": "1f646-1f3ff", - "shortnames": [":ok_woman_tone5:"], - "category": "people" - }, - ":person_getting_haircut_tone1:": { - "uc_base": "1f487-1f3fb", - "uc_output": "1f487-1f3fb", - "uc_match": "1f487-1f3fb", - "uc_greedy": "1f487-1f3fb", - "shortnames": [":haircut_tone1:"], - "category": "people" - }, - ":person_getting_haircut_tone2:": { - "uc_base": "1f487-1f3fc", - "uc_output": "1f487-1f3fc", - "uc_match": "1f487-1f3fc", - "uc_greedy": "1f487-1f3fc", - "shortnames": [":haircut_tone2:"], - "category": "people" - }, - ":person_getting_haircut_tone3:": { - "uc_base": "1f487-1f3fd", - "uc_output": "1f487-1f3fd", - "uc_match": "1f487-1f3fd", - "uc_greedy": "1f487-1f3fd", - "shortnames": [":haircut_tone3:"], - "category": "people" - }, - ":person_getting_haircut_tone4:": { - "uc_base": "1f487-1f3fe", - "uc_output": "1f487-1f3fe", - "uc_match": "1f487-1f3fe", - "uc_greedy": "1f487-1f3fe", - "shortnames": [":haircut_tone4:"], - "category": "people" - }, - ":person_getting_haircut_tone5:": { - "uc_base": "1f487-1f3ff", - "uc_output": "1f487-1f3ff", - "uc_match": "1f487-1f3ff", - "uc_greedy": "1f487-1f3ff", - "shortnames": [":haircut_tone5:"], - "category": "people" - }, - ":person_getting_massage_tone1:": { - "uc_base": "1f486-1f3fb", - "uc_output": "1f486-1f3fb", - "uc_match": "1f486-1f3fb", - "uc_greedy": "1f486-1f3fb", - "shortnames": [":massage_tone1:"], - "category": "people" - }, - ":person_getting_massage_tone2:": { - "uc_base": "1f486-1f3fc", - "uc_output": "1f486-1f3fc", - "uc_match": "1f486-1f3fc", - "uc_greedy": "1f486-1f3fc", - "shortnames": [":massage_tone2:"], - "category": "people" - }, - ":person_getting_massage_tone3:": { - "uc_base": "1f486-1f3fd", - "uc_output": "1f486-1f3fd", - "uc_match": "1f486-1f3fd", - "uc_greedy": "1f486-1f3fd", - "shortnames": [":massage_tone3:"], - "category": "people" - }, - ":person_getting_massage_tone4:": { - "uc_base": "1f486-1f3fe", - "uc_output": "1f486-1f3fe", - "uc_match": "1f486-1f3fe", - "uc_greedy": "1f486-1f3fe", - "shortnames": [":massage_tone4:"], - "category": "people" - }, - ":person_getting_massage_tone5:": { - "uc_base": "1f486-1f3ff", - "uc_output": "1f486-1f3ff", - "uc_match": "1f486-1f3ff", - "uc_greedy": "1f486-1f3ff", - "shortnames": [":massage_tone5:"], - "category": "people" - }, - ":person_golfing_tone1:": { - "uc_base": "1f3cc-1f3fb", - "uc_output": "1f3cc-1f3fb", - "uc_match": "1f3cc-fe0f-1f3fb", - "uc_greedy": "1f3cc-fe0f-1f3fb", - "shortnames": [":person_golfing_light_skin_tone:"], - "category": "activity" - }, - ":person_golfing_tone2:": { - "uc_base": "1f3cc-1f3fc", - "uc_output": "1f3cc-1f3fc", - "uc_match": "1f3cc-fe0f-1f3fc", - "uc_greedy": "1f3cc-fe0f-1f3fc", - "shortnames": [":person_golfing_medium_light_skin_tone:"], - "category": "activity" - }, - ":person_golfing_tone3:": { - "uc_base": "1f3cc-1f3fd", - "uc_output": "1f3cc-1f3fd", - "uc_match": "1f3cc-fe0f-1f3fd", - "uc_greedy": "1f3cc-fe0f-1f3fd", - "shortnames": [":person_golfing_medium_skin_tone:"], - "category": "activity" - }, - ":person_golfing_tone4:": { - "uc_base": "1f3cc-1f3fe", - "uc_output": "1f3cc-1f3fe", - "uc_match": "1f3cc-fe0f-1f3fe", - "uc_greedy": "1f3cc-fe0f-1f3fe", - "shortnames": [":person_golfing_medium_dark_skin_tone:"], - "category": "activity" - }, - ":person_golfing_tone5:": { - "uc_base": "1f3cc-1f3ff", - "uc_output": "1f3cc-1f3ff", - "uc_match": "1f3cc-fe0f-1f3ff", - "uc_greedy": "1f3cc-fe0f-1f3ff", - "shortnames": [":person_golfing_dark_skin_tone:"], - "category": "activity" - }, - ":person_in_bed_tone1:": { - "uc_base": "1f6cc-1f3fb", - "uc_output": "1f6cc-1f3fb", - "uc_match": "1f6cc-1f3fb", - "uc_greedy": "1f6cc-1f3fb", - "shortnames": [":person_in_bed_light_skin_tone:"], - "category": "objects" - }, - ":person_in_bed_tone2:": { - "uc_base": "1f6cc-1f3fc", - "uc_output": "1f6cc-1f3fc", - "uc_match": "1f6cc-1f3fc", - "uc_greedy": "1f6cc-1f3fc", - "shortnames": [":person_in_bed_medium_light_skin_tone:"], - "category": "objects" - }, - ":person_in_bed_tone3:": { - "uc_base": "1f6cc-1f3fd", - "uc_output": "1f6cc-1f3fd", - "uc_match": "1f6cc-1f3fd", - "uc_greedy": "1f6cc-1f3fd", - "shortnames": [":person_in_bed_medium_skin_tone:"], - "category": "objects" - }, - ":person_in_bed_tone4:": { - "uc_base": "1f6cc-1f3fe", - "uc_output": "1f6cc-1f3fe", - "uc_match": "1f6cc-1f3fe", - "uc_greedy": "1f6cc-1f3fe", - "shortnames": [":person_in_bed_medium_dark_skin_tone:"], - "category": "objects" - }, - ":person_in_bed_tone5:": { - "uc_base": "1f6cc-1f3ff", - "uc_output": "1f6cc-1f3ff", - "uc_match": "1f6cc-1f3ff", - "uc_greedy": "1f6cc-1f3ff", - "shortnames": [":person_in_bed_dark_skin_tone:"], - "category": "objects" - }, - ":person_in_lotus_position_tone1:": { - "uc_base": "1f9d8-1f3fb", - "uc_output": "1f9d8-1f3fb", - "uc_match": "1f9d8-1f3fb", - "uc_greedy": "1f9d8-1f3fb", - "shortnames": [":person_in_lotus_position_light_skin_tone:"], - "category": "activity" - }, - ":person_in_lotus_position_tone2:": { - "uc_base": "1f9d8-1f3fc", - "uc_output": "1f9d8-1f3fc", - "uc_match": "1f9d8-1f3fc", - "uc_greedy": "1f9d8-1f3fc", - "shortnames": [":person_in_lotus_position_medium_light_skin_tone:"], - "category": "activity" - }, - ":person_in_lotus_position_tone3:": { - "uc_base": "1f9d8-1f3fd", - "uc_output": "1f9d8-1f3fd", - "uc_match": "1f9d8-1f3fd", - "uc_greedy": "1f9d8-1f3fd", - "shortnames": [":person_in_lotus_position_medium_skin_tone:"], - "category": "activity" - }, - ":person_in_lotus_position_tone4:": { - "uc_base": "1f9d8-1f3fe", - "uc_output": "1f9d8-1f3fe", - "uc_match": "1f9d8-1f3fe", - "uc_greedy": "1f9d8-1f3fe", - "shortnames": [":person_in_lotus_position_medium_dark_skin_tone:"], - "category": "activity" - }, - ":person_in_lotus_position_tone5:": { - "uc_base": "1f9d8-1f3ff", - "uc_output": "1f9d8-1f3ff", - "uc_match": "1f9d8-1f3ff", - "uc_greedy": "1f9d8-1f3ff", - "shortnames": [":person_in_lotus_position_dark_skin_tone:"], - "category": "activity" - }, - ":person_in_steamy_room_tone1:": { - "uc_base": "1f9d6-1f3fb", - "uc_output": "1f9d6-1f3fb", - "uc_match": "1f9d6-1f3fb", - "uc_greedy": "1f9d6-1f3fb", - "shortnames": [":person_in_steamy_room_light_skin_tone:"], - "category": "people" - }, - ":person_in_steamy_room_tone2:": { - "uc_base": "1f9d6-1f3fc", - "uc_output": "1f9d6-1f3fc", - "uc_match": "1f9d6-1f3fc", - "uc_greedy": "1f9d6-1f3fc", - "shortnames": [":person_in_steamy_room_medium_light_skin_tone:"], - "category": "people" - }, - ":person_in_steamy_room_tone3:": { - "uc_base": "1f9d6-1f3fd", - "uc_output": "1f9d6-1f3fd", - "uc_match": "1f9d6-1f3fd", - "uc_greedy": "1f9d6-1f3fd", - "shortnames": [":person_in_steamy_room_medium_skin_tone:"], - "category": "people" - }, - ":person_in_steamy_room_tone4:": { - "uc_base": "1f9d6-1f3fe", - "uc_output": "1f9d6-1f3fe", - "uc_match": "1f9d6-1f3fe", - "uc_greedy": "1f9d6-1f3fe", - "shortnames": [":person_in_steamy_room_medium_dark_skin_tone:"], - "category": "people" - }, - ":person_in_steamy_room_tone5:": { - "uc_base": "1f9d6-1f3ff", - "uc_output": "1f9d6-1f3ff", - "uc_match": "1f9d6-1f3ff", - "uc_greedy": "1f9d6-1f3ff", - "shortnames": [":person_in_steamy_room_dark_skin_tone:"], - "category": "people" - }, - ":person_juggling_tone1:": { - "uc_base": "1f939-1f3fb", - "uc_output": "1f939-1f3fb", - "uc_match": "1f939-1f3fb", - "uc_greedy": "1f939-1f3fb", - "shortnames": [":juggling_tone1:", ":juggler_tone1:"], - "category": "activity" - }, - ":person_juggling_tone2:": { - "uc_base": "1f939-1f3fc", - "uc_output": "1f939-1f3fc", - "uc_match": "1f939-1f3fc", - "uc_greedy": "1f939-1f3fc", - "shortnames": [":juggling_tone2:", ":juggler_tone2:"], - "category": "activity" - }, - ":person_juggling_tone3:": { - "uc_base": "1f939-1f3fd", - "uc_output": "1f939-1f3fd", - "uc_match": "1f939-1f3fd", - "uc_greedy": "1f939-1f3fd", - "shortnames": [":juggling_tone3:", ":juggler_tone3:"], - "category": "activity" - }, - ":person_juggling_tone4:": { - "uc_base": "1f939-1f3fe", - "uc_output": "1f939-1f3fe", - "uc_match": "1f939-1f3fe", - "uc_greedy": "1f939-1f3fe", - "shortnames": [":juggling_tone4:", ":juggler_tone4:"], - "category": "activity" - }, - ":person_juggling_tone5:": { - "uc_base": "1f939-1f3ff", - "uc_output": "1f939-1f3ff", - "uc_match": "1f939-1f3ff", - "uc_greedy": "1f939-1f3ff", - "shortnames": [":juggling_tone5:", ":juggler_tone5:"], - "category": "activity" - }, - ":person_lifting_weights_tone1:": { - "uc_base": "1f3cb-1f3fb", - "uc_output": "1f3cb-1f3fb", - "uc_match": "1f3cb-fe0f-1f3fb", - "uc_greedy": "1f3cb-fe0f-1f3fb", - "shortnames": [":lifter_tone1:", ":weight_lifter_tone1:"], - "category": "activity" - }, - ":person_lifting_weights_tone2:": { - "uc_base": "1f3cb-1f3fc", - "uc_output": "1f3cb-1f3fc", - "uc_match": "1f3cb-fe0f-1f3fc", - "uc_greedy": "1f3cb-fe0f-1f3fc", - "shortnames": [":lifter_tone2:", ":weight_lifter_tone2:"], - "category": "activity" - }, - ":person_lifting_weights_tone3:": { - "uc_base": "1f3cb-1f3fd", - "uc_output": "1f3cb-1f3fd", - "uc_match": "1f3cb-fe0f-1f3fd", - "uc_greedy": "1f3cb-fe0f-1f3fd", - "shortnames": [":lifter_tone3:", ":weight_lifter_tone3:"], - "category": "activity" - }, - ":person_lifting_weights_tone4:": { - "uc_base": "1f3cb-1f3fe", - "uc_output": "1f3cb-1f3fe", - "uc_match": "1f3cb-fe0f-1f3fe", - "uc_greedy": "1f3cb-fe0f-1f3fe", - "shortnames": [":lifter_tone4:", ":weight_lifter_tone4:"], - "category": "activity" - }, - ":person_lifting_weights_tone5:": { - "uc_base": "1f3cb-1f3ff", - "uc_output": "1f3cb-1f3ff", - "uc_match": "1f3cb-fe0f-1f3ff", - "uc_greedy": "1f3cb-fe0f-1f3ff", - "shortnames": [":lifter_tone5:", ":weight_lifter_tone5:"], - "category": "activity" - }, - ":person_mountain_biking_tone1:": { - "uc_base": "1f6b5-1f3fb", - "uc_output": "1f6b5-1f3fb", - "uc_match": "1f6b5-1f3fb", - "uc_greedy": "1f6b5-1f3fb", - "shortnames": [":mountain_bicyclist_tone1:"], - "category": "activity" - }, - ":person_mountain_biking_tone2:": { - "uc_base": "1f6b5-1f3fc", - "uc_output": "1f6b5-1f3fc", - "uc_match": "1f6b5-1f3fc", - "uc_greedy": "1f6b5-1f3fc", - "shortnames": [":mountain_bicyclist_tone2:"], - "category": "activity" - }, - ":person_mountain_biking_tone3:": { - "uc_base": "1f6b5-1f3fd", - "uc_output": "1f6b5-1f3fd", - "uc_match": "1f6b5-1f3fd", - "uc_greedy": "1f6b5-1f3fd", - "shortnames": [":mountain_bicyclist_tone3:"], - "category": "activity" - }, - ":person_mountain_biking_tone4:": { - "uc_base": "1f6b5-1f3fe", - "uc_output": "1f6b5-1f3fe", - "uc_match": "1f6b5-1f3fe", - "uc_greedy": "1f6b5-1f3fe", - "shortnames": [":mountain_bicyclist_tone4:"], - "category": "activity" - }, - ":person_mountain_biking_tone5:": { - "uc_base": "1f6b5-1f3ff", - "uc_output": "1f6b5-1f3ff", - "uc_match": "1f6b5-1f3ff", - "uc_greedy": "1f6b5-1f3ff", - "shortnames": [":mountain_bicyclist_tone5:"], - "category": "activity" - }, - ":person_playing_handball_tone1:": { - "uc_base": "1f93e-1f3fb", - "uc_output": "1f93e-1f3fb", - "uc_match": "1f93e-1f3fb", - "uc_greedy": "1f93e-1f3fb", - "shortnames": [":handball_tone1:"], - "category": "activity" - }, - ":person_playing_handball_tone2:": { - "uc_base": "1f93e-1f3fc", - "uc_output": "1f93e-1f3fc", - "uc_match": "1f93e-1f3fc", - "uc_greedy": "1f93e-1f3fc", - "shortnames": [":handball_tone2:"], - "category": "activity" - }, - ":person_playing_handball_tone3:": { - "uc_base": "1f93e-1f3fd", - "uc_output": "1f93e-1f3fd", - "uc_match": "1f93e-1f3fd", - "uc_greedy": "1f93e-1f3fd", - "shortnames": [":handball_tone3:"], - "category": "activity" - }, - ":person_playing_handball_tone4:": { - "uc_base": "1f93e-1f3fe", - "uc_output": "1f93e-1f3fe", - "uc_match": "1f93e-1f3fe", - "uc_greedy": "1f93e-1f3fe", - "shortnames": [":handball_tone4:"], - "category": "activity" - }, - ":person_playing_handball_tone5:": { - "uc_base": "1f93e-1f3ff", - "uc_output": "1f93e-1f3ff", - "uc_match": "1f93e-1f3ff", - "uc_greedy": "1f93e-1f3ff", - "shortnames": [":handball_tone5:"], - "category": "activity" - }, - ":person_playing_water_polo_tone1:": { - "uc_base": "1f93d-1f3fb", - "uc_output": "1f93d-1f3fb", - "uc_match": "1f93d-1f3fb", - "uc_greedy": "1f93d-1f3fb", - "shortnames": [":water_polo_tone1:"], - "category": "activity" - }, - ":person_playing_water_polo_tone2:": { - "uc_base": "1f93d-1f3fc", - "uc_output": "1f93d-1f3fc", - "uc_match": "1f93d-1f3fc", - "uc_greedy": "1f93d-1f3fc", - "shortnames": [":water_polo_tone2:"], - "category": "activity" - }, - ":person_playing_water_polo_tone3:": { - "uc_base": "1f93d-1f3fd", - "uc_output": "1f93d-1f3fd", - "uc_match": "1f93d-1f3fd", - "uc_greedy": "1f93d-1f3fd", - "shortnames": [":water_polo_tone3:"], - "category": "activity" - }, - ":person_playing_water_polo_tone4:": { - "uc_base": "1f93d-1f3fe", - "uc_output": "1f93d-1f3fe", - "uc_match": "1f93d-1f3fe", - "uc_greedy": "1f93d-1f3fe", - "shortnames": [":water_polo_tone4:"], - "category": "activity" - }, - ":person_playing_water_polo_tone5:": { - "uc_base": "1f93d-1f3ff", - "uc_output": "1f93d-1f3ff", - "uc_match": "1f93d-1f3ff", - "uc_greedy": "1f93d-1f3ff", - "shortnames": [":water_polo_tone5:"], - "category": "activity" - }, - ":person_pouting_tone1:": { - "uc_base": "1f64e-1f3fb", - "uc_output": "1f64e-1f3fb", - "uc_match": "1f64e-1f3fb", - "uc_greedy": "1f64e-1f3fb", - "shortnames": [":person_with_pouting_face_tone1:"], - "category": "people" - }, - ":person_pouting_tone2:": { - "uc_base": "1f64e-1f3fc", - "uc_output": "1f64e-1f3fc", - "uc_match": "1f64e-1f3fc", - "uc_greedy": "1f64e-1f3fc", - "shortnames": [":person_with_pouting_face_tone2:"], - "category": "people" - }, - ":person_pouting_tone3:": { - "uc_base": "1f64e-1f3fd", - "uc_output": "1f64e-1f3fd", - "uc_match": "1f64e-1f3fd", - "uc_greedy": "1f64e-1f3fd", - "shortnames": [":person_with_pouting_face_tone3:"], - "category": "people" - }, - ":person_pouting_tone4:": { - "uc_base": "1f64e-1f3fe", - "uc_output": "1f64e-1f3fe", - "uc_match": "1f64e-1f3fe", - "uc_greedy": "1f64e-1f3fe", - "shortnames": [":person_with_pouting_face_tone4:"], - "category": "people" - }, - ":person_pouting_tone5:": { - "uc_base": "1f64e-1f3ff", - "uc_output": "1f64e-1f3ff", - "uc_match": "1f64e-1f3ff", - "uc_greedy": "1f64e-1f3ff", - "shortnames": [":person_with_pouting_face_tone5:"], - "category": "people" - }, - ":person_raising_hand_tone1:": { - "uc_base": "1f64b-1f3fb", - "uc_output": "1f64b-1f3fb", - "uc_match": "1f64b-1f3fb", - "uc_greedy": "1f64b-1f3fb", - "shortnames": [":raising_hand_tone1:"], - "category": "people" - }, - ":person_raising_hand_tone2:": { - "uc_base": "1f64b-1f3fc", - "uc_output": "1f64b-1f3fc", - "uc_match": "1f64b-1f3fc", - "uc_greedy": "1f64b-1f3fc", - "shortnames": [":raising_hand_tone2:"], - "category": "people" - }, - ":person_raising_hand_tone3:": { - "uc_base": "1f64b-1f3fd", - "uc_output": "1f64b-1f3fd", - "uc_match": "1f64b-1f3fd", - "uc_greedy": "1f64b-1f3fd", - "shortnames": [":raising_hand_tone3:"], - "category": "people" - }, - ":person_raising_hand_tone4:": { - "uc_base": "1f64b-1f3fe", - "uc_output": "1f64b-1f3fe", - "uc_match": "1f64b-1f3fe", - "uc_greedy": "1f64b-1f3fe", - "shortnames": [":raising_hand_tone4:"], - "category": "people" - }, - ":person_raising_hand_tone5:": { - "uc_base": "1f64b-1f3ff", - "uc_output": "1f64b-1f3ff", - "uc_match": "1f64b-1f3ff", - "uc_greedy": "1f64b-1f3ff", - "shortnames": [":raising_hand_tone5:"], - "category": "people" - }, - ":person_rowing_boat_tone1:": { - "uc_base": "1f6a3-1f3fb", - "uc_output": "1f6a3-1f3fb", - "uc_match": "1f6a3-1f3fb", - "uc_greedy": "1f6a3-1f3fb", - "shortnames": [":rowboat_tone1:"], - "category": "activity" - }, - ":person_rowing_boat_tone2:": { - "uc_base": "1f6a3-1f3fc", - "uc_output": "1f6a3-1f3fc", - "uc_match": "1f6a3-1f3fc", - "uc_greedy": "1f6a3-1f3fc", - "shortnames": [":rowboat_tone2:"], - "category": "activity" - }, - ":person_rowing_boat_tone3:": { - "uc_base": "1f6a3-1f3fd", - "uc_output": "1f6a3-1f3fd", - "uc_match": "1f6a3-1f3fd", - "uc_greedy": "1f6a3-1f3fd", - "shortnames": [":rowboat_tone3:"], - "category": "activity" - }, - ":person_rowing_boat_tone4:": { - "uc_base": "1f6a3-1f3fe", - "uc_output": "1f6a3-1f3fe", - "uc_match": "1f6a3-1f3fe", - "uc_greedy": "1f6a3-1f3fe", - "shortnames": [":rowboat_tone4:"], - "category": "activity" - }, - ":person_rowing_boat_tone5:": { - "uc_base": "1f6a3-1f3ff", - "uc_output": "1f6a3-1f3ff", - "uc_match": "1f6a3-1f3ff", - "uc_greedy": "1f6a3-1f3ff", - "shortnames": [":rowboat_tone5:"], - "category": "activity" - }, - ":person_running_tone1:": { - "uc_base": "1f3c3-1f3fb", - "uc_output": "1f3c3-1f3fb", - "uc_match": "1f3c3-1f3fb", - "uc_greedy": "1f3c3-1f3fb", - "shortnames": [":runner_tone1:"], - "category": "people" - }, - ":person_running_tone2:": { - "uc_base": "1f3c3-1f3fc", - "uc_output": "1f3c3-1f3fc", - "uc_match": "1f3c3-1f3fc", - "uc_greedy": "1f3c3-1f3fc", - "shortnames": [":runner_tone2:"], - "category": "people" - }, - ":person_running_tone3:": { - "uc_base": "1f3c3-1f3fd", - "uc_output": "1f3c3-1f3fd", - "uc_match": "1f3c3-1f3fd", - "uc_greedy": "1f3c3-1f3fd", - "shortnames": [":runner_tone3:"], - "category": "people" - }, - ":person_running_tone4:": { - "uc_base": "1f3c3-1f3fe", - "uc_output": "1f3c3-1f3fe", - "uc_match": "1f3c3-1f3fe", - "uc_greedy": "1f3c3-1f3fe", - "shortnames": [":runner_tone4:"], - "category": "people" - }, - ":person_running_tone5:": { - "uc_base": "1f3c3-1f3ff", - "uc_output": "1f3c3-1f3ff", - "uc_match": "1f3c3-1f3ff", - "uc_greedy": "1f3c3-1f3ff", - "shortnames": [":runner_tone5:"], - "category": "people" - }, - ":person_shrugging_tone1:": { - "uc_base": "1f937-1f3fb", - "uc_output": "1f937-1f3fb", - "uc_match": "1f937-1f3fb", - "uc_greedy": "1f937-1f3fb", - "shortnames": [":shrug_tone1:"], - "category": "people" - }, - ":person_shrugging_tone2:": { - "uc_base": "1f937-1f3fc", - "uc_output": "1f937-1f3fc", - "uc_match": "1f937-1f3fc", - "uc_greedy": "1f937-1f3fc", - "shortnames": [":shrug_tone2:"], - "category": "people" - }, - ":person_shrugging_tone3:": { - "uc_base": "1f937-1f3fd", - "uc_output": "1f937-1f3fd", - "uc_match": "1f937-1f3fd", - "uc_greedy": "1f937-1f3fd", - "shortnames": [":shrug_tone3:"], - "category": "people" - }, - ":person_shrugging_tone4:": { - "uc_base": "1f937-1f3fe", - "uc_output": "1f937-1f3fe", - "uc_match": "1f937-1f3fe", - "uc_greedy": "1f937-1f3fe", - "shortnames": [":shrug_tone4:"], - "category": "people" - }, - ":person_shrugging_tone5:": { - "uc_base": "1f937-1f3ff", - "uc_output": "1f937-1f3ff", - "uc_match": "1f937-1f3ff", - "uc_greedy": "1f937-1f3ff", - "shortnames": [":shrug_tone5:"], - "category": "people" - }, - ":person_surfing_tone1:": { - "uc_base": "1f3c4-1f3fb", - "uc_output": "1f3c4-1f3fb", - "uc_match": "1f3c4-1f3fb", - "uc_greedy": "1f3c4-1f3fb", - "shortnames": [":surfer_tone1:"], - "category": "activity" - }, - ":person_surfing_tone2:": { - "uc_base": "1f3c4-1f3fc", - "uc_output": "1f3c4-1f3fc", - "uc_match": "1f3c4-1f3fc", - "uc_greedy": "1f3c4-1f3fc", - "shortnames": [":surfer_tone2:"], - "category": "activity" - }, - ":person_surfing_tone3:": { - "uc_base": "1f3c4-1f3fd", - "uc_output": "1f3c4-1f3fd", - "uc_match": "1f3c4-1f3fd", - "uc_greedy": "1f3c4-1f3fd", - "shortnames": [":surfer_tone3:"], - "category": "activity" - }, - ":person_surfing_tone4:": { - "uc_base": "1f3c4-1f3fe", - "uc_output": "1f3c4-1f3fe", - "uc_match": "1f3c4-1f3fe", - "uc_greedy": "1f3c4-1f3fe", - "shortnames": [":surfer_tone4:"], - "category": "activity" - }, - ":person_surfing_tone5:": { - "uc_base": "1f3c4-1f3ff", - "uc_output": "1f3c4-1f3ff", - "uc_match": "1f3c4-1f3ff", - "uc_greedy": "1f3c4-1f3ff", - "shortnames": [":surfer_tone5:"], - "category": "activity" - }, - ":person_swimming_tone1:": { - "uc_base": "1f3ca-1f3fb", - "uc_output": "1f3ca-1f3fb", - "uc_match": "1f3ca-1f3fb", - "uc_greedy": "1f3ca-1f3fb", - "shortnames": [":swimmer_tone1:"], - "category": "activity" - }, - ":person_swimming_tone2:": { - "uc_base": "1f3ca-1f3fc", - "uc_output": "1f3ca-1f3fc", - "uc_match": "1f3ca-1f3fc", - "uc_greedy": "1f3ca-1f3fc", - "shortnames": [":swimmer_tone2:"], - "category": "activity" - }, - ":person_swimming_tone3:": { - "uc_base": "1f3ca-1f3fd", - "uc_output": "1f3ca-1f3fd", - "uc_match": "1f3ca-1f3fd", - "uc_greedy": "1f3ca-1f3fd", - "shortnames": [":swimmer_tone3:"], - "category": "activity" - }, - ":person_swimming_tone4:": { - "uc_base": "1f3ca-1f3fe", - "uc_output": "1f3ca-1f3fe", - "uc_match": "1f3ca-1f3fe", - "uc_greedy": "1f3ca-1f3fe", - "shortnames": [":swimmer_tone4:"], - "category": "activity" - }, - ":person_swimming_tone5:": { - "uc_base": "1f3ca-1f3ff", - "uc_output": "1f3ca-1f3ff", - "uc_match": "1f3ca-1f3ff", - "uc_greedy": "1f3ca-1f3ff", - "shortnames": [":swimmer_tone5:"], - "category": "activity" - }, - ":person_tipping_hand_tone1:": { - "uc_base": "1f481-1f3fb", - "uc_output": "1f481-1f3fb", - "uc_match": "1f481-1f3fb", - "uc_greedy": "1f481-1f3fb", - "shortnames": [":information_desk_person_tone1:"], - "category": "people" - }, - ":person_tipping_hand_tone2:": { - "uc_base": "1f481-1f3fc", - "uc_output": "1f481-1f3fc", - "uc_match": "1f481-1f3fc", - "uc_greedy": "1f481-1f3fc", - "shortnames": [":information_desk_person_tone2:"], - "category": "people" - }, - ":person_tipping_hand_tone3:": { - "uc_base": "1f481-1f3fd", - "uc_output": "1f481-1f3fd", - "uc_match": "1f481-1f3fd", - "uc_greedy": "1f481-1f3fd", - "shortnames": [":information_desk_person_tone3:"], - "category": "people" - }, - ":person_tipping_hand_tone4:": { - "uc_base": "1f481-1f3fe", - "uc_output": "1f481-1f3fe", - "uc_match": "1f481-1f3fe", - "uc_greedy": "1f481-1f3fe", - "shortnames": [":information_desk_person_tone4:"], - "category": "people" - }, - ":person_tipping_hand_tone5:": { - "uc_base": "1f481-1f3ff", - "uc_output": "1f481-1f3ff", - "uc_match": "1f481-1f3ff", - "uc_greedy": "1f481-1f3ff", - "shortnames": [":information_desk_person_tone5:"], - "category": "people" - }, - ":person_walking_tone1:": { - "uc_base": "1f6b6-1f3fb", - "uc_output": "1f6b6-1f3fb", - "uc_match": "1f6b6-1f3fb", - "uc_greedy": "1f6b6-1f3fb", - "shortnames": [":walking_tone1:"], - "category": "people" - }, - ":person_walking_tone2:": { - "uc_base": "1f6b6-1f3fc", - "uc_output": "1f6b6-1f3fc", - "uc_match": "1f6b6-1f3fc", - "uc_greedy": "1f6b6-1f3fc", - "shortnames": [":walking_tone2:"], - "category": "people" - }, - ":person_walking_tone3:": { - "uc_base": "1f6b6-1f3fd", - "uc_output": "1f6b6-1f3fd", - "uc_match": "1f6b6-1f3fd", - "uc_greedy": "1f6b6-1f3fd", - "shortnames": [":walking_tone3:"], - "category": "people" - }, - ":person_walking_tone4:": { - "uc_base": "1f6b6-1f3fe", - "uc_output": "1f6b6-1f3fe", - "uc_match": "1f6b6-1f3fe", - "uc_greedy": "1f6b6-1f3fe", - "shortnames": [":walking_tone4:"], - "category": "people" - }, - ":person_walking_tone5:": { - "uc_base": "1f6b6-1f3ff", - "uc_output": "1f6b6-1f3ff", - "uc_match": "1f6b6-1f3ff", - "uc_greedy": "1f6b6-1f3ff", - "shortnames": [":walking_tone5:"], - "category": "people" - }, - ":person_wearing_turban_tone1:": { - "uc_base": "1f473-1f3fb", - "uc_output": "1f473-1f3fb", - "uc_match": "1f473-1f3fb", - "uc_greedy": "1f473-1f3fb", - "shortnames": [":man_with_turban_tone1:"], - "category": "people" - }, - ":person_wearing_turban_tone2:": { - "uc_base": "1f473-1f3fc", - "uc_output": "1f473-1f3fc", - "uc_match": "1f473-1f3fc", - "uc_greedy": "1f473-1f3fc", - "shortnames": [":man_with_turban_tone2:"], - "category": "people" - }, - ":person_wearing_turban_tone3:": { - "uc_base": "1f473-1f3fd", - "uc_output": "1f473-1f3fd", - "uc_match": "1f473-1f3fd", - "uc_greedy": "1f473-1f3fd", - "shortnames": [":man_with_turban_tone3:"], - "category": "people" - }, - ":person_wearing_turban_tone4:": { - "uc_base": "1f473-1f3fe", - "uc_output": "1f473-1f3fe", - "uc_match": "1f473-1f3fe", - "uc_greedy": "1f473-1f3fe", - "shortnames": [":man_with_turban_tone4:"], - "category": "people" - }, - ":person_wearing_turban_tone5:": { - "uc_base": "1f473-1f3ff", - "uc_output": "1f473-1f3ff", - "uc_match": "1f473-1f3ff", - "uc_greedy": "1f473-1f3ff", - "shortnames": [":man_with_turban_tone5:"], - "category": "people" - }, - ":point_down_tone1:": { - "uc_base": "1f447-1f3fb", - "uc_output": "1f447-1f3fb", - "uc_match": "1f447-1f3fb", - "uc_greedy": "1f447-1f3fb", - "shortnames": [], - "category": "people" - }, - ":point_down_tone2:": { - "uc_base": "1f447-1f3fc", - "uc_output": "1f447-1f3fc", - "uc_match": "1f447-1f3fc", - "uc_greedy": "1f447-1f3fc", - "shortnames": [], - "category": "people" - }, - ":point_down_tone3:": { - "uc_base": "1f447-1f3fd", - "uc_output": "1f447-1f3fd", - "uc_match": "1f447-1f3fd", - "uc_greedy": "1f447-1f3fd", - "shortnames": [], - "category": "people" - }, - ":point_down_tone4:": { - "uc_base": "1f447-1f3fe", - "uc_output": "1f447-1f3fe", - "uc_match": "1f447-1f3fe", - "uc_greedy": "1f447-1f3fe", - "shortnames": [], - "category": "people" - }, - ":point_down_tone5:": { - "uc_base": "1f447-1f3ff", - "uc_output": "1f447-1f3ff", - "uc_match": "1f447-1f3ff", - "uc_greedy": "1f447-1f3ff", - "shortnames": [], - "category": "people" - }, - ":point_left_tone1:": { - "uc_base": "1f448-1f3fb", - "uc_output": "1f448-1f3fb", - "uc_match": "1f448-1f3fb", - "uc_greedy": "1f448-1f3fb", - "shortnames": [], - "category": "people" - }, - ":point_left_tone2:": { - "uc_base": "1f448-1f3fc", - "uc_output": "1f448-1f3fc", - "uc_match": "1f448-1f3fc", - "uc_greedy": "1f448-1f3fc", - "shortnames": [], - "category": "people" - }, - ":point_left_tone3:": { - "uc_base": "1f448-1f3fd", - "uc_output": "1f448-1f3fd", - "uc_match": "1f448-1f3fd", - "uc_greedy": "1f448-1f3fd", - "shortnames": [], - "category": "people" - }, - ":point_left_tone4:": { - "uc_base": "1f448-1f3fe", - "uc_output": "1f448-1f3fe", - "uc_match": "1f448-1f3fe", - "uc_greedy": "1f448-1f3fe", - "shortnames": [], - "category": "people" - }, - ":point_left_tone5:": { - "uc_base": "1f448-1f3ff", - "uc_output": "1f448-1f3ff", - "uc_match": "1f448-1f3ff", - "uc_greedy": "1f448-1f3ff", - "shortnames": [], - "category": "people" - }, - ":point_right_tone1:": { - "uc_base": "1f449-1f3fb", - "uc_output": "1f449-1f3fb", - "uc_match": "1f449-1f3fb", - "uc_greedy": "1f449-1f3fb", - "shortnames": [], - "category": "people" - }, - ":point_right_tone2:": { - "uc_base": "1f449-1f3fc", - "uc_output": "1f449-1f3fc", - "uc_match": "1f449-1f3fc", - "uc_greedy": "1f449-1f3fc", - "shortnames": [], - "category": "people" - }, - ":point_right_tone3:": { - "uc_base": "1f449-1f3fd", - "uc_output": "1f449-1f3fd", - "uc_match": "1f449-1f3fd", - "uc_greedy": "1f449-1f3fd", - "shortnames": [], - "category": "people" - }, - ":point_right_tone4:": { - "uc_base": "1f449-1f3fe", - "uc_output": "1f449-1f3fe", - "uc_match": "1f449-1f3fe", - "uc_greedy": "1f449-1f3fe", - "shortnames": [], - "category": "people" - }, - ":point_right_tone5:": { - "uc_base": "1f449-1f3ff", - "uc_output": "1f449-1f3ff", - "uc_match": "1f449-1f3ff", - "uc_greedy": "1f449-1f3ff", - "shortnames": [], - "category": "people" - }, - ":point_up_2_tone1:": { - "uc_base": "1f446-1f3fb", - "uc_output": "1f446-1f3fb", - "uc_match": "1f446-1f3fb", - "uc_greedy": "1f446-1f3fb", - "shortnames": [], - "category": "people" - }, - ":point_up_2_tone2:": { - "uc_base": "1f446-1f3fc", - "uc_output": "1f446-1f3fc", - "uc_match": "1f446-1f3fc", - "uc_greedy": "1f446-1f3fc", - "shortnames": [], - "category": "people" - }, - ":point_up_2_tone3:": { - "uc_base": "1f446-1f3fd", - "uc_output": "1f446-1f3fd", - "uc_match": "1f446-1f3fd", - "uc_greedy": "1f446-1f3fd", - "shortnames": [], - "category": "people" - }, - ":point_up_2_tone4:": { - "uc_base": "1f446-1f3fe", - "uc_output": "1f446-1f3fe", - "uc_match": "1f446-1f3fe", - "uc_greedy": "1f446-1f3fe", - "shortnames": [], - "category": "people" - }, - ":point_up_2_tone5:": { - "uc_base": "1f446-1f3ff", - "uc_output": "1f446-1f3ff", - "uc_match": "1f446-1f3ff", - "uc_greedy": "1f446-1f3ff", - "shortnames": [], - "category": "people" - }, - ":police_officer_tone1:": { - "uc_base": "1f46e-1f3fb", - "uc_output": "1f46e-1f3fb", - "uc_match": "1f46e-1f3fb", - "uc_greedy": "1f46e-1f3fb", - "shortnames": [":cop_tone1:"], - "category": "people" - }, - ":police_officer_tone2:": { - "uc_base": "1f46e-1f3fc", - "uc_output": "1f46e-1f3fc", - "uc_match": "1f46e-1f3fc", - "uc_greedy": "1f46e-1f3fc", - "shortnames": [":cop_tone2:"], - "category": "people" - }, - ":police_officer_tone3:": { - "uc_base": "1f46e-1f3fd", - "uc_output": "1f46e-1f3fd", - "uc_match": "1f46e-1f3fd", - "uc_greedy": "1f46e-1f3fd", - "shortnames": [":cop_tone3:"], - "category": "people" - }, - ":police_officer_tone4:": { - "uc_base": "1f46e-1f3fe", - "uc_output": "1f46e-1f3fe", - "uc_match": "1f46e-1f3fe", - "uc_greedy": "1f46e-1f3fe", - "shortnames": [":cop_tone4:"], - "category": "people" - }, - ":police_officer_tone5:": { - "uc_base": "1f46e-1f3ff", - "uc_output": "1f46e-1f3ff", - "uc_match": "1f46e-1f3ff", - "uc_greedy": "1f46e-1f3ff", - "shortnames": [":cop_tone5:"], - "category": "people" - }, - ":pray_tone1:": { - "uc_base": "1f64f-1f3fb", - "uc_output": "1f64f-1f3fb", - "uc_match": "1f64f-1f3fb", - "uc_greedy": "1f64f-1f3fb", - "shortnames": [], - "category": "people" - }, - ":pray_tone2:": { - "uc_base": "1f64f-1f3fc", - "uc_output": "1f64f-1f3fc", - "uc_match": "1f64f-1f3fc", - "uc_greedy": "1f64f-1f3fc", - "shortnames": [], - "category": "people" - }, - ":pray_tone3:": { - "uc_base": "1f64f-1f3fd", - "uc_output": "1f64f-1f3fd", - "uc_match": "1f64f-1f3fd", - "uc_greedy": "1f64f-1f3fd", - "shortnames": [], - "category": "people" - }, - ":pray_tone4:": { - "uc_base": "1f64f-1f3fe", - "uc_output": "1f64f-1f3fe", - "uc_match": "1f64f-1f3fe", - "uc_greedy": "1f64f-1f3fe", - "shortnames": [], - "category": "people" - }, - ":pray_tone5:": { - "uc_base": "1f64f-1f3ff", - "uc_output": "1f64f-1f3ff", - "uc_match": "1f64f-1f3ff", - "uc_greedy": "1f64f-1f3ff", - "shortnames": [], - "category": "people" - }, - ":pregnant_woman_tone1:": { - "uc_base": "1f930-1f3fb", - "uc_output": "1f930-1f3fb", - "uc_match": "1f930-1f3fb", - "uc_greedy": "1f930-1f3fb", - "shortnames": [":expecting_woman_tone1:"], - "category": "people" - }, - ":pregnant_woman_tone2:": { - "uc_base": "1f930-1f3fc", - "uc_output": "1f930-1f3fc", - "uc_match": "1f930-1f3fc", - "uc_greedy": "1f930-1f3fc", - "shortnames": [":expecting_woman_tone2:"], - "category": "people" - }, - ":pregnant_woman_tone3:": { - "uc_base": "1f930-1f3fd", - "uc_output": "1f930-1f3fd", - "uc_match": "1f930-1f3fd", - "uc_greedy": "1f930-1f3fd", - "shortnames": [":expecting_woman_tone3:"], - "category": "people" - }, - ":pregnant_woman_tone4:": { - "uc_base": "1f930-1f3fe", - "uc_output": "1f930-1f3fe", - "uc_match": "1f930-1f3fe", - "uc_greedy": "1f930-1f3fe", - "shortnames": [":expecting_woman_tone4:"], - "category": "people" - }, - ":pregnant_woman_tone5:": { - "uc_base": "1f930-1f3ff", - "uc_output": "1f930-1f3ff", - "uc_match": "1f930-1f3ff", - "uc_greedy": "1f930-1f3ff", - "shortnames": [":expecting_woman_tone5:"], - "category": "people" - }, - ":prince_tone1:": { - "uc_base": "1f934-1f3fb", - "uc_output": "1f934-1f3fb", - "uc_match": "1f934-1f3fb", - "uc_greedy": "1f934-1f3fb", - "shortnames": [], - "category": "people" - }, - ":prince_tone2:": { - "uc_base": "1f934-1f3fc", - "uc_output": "1f934-1f3fc", - "uc_match": "1f934-1f3fc", - "uc_greedy": "1f934-1f3fc", - "shortnames": [], - "category": "people" - }, - ":prince_tone3:": { - "uc_base": "1f934-1f3fd", - "uc_output": "1f934-1f3fd", - "uc_match": "1f934-1f3fd", - "uc_greedy": "1f934-1f3fd", - "shortnames": [], - "category": "people" - }, - ":prince_tone4:": { - "uc_base": "1f934-1f3fe", - "uc_output": "1f934-1f3fe", - "uc_match": "1f934-1f3fe", - "uc_greedy": "1f934-1f3fe", - "shortnames": [], - "category": "people" - }, - ":prince_tone5:": { - "uc_base": "1f934-1f3ff", - "uc_output": "1f934-1f3ff", - "uc_match": "1f934-1f3ff", - "uc_greedy": "1f934-1f3ff", - "shortnames": [], - "category": "people" - }, - ":princess_tone1:": { - "uc_base": "1f478-1f3fb", - "uc_output": "1f478-1f3fb", - "uc_match": "1f478-1f3fb", - "uc_greedy": "1f478-1f3fb", - "shortnames": [], - "category": "people" - }, - ":princess_tone2:": { - "uc_base": "1f478-1f3fc", - "uc_output": "1f478-1f3fc", - "uc_match": "1f478-1f3fc", - "uc_greedy": "1f478-1f3fc", - "shortnames": [], - "category": "people" - }, - ":princess_tone3:": { - "uc_base": "1f478-1f3fd", - "uc_output": "1f478-1f3fd", - "uc_match": "1f478-1f3fd", - "uc_greedy": "1f478-1f3fd", - "shortnames": [], - "category": "people" - }, - ":princess_tone4:": { - "uc_base": "1f478-1f3fe", - "uc_output": "1f478-1f3fe", - "uc_match": "1f478-1f3fe", - "uc_greedy": "1f478-1f3fe", - "shortnames": [], - "category": "people" - }, - ":princess_tone5:": { - "uc_base": "1f478-1f3ff", - "uc_output": "1f478-1f3ff", - "uc_match": "1f478-1f3ff", - "uc_greedy": "1f478-1f3ff", - "shortnames": [], - "category": "people" - }, - ":punch_tone1:": { - "uc_base": "1f44a-1f3fb", - "uc_output": "1f44a-1f3fb", - "uc_match": "1f44a-1f3fb", - "uc_greedy": "1f44a-1f3fb", - "shortnames": [], - "category": "people" - }, - ":punch_tone2:": { - "uc_base": "1f44a-1f3fc", - "uc_output": "1f44a-1f3fc", - "uc_match": "1f44a-1f3fc", - "uc_greedy": "1f44a-1f3fc", - "shortnames": [], - "category": "people" - }, - ":punch_tone3:": { - "uc_base": "1f44a-1f3fd", - "uc_output": "1f44a-1f3fd", - "uc_match": "1f44a-1f3fd", - "uc_greedy": "1f44a-1f3fd", - "shortnames": [], - "category": "people" - }, - ":punch_tone4:": { - "uc_base": "1f44a-1f3fe", - "uc_output": "1f44a-1f3fe", - "uc_match": "1f44a-1f3fe", - "uc_greedy": "1f44a-1f3fe", - "shortnames": [], - "category": "people" - }, - ":punch_tone5:": { - "uc_base": "1f44a-1f3ff", - "uc_output": "1f44a-1f3ff", - "uc_match": "1f44a-1f3ff", - "uc_greedy": "1f44a-1f3ff", - "shortnames": [], - "category": "people" - }, - ":raised_back_of_hand_tone1:": { - "uc_base": "1f91a-1f3fb", - "uc_output": "1f91a-1f3fb", - "uc_match": "1f91a-1f3fb", - "uc_greedy": "1f91a-1f3fb", - "shortnames": [":back_of_hand_tone1:"], - "category": "people" - }, - ":raised_back_of_hand_tone2:": { - "uc_base": "1f91a-1f3fc", - "uc_output": "1f91a-1f3fc", - "uc_match": "1f91a-1f3fc", - "uc_greedy": "1f91a-1f3fc", - "shortnames": [":back_of_hand_tone2:"], - "category": "people" - }, - ":raised_back_of_hand_tone3:": { - "uc_base": "1f91a-1f3fd", - "uc_output": "1f91a-1f3fd", - "uc_match": "1f91a-1f3fd", - "uc_greedy": "1f91a-1f3fd", - "shortnames": [":back_of_hand_tone3:"], - "category": "people" - }, - ":raised_back_of_hand_tone4:": { - "uc_base": "1f91a-1f3fe", - "uc_output": "1f91a-1f3fe", - "uc_match": "1f91a-1f3fe", - "uc_greedy": "1f91a-1f3fe", - "shortnames": [":back_of_hand_tone4:"], - "category": "people" - }, - ":raised_back_of_hand_tone5:": { - "uc_base": "1f91a-1f3ff", - "uc_output": "1f91a-1f3ff", - "uc_match": "1f91a-1f3ff", - "uc_greedy": "1f91a-1f3ff", - "shortnames": [":back_of_hand_tone5:"], - "category": "people" - }, - ":raised_hands_tone1:": { - "uc_base": "1f64c-1f3fb", - "uc_output": "1f64c-1f3fb", - "uc_match": "1f64c-1f3fb", - "uc_greedy": "1f64c-1f3fb", - "shortnames": [], - "category": "people" - }, - ":raised_hands_tone2:": { - "uc_base": "1f64c-1f3fc", - "uc_output": "1f64c-1f3fc", - "uc_match": "1f64c-1f3fc", - "uc_greedy": "1f64c-1f3fc", - "shortnames": [], - "category": "people" - }, - ":raised_hands_tone3:": { - "uc_base": "1f64c-1f3fd", - "uc_output": "1f64c-1f3fd", - "uc_match": "1f64c-1f3fd", - "uc_greedy": "1f64c-1f3fd", - "shortnames": [], - "category": "people" - }, - ":raised_hands_tone4:": { - "uc_base": "1f64c-1f3fe", - "uc_output": "1f64c-1f3fe", - "uc_match": "1f64c-1f3fe", - "uc_greedy": "1f64c-1f3fe", - "shortnames": [], - "category": "people" - }, - ":raised_hands_tone5:": { - "uc_base": "1f64c-1f3ff", - "uc_output": "1f64c-1f3ff", - "uc_match": "1f64c-1f3ff", - "uc_greedy": "1f64c-1f3ff", - "shortnames": [], - "category": "people" - }, - ":right_facing_fist_tone1:": { - "uc_base": "1f91c-1f3fb", - "uc_output": "1f91c-1f3fb", - "uc_match": "1f91c-1f3fb", - "uc_greedy": "1f91c-1f3fb", - "shortnames": [":right_fist_tone1:"], - "category": "people" - }, - ":right_facing_fist_tone2:": { - "uc_base": "1f91c-1f3fc", - "uc_output": "1f91c-1f3fc", - "uc_match": "1f91c-1f3fc", - "uc_greedy": "1f91c-1f3fc", - "shortnames": [":right_fist_tone2:"], - "category": "people" - }, - ":right_facing_fist_tone3:": { - "uc_base": "1f91c-1f3fd", - "uc_output": "1f91c-1f3fd", - "uc_match": "1f91c-1f3fd", - "uc_greedy": "1f91c-1f3fd", - "shortnames": [":right_fist_tone3:"], - "category": "people" - }, - ":right_facing_fist_tone4:": { - "uc_base": "1f91c-1f3fe", - "uc_output": "1f91c-1f3fe", - "uc_match": "1f91c-1f3fe", - "uc_greedy": "1f91c-1f3fe", - "shortnames": [":right_fist_tone4:"], - "category": "people" - }, - ":right_facing_fist_tone5:": { - "uc_base": "1f91c-1f3ff", - "uc_output": "1f91c-1f3ff", - "uc_match": "1f91c-1f3ff", - "uc_greedy": "1f91c-1f3ff", - "shortnames": [":right_fist_tone5:"], - "category": "people" - }, - ":santa_tone1:": { - "uc_base": "1f385-1f3fb", - "uc_output": "1f385-1f3fb", - "uc_match": "1f385-1f3fb", - "uc_greedy": "1f385-1f3fb", - "shortnames": [], - "category": "people" - }, - ":santa_tone2:": { - "uc_base": "1f385-1f3fc", - "uc_output": "1f385-1f3fc", - "uc_match": "1f385-1f3fc", - "uc_greedy": "1f385-1f3fc", - "shortnames": [], - "category": "people" - }, - ":santa_tone3:": { - "uc_base": "1f385-1f3fd", - "uc_output": "1f385-1f3fd", - "uc_match": "1f385-1f3fd", - "uc_greedy": "1f385-1f3fd", - "shortnames": [], - "category": "people" - }, - ":santa_tone4:": { - "uc_base": "1f385-1f3fe", - "uc_output": "1f385-1f3fe", - "uc_match": "1f385-1f3fe", - "uc_greedy": "1f385-1f3fe", - "shortnames": [], - "category": "people" - }, - ":santa_tone5:": { - "uc_base": "1f385-1f3ff", - "uc_output": "1f385-1f3ff", - "uc_match": "1f385-1f3ff", - "uc_greedy": "1f385-1f3ff", - "shortnames": [], - "category": "people" - }, - ":selfie_tone1:": { - "uc_base": "1f933-1f3fb", - "uc_output": "1f933-1f3fb", - "uc_match": "1f933-1f3fb", - "uc_greedy": "1f933-1f3fb", - "shortnames": [], - "category": "people" - }, - ":selfie_tone2:": { - "uc_base": "1f933-1f3fc", - "uc_output": "1f933-1f3fc", - "uc_match": "1f933-1f3fc", - "uc_greedy": "1f933-1f3fc", - "shortnames": [], - "category": "people" - }, - ":selfie_tone3:": { - "uc_base": "1f933-1f3fd", - "uc_output": "1f933-1f3fd", - "uc_match": "1f933-1f3fd", - "uc_greedy": "1f933-1f3fd", - "shortnames": [], - "category": "people" - }, - ":selfie_tone4:": { - "uc_base": "1f933-1f3fe", - "uc_output": "1f933-1f3fe", - "uc_match": "1f933-1f3fe", - "uc_greedy": "1f933-1f3fe", - "shortnames": [], - "category": "people" - }, - ":selfie_tone5:": { - "uc_base": "1f933-1f3ff", - "uc_output": "1f933-1f3ff", - "uc_match": "1f933-1f3ff", - "uc_greedy": "1f933-1f3ff", - "shortnames": [], - "category": "people" - }, - ":snowboarder_tone1:": { - "uc_base": "1f3c2-1f3fb", - "uc_output": "1f3c2-1f3fb", - "uc_match": "1f3c2-1f3fb", - "uc_greedy": "1f3c2-1f3fb", - "shortnames": [":snowboarder_light_skin_tone:"], - "category": "activity" - }, - ":snowboarder_tone2:": { - "uc_base": "1f3c2-1f3fc", - "uc_output": "1f3c2-1f3fc", - "uc_match": "1f3c2-1f3fc", - "uc_greedy": "1f3c2-1f3fc", - "shortnames": [":snowboarder_medium_light_skin_tone:"], - "category": "activity" - }, - ":snowboarder_tone3:": { - "uc_base": "1f3c2-1f3fd", - "uc_output": "1f3c2-1f3fd", - "uc_match": "1f3c2-1f3fd", - "uc_greedy": "1f3c2-1f3fd", - "shortnames": [":snowboarder_medium_skin_tone:"], - "category": "activity" - }, - ":snowboarder_tone4:": { - "uc_base": "1f3c2-1f3fe", - "uc_output": "1f3c2-1f3fe", - "uc_match": "1f3c2-1f3fe", - "uc_greedy": "1f3c2-1f3fe", - "shortnames": [":snowboarder_medium_dark_skin_tone:"], - "category": "activity" - }, - ":snowboarder_tone5:": { - "uc_base": "1f3c2-1f3ff", - "uc_output": "1f3c2-1f3ff", - "uc_match": "1f3c2-1f3ff", - "uc_greedy": "1f3c2-1f3ff", - "shortnames": [":snowboarder_dark_skin_tone:"], - "category": "activity" - }, - ":thumbsdown_tone1:": { - "uc_base": "1f44e-1f3fb", - "uc_output": "1f44e-1f3fb", - "uc_match": "1f44e-1f3fb", - "uc_greedy": "1f44e-1f3fb", - "shortnames": [":-1_tone1:", ":thumbdown_tone1:"], - "category": "people" - }, - ":thumbsdown_tone2:": { - "uc_base": "1f44e-1f3fc", - "uc_output": "1f44e-1f3fc", - "uc_match": "1f44e-1f3fc", - "uc_greedy": "1f44e-1f3fc", - "shortnames": [":-1_tone2:", ":thumbdown_tone2:"], - "category": "people" - }, - ":thumbsdown_tone3:": { - "uc_base": "1f44e-1f3fd", - "uc_output": "1f44e-1f3fd", - "uc_match": "1f44e-1f3fd", - "uc_greedy": "1f44e-1f3fd", - "shortnames": [":-1_tone3:", ":thumbdown_tone3:"], - "category": "people" - }, - ":thumbsdown_tone4:": { - "uc_base": "1f44e-1f3fe", - "uc_output": "1f44e-1f3fe", - "uc_match": "1f44e-1f3fe", - "uc_greedy": "1f44e-1f3fe", - "shortnames": [":-1_tone4:", ":thumbdown_tone4:"], - "category": "people" - }, - ":thumbsdown_tone5:": { - "uc_base": "1f44e-1f3ff", - "uc_output": "1f44e-1f3ff", - "uc_match": "1f44e-1f3ff", - "uc_greedy": "1f44e-1f3ff", - "shortnames": [":-1_tone5:", ":thumbdown_tone5:"], - "category": "people" - }, - ":thumbsup_tone1:": { - "uc_base": "1f44d-1f3fb", - "uc_output": "1f44d-1f3fb", - "uc_match": "1f44d-1f3fb", - "uc_greedy": "1f44d-1f3fb", - "shortnames": [":+1_tone1:", ":thumbup_tone1:"], - "category": "people" - }, - ":thumbsup_tone2:": { - "uc_base": "1f44d-1f3fc", - "uc_output": "1f44d-1f3fc", - "uc_match": "1f44d-1f3fc", - "uc_greedy": "1f44d-1f3fc", - "shortnames": [":+1_tone2:", ":thumbup_tone2:"], - "category": "people" - }, - ":thumbsup_tone3:": { - "uc_base": "1f44d-1f3fd", - "uc_output": "1f44d-1f3fd", - "uc_match": "1f44d-1f3fd", - "uc_greedy": "1f44d-1f3fd", - "shortnames": [":+1_tone3:", ":thumbup_tone3:"], - "category": "people" - }, - ":thumbsup_tone4:": { - "uc_base": "1f44d-1f3fe", - "uc_output": "1f44d-1f3fe", - "uc_match": "1f44d-1f3fe", - "uc_greedy": "1f44d-1f3fe", - "shortnames": [":+1_tone4:", ":thumbup_tone4:"], - "category": "people" - }, - ":thumbsup_tone5:": { - "uc_base": "1f44d-1f3ff", - "uc_output": "1f44d-1f3ff", - "uc_match": "1f44d-1f3ff", - "uc_greedy": "1f44d-1f3ff", - "shortnames": [":+1_tone5:", ":thumbup_tone5:"], - "category": "people" - }, - ":united_nations:": { - "uc_base": "1f1fa-1f1f3", - "uc_output": "1f1fa-1f1f3", - "uc_match": "1f1fa-1f1f3", - "uc_greedy": "1f1fa-1f1f3", - "shortnames": [], - "category": "flags" - }, - ":vampire_tone1:": { - "uc_base": "1f9db-1f3fb", - "uc_output": "1f9db-1f3fb", - "uc_match": "1f9db-1f3fb", - "uc_greedy": "1f9db-1f3fb", - "shortnames": [":vampire_light_skin_tone:"], - "category": "people" - }, - ":vampire_tone2:": { - "uc_base": "1f9db-1f3fc", - "uc_output": "1f9db-1f3fc", - "uc_match": "1f9db-1f3fc", - "uc_greedy": "1f9db-1f3fc", - "shortnames": [":vampire_medium_light_skin_tone:"], - "category": "people" - }, - ":vampire_tone3:": { - "uc_base": "1f9db-1f3fd", - "uc_output": "1f9db-1f3fd", - "uc_match": "1f9db-1f3fd", - "uc_greedy": "1f9db-1f3fd", - "shortnames": [":vampire_medium_skin_tone:"], - "category": "people" - }, - ":vampire_tone4:": { - "uc_base": "1f9db-1f3fe", - "uc_output": "1f9db-1f3fe", - "uc_match": "1f9db-1f3fe", - "uc_greedy": "1f9db-1f3fe", - "shortnames": [":vampire_medium_dark_skin_tone:"], - "category": "people" - }, - ":vampire_tone5:": { - "uc_base": "1f9db-1f3ff", - "uc_output": "1f9db-1f3ff", - "uc_match": "1f9db-1f3ff", - "uc_greedy": "1f9db-1f3ff", - "shortnames": [":vampire_dark_skin_tone:"], - "category": "people" - }, - ":vulcan_tone1:": { - "uc_base": "1f596-1f3fb", - "uc_output": "1f596-1f3fb", - "uc_match": "1f596-1f3fb", - "uc_greedy": "1f596-1f3fb", - "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone1:"], - "category": "people" - }, - ":vulcan_tone2:": { - "uc_base": "1f596-1f3fc", - "uc_output": "1f596-1f3fc", - "uc_match": "1f596-1f3fc", - "uc_greedy": "1f596-1f3fc", - "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone2:"], - "category": "people" - }, - ":vulcan_tone3:": { - "uc_base": "1f596-1f3fd", - "uc_output": "1f596-1f3fd", - "uc_match": "1f596-1f3fd", - "uc_greedy": "1f596-1f3fd", - "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone3:"], - "category": "people" - }, - ":vulcan_tone4:": { - "uc_base": "1f596-1f3fe", - "uc_output": "1f596-1f3fe", - "uc_match": "1f596-1f3fe", - "uc_greedy": "1f596-1f3fe", - "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone4:"], - "category": "people" - }, - ":vulcan_tone5:": { - "uc_base": "1f596-1f3ff", - "uc_output": "1f596-1f3ff", - "uc_match": "1f596-1f3ff", - "uc_greedy": "1f596-1f3ff", - "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone5:"], - "category": "people" - }, - ":wave_tone1:": { - "uc_base": "1f44b-1f3fb", - "uc_output": "1f44b-1f3fb", - "uc_match": "1f44b-1f3fb", - "uc_greedy": "1f44b-1f3fb", - "shortnames": [], - "category": "people" - }, - ":wave_tone2:": { - "uc_base": "1f44b-1f3fc", - "uc_output": "1f44b-1f3fc", - "uc_match": "1f44b-1f3fc", - "uc_greedy": "1f44b-1f3fc", - "shortnames": [], - "category": "people" - }, - ":wave_tone3:": { - "uc_base": "1f44b-1f3fd", - "uc_output": "1f44b-1f3fd", - "uc_match": "1f44b-1f3fd", - "uc_greedy": "1f44b-1f3fd", - "shortnames": [], - "category": "people" - }, - ":wave_tone4:": { - "uc_base": "1f44b-1f3fe", - "uc_output": "1f44b-1f3fe", - "uc_match": "1f44b-1f3fe", - "uc_greedy": "1f44b-1f3fe", - "shortnames": [], - "category": "people" - }, - ":wave_tone5:": { - "uc_base": "1f44b-1f3ff", - "uc_output": "1f44b-1f3ff", - "uc_match": "1f44b-1f3ff", - "uc_greedy": "1f44b-1f3ff", - "shortnames": [], - "category": "people" - }, - ":woman_tone1:": { - "uc_base": "1f469-1f3fb", - "uc_output": "1f469-1f3fb", - "uc_match": "1f469-1f3fb", - "uc_greedy": "1f469-1f3fb", - "shortnames": [], - "category": "people" - }, - ":woman_tone2:": { - "uc_base": "1f469-1f3fc", - "uc_output": "1f469-1f3fc", - "uc_match": "1f469-1f3fc", - "uc_greedy": "1f469-1f3fc", - "shortnames": [], - "category": "people" - }, - ":woman_tone3:": { - "uc_base": "1f469-1f3fd", - "uc_output": "1f469-1f3fd", - "uc_match": "1f469-1f3fd", - "uc_greedy": "1f469-1f3fd", - "shortnames": [], - "category": "people" - }, - ":woman_tone4:": { - "uc_base": "1f469-1f3fe", - "uc_output": "1f469-1f3fe", - "uc_match": "1f469-1f3fe", - "uc_greedy": "1f469-1f3fe", - "shortnames": [], - "category": "people" - }, - ":woman_tone5:": { - "uc_base": "1f469-1f3ff", - "uc_output": "1f469-1f3ff", - "uc_match": "1f469-1f3ff", - "uc_greedy": "1f469-1f3ff", - "shortnames": [], - "category": "people" - }, - ":woman_with_headscarf_tone1:": { - "uc_base": "1f9d5-1f3fb", - "uc_output": "1f9d5-1f3fb", - "uc_match": "1f9d5-1f3fb", - "uc_greedy": "1f9d5-1f3fb", - "shortnames": [":woman_with_headscarf_light_skin_tone:"], - "category": "people" - }, - ":woman_with_headscarf_tone2:": { - "uc_base": "1f9d5-1f3fc", - "uc_output": "1f9d5-1f3fc", - "uc_match": "1f9d5-1f3fc", - "uc_greedy": "1f9d5-1f3fc", - "shortnames": [":woman_with_headscarf_medium_light_skin_tone:"], - "category": "people" - }, - ":woman_with_headscarf_tone3:": { - "uc_base": "1f9d5-1f3fd", - "uc_output": "1f9d5-1f3fd", - "uc_match": "1f9d5-1f3fd", - "uc_greedy": "1f9d5-1f3fd", - "shortnames": [":woman_with_headscarf_medium_skin_tone:"], - "category": "people" - }, - ":woman_with_headscarf_tone4:": { - "uc_base": "1f9d5-1f3fe", - "uc_output": "1f9d5-1f3fe", - "uc_match": "1f9d5-1f3fe", - "uc_greedy": "1f9d5-1f3fe", - "shortnames": [":woman_with_headscarf_medium_dark_skin_tone:"], - "category": "people" - }, - ":woman_with_headscarf_tone5:": { - "uc_base": "1f9d5-1f3ff", - "uc_output": "1f9d5-1f3ff", - "uc_match": "1f9d5-1f3ff", - "uc_greedy": "1f9d5-1f3ff", - "shortnames": [":woman_with_headscarf_dark_skin_tone:"], - "category": "people" - }, - ":fist_tone1:": { - "uc_base": "270a-1f3fb", - "uc_output": "270a-1f3fb", - "uc_match": "270a-1f3fb", - "uc_greedy": "270a-1f3fb", - "shortnames": [], - "category": "people" - }, - ":fist_tone2:": { - "uc_base": "270a-1f3fc", - "uc_output": "270a-1f3fc", - "uc_match": "270a-1f3fc", - "uc_greedy": "270a-1f3fc", - "shortnames": [], - "category": "people" - }, - ":fist_tone3:": { - "uc_base": "270a-1f3fd", - "uc_output": "270a-1f3fd", - "uc_match": "270a-1f3fd", - "uc_greedy": "270a-1f3fd", - "shortnames": [], - "category": "people" - }, - ":fist_tone4:": { - "uc_base": "270a-1f3fe", - "uc_output": "270a-1f3fe", - "uc_match": "270a-1f3fe", - "uc_greedy": "270a-1f3fe", - "shortnames": [], - "category": "people" - }, - ":fist_tone5:": { - "uc_base": "270a-1f3ff", - "uc_output": "270a-1f3ff", - "uc_match": "270a-1f3ff", - "uc_greedy": "270a-1f3ff", - "shortnames": [], - "category": "people" - }, - ":person_bouncing_ball_tone1:": { - "uc_base": "26f9-1f3fb", - "uc_output": "26f9-1f3fb", - "uc_match": "26f9-fe0f-1f3fb", - "uc_greedy": "26f9-fe0f-1f3fb", - "shortnames": [":basketball_player_tone1:", ":person_with_ball_tone1:"], - "category": "activity" - }, - ":person_bouncing_ball_tone2:": { - "uc_base": "26f9-1f3fc", - "uc_output": "26f9-1f3fc", - "uc_match": "26f9-fe0f-1f3fc", - "uc_greedy": "26f9-fe0f-1f3fc", - "shortnames": [":basketball_player_tone2:", ":person_with_ball_tone2:"], - "category": "activity" - }, - ":person_bouncing_ball_tone3:": { - "uc_base": "26f9-1f3fd", - "uc_output": "26f9-1f3fd", - "uc_match": "26f9-fe0f-1f3fd", - "uc_greedy": "26f9-fe0f-1f3fd", - "shortnames": [":basketball_player_tone3:", ":person_with_ball_tone3:"], - "category": "activity" - }, - ":person_bouncing_ball_tone4:": { - "uc_base": "26f9-1f3fe", - "uc_output": "26f9-1f3fe", - "uc_match": "26f9-fe0f-1f3fe", - "uc_greedy": "26f9-fe0f-1f3fe", - "shortnames": [":basketball_player_tone4:", ":person_with_ball_tone4:"], - "category": "activity" - }, - ":person_bouncing_ball_tone5:": { - "uc_base": "26f9-1f3ff", - "uc_output": "26f9-1f3ff", - "uc_match": "26f9-fe0f-1f3ff", - "uc_greedy": "26f9-fe0f-1f3ff", - "shortnames": [":basketball_player_tone5:", ":person_with_ball_tone5:"], - "category": "activity" - }, - ":point_up_tone1:": { - "uc_base": "261d-1f3fb", - "uc_output": "261d-1f3fb", - "uc_match": "261d-fe0f-1f3fb", - "uc_greedy": "261d-fe0f-1f3fb", - "shortnames": [], - "category": "people" - }, - ":point_up_tone2:": { - "uc_base": "261d-1f3fc", - "uc_output": "261d-1f3fc", - "uc_match": "261d-fe0f-1f3fc", - "uc_greedy": "261d-fe0f-1f3fc", - "shortnames": [], - "category": "people" - }, - ":point_up_tone3:": { - "uc_base": "261d-1f3fd", - "uc_output": "261d-1f3fd", - "uc_match": "261d-fe0f-1f3fd", - "uc_greedy": "261d-fe0f-1f3fd", - "shortnames": [], - "category": "people" - }, - ":point_up_tone4:": { - "uc_base": "261d-1f3fe", - "uc_output": "261d-1f3fe", - "uc_match": "261d-fe0f-1f3fe", - "uc_greedy": "261d-fe0f-1f3fe", - "shortnames": [], - "category": "people" - }, - ":point_up_tone5:": { - "uc_base": "261d-1f3ff", - "uc_output": "261d-1f3ff", - "uc_match": "261d-fe0f-1f3ff", - "uc_greedy": "261d-fe0f-1f3ff", - "shortnames": [], - "category": "people" - }, - ":raised_hand_tone1:": { - "uc_base": "270b-1f3fb", - "uc_output": "270b-1f3fb", - "uc_match": "270b-1f3fb", - "uc_greedy": "270b-1f3fb", - "shortnames": [], - "category": "people" - }, - ":raised_hand_tone2:": { - "uc_base": "270b-1f3fc", - "uc_output": "270b-1f3fc", - "uc_match": "270b-1f3fc", - "uc_greedy": "270b-1f3fc", - "shortnames": [], - "category": "people" - }, - ":raised_hand_tone3:": { - "uc_base": "270b-1f3fd", - "uc_output": "270b-1f3fd", - "uc_match": "270b-1f3fd", - "uc_greedy": "270b-1f3fd", - "shortnames": [], - "category": "people" - }, - ":raised_hand_tone4:": { - "uc_base": "270b-1f3fe", - "uc_output": "270b-1f3fe", - "uc_match": "270b-1f3fe", - "uc_greedy": "270b-1f3fe", - "shortnames": [], - "category": "people" - }, - ":raised_hand_tone5:": { - "uc_base": "270b-1f3ff", - "uc_output": "270b-1f3ff", - "uc_match": "270b-1f3ff", - "uc_greedy": "270b-1f3ff", - "shortnames": [], - "category": "people" - }, - ":v_tone1:": { - "uc_base": "270c-1f3fb", - "uc_output": "270c-1f3fb", - "uc_match": "270c-fe0f-1f3fb", - "uc_greedy": "270c-fe0f-1f3fb", - "shortnames": [], - "category": "people" - }, - ":v_tone2:": { - "uc_base": "270c-1f3fc", - "uc_output": "270c-1f3fc", - "uc_match": "270c-fe0f-1f3fc", - "uc_greedy": "270c-fe0f-1f3fc", - "shortnames": [], - "category": "people" - }, - ":v_tone3:": { - "uc_base": "270c-1f3fd", - "uc_output": "270c-1f3fd", - "uc_match": "270c-fe0f-1f3fd", - "uc_greedy": "270c-fe0f-1f3fd", - "shortnames": [], - "category": "people" - }, - ":v_tone4:": { - "uc_base": "270c-1f3fe", - "uc_output": "270c-1f3fe", - "uc_match": "270c-fe0f-1f3fe", - "uc_greedy": "270c-fe0f-1f3fe", - "shortnames": [], - "category": "people" - }, - ":v_tone5:": { - "uc_base": "270c-1f3ff", - "uc_output": "270c-1f3ff", - "uc_match": "270c-fe0f-1f3ff", - "uc_greedy": "270c-fe0f-1f3ff", - "shortnames": [], - "category": "people" - }, - ":writing_hand_tone1:": { - "uc_base": "270d-1f3fb", - "uc_output": "270d-1f3fb", - "uc_match": "270d-fe0f-1f3fb", - "uc_greedy": "270d-fe0f-1f3fb", - "shortnames": [], - "category": "people" - }, - ":writing_hand_tone2:": { - "uc_base": "270d-1f3fc", - "uc_output": "270d-1f3fc", - "uc_match": "270d-fe0f-1f3fc", - "uc_greedy": "270d-fe0f-1f3fc", - "shortnames": [], - "category": "people" - }, - ":writing_hand_tone3:": { - "uc_base": "270d-1f3fd", - "uc_output": "270d-1f3fd", - "uc_match": "270d-fe0f-1f3fd", - "uc_greedy": "270d-fe0f-1f3fd", - "shortnames": [], - "category": "people" - }, - ":writing_hand_tone4:": { - "uc_base": "270d-1f3fe", - "uc_output": "270d-1f3fe", - "uc_match": "270d-fe0f-1f3fe", - "uc_greedy": "270d-fe0f-1f3fe", - "shortnames": [], - "category": "people" - }, - ":writing_hand_tone5:": { - "uc_base": "270d-1f3ff", - "uc_output": "270d-1f3ff", - "uc_match": "270d-fe0f-1f3ff", - "uc_greedy": "270d-fe0f-1f3ff", - "shortnames": [], - "category": "people" - }, - ":100:": { - "uc_base": "1f4af", - "uc_output": "1f4af", - "uc_match": "1f4af", - "uc_greedy": "1f4af", - "shortnames": [], - "category": "symbols" - }, - ":1234:": { - "uc_base": "1f522", - "uc_output": "1f522", - "uc_match": "1f522", - "uc_greedy": "1f522", - "shortnames": [], - "category": "symbols" - }, - ":8ball:": { - "uc_base": "1f3b1", - "uc_output": "1f3b1", - "uc_match": "1f3b1", - "uc_greedy": "1f3b1", - "shortnames": [], - "category": "activity" - }, - ":a:": { - "uc_base": "1f170", - "uc_output": "1f170", - "uc_match": "1f170-fe0f", - "uc_greedy": "1f170-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":ab:": { - "uc_base": "1f18e", - "uc_output": "1f18e", - "uc_match": "1f18e", - "uc_greedy": "1f18e", - "shortnames": [], - "category": "symbols" - }, - ":abc:": { - "uc_base": "1f524", - "uc_output": "1f524", - "uc_match": "1f524", - "uc_greedy": "1f524", - "shortnames": [], - "category": "symbols" - }, - ":abcd:": { - "uc_base": "1f521", - "uc_output": "1f521", - "uc_match": "1f521", - "uc_greedy": "1f521", - "shortnames": [], - "category": "symbols" - }, - ":accept:": { - "uc_base": "1f251", - "uc_output": "1f251", - "uc_match": "1f251", - "uc_greedy": "1f251", - "shortnames": [], - "category": "symbols" - }, - ":adult:": { - "uc_base": "1f9d1", - "uc_output": "1f9d1", - "uc_match": "1f9d1", - "uc_greedy": "1f9d1", - "shortnames": [], - "category": "people" - }, - ":aerial_tramway:": { - "uc_base": "1f6a1", - "uc_output": "1f6a1", - "uc_match": "1f6a1", - "uc_greedy": "1f6a1", - "shortnames": [], - "category": "travel" - }, - ":airplane_arriving:": { - "uc_base": "1f6ec", - "uc_output": "1f6ec", - "uc_match": "1f6ec", - "uc_greedy": "1f6ec", - "shortnames": [], - "category": "travel" - }, - ":airplane_departure:": { - "uc_base": "1f6eb", - "uc_output": "1f6eb", - "uc_match": "1f6eb", - "uc_greedy": "1f6eb", - "shortnames": [], - "category": "travel" - }, - ":airplane_small:": { - "uc_base": "1f6e9", - "uc_output": "1f6e9", - "uc_match": "1f6e9-fe0f", - "uc_greedy": "1f6e9-fe0f", - "shortnames": [":small_airplane:"], - "category": "travel" - }, - ":alien:": { - "uc_base": "1f47d", - "uc_output": "1f47d", - "uc_match": "1f47d-fe0f", - "uc_greedy": "1f47d-fe0f", - "shortnames": [], - "category": "people" - }, - ":ambulance:": { - "uc_base": "1f691", - "uc_output": "1f691", - "uc_match": "1f691-fe0f", - "uc_greedy": "1f691-fe0f", - "shortnames": [], - "category": "travel" - }, - ":amphora:": { - "uc_base": "1f3fa", - "uc_output": "1f3fa", - "uc_match": "1f3fa", - "uc_greedy": "1f3fa", - "shortnames": [], - "category": "objects" - }, - ":angel:": { - "uc_base": "1f47c", - "uc_output": "1f47c", - "uc_match": "1f47c", - "uc_greedy": "1f47c", - "shortnames": [], - "category": "people" - }, - ":anger:": { - "uc_base": "1f4a2", - "uc_output": "1f4a2", - "uc_match": "1f4a2", - "uc_greedy": "1f4a2", - "shortnames": [], - "category": "symbols" - }, - ":anger_right:": { - "uc_base": "1f5ef", - "uc_output": "1f5ef", - "uc_match": "1f5ef-fe0f", - "uc_greedy": "1f5ef-fe0f", - "shortnames": [":right_anger_bubble:"], - "category": "symbols" - }, - ":angry:": { - "uc_base": "1f620", - "uc_output": "1f620", - "uc_match": "1f620", - "uc_greedy": "1f620", - "shortnames": [], - "category": "people" - }, - ":anguished:": { - "uc_base": "1f627", - "uc_output": "1f627", - "uc_match": "1f627", - "uc_greedy": "1f627", - "shortnames": [], - "category": "people" - }, - ":ant:": { - "uc_base": "1f41c", - "uc_output": "1f41c", - "uc_match": "1f41c", - "uc_greedy": "1f41c", - "shortnames": [], - "category": "nature" - }, - ":apple:": { - "uc_base": "1f34e", - "uc_output": "1f34e", - "uc_match": "1f34e", - "uc_greedy": "1f34e", - "shortnames": [], - "category": "food" - }, - ":arrow_down_small:": { - "uc_base": "1f53d", - "uc_output": "1f53d", - "uc_match": "1f53d", - "uc_greedy": "1f53d", - "shortnames": [], - "category": "symbols" - }, - ":arrow_up_small:": { - "uc_base": "1f53c", - "uc_output": "1f53c", - "uc_match": "1f53c", - "uc_greedy": "1f53c", - "shortnames": [], - "category": "symbols" - }, - ":arrows_clockwise:": { - "uc_base": "1f503", - "uc_output": "1f503", - "uc_match": "1f503", - "uc_greedy": "1f503", - "shortnames": [], - "category": "symbols" - }, - ":arrows_counterclockwise:": { - "uc_base": "1f504", - "uc_output": "1f504", - "uc_match": "1f504", - "uc_greedy": "1f504", - "shortnames": [], - "category": "symbols" - }, - ":art:": { - "uc_base": "1f3a8", - "uc_output": "1f3a8", - "uc_match": "1f3a8", - "uc_greedy": "1f3a8", - "shortnames": [], - "category": "activity" - }, - ":articulated_lorry:": { - "uc_base": "1f69b", - "uc_output": "1f69b", - "uc_match": "1f69b", - "uc_greedy": "1f69b", - "shortnames": [], - "category": "travel" - }, - ":astonished:": { - "uc_base": "1f632", - "uc_output": "1f632", - "uc_match": "1f632", - "uc_greedy": "1f632", - "shortnames": [], - "category": "people" - }, - ":athletic_shoe:": { - "uc_base": "1f45f", - "uc_output": "1f45f", - "uc_match": "1f45f", - "uc_greedy": "1f45f", - "shortnames": [], - "category": "people" - }, - ":atm:": { - "uc_base": "1f3e7", - "uc_output": "1f3e7", - "uc_match": "1f3e7", - "uc_greedy": "1f3e7", - "shortnames": [], - "category": "symbols" - }, - ":avocado:": { - "uc_base": "1f951", - "uc_output": "1f951", - "uc_match": "1f951", - "uc_greedy": "1f951", - "shortnames": [], - "category": "food" - }, - ":b:": { - "uc_base": "1f171", - "uc_output": "1f171", - "uc_match": "1f171-fe0f", - "uc_greedy": "1f171-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":baby:": { - "uc_base": "1f476", - "uc_output": "1f476", - "uc_match": "1f476", - "uc_greedy": "1f476", - "shortnames": [], - "category": "people" - }, - ":baby_bottle:": { - "uc_base": "1f37c", - "uc_output": "1f37c", - "uc_match": "1f37c", - "uc_greedy": "1f37c", - "shortnames": [], - "category": "food" - }, - ":baby_chick:": { - "uc_base": "1f424", - "uc_output": "1f424", - "uc_match": "1f424", - "uc_greedy": "1f424", - "shortnames": [], - "category": "nature" - }, - ":baby_symbol:": { - "uc_base": "1f6bc", - "uc_output": "1f6bc", - "uc_match": "1f6bc-fe0f", - "uc_greedy": "1f6bc-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":back:": { - "uc_base": "1f519", - "uc_output": "1f519", - "uc_match": "1f519", - "uc_greedy": "1f519", - "shortnames": [], - "category": "symbols" - }, - ":bacon:": { - "uc_base": "1f953", - "uc_output": "1f953", - "uc_match": "1f953", - "uc_greedy": "1f953", - "shortnames": [], - "category": "food" - }, - ":badminton:": { - "uc_base": "1f3f8", - "uc_output": "1f3f8", - "uc_match": "1f3f8", - "uc_greedy": "1f3f8", - "shortnames": [], - "category": "activity" - }, - ":baggage_claim:": { - "uc_base": "1f6c4", - "uc_output": "1f6c4", - "uc_match": "1f6c4", - "uc_greedy": "1f6c4", - "shortnames": [], - "category": "symbols" - }, - ":balloon:": { - "uc_base": "1f388", - "uc_output": "1f388", - "uc_match": "1f388", - "uc_greedy": "1f388", - "shortnames": [], - "category": "objects" - }, - ":ballot_box:": { - "uc_base": "1f5f3", - "uc_output": "1f5f3", - "uc_match": "1f5f3-fe0f", - "uc_greedy": "1f5f3-fe0f", - "shortnames": [":ballot_box_with_ballot:"], - "category": "objects" - }, - ":bamboo:": { - "uc_base": "1f38d", - "uc_output": "1f38d", - "uc_match": "1f38d", - "uc_greedy": "1f38d", - "shortnames": [], - "category": "nature" - }, - ":banana:": { - "uc_base": "1f34c", - "uc_output": "1f34c", - "uc_match": "1f34c", - "uc_greedy": "1f34c", - "shortnames": [], - "category": "food" - }, - ":bank:": { - "uc_base": "1f3e6", - "uc_output": "1f3e6", - "uc_match": "1f3e6", - "uc_greedy": "1f3e6", - "shortnames": [], - "category": "travel" - }, - ":bar_chart:": { - "uc_base": "1f4ca", - "uc_output": "1f4ca", - "uc_match": "1f4ca", - "uc_greedy": "1f4ca", - "shortnames": [], - "category": "objects" - }, - ":barber:": { - "uc_base": "1f488", - "uc_output": "1f488", - "uc_match": "1f488", - "uc_greedy": "1f488", - "shortnames": [], - "category": "objects" - }, - ":basketball:": { - "uc_base": "1f3c0", - "uc_output": "1f3c0", - "uc_match": "1f3c0", - "uc_greedy": "1f3c0", - "shortnames": [], - "category": "activity" - }, - ":bat:": { - "uc_base": "1f987", - "uc_output": "1f987", - "uc_match": "1f987", - "uc_greedy": "1f987", - "shortnames": [], - "category": "nature" - }, - ":bath:": { - "uc_base": "1f6c0", - "uc_output": "1f6c0", - "uc_match": "1f6c0", - "uc_greedy": "1f6c0", - "shortnames": [], - "category": "objects" - }, - ":bathtub:": { - "uc_base": "1f6c1", - "uc_output": "1f6c1", - "uc_match": "1f6c1", - "uc_greedy": "1f6c1", - "shortnames": [], - "category": "objects" - }, - ":battery:": { - "uc_base": "1f50b", - "uc_output": "1f50b", - "uc_match": "1f50b", - "uc_greedy": "1f50b", - "shortnames": [], - "category": "objects" - }, - ":beach:": { - "uc_base": "1f3d6", - "uc_output": "1f3d6", - "uc_match": "1f3d6-fe0f", - "uc_greedy": "1f3d6-fe0f", - "shortnames": [":beach_with_umbrella:"], - "category": "travel" - }, - ":bear:": { - "uc_base": "1f43b", - "uc_output": "1f43b", - "uc_match": "1f43b", - "uc_greedy": "1f43b", - "shortnames": [], - "category": "nature" - }, - ":bearded_person:": { - "uc_base": "1f9d4", - "uc_output": "1f9d4", - "uc_match": "1f9d4", - "uc_greedy": "1f9d4", - "shortnames": [], - "category": "people" - }, - ":bed:": { - "uc_base": "1f6cf", - "uc_output": "1f6cf", - "uc_match": "1f6cf-fe0f", - "uc_greedy": "1f6cf-fe0f", - "shortnames": [], - "category": "objects" - }, - ":bee:": { - "uc_base": "1f41d", - "uc_output": "1f41d", - "uc_match": "1f41d", - "uc_greedy": "1f41d", - "shortnames": [], - "category": "nature" - }, - ":beer:": { - "uc_base": "1f37a", - "uc_output": "1f37a", - "uc_match": "1f37a", - "uc_greedy": "1f37a", - "shortnames": [], - "category": "food" - }, - ":beers:": { - "uc_base": "1f37b", - "uc_output": "1f37b", - "uc_match": "1f37b", - "uc_greedy": "1f37b", - "shortnames": [], - "category": "food" - }, - ":beetle:": { - "uc_base": "1f41e", - "uc_output": "1f41e", - "uc_match": "1f41e", - "uc_greedy": "1f41e", - "shortnames": [], - "category": "nature" - }, - ":beginner:": { - "uc_base": "1f530", - "uc_output": "1f530", - "uc_match": "1f530", - "uc_greedy": "1f530", - "shortnames": [], - "category": "symbols" - }, - ":bell:": { - "uc_base": "1f514", - "uc_output": "1f514", - "uc_match": "1f514", - "uc_greedy": "1f514", - "shortnames": [], - "category": "symbols" - }, - ":bellhop:": { - "uc_base": "1f6ce", - "uc_output": "1f6ce", - "uc_match": "1f6ce-fe0f", - "uc_greedy": "1f6ce-fe0f", - "shortnames": [":bellhop_bell:"], - "category": "objects" - }, - ":bento:": { - "uc_base": "1f371", - "uc_output": "1f371", - "uc_match": "1f371", - "uc_greedy": "1f371", - "shortnames": [], - "category": "food" - }, - ":bike:": { - "uc_base": "1f6b2", - "uc_output": "1f6b2", - "uc_match": "1f6b2-fe0f", - "uc_greedy": "1f6b2-fe0f", - "shortnames": [], - "category": "travel" - }, - ":bikini:": { - "uc_base": "1f459", - "uc_output": "1f459", - "uc_match": "1f459", - "uc_greedy": "1f459", - "shortnames": [], - "category": "people" - }, - ":billed_cap:": { - "uc_base": "1f9e2", - "uc_output": "1f9e2", - "uc_match": "1f9e2", - "uc_greedy": "1f9e2", - "shortnames": [], - "category": "people" - }, - ":bird:": { - "uc_base": "1f426", - "uc_output": "1f426", - "uc_match": "1f426-fe0f", - "uc_greedy": "1f426-fe0f", - "shortnames": [], - "category": "nature" - }, - ":birthday:": { - "uc_base": "1f382", - "uc_output": "1f382", - "uc_match": "1f382", - "uc_greedy": "1f382", - "shortnames": [], - "category": "food" - }, - ":black_heart:": { - "uc_base": "1f5a4", - "uc_output": "1f5a4", - "uc_match": "1f5a4", - "uc_greedy": "1f5a4", - "shortnames": [], - "category": "symbols" - }, - ":black_joker:": { - "uc_base": "1f0cf", - "uc_output": "1f0cf", - "uc_match": "1f0cf", - "uc_greedy": "1f0cf", - "shortnames": [], - "category": "symbols" - }, - ":black_square_button:": { - "uc_base": "1f532", - "uc_output": "1f532", - "uc_match": "1f532", - "uc_greedy": "1f532", - "shortnames": [], - "category": "symbols" - }, - ":blond_haired_person:": { - "uc_base": "1f471", - "uc_output": "1f471", - "uc_match": "1f471", - "uc_greedy": "1f471", - "shortnames": [":person_with_blond_hair:"], - "category": "people" - }, - ":blossom:": { - "uc_base": "1f33c", - "uc_output": "1f33c", - "uc_match": "1f33c", - "uc_greedy": "1f33c", - "shortnames": [], - "category": "nature" - }, - ":blowfish:": { - "uc_base": "1f421", - "uc_output": "1f421", - "uc_match": "1f421", - "uc_greedy": "1f421", - "shortnames": [], - "category": "nature" - }, - ":blue_book:": { - "uc_base": "1f4d8", - "uc_output": "1f4d8", - "uc_match": "1f4d8", - "uc_greedy": "1f4d8", - "shortnames": [], - "category": "objects" - }, - ":blue_car:": { - "uc_base": "1f699", - "uc_output": "1f699", - "uc_match": "1f699", - "uc_greedy": "1f699", - "shortnames": [], - "category": "travel" - }, - ":blue_circle:": { - "uc_base": "1f535", - "uc_output": "1f535", - "uc_match": "1f535", - "uc_greedy": "1f535", - "shortnames": [], - "category": "symbols" - }, - ":blue_heart:": { - "uc_base": "1f499", - "uc_output": "1f499", - "uc_match": "1f499", - "uc_greedy": "1f499", - "shortnames": [], - "category": "symbols" - }, - ":blush:": { - "uc_base": "1f60a", - "uc_output": "1f60a", - "uc_match": "1f60a", - "uc_greedy": "1f60a", - "shortnames": [], - "category": "people" - }, - ":boar:": { - "uc_base": "1f417", - "uc_output": "1f417", - "uc_match": "1f417", - "uc_greedy": "1f417", - "shortnames": [], - "category": "nature" - }, - ":bomb:": { - "uc_base": "1f4a3", - "uc_output": "1f4a3", - "uc_match": "1f4a3-fe0f", - "uc_greedy": "1f4a3-fe0f", - "shortnames": [], - "category": "objects" - }, - ":book:": { - "uc_base": "1f4d6", - "uc_output": "1f4d6", - "uc_match": "1f4d6", - "uc_greedy": "1f4d6", - "shortnames": [], - "category": "objects" - }, - ":bookmark:": { - "uc_base": "1f516", - "uc_output": "1f516", - "uc_match": "1f516", - "uc_greedy": "1f516", - "shortnames": [], - "category": "objects" - }, - ":bookmark_tabs:": { - "uc_base": "1f4d1", - "uc_output": "1f4d1", - "uc_match": "1f4d1", - "uc_greedy": "1f4d1", - "shortnames": [], - "category": "objects" - }, - ":books:": { - "uc_base": "1f4da", - "uc_output": "1f4da", - "uc_match": "1f4da-fe0f", - "uc_greedy": "1f4da-fe0f", - "shortnames": [], - "category": "objects" - }, - ":boom:": { - "uc_base": "1f4a5", - "uc_output": "1f4a5", - "uc_match": "1f4a5", - "uc_greedy": "1f4a5", - "shortnames": [], - "category": "nature" - }, - ":boot:": { - "uc_base": "1f462", - "uc_output": "1f462", - "uc_match": "1f462", - "uc_greedy": "1f462", - "shortnames": [], - "category": "people" - }, - ":bouquet:": { - "uc_base": "1f490", - "uc_output": "1f490", - "uc_match": "1f490", - "uc_greedy": "1f490", - "shortnames": [], - "category": "nature" - }, - ":bow_and_arrow:": { - "uc_base": "1f3f9", - "uc_output": "1f3f9", - "uc_match": "1f3f9", - "uc_greedy": "1f3f9", - "shortnames": [":archery:"], - "category": "activity" - }, - ":bowl_with_spoon:": { - "uc_base": "1f963", - "uc_output": "1f963", - "uc_match": "1f963", - "uc_greedy": "1f963", - "shortnames": [], - "category": "food" - }, - ":bowling:": { - "uc_base": "1f3b3", - "uc_output": "1f3b3", - "uc_match": "1f3b3", - "uc_greedy": "1f3b3", - "shortnames": [], - "category": "activity" - }, - ":boxing_glove:": { - "uc_base": "1f94a", - "uc_output": "1f94a", - "uc_match": "1f94a", - "uc_greedy": "1f94a", - "shortnames": [":boxing_gloves:"], - "category": "activity" - }, - ":boy:": { - "uc_base": "1f466", - "uc_output": "1f466", - "uc_match": "1f466", - "uc_greedy": "1f466", - "shortnames": [], - "category": "people" - }, - ":brain:": { - "uc_base": "1f9e0", - "uc_output": "1f9e0", - "uc_match": "1f9e0", - "uc_greedy": "1f9e0", - "shortnames": [], - "category": "people" - }, - ":bread:": { - "uc_base": "1f35e", - "uc_output": "1f35e", - "uc_match": "1f35e", - "uc_greedy": "1f35e", - "shortnames": [], - "category": "food" - }, - ":breast_feeding:": { - "uc_base": "1f931", - "uc_output": "1f931", - "uc_match": "1f931", - "uc_greedy": "1f931", - "shortnames": [], - "category": "people" - }, - ":bride_with_veil:": { - "uc_base": "1f470", - "uc_output": "1f470", - "uc_match": "1f470", - "uc_greedy": "1f470", - "shortnames": [], - "category": "people" - }, - ":bridge_at_night:": { - "uc_base": "1f309", - "uc_output": "1f309", - "uc_match": "1f309", - "uc_greedy": "1f309", - "shortnames": [], - "category": "travel" - }, - ":briefcase:": { - "uc_base": "1f4bc", - "uc_output": "1f4bc", - "uc_match": "1f4bc", - "uc_greedy": "1f4bc", - "shortnames": [], - "category": "people" - }, - ":broccoli:": { - "uc_base": "1f966", - "uc_output": "1f966", - "uc_match": "1f966", - "uc_greedy": "1f966", - "shortnames": [], - "category": "food" - }, - ":broken_heart:": { - "uc_base": "1f494", - "uc_output": "1f494", - "uc_match": "1f494", - "uc_greedy": "1f494", - "shortnames": [], - "category": "symbols" - }, - ":bug:": { - "uc_base": "1f41b", - "uc_output": "1f41b", - "uc_match": "1f41b", - "uc_greedy": "1f41b", - "shortnames": [], - "category": "nature" - }, - ":bulb:": { - "uc_base": "1f4a1", - "uc_output": "1f4a1", - "uc_match": "1f4a1", - "uc_greedy": "1f4a1", - "shortnames": [], - "category": "objects" - }, - ":bullettrain_front:": { - "uc_base": "1f685", - "uc_output": "1f685", - "uc_match": "1f685", - "uc_greedy": "1f685", - "shortnames": [], - "category": "travel" - }, - ":bullettrain_side:": { - "uc_base": "1f684", - "uc_output": "1f684", - "uc_match": "1f684", - "uc_greedy": "1f684", - "shortnames": [], - "category": "travel" - }, - ":burrito:": { - "uc_base": "1f32f", - "uc_output": "1f32f", - "uc_match": "1f32f", - "uc_greedy": "1f32f", - "shortnames": [], - "category": "food" - }, - ":bus:": { - "uc_base": "1f68c", - "uc_output": "1f68c", - "uc_match": "1f68c", - "uc_greedy": "1f68c", - "shortnames": [], - "category": "travel" - }, - ":busstop:": { - "uc_base": "1f68f", - "uc_output": "1f68f", - "uc_match": "1f68f", - "uc_greedy": "1f68f", - "shortnames": [], - "category": "travel" - }, - ":bust_in_silhouette:": { - "uc_base": "1f464", - "uc_output": "1f464", - "uc_match": "1f464", - "uc_greedy": "1f464", - "shortnames": [], - "category": "people" - }, - ":busts_in_silhouette:": { - "uc_base": "1f465", - "uc_output": "1f465", - "uc_match": "1f465", - "uc_greedy": "1f465", - "shortnames": [], - "category": "people" - }, - ":butterfly:": { - "uc_base": "1f98b", - "uc_output": "1f98b", - "uc_match": "1f98b", - "uc_greedy": "1f98b", - "shortnames": [], - "category": "nature" - }, - ":cactus:": { - "uc_base": "1f335", - "uc_output": "1f335", - "uc_match": "1f335", - "uc_greedy": "1f335", - "shortnames": [], - "category": "nature" - }, - ":cake:": { - "uc_base": "1f370", - "uc_output": "1f370", - "uc_match": "1f370", - "uc_greedy": "1f370", - "shortnames": [], - "category": "food" - }, - ":calendar:": { - "uc_base": "1f4c6", - "uc_output": "1f4c6", - "uc_match": "1f4c6", - "uc_greedy": "1f4c6", - "shortnames": [], - "category": "objects" - }, - ":calendar_spiral:": { - "uc_base": "1f5d3", - "uc_output": "1f5d3", - "uc_match": "1f5d3-fe0f", - "uc_greedy": "1f5d3-fe0f", - "shortnames": [":spiral_calendar_pad:"], - "category": "objects" - }, - ":call_me:": { - "uc_base": "1f919", - "uc_output": "1f919", - "uc_match": "1f919", - "uc_greedy": "1f919", - "shortnames": [":call_me_hand:"], - "category": "people" - }, - ":calling:": { - "uc_base": "1f4f2", - "uc_output": "1f4f2", - "uc_match": "1f4f2", - "uc_greedy": "1f4f2", - "shortnames": [], - "category": "objects" - }, - ":camel:": { - "uc_base": "1f42b", - "uc_output": "1f42b", - "uc_match": "1f42b", - "uc_greedy": "1f42b", - "shortnames": [], - "category": "nature" - }, - ":camera:": { - "uc_base": "1f4f7", - "uc_output": "1f4f7", - "uc_match": "1f4f7-fe0f", - "uc_greedy": "1f4f7-fe0f", - "shortnames": [], - "category": "objects" - }, - ":camera_with_flash:": { - "uc_base": "1f4f8", - "uc_output": "1f4f8", - "uc_match": "1f4f8", - "uc_greedy": "1f4f8", - "shortnames": [], - "category": "objects" - }, - ":camping:": { - "uc_base": "1f3d5", - "uc_output": "1f3d5", - "uc_match": "1f3d5-fe0f", - "uc_greedy": "1f3d5-fe0f", - "shortnames": [], - "category": "travel" - }, - ":candle:": { - "uc_base": "1f56f", - "uc_output": "1f56f", - "uc_match": "1f56f-fe0f", - "uc_greedy": "1f56f-fe0f", - "shortnames": [], - "category": "objects" - }, - ":candy:": { - "uc_base": "1f36c", - "uc_output": "1f36c", - "uc_match": "1f36c", - "uc_greedy": "1f36c", - "shortnames": [], - "category": "food" - }, - ":canned_food:": { - "uc_base": "1f96b", - "uc_output": "1f96b", - "uc_match": "1f96b", - "uc_greedy": "1f96b", - "shortnames": [], - "category": "food" - }, - ":canoe:": { - "uc_base": "1f6f6", - "uc_output": "1f6f6", - "uc_match": "1f6f6", - "uc_greedy": "1f6f6", - "shortnames": [":kayak:"], - "category": "travel" - }, - ":capital_abcd:": { - "uc_base": "1f520", - "uc_output": "1f520", - "uc_match": "1f520", - "uc_greedy": "1f520", - "shortnames": [], - "category": "symbols" - }, - ":card_box:": { - "uc_base": "1f5c3", - "uc_output": "1f5c3", - "uc_match": "1f5c3-fe0f", - "uc_greedy": "1f5c3-fe0f", - "shortnames": [":card_file_box:"], - "category": "objects" - }, - ":card_index:": { - "uc_base": "1f4c7", - "uc_output": "1f4c7", - "uc_match": "1f4c7", - "uc_greedy": "1f4c7", - "shortnames": [], - "category": "objects" - }, - ":carousel_horse:": { - "uc_base": "1f3a0", - "uc_output": "1f3a0", - "uc_match": "1f3a0", - "uc_greedy": "1f3a0", - "shortnames": [], - "category": "travel" - }, - ":carrot:": { - "uc_base": "1f955", - "uc_output": "1f955", - "uc_match": "1f955", - "uc_greedy": "1f955", - "shortnames": [], - "category": "food" - }, - ":cat2:": { - "uc_base": "1f408", - "uc_output": "1f408", - "uc_match": "1f408-fe0f", - "uc_greedy": "1f408-fe0f", - "shortnames": [], - "category": "nature" - }, - ":cat:": { - "uc_base": "1f431", - "uc_output": "1f431", - "uc_match": "1f431", - "uc_greedy": "1f431", - "shortnames": [], - "category": "nature" - }, - ":cd:": { - "uc_base": "1f4bf", - "uc_output": "1f4bf", - "uc_match": "1f4bf-fe0f", - "uc_greedy": "1f4bf-fe0f", - "shortnames": [], - "category": "objects" - }, - ":champagne:": { - "uc_base": "1f37e", - "uc_output": "1f37e", - "uc_match": "1f37e", - "uc_greedy": "1f37e", - "shortnames": [":bottle_with_popping_cork:"], - "category": "food" - }, - ":champagne_glass:": { - "uc_base": "1f942", - "uc_output": "1f942", - "uc_match": "1f942", - "uc_greedy": "1f942", - "shortnames": [":clinking_glass:"], - "category": "food" - }, - ":chart:": { - "uc_base": "1f4b9", - "uc_output": "1f4b9", - "uc_match": "1f4b9", - "uc_greedy": "1f4b9", - "shortnames": [], - "category": "symbols" - }, - ":chart_with_downwards_trend:": { - "uc_base": "1f4c9", - "uc_output": "1f4c9", - "uc_match": "1f4c9", - "uc_greedy": "1f4c9", - "shortnames": [], - "category": "objects" - }, - ":chart_with_upwards_trend:": { - "uc_base": "1f4c8", - "uc_output": "1f4c8", - "uc_match": "1f4c8", - "uc_greedy": "1f4c8", - "shortnames": [], - "category": "objects" - }, - ":checkered_flag:": { - "uc_base": "1f3c1", - "uc_output": "1f3c1", - "uc_match": "1f3c1", - "uc_greedy": "1f3c1", - "shortnames": [], - "category": "flags" - }, - ":cheese:": { - "uc_base": "1f9c0", - "uc_output": "1f9c0", - "uc_match": "1f9c0", - "uc_greedy": "1f9c0", - "shortnames": [":cheese_wedge:"], - "category": "food" - }, - ":cherries:": { - "uc_base": "1f352", - "uc_output": "1f352", - "uc_match": "1f352", - "uc_greedy": "1f352", - "shortnames": [], - "category": "food" - }, - ":cherry_blossom:": { - "uc_base": "1f338", - "uc_output": "1f338", - "uc_match": "1f338", - "uc_greedy": "1f338", - "shortnames": [], - "category": "nature" - }, - ":chestnut:": { - "uc_base": "1f330", - "uc_output": "1f330", - "uc_match": "1f330", - "uc_greedy": "1f330", - "shortnames": [], - "category": "food" - }, - ":chicken:": { - "uc_base": "1f414", - "uc_output": "1f414", - "uc_match": "1f414", - "uc_greedy": "1f414", - "shortnames": [], - "category": "nature" - }, - ":child:": { - "uc_base": "1f9d2", - "uc_output": "1f9d2", - "uc_match": "1f9d2", - "uc_greedy": "1f9d2", - "shortnames": [], - "category": "people" - }, - ":children_crossing:": { - "uc_base": "1f6b8", - "uc_output": "1f6b8", - "uc_match": "1f6b8", - "uc_greedy": "1f6b8", - "shortnames": [], - "category": "symbols" - }, - ":chipmunk:": { - "uc_base": "1f43f", - "uc_output": "1f43f", - "uc_match": "1f43f-fe0f", - "uc_greedy": "1f43f-fe0f", - "shortnames": [], - "category": "nature" - }, - ":chocolate_bar:": { - "uc_base": "1f36b", - "uc_output": "1f36b", - "uc_match": "1f36b", - "uc_greedy": "1f36b", - "shortnames": [], - "category": "food" - }, - ":chopsticks:": { - "uc_base": "1f962", - "uc_output": "1f962", - "uc_match": "1f962", - "uc_greedy": "1f962", - "shortnames": [], - "category": "food" - }, - ":christmas_tree:": { - "uc_base": "1f384", - "uc_output": "1f384", - "uc_match": "1f384", - "uc_greedy": "1f384", - "shortnames": [], - "category": "nature" - }, - ":cinema:": { - "uc_base": "1f3a6", - "uc_output": "1f3a6", - "uc_match": "1f3a6", - "uc_greedy": "1f3a6", - "shortnames": [], - "category": "symbols" - }, - ":circus_tent:": { - "uc_base": "1f3aa", - "uc_output": "1f3aa", - "uc_match": "1f3aa", - "uc_greedy": "1f3aa", - "shortnames": [], - "category": "activity" - }, - ":city_dusk:": { - "uc_base": "1f306", - "uc_output": "1f306", - "uc_match": "1f306", - "uc_greedy": "1f306", - "shortnames": [], - "category": "travel" - }, - ":city_sunset:": { - "uc_base": "1f307", - "uc_output": "1f307", - "uc_match": "1f307", - "uc_greedy": "1f307", - "shortnames": [":city_sunrise:"], - "category": "travel" - }, - ":cityscape:": { - "uc_base": "1f3d9", - "uc_output": "1f3d9", - "uc_match": "1f3d9-fe0f", - "uc_greedy": "1f3d9-fe0f", - "shortnames": [], - "category": "travel" - }, - ":cl:": { - "uc_base": "1f191", - "uc_output": "1f191", - "uc_match": "1f191", - "uc_greedy": "1f191", - "shortnames": [], - "category": "symbols" - }, - ":clap:": { - "uc_base": "1f44f", - "uc_output": "1f44f", - "uc_match": "1f44f", - "uc_greedy": "1f44f", - "shortnames": [], - "category": "people" - }, - ":clapper:": { - "uc_base": "1f3ac", - "uc_output": "1f3ac", - "uc_match": "1f3ac-fe0f", - "uc_greedy": "1f3ac-fe0f", - "shortnames": [], - "category": "activity" - }, - ":classical_building:": { - "uc_base": "1f3db", - "uc_output": "1f3db", - "uc_match": "1f3db-fe0f", - "uc_greedy": "1f3db-fe0f", - "shortnames": [], - "category": "travel" - }, - ":clipboard:": { - "uc_base": "1f4cb", - "uc_output": "1f4cb", - "uc_match": "1f4cb-fe0f", - "uc_greedy": "1f4cb-fe0f", - "shortnames": [], - "category": "objects" - }, - ":clock1030:": { - "uc_base": "1f565", - "uc_output": "1f565", - "uc_match": "1f565-fe0f", - "uc_greedy": "1f565-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock10:": { - "uc_base": "1f559", - "uc_output": "1f559", - "uc_match": "1f559-fe0f", - "uc_greedy": "1f559-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock1130:": { - "uc_base": "1f566", - "uc_output": "1f566", - "uc_match": "1f566-fe0f", - "uc_greedy": "1f566-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock11:": { - "uc_base": "1f55a", - "uc_output": "1f55a", - "uc_match": "1f55a-fe0f", - "uc_greedy": "1f55a-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock1230:": { - "uc_base": "1f567", - "uc_output": "1f567", - "uc_match": "1f567-fe0f", - "uc_greedy": "1f567-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock12:": { - "uc_base": "1f55b", - "uc_output": "1f55b", - "uc_match": "1f55b-fe0f", - "uc_greedy": "1f55b-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock130:": { - "uc_base": "1f55c", - "uc_output": "1f55c", - "uc_match": "1f55c-fe0f", - "uc_greedy": "1f55c-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock1:": { - "uc_base": "1f550", - "uc_output": "1f550", - "uc_match": "1f550-fe0f", - "uc_greedy": "1f550-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock230:": { - "uc_base": "1f55d", - "uc_output": "1f55d", - "uc_match": "1f55d-fe0f", - "uc_greedy": "1f55d-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock2:": { - "uc_base": "1f551", - "uc_output": "1f551", - "uc_match": "1f551-fe0f", - "uc_greedy": "1f551-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock330:": { - "uc_base": "1f55e", - "uc_output": "1f55e", - "uc_match": "1f55e-fe0f", - "uc_greedy": "1f55e-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock3:": { - "uc_base": "1f552", - "uc_output": "1f552", - "uc_match": "1f552-fe0f", - "uc_greedy": "1f552-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock430:": { - "uc_base": "1f55f", - "uc_output": "1f55f", - "uc_match": "1f55f-fe0f", - "uc_greedy": "1f55f-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock4:": { - "uc_base": "1f553", - "uc_output": "1f553", - "uc_match": "1f553-fe0f", - "uc_greedy": "1f553-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock530:": { - "uc_base": "1f560", - "uc_output": "1f560", - "uc_match": "1f560-fe0f", - "uc_greedy": "1f560-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock5:": { - "uc_base": "1f554", - "uc_output": "1f554", - "uc_match": "1f554-fe0f", - "uc_greedy": "1f554-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock630:": { - "uc_base": "1f561", - "uc_output": "1f561", - "uc_match": "1f561-fe0f", - "uc_greedy": "1f561-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock6:": { - "uc_base": "1f555", - "uc_output": "1f555", - "uc_match": "1f555-fe0f", - "uc_greedy": "1f555-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock730:": { - "uc_base": "1f562", - "uc_output": "1f562", - "uc_match": "1f562-fe0f", - "uc_greedy": "1f562-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock7:": { - "uc_base": "1f556", - "uc_output": "1f556", - "uc_match": "1f556-fe0f", - "uc_greedy": "1f556-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock830:": { - "uc_base": "1f563", - "uc_output": "1f563", - "uc_match": "1f563-fe0f", - "uc_greedy": "1f563-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock8:": { - "uc_base": "1f557", - "uc_output": "1f557", - "uc_match": "1f557-fe0f", - "uc_greedy": "1f557-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock930:": { - "uc_base": "1f564", - "uc_output": "1f564", - "uc_match": "1f564-fe0f", - "uc_greedy": "1f564-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock9:": { - "uc_base": "1f558", - "uc_output": "1f558", - "uc_match": "1f558-fe0f", - "uc_greedy": "1f558-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":clock:": { - "uc_base": "1f570", - "uc_output": "1f570", - "uc_match": "1f570-fe0f", - "uc_greedy": "1f570-fe0f", - "shortnames": [":mantlepiece_clock:"], - "category": "objects" - }, - ":closed_book:": { - "uc_base": "1f4d5", - "uc_output": "1f4d5", - "uc_match": "1f4d5", - "uc_greedy": "1f4d5", - "shortnames": [], - "category": "objects" - }, - ":closed_lock_with_key:": { - "uc_base": "1f510", - "uc_output": "1f510", - "uc_match": "1f510", - "uc_greedy": "1f510", - "shortnames": [], - "category": "objects" - }, - ":closed_umbrella:": { - "uc_base": "1f302", - "uc_output": "1f302", - "uc_match": "1f302", - "uc_greedy": "1f302", - "shortnames": [], - "category": "people" - }, - ":cloud_lightning:": { - "uc_base": "1f329", - "uc_output": "1f329", - "uc_match": "1f329-fe0f", - "uc_greedy": "1f329-fe0f", - "shortnames": [":cloud_with_lightning:"], - "category": "nature" - }, - ":cloud_rain:": { - "uc_base": "1f327", - "uc_output": "1f327", - "uc_match": "1f327-fe0f", - "uc_greedy": "1f327-fe0f", - "shortnames": [":cloud_with_rain:"], - "category": "nature" - }, - ":cloud_snow:": { - "uc_base": "1f328", - "uc_output": "1f328", - "uc_match": "1f328-fe0f", - "uc_greedy": "1f328-fe0f", - "shortnames": [":cloud_with_snow:"], - "category": "nature" - }, - ":cloud_tornado:": { - "uc_base": "1f32a", - "uc_output": "1f32a", - "uc_match": "1f32a-fe0f", - "uc_greedy": "1f32a-fe0f", - "shortnames": [":cloud_with_tornado:"], - "category": "nature" - }, - ":clown:": { - "uc_base": "1f921", - "uc_output": "1f921", - "uc_match": "1f921", - "uc_greedy": "1f921", - "shortnames": [":clown_face:"], - "category": "people" - }, - ":coat:": { - "uc_base": "1f9e5", - "uc_output": "1f9e5", - "uc_match": "1f9e5", - "uc_greedy": "1f9e5", - "shortnames": [], - "category": "people" - }, - ":cocktail:": { - "uc_base": "1f378", - "uc_output": "1f378", - "uc_match": "1f378-fe0f", - "uc_greedy": "1f378-fe0f", - "shortnames": [], - "category": "food" - }, - ":coconut:": { - "uc_base": "1f965", - "uc_output": "1f965", - "uc_match": "1f965", - "uc_greedy": "1f965", - "shortnames": [], - "category": "food" - }, - ":cold_sweat:": { - "uc_base": "1f630", - "uc_output": "1f630", - "uc_match": "1f630", - "uc_greedy": "1f630", - "shortnames": [], - "category": "people" - }, - ":compression:": { - "uc_base": "1f5dc", - "uc_output": "1f5dc", - "uc_match": "1f5dc-fe0f", - "uc_greedy": "1f5dc-fe0f", - "shortnames": [], - "category": "objects" - }, - ":computer:": { - "uc_base": "1f4bb", - "uc_output": "1f4bb", - "uc_match": "1f4bb-fe0f", - "uc_greedy": "1f4bb-fe0f", - "shortnames": [], - "category": "objects" - }, - ":confetti_ball:": { - "uc_base": "1f38a", - "uc_output": "1f38a", - "uc_match": "1f38a", - "uc_greedy": "1f38a", - "shortnames": [], - "category": "objects" - }, - ":confounded:": { - "uc_base": "1f616", - "uc_output": "1f616", - "uc_match": "1f616", - "uc_greedy": "1f616", - "shortnames": [], - "category": "people" - }, - ":confused:": { - "uc_base": "1f615", - "uc_output": "1f615", - "uc_match": "1f615", - "uc_greedy": "1f615", - "shortnames": [], - "category": "people" - }, - ":construction:": { - "uc_base": "1f6a7", - "uc_output": "1f6a7", - "uc_match": "1f6a7", - "uc_greedy": "1f6a7", - "shortnames": [], - "category": "travel" - }, - ":construction_site:": { - "uc_base": "1f3d7", - "uc_output": "1f3d7", - "uc_match": "1f3d7-fe0f", - "uc_greedy": "1f3d7-fe0f", - "shortnames": [":building_construction:"], - "category": "travel" - }, - ":construction_worker:": { - "uc_base": "1f477", - "uc_output": "1f477", - "uc_match": "1f477", - "uc_greedy": "1f477", - "shortnames": [], - "category": "people" - }, - ":control_knobs:": { - "uc_base": "1f39b", - "uc_output": "1f39b", - "uc_match": "1f39b-fe0f", - "uc_greedy": "1f39b-fe0f", - "shortnames": [], - "category": "objects" - }, - ":convenience_store:": { - "uc_base": "1f3ea", - "uc_output": "1f3ea", - "uc_match": "1f3ea", - "uc_greedy": "1f3ea", - "shortnames": [], - "category": "travel" - }, - ":cookie:": { - "uc_base": "1f36a", - "uc_output": "1f36a", - "uc_match": "1f36a", - "uc_greedy": "1f36a", - "shortnames": [], - "category": "food" - }, - ":cooking:": { - "uc_base": "1f373", - "uc_output": "1f373", - "uc_match": "1f373", - "uc_greedy": "1f373", - "shortnames": [], - "category": "food" - }, - ":cool:": { - "uc_base": "1f192", - "uc_output": "1f192", - "uc_match": "1f192", - "uc_greedy": "1f192", - "shortnames": [], - "category": "symbols" - }, - ":corn:": { - "uc_base": "1f33d", - "uc_output": "1f33d", - "uc_match": "1f33d", - "uc_greedy": "1f33d", - "shortnames": [], - "category": "food" - }, - ":couch:": { - "uc_base": "1f6cb", - "uc_output": "1f6cb", - "uc_match": "1f6cb-fe0f", - "uc_greedy": "1f6cb-fe0f", - "shortnames": [":couch_and_lamp:"], - "category": "objects" - }, - ":couple:": { - "uc_base": "1f46b", - "uc_output": "1f46b", - "uc_match": "1f46b", - "uc_greedy": "1f46b", - "shortnames": [], - "category": "people" - }, - ":couple_with_heart:": { - "uc_base": "1f491", - "uc_output": "1f491", - "uc_match": "1f491", - "uc_greedy": "1f491", - "shortnames": [], - "category": "people" - }, - ":couplekiss:": { - "uc_base": "1f48f", - "uc_output": "1f48f", - "uc_match": "1f48f", - "uc_greedy": "1f48f", - "shortnames": [], - "category": "people" - }, - ":cow2:": { - "uc_base": "1f404", - "uc_output": "1f404", - "uc_match": "1f404", - "uc_greedy": "1f404", - "shortnames": [], - "category": "nature" - }, - ":cow:": { - "uc_base": "1f42e", - "uc_output": "1f42e", - "uc_match": "1f42e", - "uc_greedy": "1f42e", - "shortnames": [], - "category": "nature" - }, - ":cowboy:": { - "uc_base": "1f920", - "uc_output": "1f920", - "uc_match": "1f920", - "uc_greedy": "1f920", - "shortnames": [":face_with_cowboy_hat:"], - "category": "people" - }, - ":crab:": { - "uc_base": "1f980", - "uc_output": "1f980", - "uc_match": "1f980", - "uc_greedy": "1f980", - "shortnames": [], - "category": "nature" - }, - ":crayon:": { - "uc_base": "1f58d", - "uc_output": "1f58d", - "uc_match": "1f58d-fe0f", - "uc_greedy": "1f58d-fe0f", - "shortnames": [":lower_left_crayon:"], - "category": "objects" - }, - ":crazy_face:": { - "uc_base": "1f92a", - "uc_output": "1f92a", - "uc_match": "1f92a", - "uc_greedy": "1f92a", - "shortnames": [], - "category": "people" - }, - ":credit_card:": { - "uc_base": "1f4b3", - "uc_output": "1f4b3", - "uc_match": "1f4b3-fe0f", - "uc_greedy": "1f4b3-fe0f", - "shortnames": [], - "category": "objects" - }, - ":crescent_moon:": { - "uc_base": "1f319", - "uc_output": "1f319", - "uc_match": "1f319", - "uc_greedy": "1f319", - "shortnames": [], - "category": "nature" - }, - ":cricket:": { - "uc_base": "1f997", - "uc_output": "1f997", - "uc_match": "1f997", - "uc_greedy": "1f997", - "shortnames": [], - "category": "nature" - }, - ":cricket_game:": { - "uc_base": "1f3cf", - "uc_output": "1f3cf", - "uc_match": "1f3cf", - "uc_greedy": "1f3cf", - "shortnames": [":cricket_bat_ball:"], - "category": "activity" - }, - ":crocodile:": { - "uc_base": "1f40a", - "uc_output": "1f40a", - "uc_match": "1f40a", - "uc_greedy": "1f40a", - "shortnames": [], - "category": "nature" - }, - ":croissant:": { - "uc_base": "1f950", - "uc_output": "1f950", - "uc_match": "1f950", - "uc_greedy": "1f950", - "shortnames": [], - "category": "food" - }, - ":crossed_flags:": { - "uc_base": "1f38c", - "uc_output": "1f38c", - "uc_match": "1f38c", - "uc_greedy": "1f38c", - "shortnames": [], - "category": "flags" - }, - ":crown:": { - "uc_base": "1f451", - "uc_output": "1f451", - "uc_match": "1f451", - "uc_greedy": "1f451", - "shortnames": [], - "category": "people" - }, - ":cruise_ship:": { - "uc_base": "1f6f3", - "uc_output": "1f6f3", - "uc_match": "1f6f3-fe0f", - "uc_greedy": "1f6f3-fe0f", - "shortnames": [":passenger_ship:"], - "category": "travel" - }, - ":cry:": { - "uc_base": "1f622", - "uc_output": "1f622", - "uc_match": "1f622", - "uc_greedy": "1f622", - "shortnames": [], - "category": "people" - }, - ":crying_cat_face:": { - "uc_base": "1f63f", - "uc_output": "1f63f", - "uc_match": "1f63f", - "uc_greedy": "1f63f", - "shortnames": [], - "category": "people" - }, - ":crystal_ball:": { - "uc_base": "1f52e", - "uc_output": "1f52e", - "uc_match": "1f52e", - "uc_greedy": "1f52e", - "shortnames": [], - "category": "objects" - }, - ":cucumber:": { - "uc_base": "1f952", - "uc_output": "1f952", - "uc_match": "1f952", - "uc_greedy": "1f952", - "shortnames": [], - "category": "food" - }, - ":cup_with_straw:": { - "uc_base": "1f964", - "uc_output": "1f964", - "uc_match": "1f964", - "uc_greedy": "1f964", - "shortnames": [], - "category": "food" - }, - ":cupid:": { - "uc_base": "1f498", - "uc_output": "1f498", - "uc_match": "1f498", - "uc_greedy": "1f498", - "shortnames": [], - "category": "symbols" - }, - ":curling_stone:": { - "uc_base": "1f94c", - "uc_output": "1f94c", - "uc_match": "1f94c", - "uc_greedy": "1f94c", - "shortnames": [], - "category": "activity" - }, - ":currency_exchange:": { - "uc_base": "1f4b1", - "uc_output": "1f4b1", - "uc_match": "1f4b1", - "uc_greedy": "1f4b1", - "shortnames": [], - "category": "symbols" - }, - ":curry:": { - "uc_base": "1f35b", - "uc_output": "1f35b", - "uc_match": "1f35b", - "uc_greedy": "1f35b", - "shortnames": [], - "category": "food" - }, - ":custard:": { - "uc_base": "1f36e", - "uc_output": "1f36e", - "uc_match": "1f36e", - "uc_greedy": "1f36e", - "shortnames": [":pudding:", ":flan:"], - "category": "food" - }, - ":customs:": { - "uc_base": "1f6c3", - "uc_output": "1f6c3", - "uc_match": "1f6c3", - "uc_greedy": "1f6c3", - "shortnames": [], - "category": "symbols" - }, - ":cut_of_meat:": { - "uc_base": "1f969", - "uc_output": "1f969", - "uc_match": "1f969", - "uc_greedy": "1f969", - "shortnames": [], - "category": "food" - }, - ":cyclone:": { - "uc_base": "1f300", - "uc_output": "1f300", - "uc_match": "1f300", - "uc_greedy": "1f300", - "shortnames": [], - "category": "symbols" - }, - ":dagger:": { - "uc_base": "1f5e1", - "uc_output": "1f5e1", - "uc_match": "1f5e1-fe0f", - "uc_greedy": "1f5e1-fe0f", - "shortnames": [":dagger_knife:"], - "category": "objects" - }, - ":dancer:": { - "uc_base": "1f483", - "uc_output": "1f483", - "uc_match": "1f483", - "uc_greedy": "1f483", - "shortnames": [], - "category": "people" - }, - ":dango:": { - "uc_base": "1f361", - "uc_output": "1f361", - "uc_match": "1f361", - "uc_greedy": "1f361", - "shortnames": [], - "category": "food" - }, - ":dark_sunglasses:": { - "uc_base": "1f576", - "uc_output": "1f576", - "uc_match": "1f576-fe0f", - "uc_greedy": "1f576-fe0f", - "shortnames": [], - "category": "people" - }, - ":dart:": { - "uc_base": "1f3af", - "uc_output": "1f3af", - "uc_match": "1f3af", - "uc_greedy": "1f3af", - "shortnames": [], - "category": "activity" - }, - ":dash:": { - "uc_base": "1f4a8", - "uc_output": "1f4a8", - "uc_match": "1f4a8", - "uc_greedy": "1f4a8", - "shortnames": [], - "category": "nature" - }, - ":date:": { - "uc_base": "1f4c5", - "uc_output": "1f4c5", - "uc_match": "1f4c5", - "uc_greedy": "1f4c5", - "shortnames": [], - "category": "objects" - }, - ":deciduous_tree:": { - "uc_base": "1f333", - "uc_output": "1f333", - "uc_match": "1f333", - "uc_greedy": "1f333", - "shortnames": [], - "category": "nature" - }, - ":deer:": { - "uc_base": "1f98c", - "uc_output": "1f98c", - "uc_match": "1f98c", - "uc_greedy": "1f98c", - "shortnames": [], - "category": "nature" - }, - ":department_store:": { - "uc_base": "1f3ec", - "uc_output": "1f3ec", - "uc_match": "1f3ec", - "uc_greedy": "1f3ec", - "shortnames": [], - "category": "travel" - }, - ":desert:": { - "uc_base": "1f3dc", - "uc_output": "1f3dc", - "uc_match": "1f3dc-fe0f", - "uc_greedy": "1f3dc-fe0f", - "shortnames": [], - "category": "travel" - }, - ":desktop:": { - "uc_base": "1f5a5", - "uc_output": "1f5a5", - "uc_match": "1f5a5-fe0f", - "uc_greedy": "1f5a5-fe0f", - "shortnames": [":desktop_computer:"], - "category": "objects" - }, - ":detective:": { - "uc_base": "1f575", - "uc_output": "1f575", - "uc_match": "1f575-fe0f", - "uc_greedy": "1f575-fe0f", - "shortnames": [":spy:", ":sleuth_or_spy:"], - "category": "people" - }, - ":diamond_shape_with_a_dot_inside:": { - "uc_base": "1f4a0", - "uc_output": "1f4a0", - "uc_match": "1f4a0", - "uc_greedy": "1f4a0", - "shortnames": [], - "category": "symbols" - }, - ":disappointed:": { - "uc_base": "1f61e", - "uc_output": "1f61e", - "uc_match": "1f61e", - "uc_greedy": "1f61e", - "shortnames": [], - "category": "people" - }, - ":disappointed_relieved:": { - "uc_base": "1f625", - "uc_output": "1f625", - "uc_match": "1f625", - "uc_greedy": "1f625", - "shortnames": [], - "category": "people" - }, - ":dividers:": { - "uc_base": "1f5c2", - "uc_output": "1f5c2", - "uc_match": "1f5c2-fe0f", - "uc_greedy": "1f5c2-fe0f", - "shortnames": [":card_index_dividers:"], - "category": "objects" - }, - ":dizzy:": { - "uc_base": "1f4ab", - "uc_output": "1f4ab", - "uc_match": "1f4ab", - "uc_greedy": "1f4ab", - "shortnames": [], - "category": "nature" - }, - ":dizzy_face:": { - "uc_base": "1f635", - "uc_output": "1f635", - "uc_match": "1f635", - "uc_greedy": "1f635", - "shortnames": [], - "category": "people" - }, - ":do_not_litter:": { - "uc_base": "1f6af", - "uc_output": "1f6af", - "uc_match": "1f6af", - "uc_greedy": "1f6af", - "shortnames": [], - "category": "symbols" - }, - ":dog2:": { - "uc_base": "1f415", - "uc_output": "1f415", - "uc_match": "1f415-fe0f", - "uc_greedy": "1f415-fe0f", - "shortnames": [], - "category": "nature" - }, - ":dog:": { - "uc_base": "1f436", - "uc_output": "1f436", - "uc_match": "1f436", - "uc_greedy": "1f436", - "shortnames": [], - "category": "nature" - }, - ":dollar:": { - "uc_base": "1f4b5", - "uc_output": "1f4b5", - "uc_match": "1f4b5", - "uc_greedy": "1f4b5", - "shortnames": [], - "category": "objects" - }, - ":dolls:": { - "uc_base": "1f38e", - "uc_output": "1f38e", - "uc_match": "1f38e", - "uc_greedy": "1f38e", - "shortnames": [], - "category": "objects" - }, - ":dolphin:": { - "uc_base": "1f42c", - "uc_output": "1f42c", - "uc_match": "1f42c", - "uc_greedy": "1f42c", - "shortnames": [], - "category": "nature" - }, - ":door:": { - "uc_base": "1f6aa", - "uc_output": "1f6aa", - "uc_match": "1f6aa", - "uc_greedy": "1f6aa", - "shortnames": [], - "category": "objects" - }, - ":doughnut:": { - "uc_base": "1f369", - "uc_output": "1f369", - "uc_match": "1f369", - "uc_greedy": "1f369", - "shortnames": [], - "category": "food" - }, - ":dove:": { - "uc_base": "1f54a", - "uc_output": "1f54a", - "uc_match": "1f54a-fe0f", - "uc_greedy": "1f54a-fe0f", - "shortnames": [":dove_of_peace:"], - "category": "nature" - }, - ":dragon:": { - "uc_base": "1f409", - "uc_output": "1f409", - "uc_match": "1f409", - "uc_greedy": "1f409", - "shortnames": [], - "category": "nature" - }, - ":dragon_face:": { - "uc_base": "1f432", - "uc_output": "1f432", - "uc_match": "1f432", - "uc_greedy": "1f432", - "shortnames": [], - "category": "nature" - }, - ":dress:": { - "uc_base": "1f457", - "uc_output": "1f457", - "uc_match": "1f457", - "uc_greedy": "1f457", - "shortnames": [], - "category": "people" - }, - ":dromedary_camel:": { - "uc_base": "1f42a", - "uc_output": "1f42a", - "uc_match": "1f42a", - "uc_greedy": "1f42a", - "shortnames": [], - "category": "nature" - }, - ":drooling_face:": { - "uc_base": "1f924", - "uc_output": "1f924", - "uc_match": "1f924", - "uc_greedy": "1f924", - "shortnames": [":drool:"], - "category": "people" - }, - ":droplet:": { - "uc_base": "1f4a7", - "uc_output": "1f4a7", - "uc_match": "1f4a7", - "uc_greedy": "1f4a7", - "shortnames": [], - "category": "nature" - }, - ":drum:": { - "uc_base": "1f941", - "uc_output": "1f941", - "uc_match": "1f941", - "uc_greedy": "1f941", - "shortnames": [":drum_with_drumsticks:"], - "category": "activity" - }, - ":duck:": { - "uc_base": "1f986", - "uc_output": "1f986", - "uc_match": "1f986", - "uc_greedy": "1f986", - "shortnames": [], - "category": "nature" - }, - ":dumpling:": { - "uc_base": "1f95f", - "uc_output": "1f95f", - "uc_match": "1f95f", - "uc_greedy": "1f95f", - "shortnames": [], - "category": "food" - }, - ":dvd:": { - "uc_base": "1f4c0", - "uc_output": "1f4c0", - "uc_match": "1f4c0", - "uc_greedy": "1f4c0", - "shortnames": [], - "category": "objects" - }, - ":e-mail:": { - "uc_base": "1f4e7", - "uc_output": "1f4e7", - "uc_match": "1f4e7", - "uc_greedy": "1f4e7", - "shortnames": [":email:"], - "category": "objects" - }, - ":eagle:": { - "uc_base": "1f985", - "uc_output": "1f985", - "uc_match": "1f985", - "uc_greedy": "1f985", - "shortnames": [], - "category": "nature" - }, - ":ear:": { - "uc_base": "1f442", - "uc_output": "1f442", - "uc_match": "1f442-fe0f", - "uc_greedy": "1f442-fe0f", - "shortnames": [], - "category": "people" - }, - ":ear_of_rice:": { - "uc_base": "1f33e", - "uc_output": "1f33e", - "uc_match": "1f33e", - "uc_greedy": "1f33e", - "shortnames": [], - "category": "nature" - }, - ":earth_africa:": { - "uc_base": "1f30d", - "uc_output": "1f30d", - "uc_match": "1f30d-fe0f", - "uc_greedy": "1f30d-fe0f", - "shortnames": [], - "category": "nature" - }, - ":earth_americas:": { - "uc_base": "1f30e", - "uc_output": "1f30e", - "uc_match": "1f30e-fe0f", - "uc_greedy": "1f30e-fe0f", - "shortnames": [], - "category": "nature" - }, - ":earth_asia:": { - "uc_base": "1f30f", - "uc_output": "1f30f", - "uc_match": "1f30f-fe0f", - "uc_greedy": "1f30f-fe0f", - "shortnames": [], - "category": "nature" - }, - ":egg:": { - "uc_base": "1f95a", - "uc_output": "1f95a", - "uc_match": "1f95a", - "uc_greedy": "1f95a", - "shortnames": [], - "category": "food" - }, - ":eggplant:": { - "uc_base": "1f346", - "uc_output": "1f346", - "uc_match": "1f346", - "uc_greedy": "1f346", - "shortnames": [], - "category": "food" - }, - ":electric_plug:": { - "uc_base": "1f50c", - "uc_output": "1f50c", - "uc_match": "1f50c", - "uc_greedy": "1f50c", - "shortnames": [], - "category": "objects" - }, - ":elephant:": { - "uc_base": "1f418", - "uc_output": "1f418", - "uc_match": "1f418", - "uc_greedy": "1f418", - "shortnames": [], - "category": "nature" - }, - ":elf:": { - "uc_base": "1f9dd", - "uc_output": "1f9dd", - "uc_match": "1f9dd", - "uc_greedy": "1f9dd", - "shortnames": [], - "category": "people" - }, - ":end:": { - "uc_base": "1f51a", - "uc_output": "1f51a", - "uc_match": "1f51a", - "uc_greedy": "1f51a", - "shortnames": [], - "category": "symbols" - }, - ":envelope_with_arrow:": { - "uc_base": "1f4e9", - "uc_output": "1f4e9", - "uc_match": "1f4e9", - "uc_greedy": "1f4e9", - "shortnames": [], - "category": "objects" - }, - ":euro:": { - "uc_base": "1f4b6", - "uc_output": "1f4b6", - "uc_match": "1f4b6", - "uc_greedy": "1f4b6", - "shortnames": [], - "category": "objects" - }, - ":european_castle:": { - "uc_base": "1f3f0", - "uc_output": "1f3f0", - "uc_match": "1f3f0", - "uc_greedy": "1f3f0", - "shortnames": [], - "category": "travel" - }, - ":european_post_office:": { - "uc_base": "1f3e4", - "uc_output": "1f3e4", - "uc_match": "1f3e4", - "uc_greedy": "1f3e4", - "shortnames": [], - "category": "travel" - }, - ":evergreen_tree:": { - "uc_base": "1f332", - "uc_output": "1f332", - "uc_match": "1f332", - "uc_greedy": "1f332", - "shortnames": [], - "category": "nature" - }, - ":exploding_head:": { - "uc_base": "1f92f", - "uc_output": "1f92f", - "uc_match": "1f92f", - "uc_greedy": "1f92f", - "shortnames": [], - "category": "people" - }, - ":expressionless:": { - "uc_base": "1f611", - "uc_output": "1f611", - "uc_match": "1f611", - "uc_greedy": "1f611", - "shortnames": [], - "category": "people" - }, - ":eye:": { - "uc_base": "1f441", - "uc_output": "1f441", - "uc_match": "1f441-fe0f", - "uc_greedy": "1f441-fe0f", - "shortnames": [], - "category": "people" - }, - ":eyeglasses:": { - "uc_base": "1f453", - "uc_output": "1f453", - "uc_match": "1f453-fe0f", - "uc_greedy": "1f453-fe0f", - "shortnames": [], - "category": "people" - }, - ":eyes:": { - "uc_base": "1f440", - "uc_output": "1f440", - "uc_match": "1f440", - "uc_greedy": "1f440", - "shortnames": [], - "category": "people" - }, - ":face_vomiting:": { - "uc_base": "1f92e", - "uc_output": "1f92e", - "uc_match": "1f92e", - "uc_greedy": "1f92e", - "shortnames": [], - "category": "people" - }, - ":face_with_hand_over_mouth:": { - "uc_base": "1f92d", - "uc_output": "1f92d", - "uc_match": "1f92d", - "uc_greedy": "1f92d", - "shortnames": [], - "category": "people" - }, - ":face_with_monocle:": { - "uc_base": "1f9d0", - "uc_output": "1f9d0", - "uc_match": "1f9d0", - "uc_greedy": "1f9d0", - "shortnames": [], - "category": "people" - }, - ":face_with_raised_eyebrow:": { - "uc_base": "1f928", - "uc_output": "1f928", - "uc_match": "1f928", - "uc_greedy": "1f928", - "shortnames": [], - "category": "people" - }, - ":face_with_symbols_over_mouth:": { - "uc_base": "1f92c", - "uc_output": "1f92c", - "uc_match": "1f92c", - "uc_greedy": "1f92c", - "shortnames": [], - "category": "people" - }, - ":factory:": { - "uc_base": "1f3ed", - "uc_output": "1f3ed", - "uc_match": "1f3ed-fe0f", - "uc_greedy": "1f3ed-fe0f", - "shortnames": [], - "category": "travel" - }, - ":fairy:": { - "uc_base": "1f9da", - "uc_output": "1f9da", - "uc_match": "1f9da", - "uc_greedy": "1f9da", - "shortnames": [], - "category": "people" - }, - ":fallen_leaf:": { - "uc_base": "1f342", - "uc_output": "1f342", - "uc_match": "1f342", - "uc_greedy": "1f342", - "shortnames": [], - "category": "nature" - }, - ":family:": { - "uc_base": "1f46a", - "uc_output": "1f46a", - "uc_match": "1f46a-fe0f", - "uc_greedy": "1f46a-fe0f", - "shortnames": [], - "category": "people" - }, - ":fax:": { - "uc_base": "1f4e0", - "uc_output": "1f4e0", - "uc_match": "1f4e0", - "uc_greedy": "1f4e0", - "shortnames": [], - "category": "objects" - }, - ":fearful:": { - "uc_base": "1f628", - "uc_output": "1f628", - "uc_match": "1f628", - "uc_greedy": "1f628", - "shortnames": [], - "category": "people" - }, - ":feet:": { - "uc_base": "1f43e", - "uc_output": "1f43e", - "uc_match": "1f43e", - "uc_greedy": "1f43e", - "shortnames": [":paw_prints:"], - "category": "nature" - }, - ":ferris_wheel:": { - "uc_base": "1f3a1", - "uc_output": "1f3a1", - "uc_match": "1f3a1", - "uc_greedy": "1f3a1", - "shortnames": [], - "category": "travel" - }, - ":field_hockey:": { - "uc_base": "1f3d1", - "uc_output": "1f3d1", - "uc_match": "1f3d1", - "uc_greedy": "1f3d1", - "shortnames": [], - "category": "activity" - }, - ":file_cabinet:": { - "uc_base": "1f5c4", - "uc_output": "1f5c4", - "uc_match": "1f5c4-fe0f", - "uc_greedy": "1f5c4-fe0f", - "shortnames": [], - "category": "objects" - }, - ":file_folder:": { - "uc_base": "1f4c1", - "uc_output": "1f4c1", - "uc_match": "1f4c1", - "uc_greedy": "1f4c1", - "shortnames": [], - "category": "objects" - }, - ":film_frames:": { - "uc_base": "1f39e", - "uc_output": "1f39e", - "uc_match": "1f39e-fe0f", - "uc_greedy": "1f39e-fe0f", - "shortnames": [], - "category": "objects" - }, - ":fingers_crossed:": { - "uc_base": "1f91e", - "uc_output": "1f91e", - "uc_match": "1f91e", - "uc_greedy": "1f91e", - "shortnames": [":hand_with_index_and_middle_finger_crossed:"], - "category": "people" - }, - ":fire:": { - "uc_base": "1f525", - "uc_output": "1f525", - "uc_match": "1f525", - "uc_greedy": "1f525", - "shortnames": [":flame:"], - "category": "nature" - }, - ":fire_engine:": { - "uc_base": "1f692", - "uc_output": "1f692", - "uc_match": "1f692", - "uc_greedy": "1f692", - "shortnames": [], - "category": "travel" - }, - ":fireworks:": { - "uc_base": "1f386", - "uc_output": "1f386", - "uc_match": "1f386", - "uc_greedy": "1f386", - "shortnames": [], - "category": "travel" - }, - ":first_place:": { - "uc_base": "1f947", - "uc_output": "1f947", - "uc_match": "1f947", - "uc_greedy": "1f947", - "shortnames": [":first_place_medal:"], - "category": "activity" - }, - ":first_quarter_moon:": { - "uc_base": "1f313", - "uc_output": "1f313", - "uc_match": "1f313", - "uc_greedy": "1f313", - "shortnames": [], - "category": "nature" - }, - ":first_quarter_moon_with_face:": { - "uc_base": "1f31b", - "uc_output": "1f31b", - "uc_match": "1f31b", - "uc_greedy": "1f31b", - "shortnames": [], - "category": "nature" - }, - ":fish:": { - "uc_base": "1f41f", - "uc_output": "1f41f", - "uc_match": "1f41f-fe0f", - "uc_greedy": "1f41f-fe0f", - "shortnames": [], - "category": "nature" - }, - ":fish_cake:": { - "uc_base": "1f365", - "uc_output": "1f365", - "uc_match": "1f365", - "uc_greedy": "1f365", - "shortnames": [], - "category": "food" - }, - ":fishing_pole_and_fish:": { - "uc_base": "1f3a3", - "uc_output": "1f3a3", - "uc_match": "1f3a3", - "uc_greedy": "1f3a3", - "shortnames": [], - "category": "activity" - }, - ":flag_black:": { - "uc_base": "1f3f4", - "uc_output": "1f3f4", - "uc_match": "1f3f4", - "uc_greedy": "1f3f4", - "shortnames": [":waving_black_flag:"], - "category": "flags" - }, - ":flag_white:": { - "uc_base": "1f3f3", - "uc_output": "1f3f3", - "uc_match": "1f3f3-fe0f", - "uc_greedy": "1f3f3-fe0f", - "shortnames": [":waving_white_flag:"], - "category": "flags" - }, - ":flags:": { - "uc_base": "1f38f", - "uc_output": "1f38f", - "uc_match": "1f38f", - "uc_greedy": "1f38f", - "shortnames": [], - "category": "objects" - }, - ":flashlight:": { - "uc_base": "1f526", - "uc_output": "1f526", - "uc_match": "1f526", - "uc_greedy": "1f526", - "shortnames": [], - "category": "objects" - }, - ":floppy_disk:": { - "uc_base": "1f4be", - "uc_output": "1f4be", - "uc_match": "1f4be", - "uc_greedy": "1f4be", - "shortnames": [], - "category": "objects" - }, - ":flower_playing_cards:": { - "uc_base": "1f3b4", - "uc_output": "1f3b4", - "uc_match": "1f3b4", - "uc_greedy": "1f3b4", - "shortnames": [], - "category": "symbols" - }, - ":flushed:": { - "uc_base": "1f633", - "uc_output": "1f633", - "uc_match": "1f633", - "uc_greedy": "1f633", - "shortnames": [], - "category": "people" - }, - ":flying_saucer:": { - "uc_base": "1f6f8", - "uc_output": "1f6f8", - "uc_match": "1f6f8", - "uc_greedy": "1f6f8", - "shortnames": [], - "category": "travel" - }, - ":fog:": { - "uc_base": "1f32b", - "uc_output": "1f32b", - "uc_match": "1f32b-fe0f", - "uc_greedy": "1f32b-fe0f", - "shortnames": [], - "category": "nature" - }, - ":foggy:": { - "uc_base": "1f301", - "uc_output": "1f301", - "uc_match": "1f301", - "uc_greedy": "1f301", - "shortnames": [], - "category": "travel" - }, - ":football:": { - "uc_base": "1f3c8", - "uc_output": "1f3c8", - "uc_match": "1f3c8", - "uc_greedy": "1f3c8", - "shortnames": [], - "category": "activity" - }, - ":footprints:": { - "uc_base": "1f463", - "uc_output": "1f463", - "uc_match": "1f463", - "uc_greedy": "1f463", - "shortnames": [], - "category": "people" - }, - ":fork_and_knife:": { - "uc_base": "1f374", - "uc_output": "1f374", - "uc_match": "1f374", - "uc_greedy": "1f374", - "shortnames": [], - "category": "food" - }, - ":fork_knife_plate:": { - "uc_base": "1f37d", - "uc_output": "1f37d", - "uc_match": "1f37d-fe0f", - "uc_greedy": "1f37d-fe0f", - "shortnames": [":fork_and_knife_with_plate:"], - "category": "food" - }, - ":fortune_cookie:": { - "uc_base": "1f960", - "uc_output": "1f960", - "uc_match": "1f960", - "uc_greedy": "1f960", - "shortnames": [], - "category": "food" - }, - ":four_leaf_clover:": { - "uc_base": "1f340", - "uc_output": "1f340", - "uc_match": "1f340", - "uc_greedy": "1f340", - "shortnames": [], - "category": "nature" - }, - ":fox:": { - "uc_base": "1f98a", - "uc_output": "1f98a", - "uc_match": "1f98a", - "uc_greedy": "1f98a", - "shortnames": [":fox_face:"], - "category": "nature" - }, - ":frame_photo:": { - "uc_base": "1f5bc", - "uc_output": "1f5bc", - "uc_match": "1f5bc-fe0f", - "uc_greedy": "1f5bc-fe0f", - "shortnames": [":frame_with_picture:"], - "category": "objects" - }, - ":free:": { - "uc_base": "1f193", - "uc_output": "1f193", - "uc_match": "1f193", - "uc_greedy": "1f193", - "shortnames": [], - "category": "symbols" - }, - ":french_bread:": { - "uc_base": "1f956", - "uc_output": "1f956", - "uc_match": "1f956", - "uc_greedy": "1f956", - "shortnames": [":baguette_bread:"], - "category": "food" - }, - ":fried_shrimp:": { - "uc_base": "1f364", - "uc_output": "1f364", - "uc_match": "1f364", - "uc_greedy": "1f364", - "shortnames": [], - "category": "food" - }, - ":fries:": { - "uc_base": "1f35f", - "uc_output": "1f35f", - "uc_match": "1f35f", - "uc_greedy": "1f35f", - "shortnames": [], - "category": "food" - }, - ":frog:": { - "uc_base": "1f438", - "uc_output": "1f438", - "uc_match": "1f438", - "uc_greedy": "1f438", - "shortnames": [], - "category": "nature" - }, - ":frowning:": { - "uc_base": "1f626", - "uc_output": "1f626", - "uc_match": "1f626", - "uc_greedy": "1f626", - "shortnames": [], - "category": "people" - }, - ":full_moon:": { - "uc_base": "1f315", - "uc_output": "1f315", - "uc_match": "1f315-fe0f", - "uc_greedy": "1f315-fe0f", - "shortnames": [], - "category": "nature" - }, - ":full_moon_with_face:": { - "uc_base": "1f31d", - "uc_output": "1f31d", - "uc_match": "1f31d", - "uc_greedy": "1f31d", - "shortnames": [], - "category": "nature" - }, - ":game_die:": { - "uc_base": "1f3b2", - "uc_output": "1f3b2", - "uc_match": "1f3b2", - "uc_greedy": "1f3b2", - "shortnames": [], - "category": "activity" - }, - ":gem:": { - "uc_base": "1f48e", - "uc_output": "1f48e", - "uc_match": "1f48e", - "uc_greedy": "1f48e", - "shortnames": [], - "category": "objects" - }, - ":genie:": { - "uc_base": "1f9de", - "uc_output": "1f9de", - "uc_match": "1f9de", - "uc_greedy": "1f9de", - "shortnames": [], - "category": "people" - }, - ":ghost:": { - "uc_base": "1f47b", - "uc_output": "1f47b", - "uc_match": "1f47b", - "uc_greedy": "1f47b", - "shortnames": [], - "category": "people" - }, - ":gift:": { - "uc_base": "1f381", - "uc_output": "1f381", - "uc_match": "1f381", - "uc_greedy": "1f381", - "shortnames": [], - "category": "objects" - }, - ":gift_heart:": { - "uc_base": "1f49d", - "uc_output": "1f49d", - "uc_match": "1f49d", - "uc_greedy": "1f49d", - "shortnames": [], - "category": "symbols" - }, - ":giraffe:": { - "uc_base": "1f992", - "uc_output": "1f992", - "uc_match": "1f992", - "uc_greedy": "1f992", - "shortnames": [], - "category": "nature" - }, - ":girl:": { - "uc_base": "1f467", - "uc_output": "1f467", - "uc_match": "1f467", - "uc_greedy": "1f467", - "shortnames": [], - "category": "people" - }, - ":globe_with_meridians:": { - "uc_base": "1f310", - "uc_output": "1f310", - "uc_match": "1f310", - "uc_greedy": "1f310", - "shortnames": [], - "category": "symbols" - }, - ":gloves:": { - "uc_base": "1f9e4", - "uc_output": "1f9e4", - "uc_match": "1f9e4", - "uc_greedy": "1f9e4", - "shortnames": [], - "category": "people" - }, - ":goal:": { - "uc_base": "1f945", - "uc_output": "1f945", - "uc_match": "1f945", - "uc_greedy": "1f945", - "shortnames": [":goal_net:"], - "category": "activity" - }, - ":goat:": { - "uc_base": "1f410", - "uc_output": "1f410", - "uc_match": "1f410", - "uc_greedy": "1f410", - "shortnames": [], - "category": "nature" - }, - ":gorilla:": { - "uc_base": "1f98d", - "uc_output": "1f98d", - "uc_match": "1f98d", - "uc_greedy": "1f98d", - "shortnames": [], - "category": "nature" - }, - ":grapes:": { - "uc_base": "1f347", - "uc_output": "1f347", - "uc_match": "1f347", - "uc_greedy": "1f347", - "shortnames": [], - "category": "food" - }, - ":green_apple:": { - "uc_base": "1f34f", - "uc_output": "1f34f", - "uc_match": "1f34f", - "uc_greedy": "1f34f", - "shortnames": [], - "category": "food" - }, - ":green_book:": { - "uc_base": "1f4d7", - "uc_output": "1f4d7", - "uc_match": "1f4d7", - "uc_greedy": "1f4d7", - "shortnames": [], - "category": "objects" - }, - ":green_heart:": { - "uc_base": "1f49a", - "uc_output": "1f49a", - "uc_match": "1f49a", - "uc_greedy": "1f49a", - "shortnames": [], - "category": "symbols" - }, - ":grimacing:": { - "uc_base": "1f62c", - "uc_output": "1f62c", - "uc_match": "1f62c", - "uc_greedy": "1f62c", - "shortnames": [], - "category": "people" - }, - ":grin:": { - "uc_base": "1f601", - "uc_output": "1f601", - "uc_match": "1f601", - "uc_greedy": "1f601", - "shortnames": [], - "category": "people" - }, - ":grinning:": { - "uc_base": "1f600", - "uc_output": "1f600", - "uc_match": "1f600", - "uc_greedy": "1f600", - "shortnames": [], - "category": "people" - }, - ":guard:": { - "uc_base": "1f482", - "uc_output": "1f482", - "uc_match": "1f482", - "uc_greedy": "1f482", - "shortnames": [":guardsman:"], - "category": "people" - }, - ":guitar:": { - "uc_base": "1f3b8", - "uc_output": "1f3b8", - "uc_match": "1f3b8", - "uc_greedy": "1f3b8", - "shortnames": [], - "category": "activity" - }, - ":gun:": { - "uc_base": "1f52b", - "uc_output": "1f52b", - "uc_match": "1f52b", - "uc_greedy": "1f52b", - "shortnames": [], - "category": "objects" - }, - ":hamburger:": { - "uc_base": "1f354", - "uc_output": "1f354", - "uc_match": "1f354", - "uc_greedy": "1f354", - "shortnames": [], - "category": "food" - }, - ":hammer:": { - "uc_base": "1f528", - "uc_output": "1f528", - "uc_match": "1f528", - "uc_greedy": "1f528", - "shortnames": [], - "category": "objects" - }, - ":hamster:": { - "uc_base": "1f439", - "uc_output": "1f439", - "uc_match": "1f439", - "uc_greedy": "1f439", - "shortnames": [], - "category": "nature" - }, - ":hand_splayed:": { - "uc_base": "1f590", - "uc_output": "1f590", - "uc_match": "1f590-fe0f", - "uc_greedy": "1f590-fe0f", - "shortnames": [":raised_hand_with_fingers_splayed:"], - "category": "people" - }, - ":handbag:": { - "uc_base": "1f45c", - "uc_output": "1f45c", - "uc_match": "1f45c", - "uc_greedy": "1f45c", - "shortnames": [], - "category": "people" - }, - ":handshake:": { - "uc_base": "1f91d", - "uc_output": "1f91d", - "uc_match": "1f91d", - "uc_greedy": "1f91d", - "shortnames": [":shaking_hands:"], - "category": "people" - }, - ":hatched_chick:": { - "uc_base": "1f425", - "uc_output": "1f425", - "uc_match": "1f425", - "uc_greedy": "1f425", - "shortnames": [], - "category": "nature" - }, - ":hatching_chick:": { - "uc_base": "1f423", - "uc_output": "1f423", - "uc_match": "1f423", - "uc_greedy": "1f423", - "shortnames": [], - "category": "nature" - }, - ":head_bandage:": { - "uc_base": "1f915", - "uc_output": "1f915", - "uc_match": "1f915", - "uc_greedy": "1f915", - "shortnames": [":face_with_head_bandage:"], - "category": "people" - }, - ":headphones:": { - "uc_base": "1f3a7", - "uc_output": "1f3a7", - "uc_match": "1f3a7-fe0f", - "uc_greedy": "1f3a7-fe0f", - "shortnames": [], - "category": "activity" - }, - ":hear_no_evil:": { - "uc_base": "1f649", - "uc_output": "1f649", - "uc_match": "1f649", - "uc_greedy": "1f649", - "shortnames": [], - "category": "nature" - }, - ":heart_decoration:": { - "uc_base": "1f49f", - "uc_output": "1f49f", - "uc_match": "1f49f", - "uc_greedy": "1f49f", - "shortnames": [], - "category": "symbols" - }, - ":heart_eyes:": { - "uc_base": "1f60d", - "uc_output": "1f60d", - "uc_match": "1f60d", - "uc_greedy": "1f60d", - "shortnames": [], - "category": "people" - }, - ":heart_eyes_cat:": { - "uc_base": "1f63b", - "uc_output": "1f63b", - "uc_match": "1f63b", - "uc_greedy": "1f63b", - "shortnames": [], - "category": "people" - }, - ":heartbeat:": { - "uc_base": "1f493", - "uc_output": "1f493", - "uc_match": "1f493", - "uc_greedy": "1f493", - "shortnames": [], - "category": "symbols" - }, - ":heartpulse:": { - "uc_base": "1f497", - "uc_output": "1f497", - "uc_match": "1f497", - "uc_greedy": "1f497", - "shortnames": [], - "category": "symbols" - }, - ":heavy_dollar_sign:": { - "uc_base": "1f4b2", - "uc_output": "1f4b2", - "uc_match": "1f4b2", - "uc_greedy": "1f4b2", - "shortnames": [], - "category": "symbols" - }, - ":hedgehog:": { - "uc_base": "1f994", - "uc_output": "1f994", - "uc_match": "1f994", - "uc_greedy": "1f994", - "shortnames": [], - "category": "nature" - }, - ":helicopter:": { - "uc_base": "1f681", - "uc_output": "1f681", - "uc_match": "1f681", - "uc_greedy": "1f681", - "shortnames": [], - "category": "travel" - }, - ":herb:": { - "uc_base": "1f33f", - "uc_output": "1f33f", - "uc_match": "1f33f", - "uc_greedy": "1f33f", - "shortnames": [], - "category": "nature" - }, - ":hibiscus:": { - "uc_base": "1f33a", - "uc_output": "1f33a", - "uc_match": "1f33a", - "uc_greedy": "1f33a", - "shortnames": [], - "category": "nature" - }, - ":high_brightness:": { - "uc_base": "1f506", - "uc_output": "1f506", - "uc_match": "1f506", - "uc_greedy": "1f506", - "shortnames": [], - "category": "symbols" - }, - ":high_heel:": { - "uc_base": "1f460", - "uc_output": "1f460", - "uc_match": "1f460", - "uc_greedy": "1f460", - "shortnames": [], - "category": "people" - }, - ":hockey:": { - "uc_base": "1f3d2", - "uc_output": "1f3d2", - "uc_match": "1f3d2", - "uc_greedy": "1f3d2", - "shortnames": [], - "category": "activity" - }, - ":hole:": { - "uc_base": "1f573", - "uc_output": "1f573", - "uc_match": "1f573-fe0f", - "uc_greedy": "1f573-fe0f", - "shortnames": [], - "category": "objects" - }, - ":homes:": { - "uc_base": "1f3d8", - "uc_output": "1f3d8", - "uc_match": "1f3d8-fe0f", - "uc_greedy": "1f3d8-fe0f", - "shortnames": [":house_buildings:"], - "category": "travel" - }, - ":honey_pot:": { - "uc_base": "1f36f", - "uc_output": "1f36f", - "uc_match": "1f36f", - "uc_greedy": "1f36f", - "shortnames": [], - "category": "food" - }, - ":horse:": { - "uc_base": "1f434", - "uc_output": "1f434", - "uc_match": "1f434", - "uc_greedy": "1f434", - "shortnames": [], - "category": "nature" - }, - ":horse_racing:": { - "uc_base": "1f3c7", - "uc_output": "1f3c7", - "uc_match": "1f3c7", - "uc_greedy": "1f3c7", - "shortnames": [], - "category": "activity" - }, - ":hospital:": { - "uc_base": "1f3e5", - "uc_output": "1f3e5", - "uc_match": "1f3e5", - "uc_greedy": "1f3e5", - "shortnames": [], - "category": "travel" - }, - ":hot_pepper:": { - "uc_base": "1f336", - "uc_output": "1f336", - "uc_match": "1f336-fe0f", - "uc_greedy": "1f336-fe0f", - "shortnames": [], - "category": "food" - }, - ":hotdog:": { - "uc_base": "1f32d", - "uc_output": "1f32d", - "uc_match": "1f32d", - "uc_greedy": "1f32d", - "shortnames": [":hot_dog:"], - "category": "food" - }, - ":hotel:": { - "uc_base": "1f3e8", - "uc_output": "1f3e8", - "uc_match": "1f3e8", - "uc_greedy": "1f3e8", - "shortnames": [], - "category": "travel" - }, - ":house:": { - "uc_base": "1f3e0", - "uc_output": "1f3e0", - "uc_match": "1f3e0-fe0f", - "uc_greedy": "1f3e0-fe0f", - "shortnames": [], - "category": "travel" - }, - ":house_abandoned:": { - "uc_base": "1f3da", - "uc_output": "1f3da", - "uc_match": "1f3da-fe0f", - "uc_greedy": "1f3da-fe0f", - "shortnames": [":derelict_house_building:"], - "category": "travel" - }, - ":house_with_garden:": { - "uc_base": "1f3e1", - "uc_output": "1f3e1", - "uc_match": "1f3e1", - "uc_greedy": "1f3e1", - "shortnames": [], - "category": "travel" - }, - ":hugging:": { - "uc_base": "1f917", - "uc_output": "1f917", - "uc_match": "1f917", - "uc_greedy": "1f917", - "shortnames": [":hugging_face:"], - "category": "people" - }, - ":hushed:": { - "uc_base": "1f62f", - "uc_output": "1f62f", - "uc_match": "1f62f", - "uc_greedy": "1f62f", - "shortnames": [], - "category": "people" - }, - ":ice_cream:": { - "uc_base": "1f368", - "uc_output": "1f368", - "uc_match": "1f368", - "uc_greedy": "1f368", - "shortnames": [], - "category": "food" - }, - ":icecream:": { - "uc_base": "1f366", - "uc_output": "1f366", - "uc_match": "1f366", - "uc_greedy": "1f366", - "shortnames": [], - "category": "food" - }, - ":id:": { - "uc_base": "1f194", - "uc_output": "1f194", - "uc_match": "1f194", - "uc_greedy": "1f194", - "shortnames": [], - "category": "symbols" - }, - ":ideograph_advantage:": { - "uc_base": "1f250", - "uc_output": "1f250", - "uc_match": "1f250", - "uc_greedy": "1f250", - "shortnames": [], - "category": "symbols" - }, - ":imp:": { - "uc_base": "1f47f", - "uc_output": "1f47f", - "uc_match": "1f47f", - "uc_greedy": "1f47f", - "shortnames": [], - "category": "people" - }, - ":inbox_tray:": { - "uc_base": "1f4e5", - "uc_output": "1f4e5", - "uc_match": "1f4e5-fe0f", - "uc_greedy": "1f4e5-fe0f", - "shortnames": [], - "category": "objects" - }, - ":incoming_envelope:": { - "uc_base": "1f4e8", - "uc_output": "1f4e8", - "uc_match": "1f4e8", - "uc_greedy": "1f4e8", - "shortnames": [], - "category": "objects" - }, - ":innocent:": { - "uc_base": "1f607", - "uc_output": "1f607", - "uc_match": "1f607", - "uc_greedy": "1f607", - "shortnames": [], - "category": "people" - }, - ":iphone:": { - "uc_base": "1f4f1", - "uc_output": "1f4f1", - "uc_match": "1f4f1", - "uc_greedy": "1f4f1", - "shortnames": [], - "category": "objects" - }, - ":island:": { - "uc_base": "1f3dd", - "uc_output": "1f3dd", - "uc_match": "1f3dd-fe0f", - "uc_greedy": "1f3dd-fe0f", - "shortnames": [":desert_island:"], - "category": "travel" - }, - ":izakaya_lantern:": { - "uc_base": "1f3ee", - "uc_output": "1f3ee", - "uc_match": "1f3ee", - "uc_greedy": "1f3ee", - "shortnames": [], - "category": "objects" - }, - ":jack_o_lantern:": { - "uc_base": "1f383", - "uc_output": "1f383", - "uc_match": "1f383", - "uc_greedy": "1f383", - "shortnames": [], - "category": "people" - }, - ":japan:": { - "uc_base": "1f5fe", - "uc_output": "1f5fe", - "uc_match": "1f5fe", - "uc_greedy": "1f5fe", - "shortnames": [], - "category": "travel" - }, - ":japanese_castle:": { - "uc_base": "1f3ef", - "uc_output": "1f3ef", - "uc_match": "1f3ef", - "uc_greedy": "1f3ef", - "shortnames": [], - "category": "travel" - }, - ":japanese_goblin:": { - "uc_base": "1f47a", - "uc_output": "1f47a", - "uc_match": "1f47a", - "uc_greedy": "1f47a", - "shortnames": [], - "category": "people" - }, - ":japanese_ogre:": { - "uc_base": "1f479", - "uc_output": "1f479", - "uc_match": "1f479", - "uc_greedy": "1f479", - "shortnames": [], - "category": "people" - }, - ":jeans:": { - "uc_base": "1f456", - "uc_output": "1f456", - "uc_match": "1f456", - "uc_greedy": "1f456", - "shortnames": [], - "category": "people" - }, - ":joy:": { - "uc_base": "1f602", - "uc_output": "1f602", - "uc_match": "1f602", - "uc_greedy": "1f602", - "shortnames": [], - "category": "people" - }, - ":joy_cat:": { - "uc_base": "1f639", - "uc_output": "1f639", - "uc_match": "1f639", - "uc_greedy": "1f639", - "shortnames": [], - "category": "people" - }, - ":joystick:": { - "uc_base": "1f579", - "uc_output": "1f579", - "uc_match": "1f579-fe0f", - "uc_greedy": "1f579-fe0f", - "shortnames": [], - "category": "objects" - }, - ":kaaba:": { - "uc_base": "1f54b", - "uc_output": "1f54b", - "uc_match": "1f54b", - "uc_greedy": "1f54b", - "shortnames": [], - "category": "travel" - }, - ":key2:": { - "uc_base": "1f5dd", - "uc_output": "1f5dd", - "uc_match": "1f5dd-fe0f", - "uc_greedy": "1f5dd", - "shortnames": [":old_key:"], - "category": "objects" - }, - ":key:": { - "uc_base": "1f511", - "uc_output": "1f511", - "uc_match": "1f511", - "uc_greedy": "1f511", - "shortnames": [], - "category": "objects" - }, - ":keycap_ten:": { - "uc_base": "1f51f", - "uc_output": "1f51f", - "uc_match": "1f51f", - "uc_greedy": "1f51f", - "shortnames": [], - "category": "symbols" - }, - ":kimono:": { - "uc_base": "1f458", - "uc_output": "1f458", - "uc_match": "1f458", - "uc_greedy": "1f458", - "shortnames": [], - "category": "people" - }, - ":kiss:": { - "uc_base": "1f48b", - "uc_output": "1f48b", - "uc_match": "1f48b", - "uc_greedy": "1f48b", - "shortnames": [], - "category": "people" - }, - ":kissing:": { - "uc_base": "1f617", - "uc_output": "1f617", - "uc_match": "1f617", - "uc_greedy": "1f617", - "shortnames": [], - "category": "people" - }, - ":kissing_cat:": { - "uc_base": "1f63d", - "uc_output": "1f63d", - "uc_match": "1f63d", - "uc_greedy": "1f63d", - "shortnames": [], - "category": "people" - }, - ":kissing_closed_eyes:": { - "uc_base": "1f61a", - "uc_output": "1f61a", - "uc_match": "1f61a", - "uc_greedy": "1f61a", - "shortnames": [], - "category": "people" - }, - ":kissing_heart:": { - "uc_base": "1f618", - "uc_output": "1f618", - "uc_match": "1f618", - "uc_greedy": "1f618", - "shortnames": [], - "category": "people" - }, - ":kissing_smiling_eyes:": { - "uc_base": "1f619", - "uc_output": "1f619", - "uc_match": "1f619", - "uc_greedy": "1f619", - "shortnames": [], - "category": "people" - }, - ":kiwi:": { - "uc_base": "1f95d", - "uc_output": "1f95d", - "uc_match": "1f95d", - "uc_greedy": "1f95d", - "shortnames": [":kiwifruit:"], - "category": "food" - }, - ":knife:": { - "uc_base": "1f52a", - "uc_output": "1f52a", - "uc_match": "1f52a", - "uc_greedy": "1f52a", - "shortnames": [], - "category": "objects" - }, - ":koala:": { - "uc_base": "1f428", - "uc_output": "1f428", - "uc_match": "1f428", - "uc_greedy": "1f428", - "shortnames": [], - "category": "nature" - }, - ":koko:": { - "uc_base": "1f201", - "uc_output": "1f201", - "uc_match": "1f201", - "uc_greedy": "1f201", - "shortnames": [], - "category": "symbols" - }, - ":label:": { - "uc_base": "1f3f7", - "uc_output": "1f3f7", - "uc_match": "1f3f7-fe0f", - "uc_greedy": "1f3f7-fe0f", - "shortnames": [], - "category": "objects" - }, - ":large_blue_diamond:": { - "uc_base": "1f537", - "uc_output": "1f537", - "uc_match": "1f537", - "uc_greedy": "1f537", - "shortnames": [], - "category": "symbols" - }, - ":large_orange_diamond:": { - "uc_base": "1f536", - "uc_output": "1f536", - "uc_match": "1f536", - "uc_greedy": "1f536", - "shortnames": [], - "category": "symbols" - }, - ":last_quarter_moon:": { - "uc_base": "1f317", - "uc_output": "1f317", - "uc_match": "1f317", - "uc_greedy": "1f317", - "shortnames": [], - "category": "nature" - }, - ":last_quarter_moon_with_face:": { - "uc_base": "1f31c", - "uc_output": "1f31c", - "uc_match": "1f31c-fe0f", - "uc_greedy": "1f31c-fe0f", - "shortnames": [], - "category": "nature" - }, - ":laughing:": { - "uc_base": "1f606", - "uc_output": "1f606", - "uc_match": "1f606", - "uc_greedy": "1f606", - "shortnames": [":satisfied:"], - "category": "people" - }, - ":leaves:": { - "uc_base": "1f343", - "uc_output": "1f343", - "uc_match": "1f343", - "uc_greedy": "1f343", - "shortnames": [], - "category": "nature" - }, - ":ledger:": { - "uc_base": "1f4d2", - "uc_output": "1f4d2", - "uc_match": "1f4d2", - "uc_greedy": "1f4d2", - "shortnames": [], - "category": "objects" - }, - ":left_facing_fist:": { - "uc_base": "1f91b", - "uc_output": "1f91b", - "uc_match": "1f91b", - "uc_greedy": "1f91b", - "shortnames": [":left_fist:"], - "category": "people" - }, - ":left_luggage:": { - "uc_base": "1f6c5", - "uc_output": "1f6c5", - "uc_match": "1f6c5", - "uc_greedy": "1f6c5", - "shortnames": [], - "category": "symbols" - }, - ":lemon:": { - "uc_base": "1f34b", - "uc_output": "1f34b", - "uc_match": "1f34b", - "uc_greedy": "1f34b", - "shortnames": [], - "category": "food" - }, - ":leopard:": { - "uc_base": "1f406", - "uc_output": "1f406", - "uc_match": "1f406", - "uc_greedy": "1f406", - "shortnames": [], - "category": "nature" - }, - ":level_slider:": { - "uc_base": "1f39a", - "uc_output": "1f39a", - "uc_match": "1f39a-fe0f", - "uc_greedy": "1f39a-fe0f", - "shortnames": [], - "category": "objects" - }, - ":levitate:": { - "uc_base": "1f574", - "uc_output": "1f574", - "uc_match": "1f574-fe0f", - "uc_greedy": "1f574-fe0f", - "shortnames": [":man_in_business_suit_levitating:"], - "category": "people" - }, - ":light_rail:": { - "uc_base": "1f688", - "uc_output": "1f688", - "uc_match": "1f688", - "uc_greedy": "1f688", - "shortnames": [], - "category": "travel" - }, - ":link:": { - "uc_base": "1f517", - "uc_output": "1f517", - "uc_match": "1f517", - "uc_greedy": "1f517", - "shortnames": [], - "category": "objects" - }, - ":lion_face:": { - "uc_base": "1f981", - "uc_output": "1f981", - "uc_match": "1f981", - "uc_greedy": "1f981", - "shortnames": [":lion:"], - "category": "nature" - }, - ":lips:": { - "uc_base": "1f444", - "uc_output": "1f444", - "uc_match": "1f444", - "uc_greedy": "1f444", - "shortnames": [], - "category": "people" - }, - ":lipstick:": { - "uc_base": "1f484", - "uc_output": "1f484", - "uc_match": "1f484", - "uc_greedy": "1f484", - "shortnames": [], - "category": "people" - }, - ":lizard:": { - "uc_base": "1f98e", - "uc_output": "1f98e", - "uc_match": "1f98e", - "uc_greedy": "1f98e", - "shortnames": [], - "category": "nature" - }, - ":lock:": { - "uc_base": "1f512", - "uc_output": "1f512", - "uc_match": "1f512-fe0f", - "uc_greedy": "1f512-fe0f", - "shortnames": [], - "category": "objects" - }, - ":lock_with_ink_pen:": { - "uc_base": "1f50f", - "uc_output": "1f50f", - "uc_match": "1f50f", - "uc_greedy": "1f50f", - "shortnames": [], - "category": "objects" - }, - ":lollipop:": { - "uc_base": "1f36d", - "uc_output": "1f36d", - "uc_match": "1f36d", - "uc_greedy": "1f36d", - "shortnames": [], - "category": "food" - }, - ":loud_sound:": { - "uc_base": "1f50a", - "uc_output": "1f50a", - "uc_match": "1f50a", - "uc_greedy": "1f50a", - "shortnames": [], - "category": "symbols" - }, - ":loudspeaker:": { - "uc_base": "1f4e2", - "uc_output": "1f4e2", - "uc_match": "1f4e2", - "uc_greedy": "1f4e2", - "shortnames": [], - "category": "symbols" - }, - ":love_hotel:": { - "uc_base": "1f3e9", - "uc_output": "1f3e9", - "uc_match": "1f3e9", - "uc_greedy": "1f3e9", - "shortnames": [], - "category": "travel" - }, - ":love_letter:": { - "uc_base": "1f48c", - "uc_output": "1f48c", - "uc_match": "1f48c", - "uc_greedy": "1f48c", - "shortnames": [], - "category": "objects" - }, - ":love_you_gesture:": { - "uc_base": "1f91f", - "uc_output": "1f91f", - "uc_match": "1f91f", - "uc_greedy": "1f91f", - "shortnames": [], - "category": "people" - }, - ":low_brightness:": { - "uc_base": "1f505", - "uc_output": "1f505", - "uc_match": "1f505", - "uc_greedy": "1f505", - "shortnames": [], - "category": "symbols" - }, - ":lying_face:": { - "uc_base": "1f925", - "uc_output": "1f925", - "uc_match": "1f925", - "uc_greedy": "1f925", - "shortnames": [":liar:"], - "category": "people" - }, - ":mag:": { - "uc_base": "1f50d", - "uc_output": "1f50d", - "uc_match": "1f50d-fe0f", - "uc_greedy": "1f50d-fe0f", - "shortnames": [], - "category": "objects" - }, - ":mag_right:": { - "uc_base": "1f50e", - "uc_output": "1f50e", - "uc_match": "1f50e", - "uc_greedy": "1f50e", - "shortnames": [], - "category": "objects" - }, - ":mage:": { - "uc_base": "1f9d9", - "uc_output": "1f9d9", - "uc_match": "1f9d9", - "uc_greedy": "1f9d9", - "shortnames": [], - "category": "people" - }, - ":mahjong:": { - "uc_base": "1f004", - "uc_output": "1f004", - "uc_match": "1f004-fe0f", - "uc_greedy": "1f004-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":mailbox:": { - "uc_base": "1f4eb", - "uc_output": "1f4eb", - "uc_match": "1f4eb-fe0f", - "uc_greedy": "1f4eb-fe0f", - "shortnames": [], - "category": "objects" - }, - ":mailbox_closed:": { - "uc_base": "1f4ea", - "uc_output": "1f4ea", - "uc_match": "1f4ea-fe0f", - "uc_greedy": "1f4ea-fe0f", - "shortnames": [], - "category": "objects" - }, - ":mailbox_with_mail:": { - "uc_base": "1f4ec", - "uc_output": "1f4ec", - "uc_match": "1f4ec-fe0f", - "uc_greedy": "1f4ec-fe0f", - "shortnames": [], - "category": "objects" - }, - ":mailbox_with_no_mail:": { - "uc_base": "1f4ed", - "uc_output": "1f4ed", - "uc_match": "1f4ed-fe0f", - "uc_greedy": "1f4ed-fe0f", - "shortnames": [], - "category": "objects" - }, - ":man:": { - "uc_base": "1f468", - "uc_output": "1f468", - "uc_match": "1f468", - "uc_greedy": "1f468", - "shortnames": [], - "category": "people" - }, - ":man_dancing:": { - "uc_base": "1f57a", - "uc_output": "1f57a", - "uc_match": "1f57a", - "uc_greedy": "1f57a", - "shortnames": [":male_dancer:"], - "category": "people" - }, - ":man_in_tuxedo:": { - "uc_base": "1f935", - "uc_output": "1f935", - "uc_match": "1f935", - "uc_greedy": "1f935", - "shortnames": [], - "category": "people" - }, - ":man_with_chinese_cap:": { - "uc_base": "1f472", - "uc_output": "1f472", - "uc_match": "1f472", - "uc_greedy": "1f472", - "shortnames": [":man_with_gua_pi_mao:"], - "category": "people" - }, - ":mans_shoe:": { - "uc_base": "1f45e", - "uc_output": "1f45e", - "uc_match": "1f45e", - "uc_greedy": "1f45e", - "shortnames": [], - "category": "people" - }, - ":map:": { - "uc_base": "1f5fa", - "uc_output": "1f5fa", - "uc_match": "1f5fa-fe0f", - "uc_greedy": "1f5fa-fe0f", - "shortnames": [":world_map:"], - "category": "travel" - }, - ":maple_leaf:": { - "uc_base": "1f341", - "uc_output": "1f341", - "uc_match": "1f341", - "uc_greedy": "1f341", - "shortnames": [], - "category": "nature" - }, - ":martial_arts_uniform:": { - "uc_base": "1f94b", - "uc_output": "1f94b", - "uc_match": "1f94b", - "uc_greedy": "1f94b", - "shortnames": [":karate_uniform:"], - "category": "activity" - }, - ":mask:": { - "uc_base": "1f637", - "uc_output": "1f637", - "uc_match": "1f637", - "uc_greedy": "1f637", - "shortnames": [], - "category": "people" - }, - ":meat_on_bone:": { - "uc_base": "1f356", - "uc_output": "1f356", - "uc_match": "1f356", - "uc_greedy": "1f356", - "shortnames": [], - "category": "food" - }, - ":medal:": { - "uc_base": "1f3c5", - "uc_output": "1f3c5", - "uc_match": "1f3c5", - "uc_greedy": "1f3c5", - "shortnames": [":sports_medal:"], - "category": "activity" - }, - ":mega:": { - "uc_base": "1f4e3", - "uc_output": "1f4e3", - "uc_match": "1f4e3", - "uc_greedy": "1f4e3", - "shortnames": [], - "category": "symbols" - }, - ":melon:": { - "uc_base": "1f348", - "uc_output": "1f348", - "uc_match": "1f348", - "uc_greedy": "1f348", - "shortnames": [], - "category": "food" - }, - ":menorah:": { - "uc_base": "1f54e", - "uc_output": "1f54e", - "uc_match": "1f54e", - "uc_greedy": "1f54e", - "shortnames": [], - "category": "symbols" - }, - ":mens:": { - "uc_base": "1f6b9", - "uc_output": "1f6b9", - "uc_match": "1f6b9-fe0f", - "uc_greedy": "1f6b9-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":merperson:": { - "uc_base": "1f9dc", - "uc_output": "1f9dc", - "uc_match": "1f9dc", - "uc_greedy": "1f9dc", - "shortnames": [], - "category": "people" - }, - ":metal:": { - "uc_base": "1f918", - "uc_output": "1f918", - "uc_match": "1f918", - "uc_greedy": "1f918", - "shortnames": [":sign_of_the_horns:"], - "category": "people" - }, - ":metro:": { - "uc_base": "1f687", - "uc_output": "1f687", - "uc_match": "1f687-fe0f", - "uc_greedy": "1f687-fe0f", - "shortnames": [], - "category": "travel" - }, - ":microphone2:": { - "uc_base": "1f399", - "uc_output": "1f399", - "uc_match": "1f399-fe0f", - "uc_greedy": "1f399-fe0f", - "shortnames": [":studio_microphone:"], - "category": "objects" - }, - ":microphone:": { - "uc_base": "1f3a4", - "uc_output": "1f3a4", - "uc_match": "1f3a4", - "uc_greedy": "1f3a4", - "shortnames": [], - "category": "activity" - }, - ":microscope:": { - "uc_base": "1f52c", - "uc_output": "1f52c", - "uc_match": "1f52c", - "uc_greedy": "1f52c", - "shortnames": [], - "category": "objects" - }, - ":middle_finger:": { - "uc_base": "1f595", - "uc_output": "1f595", - "uc_match": "1f595", - "uc_greedy": "1f595", - "shortnames": [":reversed_hand_with_middle_finger_extended:"], - "category": "people" - }, - ":military_medal:": { - "uc_base": "1f396", - "uc_output": "1f396", - "uc_match": "1f396-fe0f", - "uc_greedy": "1f396-fe0f", - "shortnames": [], - "category": "activity" - }, - ":milk:": { - "uc_base": "1f95b", - "uc_output": "1f95b", - "uc_match": "1f95b", - "uc_greedy": "1f95b", - "shortnames": [":glass_of_milk:"], - "category": "food" - }, - ":milky_way:": { - "uc_base": "1f30c", - "uc_output": "1f30c", - "uc_match": "1f30c", - "uc_greedy": "1f30c", - "shortnames": [], - "category": "travel" - }, - ":minibus:": { - "uc_base": "1f690", - "uc_output": "1f690", - "uc_match": "1f690", - "uc_greedy": "1f690", - "shortnames": [], - "category": "travel" - }, - ":minidisc:": { - "uc_base": "1f4bd", - "uc_output": "1f4bd", - "uc_match": "1f4bd", - "uc_greedy": "1f4bd", - "shortnames": [], - "category": "objects" - }, - ":mobile_phone_off:": { - "uc_base": "1f4f4", - "uc_output": "1f4f4", - "uc_match": "1f4f4", - "uc_greedy": "1f4f4", - "shortnames": [], - "category": "symbols" - }, - ":money_mouth:": { - "uc_base": "1f911", - "uc_output": "1f911", - "uc_match": "1f911", - "uc_greedy": "1f911", - "shortnames": [":money_mouth_face:"], - "category": "people" - }, - ":money_with_wings:": { - "uc_base": "1f4b8", - "uc_output": "1f4b8", - "uc_match": "1f4b8", - "uc_greedy": "1f4b8", - "shortnames": [], - "category": "objects" - }, - ":moneybag:": { - "uc_base": "1f4b0", - "uc_output": "1f4b0", - "uc_match": "1f4b0-fe0f", - "uc_greedy": "1f4b0-fe0f", - "shortnames": [], - "category": "objects" - }, - ":monkey:": { - "uc_base": "1f412", - "uc_output": "1f412", - "uc_match": "1f412", - "uc_greedy": "1f412", - "shortnames": [], - "category": "nature" - }, - ":monkey_face:": { - "uc_base": "1f435", - "uc_output": "1f435", - "uc_match": "1f435", - "uc_greedy": "1f435", - "shortnames": [], - "category": "nature" - }, - ":monorail:": { - "uc_base": "1f69d", - "uc_output": "1f69d", - "uc_match": "1f69d", - "uc_greedy": "1f69d", - "shortnames": [], - "category": "travel" - }, - ":mortar_board:": { - "uc_base": "1f393", - "uc_output": "1f393", - "uc_match": "1f393-fe0f", - "uc_greedy": "1f393-fe0f", - "shortnames": [], - "category": "people" - }, - ":mosque:": { - "uc_base": "1f54c", - "uc_output": "1f54c", - "uc_match": "1f54c", - "uc_greedy": "1f54c", - "shortnames": [], - "category": "travel" - }, - ":motor_scooter:": { - "uc_base": "1f6f5", - "uc_output": "1f6f5", - "uc_match": "1f6f5", - "uc_greedy": "1f6f5", - "shortnames": [":motorbike:"], - "category": "travel" - }, - ":motorboat:": { - "uc_base": "1f6e5", - "uc_output": "1f6e5", - "uc_match": "1f6e5-fe0f", - "uc_greedy": "1f6e5-fe0f", - "shortnames": [], - "category": "travel" - }, - ":motorcycle:": { - "uc_base": "1f3cd", - "uc_output": "1f3cd", - "uc_match": "1f3cd-fe0f", - "uc_greedy": "1f3cd-fe0f", - "shortnames": [":racing_motorcycle:"], - "category": "travel" - }, - ":motorway:": { - "uc_base": "1f6e3", - "uc_output": "1f6e3", - "uc_match": "1f6e3-fe0f", - "uc_greedy": "1f6e3-fe0f", - "shortnames": [], - "category": "travel" - }, - ":mount_fuji:": { - "uc_base": "1f5fb", - "uc_output": "1f5fb", - "uc_match": "1f5fb", - "uc_greedy": "1f5fb", - "shortnames": [], - "category": "travel" - }, - ":mountain_cableway:": { - "uc_base": "1f6a0", - "uc_output": "1f6a0", - "uc_match": "1f6a0", - "uc_greedy": "1f6a0", - "shortnames": [], - "category": "travel" - }, - ":mountain_railway:": { - "uc_base": "1f69e", - "uc_output": "1f69e", - "uc_match": "1f69e", - "uc_greedy": "1f69e", - "shortnames": [], - "category": "travel" - }, - ":mountain_snow:": { - "uc_base": "1f3d4", - "uc_output": "1f3d4", - "uc_match": "1f3d4-fe0f", - "uc_greedy": "1f3d4-fe0f", - "shortnames": [":snow_capped_mountain:"], - "category": "travel" - }, - ":mouse2:": { - "uc_base": "1f401", - "uc_output": "1f401", - "uc_match": "1f401", - "uc_greedy": "1f401", - "shortnames": [], - "category": "nature" - }, - ":mouse:": { - "uc_base": "1f42d", - "uc_output": "1f42d", - "uc_match": "1f42d", - "uc_greedy": "1f42d", - "shortnames": [], - "category": "nature" - }, - ":mouse_three_button:": { - "uc_base": "1f5b1", - "uc_output": "1f5b1", - "uc_match": "1f5b1-fe0f", - "uc_greedy": "1f5b1-fe0f", - "shortnames": [":three_button_mouse:"], - "category": "objects" - }, - ":movie_camera:": { - "uc_base": "1f3a5", - "uc_output": "1f3a5", - "uc_match": "1f3a5", - "uc_greedy": "1f3a5", - "shortnames": [], - "category": "objects" - }, - ":moyai:": { - "uc_base": "1f5ff", - "uc_output": "1f5ff", - "uc_match": "1f5ff", - "uc_greedy": "1f5ff", - "shortnames": [], - "category": "travel" - }, - ":mrs_claus:": { - "uc_base": "1f936", - "uc_output": "1f936", - "uc_match": "1f936", - "uc_greedy": "1f936", - "shortnames": [":mother_christmas:"], - "category": "people" - }, - ":muscle:": { - "uc_base": "1f4aa", - "uc_output": "1f4aa", - "uc_match": "1f4aa", - "uc_greedy": "1f4aa", - "shortnames": [], - "category": "people" - }, - ":mushroom:": { - "uc_base": "1f344", - "uc_output": "1f344", - "uc_match": "1f344", - "uc_greedy": "1f344", - "shortnames": [], - "category": "nature" - }, - ":musical_keyboard:": { - "uc_base": "1f3b9", - "uc_output": "1f3b9", - "uc_match": "1f3b9", - "uc_greedy": "1f3b9", - "shortnames": [], - "category": "activity" - }, - ":musical_note:": { - "uc_base": "1f3b5", - "uc_output": "1f3b5", - "uc_match": "1f3b5", - "uc_greedy": "1f3b5", - "shortnames": [], - "category": "symbols" - }, - ":musical_score:": { - "uc_base": "1f3bc", - "uc_output": "1f3bc", - "uc_match": "1f3bc", - "uc_greedy": "1f3bc", - "shortnames": [], - "category": "activity" - }, - ":mute:": { - "uc_base": "1f507", - "uc_output": "1f507", - "uc_match": "1f507", - "uc_greedy": "1f507", - "shortnames": [], - "category": "symbols" - }, - ":nail_care:": { - "uc_base": "1f485", - "uc_output": "1f485", - "uc_match": "1f485", - "uc_greedy": "1f485", - "shortnames": [], - "category": "people" - }, - ":name_badge:": { - "uc_base": "1f4db", - "uc_output": "1f4db", - "uc_match": "1f4db", - "uc_greedy": "1f4db", - "shortnames": [], - "category": "symbols" - }, - ":nauseated_face:": { - "uc_base": "1f922", - "uc_output": "1f922", - "uc_match": "1f922", - "uc_greedy": "1f922", - "shortnames": [":sick:"], - "category": "people" - }, - ":necktie:": { - "uc_base": "1f454", - "uc_output": "1f454", - "uc_match": "1f454", - "uc_greedy": "1f454", - "shortnames": [], - "category": "people" - }, - ":nerd:": { - "uc_base": "1f913", - "uc_output": "1f913", - "uc_match": "1f913", - "uc_greedy": "1f913", - "shortnames": [":nerd_face:"], - "category": "people" - }, - ":neutral_face:": { - "uc_base": "1f610", - "uc_output": "1f610", - "uc_match": "1f610-fe0f", - "uc_greedy": "1f610-fe0f", - "shortnames": [], - "category": "people" - }, - ":new:": { - "uc_base": "1f195", - "uc_output": "1f195", - "uc_match": "1f195", - "uc_greedy": "1f195", - "shortnames": [], - "category": "symbols" - }, - ":new_moon:": { - "uc_base": "1f311", - "uc_output": "1f311", - "uc_match": "1f311", - "uc_greedy": "1f311", - "shortnames": [], - "category": "nature" - }, - ":new_moon_with_face:": { - "uc_base": "1f31a", - "uc_output": "1f31a", - "uc_match": "1f31a", - "uc_greedy": "1f31a", - "shortnames": [], - "category": "nature" - }, - ":newspaper2:": { - "uc_base": "1f5de", - "uc_output": "1f5de", - "uc_match": "1f5de-fe0f", - "uc_greedy": "1f5de-fe0f", - "shortnames": [":rolled_up_newspaper:"], - "category": "objects" - }, - ":newspaper:": { - "uc_base": "1f4f0", - "uc_output": "1f4f0", - "uc_match": "1f4f0", - "uc_greedy": "1f4f0", - "shortnames": [], - "category": "objects" - }, - ":ng:": { - "uc_base": "1f196", - "uc_output": "1f196", - "uc_match": "1f196", - "uc_greedy": "1f196", - "shortnames": [], - "category": "symbols" - }, - ":night_with_stars:": { - "uc_base": "1f303", - "uc_output": "1f303", - "uc_match": "1f303", - "uc_greedy": "1f303", - "shortnames": [], - "category": "travel" - }, - ":no_bell:": { - "uc_base": "1f515", - "uc_output": "1f515", - "uc_match": "1f515", - "uc_greedy": "1f515", - "shortnames": [], - "category": "symbols" - }, - ":no_bicycles:": { - "uc_base": "1f6b3", - "uc_output": "1f6b3", - "uc_match": "1f6b3", - "uc_greedy": "1f6b3", - "shortnames": [], - "category": "symbols" - }, - ":no_entry_sign:": { - "uc_base": "1f6ab", - "uc_output": "1f6ab", - "uc_match": "1f6ab", - "uc_greedy": "1f6ab", - "shortnames": [], - "category": "symbols" - }, - ":no_mobile_phones:": { - "uc_base": "1f4f5", - "uc_output": "1f4f5", - "uc_match": "1f4f5", - "uc_greedy": "1f4f5", - "shortnames": [], - "category": "symbols" - }, - ":no_mouth:": { - "uc_base": "1f636", - "uc_output": "1f636", - "uc_match": "1f636", - "uc_greedy": "1f636", - "shortnames": [], - "category": "people" - }, - ":no_pedestrians:": { - "uc_base": "1f6b7", - "uc_output": "1f6b7", - "uc_match": "1f6b7", - "uc_greedy": "1f6b7", - "shortnames": [], - "category": "symbols" - }, - ":no_smoking:": { - "uc_base": "1f6ad", - "uc_output": "1f6ad", - "uc_match": "1f6ad-fe0f", - "uc_greedy": "1f6ad-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":non-potable_water:": { - "uc_base": "1f6b1", - "uc_output": "1f6b1", - "uc_match": "1f6b1", - "uc_greedy": "1f6b1", - "shortnames": [], - "category": "symbols" - }, - ":nose:": { - "uc_base": "1f443", - "uc_output": "1f443", - "uc_match": "1f443", - "uc_greedy": "1f443", - "shortnames": [], - "category": "people" - }, - ":notebook:": { - "uc_base": "1f4d3", - "uc_output": "1f4d3", - "uc_match": "1f4d3", - "uc_greedy": "1f4d3", - "shortnames": [], - "category": "objects" - }, - ":notebook_with_decorative_cover:": { - "uc_base": "1f4d4", - "uc_output": "1f4d4", - "uc_match": "1f4d4", - "uc_greedy": "1f4d4", - "shortnames": [], - "category": "objects" - }, - ":notepad_spiral:": { - "uc_base": "1f5d2", - "uc_output": "1f5d2", - "uc_match": "1f5d2-fe0f", - "uc_greedy": "1f5d2-fe0f", - "shortnames": [":spiral_note_pad:"], - "category": "objects" - }, - ":notes:": { - "uc_base": "1f3b6", - "uc_output": "1f3b6", - "uc_match": "1f3b6", - "uc_greedy": "1f3b6", - "shortnames": [], - "category": "symbols" - }, - ":nut_and_bolt:": { - "uc_base": "1f529", - "uc_output": "1f529", - "uc_match": "1f529", - "uc_greedy": "1f529", - "shortnames": [], - "category": "objects" - }, - ":o2:": { - "uc_base": "1f17e", - "uc_output": "1f17e", - "uc_match": "1f17e-fe0f", - "uc_greedy": "1f17e-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":ocean:": { - "uc_base": "1f30a", - "uc_output": "1f30a", - "uc_match": "1f30a", - "uc_greedy": "1f30a", - "shortnames": [], - "category": "nature" - }, - ":octagonal_sign:": { - "uc_base": "1f6d1", - "uc_output": "1f6d1", - "uc_match": "1f6d1", - "uc_greedy": "1f6d1", - "shortnames": [":stop_sign:"], - "category": "symbols" - }, - ":octopus:": { - "uc_base": "1f419", - "uc_output": "1f419", - "uc_match": "1f419", - "uc_greedy": "1f419", - "shortnames": [], - "category": "nature" - }, - ":oden:": { - "uc_base": "1f362", - "uc_output": "1f362", - "uc_match": "1f362", - "uc_greedy": "1f362", - "shortnames": [], - "category": "food" - }, - ":office:": { - "uc_base": "1f3e2", - "uc_output": "1f3e2", - "uc_match": "1f3e2", - "uc_greedy": "1f3e2", - "shortnames": [], - "category": "travel" - }, - ":oil:": { - "uc_base": "1f6e2", - "uc_output": "1f6e2", - "uc_match": "1f6e2-fe0f", - "uc_greedy": "1f6e2-fe0f", - "shortnames": [":oil_drum:"], - "category": "objects" - }, - ":ok:": { - "uc_base": "1f197", - "uc_output": "1f197", - "uc_match": "1f197", - "uc_greedy": "1f197", - "shortnames": [], - "category": "symbols" - }, - ":ok_hand:": { - "uc_base": "1f44c", - "uc_output": "1f44c", - "uc_match": "1f44c", - "uc_greedy": "1f44c", - "shortnames": [], - "category": "people" - }, - ":older_adult:": { - "uc_base": "1f9d3", - "uc_output": "1f9d3", - "uc_match": "1f9d3", - "uc_greedy": "1f9d3", - "shortnames": [], - "category": "people" - }, - ":older_man:": { - "uc_base": "1f474", - "uc_output": "1f474", - "uc_match": "1f474", - "uc_greedy": "1f474", - "shortnames": [], - "category": "people" - }, - ":older_woman:": { - "uc_base": "1f475", - "uc_output": "1f475", - "uc_match": "1f475", - "uc_greedy": "1f475", - "shortnames": [":grandma:"], - "category": "people" - }, - ":om_symbol:": { - "uc_base": "1f549", - "uc_output": "1f549", - "uc_match": "1f549-fe0f", - "uc_greedy": "1f549", - "shortnames": [], - "category": "symbols" - }, - ":on:": { - "uc_base": "1f51b", - "uc_output": "1f51b", - "uc_match": "1f51b", - "uc_greedy": "1f51b", - "shortnames": [], - "category": "symbols" - }, - ":oncoming_automobile:": { - "uc_base": "1f698", - "uc_output": "1f698", - "uc_match": "1f698-fe0f", - "uc_greedy": "1f698-fe0f", - "shortnames": [], - "category": "travel" - }, - ":oncoming_bus:": { - "uc_base": "1f68d", - "uc_output": "1f68d", - "uc_match": "1f68d-fe0f", - "uc_greedy": "1f68d-fe0f", - "shortnames": [], - "category": "travel" - }, - ":oncoming_police_car:": { - "uc_base": "1f694", - "uc_output": "1f694", - "uc_match": "1f694-fe0f", - "uc_greedy": "1f694-fe0f", - "shortnames": [], - "category": "travel" - }, - ":oncoming_taxi:": { - "uc_base": "1f696", - "uc_output": "1f696", - "uc_match": "1f696", - "uc_greedy": "1f696", - "shortnames": [], - "category": "travel" - }, - ":open_file_folder:": { - "uc_base": "1f4c2", - "uc_output": "1f4c2", - "uc_match": "1f4c2", - "uc_greedy": "1f4c2", - "shortnames": [], - "category": "objects" - }, - ":open_hands:": { - "uc_base": "1f450", - "uc_output": "1f450", - "uc_match": "1f450", - "uc_greedy": "1f450", - "shortnames": [], - "category": "people" - }, - ":open_mouth:": { - "uc_base": "1f62e", - "uc_output": "1f62e", - "uc_match": "1f62e", - "uc_greedy": "1f62e", - "shortnames": [], - "category": "people" - }, - ":orange_book:": { - "uc_base": "1f4d9", - "uc_output": "1f4d9", - "uc_match": "1f4d9", - "uc_greedy": "1f4d9", - "shortnames": [], - "category": "objects" - }, - ":orange_heart:": { - "uc_base": "1f9e1", - "uc_output": "1f9e1", - "uc_match": "1f9e1", - "uc_greedy": "1f9e1", - "shortnames": [], - "category": "symbols" - }, - ":outbox_tray:": { - "uc_base": "1f4e4", - "uc_output": "1f4e4", - "uc_match": "1f4e4-fe0f", - "uc_greedy": "1f4e4-fe0f", - "shortnames": [], - "category": "objects" - }, - ":owl:": { - "uc_base": "1f989", - "uc_output": "1f989", - "uc_match": "1f989", - "uc_greedy": "1f989", - "shortnames": [], - "category": "nature" - }, - ":ox:": { - "uc_base": "1f402", - "uc_output": "1f402", - "uc_match": "1f402", - "uc_greedy": "1f402", - "shortnames": [], - "category": "nature" - }, - ":package:": { - "uc_base": "1f4e6", - "uc_output": "1f4e6", - "uc_match": "1f4e6-fe0f", - "uc_greedy": "1f4e6-fe0f", - "shortnames": [], - "category": "objects" - }, - ":page_facing_up:": { - "uc_base": "1f4c4", - "uc_output": "1f4c4", - "uc_match": "1f4c4", - "uc_greedy": "1f4c4", - "shortnames": [], - "category": "objects" - }, - ":page_with_curl:": { - "uc_base": "1f4c3", - "uc_output": "1f4c3", - "uc_match": "1f4c3", - "uc_greedy": "1f4c3", - "shortnames": [], - "category": "objects" - }, - ":pager:": { - "uc_base": "1f4df", - "uc_output": "1f4df", - "uc_match": "1f4df-fe0f", - "uc_greedy": "1f4df-fe0f", - "shortnames": [], - "category": "objects" - }, - ":paintbrush:": { - "uc_base": "1f58c", - "uc_output": "1f58c", - "uc_match": "1f58c-fe0f", - "uc_greedy": "1f58c-fe0f", - "shortnames": [":lower_left_paintbrush:"], - "category": "objects" - }, - ":palm_tree:": { - "uc_base": "1f334", - "uc_output": "1f334", - "uc_match": "1f334", - "uc_greedy": "1f334", - "shortnames": [], - "category": "nature" - }, - ":palms_up_together:": { - "uc_base": "1f932", - "uc_output": "1f932", - "uc_match": "1f932", - "uc_greedy": "1f932", - "shortnames": [], - "category": "people" - }, - ":pancakes:": { - "uc_base": "1f95e", - "uc_output": "1f95e", - "uc_match": "1f95e", - "uc_greedy": "1f95e", - "shortnames": [], - "category": "food" - }, - ":panda_face:": { - "uc_base": "1f43c", - "uc_output": "1f43c", - "uc_match": "1f43c", - "uc_greedy": "1f43c", - "shortnames": [], - "category": "nature" - }, - ":paperclip:": { - "uc_base": "1f4ce", - "uc_output": "1f4ce", - "uc_match": "1f4ce", - "uc_greedy": "1f4ce", - "shortnames": [], - "category": "objects" - }, - ":paperclips:": { - "uc_base": "1f587", - "uc_output": "1f587", - "uc_match": "1f587-fe0f", - "uc_greedy": "1f587-fe0f", - "shortnames": [":linked_paperclips:"], - "category": "objects" - }, - ":park:": { - "uc_base": "1f3de", - "uc_output": "1f3de", - "uc_match": "1f3de-fe0f", - "uc_greedy": "1f3de-fe0f", - "shortnames": [":national_park:"], - "category": "travel" - }, - ":parking:": { - "uc_base": "1f17f", - "uc_output": "1f17f", - "uc_match": "1f17f-fe0f", - "uc_greedy": "1f17f-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":passport_control:": { - "uc_base": "1f6c2", - "uc_output": "1f6c2", - "uc_match": "1f6c2", - "uc_greedy": "1f6c2", - "shortnames": [], - "category": "symbols" - }, - ":peach:": { - "uc_base": "1f351", - "uc_output": "1f351", - "uc_match": "1f351", - "uc_greedy": "1f351", - "shortnames": [], - "category": "food" - }, - ":peanuts:": { - "uc_base": "1f95c", - "uc_output": "1f95c", - "uc_match": "1f95c", - "uc_greedy": "1f95c", - "shortnames": [":shelled_peanut:"], - "category": "food" - }, - ":pear:": { - "uc_base": "1f350", - "uc_output": "1f350", - "uc_match": "1f350", - "uc_greedy": "1f350", - "shortnames": [], - "category": "food" - }, - ":pen_ballpoint:": { - "uc_base": "1f58a", - "uc_output": "1f58a", - "uc_match": "1f58a-fe0f", - "uc_greedy": "1f58a-fe0f", - "shortnames": [":lower_left_ballpoint_pen:"], - "category": "objects" - }, - ":pen_fountain:": { - "uc_base": "1f58b", - "uc_output": "1f58b", - "uc_match": "1f58b-fe0f", - "uc_greedy": "1f58b-fe0f", - "shortnames": [":lower_left_fountain_pen:"], - "category": "objects" - }, - ":pencil:": { - "uc_base": "1f4dd", - "uc_output": "1f4dd", - "uc_match": "1f4dd", - "uc_greedy": "1f4dd", - "shortnames": [":memo:"], - "category": "objects" - }, - ":penguin:": { - "uc_base": "1f427", - "uc_output": "1f427", - "uc_match": "1f427", - "uc_greedy": "1f427", - "shortnames": [], - "category": "nature" - }, - ":pensive:": { - "uc_base": "1f614", - "uc_output": "1f614", - "uc_match": "1f614", - "uc_greedy": "1f614", - "shortnames": [], - "category": "people" - }, - ":people_with_bunny_ears_partying:": { - "uc_base": "1f46f", - "uc_output": "1f46f", - "uc_match": "1f46f", - "uc_greedy": "1f46f", - "shortnames": [":dancers:"], - "category": "people" - }, - ":people_wrestling:": { - "uc_base": "1f93c", - "uc_output": "1f93c", - "uc_match": "1f93c", - "uc_greedy": "1f93c", - "shortnames": [":wrestlers:", ":wrestling:"], - "category": "activity" - }, - ":performing_arts:": { - "uc_base": "1f3ad", - "uc_output": "1f3ad", - "uc_match": "1f3ad-fe0f", - "uc_greedy": "1f3ad-fe0f", - "shortnames": [], - "category": "activity" - }, - ":persevere:": { - "uc_base": "1f623", - "uc_output": "1f623", - "uc_match": "1f623", - "uc_greedy": "1f623", - "shortnames": [], - "category": "people" - }, - ":person_biking:": { - "uc_base": "1f6b4", - "uc_output": "1f6b4", - "uc_match": "1f6b4", - "uc_greedy": "1f6b4", - "shortnames": [":bicyclist:"], - "category": "activity" - }, - ":person_bowing:": { - "uc_base": "1f647", - "uc_output": "1f647", - "uc_match": "1f647", - "uc_greedy": "1f647", - "shortnames": [":bow:"], - "category": "people" - }, - ":person_climbing:": { - "uc_base": "1f9d7", - "uc_output": "1f9d7", - "uc_match": "1f9d7", - "uc_greedy": "1f9d7", - "shortnames": [], - "category": "activity" - }, - ":person_doing_cartwheel:": { - "uc_base": "1f938", - "uc_output": "1f938", - "uc_match": "1f938", - "uc_greedy": "1f938", - "shortnames": [":cartwheel:"], - "category": "activity" - }, - ":person_facepalming:": { - "uc_base": "1f926", - "uc_output": "1f926", - "uc_match": "1f926", - "uc_greedy": "1f926", - "shortnames": [":face_palm:", ":facepalm:"], - "category": "people" - }, - ":person_fencing:": { - "uc_base": "1f93a", - "uc_output": "1f93a", - "uc_match": "1f93a", - "uc_greedy": "1f93a", - "shortnames": [":fencer:", ":fencing:"], - "category": "activity" - }, - ":person_frowning:": { - "uc_base": "1f64d", - "uc_output": "1f64d", - "uc_match": "1f64d", - "uc_greedy": "1f64d", - "shortnames": [], - "category": "people" - }, - ":person_gesturing_no:": { - "uc_base": "1f645", - "uc_output": "1f645", - "uc_match": "1f645", - "uc_greedy": "1f645", - "shortnames": [":no_good:"], - "category": "people" - }, - ":person_gesturing_ok:": { - "uc_base": "1f646", - "uc_output": "1f646", - "uc_match": "1f646", - "uc_greedy": "1f646", - "shortnames": [":ok_woman:"], - "category": "people" - }, - ":person_getting_haircut:": { - "uc_base": "1f487", - "uc_output": "1f487", - "uc_match": "1f487", - "uc_greedy": "1f487", - "shortnames": [":haircut:"], - "category": "people" - }, - ":person_getting_massage:": { - "uc_base": "1f486", - "uc_output": "1f486", - "uc_match": "1f486", - "uc_greedy": "1f486", - "shortnames": [":massage:"], - "category": "people" - }, - ":person_golfing:": { - "uc_base": "1f3cc", - "uc_output": "1f3cc", - "uc_match": "1f3cc-fe0f", - "uc_greedy": "1f3cc-fe0f", - "shortnames": [":golfer:"], - "category": "activity" - }, - ":person_in_lotus_position:": { - "uc_base": "1f9d8", - "uc_output": "1f9d8", - "uc_match": "1f9d8", - "uc_greedy": "1f9d8", - "shortnames": [], - "category": "activity" - }, - ":person_in_steamy_room:": { - "uc_base": "1f9d6", - "uc_output": "1f9d6", - "uc_match": "1f9d6", - "uc_greedy": "1f9d6", - "shortnames": [], - "category": "people" - }, - ":person_juggling:": { - "uc_base": "1f939", - "uc_output": "1f939", - "uc_match": "1f939", - "uc_greedy": "1f939", - "shortnames": [":juggling:", ":juggler:"], - "category": "activity" - }, - ":person_lifting_weights:": { - "uc_base": "1f3cb", - "uc_output": "1f3cb", - "uc_match": "1f3cb-fe0f", - "uc_greedy": "1f3cb-fe0f", - "shortnames": [":lifter:", ":weight_lifter:"], - "category": "activity" - }, - ":person_mountain_biking:": { - "uc_base": "1f6b5", - "uc_output": "1f6b5", - "uc_match": "1f6b5", - "uc_greedy": "1f6b5", - "shortnames": [":mountain_bicyclist:"], - "category": "activity" - }, - ":person_playing_handball:": { - "uc_base": "1f93e", - "uc_output": "1f93e", - "uc_match": "1f93e", - "uc_greedy": "1f93e", - "shortnames": [":handball:"], - "category": "activity" - }, - ":person_playing_water_polo:": { - "uc_base": "1f93d", - "uc_output": "1f93d", - "uc_match": "1f93d", - "uc_greedy": "1f93d", - "shortnames": [":water_polo:"], - "category": "activity" - }, - ":person_pouting:": { - "uc_base": "1f64e", - "uc_output": "1f64e", - "uc_match": "1f64e", - "uc_greedy": "1f64e", - "shortnames": [":person_with_pouting_face:"], - "category": "people" - }, - ":person_raising_hand:": { - "uc_base": "1f64b", - "uc_output": "1f64b", - "uc_match": "1f64b", - "uc_greedy": "1f64b", - "shortnames": [":raising_hand:"], - "category": "people" - }, - ":person_rowing_boat:": { - "uc_base": "1f6a3", - "uc_output": "1f6a3", - "uc_match": "1f6a3", - "uc_greedy": "1f6a3", - "shortnames": [":rowboat:"], - "category": "activity" - }, - ":person_running:": { - "uc_base": "1f3c3", - "uc_output": "1f3c3", - "uc_match": "1f3c3", - "uc_greedy": "1f3c3", - "shortnames": [":runner:"], - "category": "people" - }, - ":person_shrugging:": { - "uc_base": "1f937", - "uc_output": "1f937", - "uc_match": "1f937", - "uc_greedy": "1f937", - "shortnames": [":shrug:"], - "category": "people" - }, - ":person_surfing:": { - "uc_base": "1f3c4", - "uc_output": "1f3c4", - "uc_match": "1f3c4-fe0f", - "uc_greedy": "1f3c4-fe0f", - "shortnames": [":surfer:"], - "category": "activity" - }, - ":person_swimming:": { - "uc_base": "1f3ca", - "uc_output": "1f3ca", - "uc_match": "1f3ca-fe0f", - "uc_greedy": "1f3ca-fe0f", - "shortnames": [":swimmer:"], - "category": "activity" - }, - ":person_tipping_hand:": { - "uc_base": "1f481", - "uc_output": "1f481", - "uc_match": "1f481", - "uc_greedy": "1f481", - "shortnames": [":information_desk_person:"], - "category": "people" - }, - ":person_walking:": { - "uc_base": "1f6b6", - "uc_output": "1f6b6", - "uc_match": "1f6b6", - "uc_greedy": "1f6b6", - "shortnames": [":walking:"], - "category": "people" - }, - ":person_wearing_turban:": { - "uc_base": "1f473", - "uc_output": "1f473", - "uc_match": "1f473", - "uc_greedy": "1f473", - "shortnames": [":man_with_turban:"], - "category": "people" - }, - ":pie:": { - "uc_base": "1f967", - "uc_output": "1f967", - "uc_match": "1f967", - "uc_greedy": "1f967", - "shortnames": [], - "category": "food" - }, - ":pig2:": { - "uc_base": "1f416", - "uc_output": "1f416", - "uc_match": "1f416", - "uc_greedy": "1f416", - "shortnames": [], - "category": "nature" - }, - ":pig:": { - "uc_base": "1f437", - "uc_output": "1f437", - "uc_match": "1f437", - "uc_greedy": "1f437", - "shortnames": [], - "category": "nature" - }, - ":pig_nose:": { - "uc_base": "1f43d", - "uc_output": "1f43d", - "uc_match": "1f43d", - "uc_greedy": "1f43d", - "shortnames": [], - "category": "nature" - }, - ":pill:": { - "uc_base": "1f48a", - "uc_output": "1f48a", - "uc_match": "1f48a", - "uc_greedy": "1f48a", - "shortnames": [], - "category": "objects" - }, - ":pineapple:": { - "uc_base": "1f34d", - "uc_output": "1f34d", - "uc_match": "1f34d", - "uc_greedy": "1f34d", - "shortnames": [], - "category": "food" - }, - ":ping_pong:": { - "uc_base": "1f3d3", - "uc_output": "1f3d3", - "uc_match": "1f3d3", - "uc_greedy": "1f3d3", - "shortnames": [":table_tennis:"], - "category": "activity" - }, - ":pizza:": { - "uc_base": "1f355", - "uc_output": "1f355", - "uc_match": "1f355", - "uc_greedy": "1f355", - "shortnames": [], - "category": "food" - }, - ":place_of_worship:": { - "uc_base": "1f6d0", - "uc_output": "1f6d0", - "uc_match": "1f6d0", - "uc_greedy": "1f6d0", - "shortnames": [":worship_symbol:"], - "category": "symbols" - }, - ":point_down:": { - "uc_base": "1f447", - "uc_output": "1f447", - "uc_match": "1f447-fe0f", - "uc_greedy": "1f447-fe0f", - "shortnames": [], - "category": "people" - }, - ":point_left:": { - "uc_base": "1f448", - "uc_output": "1f448", - "uc_match": "1f448-fe0f", - "uc_greedy": "1f448-fe0f", - "shortnames": [], - "category": "people" - }, - ":point_right:": { - "uc_base": "1f449", - "uc_output": "1f449", - "uc_match": "1f449-fe0f", - "uc_greedy": "1f449-fe0f", - "shortnames": [], - "category": "people" - }, - ":point_up_2:": { - "uc_base": "1f446", - "uc_output": "1f446", - "uc_match": "1f446-fe0f", - "uc_greedy": "1f446-fe0f", - "shortnames": [], - "category": "people" - }, - ":police_car:": { - "uc_base": "1f693", - "uc_output": "1f693", - "uc_match": "1f693", - "uc_greedy": "1f693", - "shortnames": [], - "category": "travel" - }, - ":police_officer:": { - "uc_base": "1f46e", - "uc_output": "1f46e", - "uc_match": "1f46e", - "uc_greedy": "1f46e", - "shortnames": [":cop:"], - "category": "people" - }, - ":poodle:": { - "uc_base": "1f429", - "uc_output": "1f429", - "uc_match": "1f429", - "uc_greedy": "1f429", - "shortnames": [], - "category": "nature" - }, - ":poop:": { - "uc_base": "1f4a9", - "uc_output": "1f4a9", - "uc_match": "1f4a9", - "uc_greedy": "1f4a9", - "shortnames": [":shit:", ":hankey:", ":poo:"], - "category": "people" - }, - ":popcorn:": { - "uc_base": "1f37f", - "uc_output": "1f37f", - "uc_match": "1f37f", - "uc_greedy": "1f37f", - "shortnames": [], - "category": "food" - }, - ":post_office:": { - "uc_base": "1f3e3", - "uc_output": "1f3e3", - "uc_match": "1f3e3", - "uc_greedy": "1f3e3", - "shortnames": [], - "category": "travel" - }, - ":postal_horn:": { - "uc_base": "1f4ef", - "uc_output": "1f4ef", - "uc_match": "1f4ef", - "uc_greedy": "1f4ef", - "shortnames": [], - "category": "objects" - }, - ":postbox:": { - "uc_base": "1f4ee", - "uc_output": "1f4ee", - "uc_match": "1f4ee", - "uc_greedy": "1f4ee", - "shortnames": [], - "category": "objects" - }, - ":potable_water:": { - "uc_base": "1f6b0", - "uc_output": "1f6b0", - "uc_match": "1f6b0", - "uc_greedy": "1f6b0", - "shortnames": [], - "category": "objects" - }, - ":potato:": { - "uc_base": "1f954", - "uc_output": "1f954", - "uc_match": "1f954", - "uc_greedy": "1f954", - "shortnames": [], - "category": "food" - }, - ":pouch:": { - "uc_base": "1f45d", - "uc_output": "1f45d", - "uc_match": "1f45d", - "uc_greedy": "1f45d", - "shortnames": [], - "category": "people" - }, - ":poultry_leg:": { - "uc_base": "1f357", - "uc_output": "1f357", - "uc_match": "1f357", - "uc_greedy": "1f357", - "shortnames": [], - "category": "food" - }, - ":pound:": { - "uc_base": "1f4b7", - "uc_output": "1f4b7", - "uc_match": "1f4b7", - "uc_greedy": "1f4b7", - "shortnames": [], - "category": "objects" - }, - ":pouting_cat:": { - "uc_base": "1f63e", - "uc_output": "1f63e", - "uc_match": "1f63e", - "uc_greedy": "1f63e", - "shortnames": [], - "category": "people" - }, - ":pray:": { - "uc_base": "1f64f", - "uc_output": "1f64f", - "uc_match": "1f64f", - "uc_greedy": "1f64f", - "shortnames": [], - "category": "people" - }, - ":prayer_beads:": { - "uc_base": "1f4ff", - "uc_output": "1f4ff", - "uc_match": "1f4ff", - "uc_greedy": "1f4ff", - "shortnames": [], - "category": "objects" - }, - ":pregnant_woman:": { - "uc_base": "1f930", - "uc_output": "1f930", - "uc_match": "1f930", - "uc_greedy": "1f930", - "shortnames": [":expecting_woman:"], - "category": "people" - }, - ":pretzel:": { - "uc_base": "1f968", - "uc_output": "1f968", - "uc_match": "1f968", - "uc_greedy": "1f968", - "shortnames": [], - "category": "food" - }, - ":prince:": { - "uc_base": "1f934", - "uc_output": "1f934", - "uc_match": "1f934", - "uc_greedy": "1f934", - "shortnames": [], - "category": "people" - }, - ":princess:": { - "uc_base": "1f478", - "uc_output": "1f478", - "uc_match": "1f478", - "uc_greedy": "1f478", - "shortnames": [], - "category": "people" - }, - ":printer:": { - "uc_base": "1f5a8", - "uc_output": "1f5a8", - "uc_match": "1f5a8-fe0f", - "uc_greedy": "1f5a8-fe0f", - "shortnames": [], - "category": "objects" - }, - ":projector:": { - "uc_base": "1f4fd", - "uc_output": "1f4fd", - "uc_match": "1f4fd-fe0f", - "uc_greedy": "1f4fd-fe0f", - "shortnames": [":film_projector:"], - "category": "objects" - }, - ":punch:": { - "uc_base": "1f44a", - "uc_output": "1f44a", - "uc_match": "1f44a", - "uc_greedy": "1f44a", - "shortnames": [], - "category": "people" - }, - ":purple_heart:": { - "uc_base": "1f49c", - "uc_output": "1f49c", - "uc_match": "1f49c", - "uc_greedy": "1f49c", - "shortnames": [], - "category": "symbols" - }, - ":purse:": { - "uc_base": "1f45b", - "uc_output": "1f45b", - "uc_match": "1f45b", - "uc_greedy": "1f45b", - "shortnames": [], - "category": "people" - }, - ":pushpin:": { - "uc_base": "1f4cc", - "uc_output": "1f4cc", - "uc_match": "1f4cc", - "uc_greedy": "1f4cc", - "shortnames": [], - "category": "objects" - }, - ":put_litter_in_its_place:": { - "uc_base": "1f6ae", - "uc_output": "1f6ae", - "uc_match": "1f6ae", - "uc_greedy": "1f6ae", - "shortnames": [], - "category": "symbols" - }, - ":rabbit2:": { - "uc_base": "1f407", - "uc_output": "1f407", - "uc_match": "1f407", - "uc_greedy": "1f407", - "shortnames": [], - "category": "nature" - }, - ":rabbit:": { - "uc_base": "1f430", - "uc_output": "1f430", - "uc_match": "1f430", - "uc_greedy": "1f430", - "shortnames": [], - "category": "nature" - }, - ":race_car:": { - "uc_base": "1f3ce", - "uc_output": "1f3ce", - "uc_match": "1f3ce-fe0f", - "uc_greedy": "1f3ce-fe0f", - "shortnames": [":racing_car:"], - "category": "travel" - }, - ":racehorse:": { - "uc_base": "1f40e", - "uc_output": "1f40e", - "uc_match": "1f40e", - "uc_greedy": "1f40e", - "shortnames": [], - "category": "nature" - }, - ":radio:": { - "uc_base": "1f4fb", - "uc_output": "1f4fb", - "uc_match": "1f4fb-fe0f", - "uc_greedy": "1f4fb-fe0f", - "shortnames": [], - "category": "objects" - }, - ":radio_button:": { - "uc_base": "1f518", - "uc_output": "1f518", - "uc_match": "1f518", - "uc_greedy": "1f518", - "shortnames": [], - "category": "symbols" - }, - ":rage:": { - "uc_base": "1f621", - "uc_output": "1f621", - "uc_match": "1f621", - "uc_greedy": "1f621", - "shortnames": [], - "category": "people" - }, - ":railway_car:": { - "uc_base": "1f683", - "uc_output": "1f683", - "uc_match": "1f683", - "uc_greedy": "1f683", - "shortnames": [], - "category": "travel" - }, - ":railway_track:": { - "uc_base": "1f6e4", - "uc_output": "1f6e4", - "uc_match": "1f6e4-fe0f", - "uc_greedy": "1f6e4-fe0f", - "shortnames": [":railroad_track:"], - "category": "travel" - }, - ":rainbow:": { - "uc_base": "1f308", - "uc_output": "1f308", - "uc_match": "1f308", - "uc_greedy": "1f308", - "shortnames": [], - "category": "nature" - }, - ":raised_back_of_hand:": { - "uc_base": "1f91a", - "uc_output": "1f91a", - "uc_match": "1f91a", - "uc_greedy": "1f91a", - "shortnames": [":back_of_hand:"], - "category": "people" - }, - ":raised_hands:": { - "uc_base": "1f64c", - "uc_output": "1f64c", - "uc_match": "1f64c", - "uc_greedy": "1f64c", - "shortnames": [], - "category": "people" - }, - ":ram:": { - "uc_base": "1f40f", - "uc_output": "1f40f", - "uc_match": "1f40f", - "uc_greedy": "1f40f", - "shortnames": [], - "category": "nature" - }, - ":ramen:": { - "uc_base": "1f35c", - "uc_output": "1f35c", - "uc_match": "1f35c", - "uc_greedy": "1f35c", - "shortnames": [], - "category": "food" - }, - ":rat:": { - "uc_base": "1f400", - "uc_output": "1f400", - "uc_match": "1f400", - "uc_greedy": "1f400", - "shortnames": [], - "category": "nature" - }, - ":red_car:": { - "uc_base": "1f697", - "uc_output": "1f697", - "uc_match": "1f697", - "uc_greedy": "1f697", - "shortnames": [], - "category": "travel" - }, - ":red_circle:": { - "uc_base": "1f534", - "uc_output": "1f534", - "uc_match": "1f534", - "uc_greedy": "1f534", - "shortnames": [], - "category": "symbols" - }, - ":regional_indicator_a:": { - "uc_base": "1f1e6", - "uc_output": "1f1e6", - "uc_match": "1f1e6", - "uc_greedy": "1f1e6", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_b:": { - "uc_base": "1f1e7", - "uc_output": "1f1e7", - "uc_match": "1f1e7", - "uc_greedy": "1f1e7", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_c:": { - "uc_base": "1f1e8", - "uc_output": "1f1e8", - "uc_match": "1f1e8", - "uc_greedy": "1f1e8", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_d:": { - "uc_base": "1f1e9", - "uc_output": "1f1e9", - "uc_match": "1f1e9", - "uc_greedy": "1f1e9", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_e:": { - "uc_base": "1f1ea", - "uc_output": "1f1ea", - "uc_match": "1f1ea", - "uc_greedy": "1f1ea", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_f:": { - "uc_base": "1f1eb", - "uc_output": "1f1eb", - "uc_match": "1f1eb", - "uc_greedy": "1f1eb", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_g:": { - "uc_base": "1f1ec", - "uc_output": "1f1ec", - "uc_match": "1f1ec", - "uc_greedy": "1f1ec", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_h:": { - "uc_base": "1f1ed", - "uc_output": "1f1ed", - "uc_match": "1f1ed", - "uc_greedy": "1f1ed", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_i:": { - "uc_base": "1f1ee", - "uc_output": "1f1ee", - "uc_match": "1f1ee", - "uc_greedy": "1f1ee", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_j:": { - "uc_base": "1f1ef", - "uc_output": "1f1ef", - "uc_match": "1f1ef", - "uc_greedy": "1f1ef", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_k:": { - "uc_base": "1f1f0", - "uc_output": "1f1f0", - "uc_match": "1f1f0", - "uc_greedy": "1f1f0", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_l:": { - "uc_base": "1f1f1", - "uc_output": "1f1f1", - "uc_match": "1f1f1", - "uc_greedy": "1f1f1", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_m:": { - "uc_base": "1f1f2", - "uc_output": "1f1f2", - "uc_match": "1f1f2", - "uc_greedy": "1f1f2", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_n:": { - "uc_base": "1f1f3", - "uc_output": "1f1f3", - "uc_match": "1f1f3", - "uc_greedy": "1f1f3", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_o:": { - "uc_base": "1f1f4", - "uc_output": "1f1f4", - "uc_match": "1f1f4", - "uc_greedy": "1f1f4", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_p:": { - "uc_base": "1f1f5", - "uc_output": "1f1f5", - "uc_match": "1f1f5", - "uc_greedy": "1f1f5", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_q:": { - "uc_base": "1f1f6", - "uc_output": "1f1f6", - "uc_match": "1f1f6", - "uc_greedy": "1f1f6", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_r:": { - "uc_base": "1f1f7", - "uc_output": "1f1f7", - "uc_match": "1f1f7", - "uc_greedy": "1f1f7", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_s:": { - "uc_base": "1f1f8", - "uc_output": "1f1f8", - "uc_match": "1f1f8", - "uc_greedy": "1f1f8", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_t:": { - "uc_base": "1f1f9", - "uc_output": "1f1f9", - "uc_match": "1f1f9", - "uc_greedy": "1f1f9", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_u:": { - "uc_base": "1f1fa", - "uc_output": "1f1fa", - "uc_match": "1f1fa", - "uc_greedy": "1f1fa", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_v:": { - "uc_base": "1f1fb", - "uc_output": "1f1fb", - "uc_match": "1f1fb", - "uc_greedy": "1f1fb", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_w:": { - "uc_base": "1f1fc", - "uc_output": "1f1fc", - "uc_match": "1f1fc", - "uc_greedy": "1f1fc", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_x:": { - "uc_base": "1f1fd", - "uc_output": "1f1fd", - "uc_match": "1f1fd", - "uc_greedy": "1f1fd", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_y:": { - "uc_base": "1f1fe", - "uc_output": "1f1fe", - "uc_match": "1f1fe", - "uc_greedy": "1f1fe", - "shortnames": [], - "category": "regional" - }, - ":regional_indicator_z:": { - "uc_base": "1f1ff", - "uc_output": "1f1ff", - "uc_match": "1f1ff", - "uc_greedy": "1f1ff", - "shortnames": [], - "category": "regional" - }, - ":relieved:": { - "uc_base": "1f60c", - "uc_output": "1f60c", - "uc_match": "1f60c", - "uc_greedy": "1f60c", - "shortnames": [], - "category": "people" - }, - ":reminder_ribbon:": { - "uc_base": "1f397", - "uc_output": "1f397", - "uc_match": "1f397-fe0f", - "uc_greedy": "1f397-fe0f", - "shortnames": [], - "category": "activity" - }, - ":repeat:": { - "uc_base": "1f501", - "uc_output": "1f501", - "uc_match": "1f501", - "uc_greedy": "1f501", - "shortnames": [], - "category": "symbols" - }, - ":repeat_one:": { - "uc_base": "1f502", - "uc_output": "1f502", - "uc_match": "1f502", - "uc_greedy": "1f502", - "shortnames": [], - "category": "symbols" - }, - ":restroom:": { - "uc_base": "1f6bb", - "uc_output": "1f6bb", - "uc_match": "1f6bb", - "uc_greedy": "1f6bb", - "shortnames": [], - "category": "symbols" - }, - ":revolving_hearts:": { - "uc_base": "1f49e", - "uc_output": "1f49e", - "uc_match": "1f49e", - "uc_greedy": "1f49e", - "shortnames": [], - "category": "symbols" - }, - ":rhino:": { - "uc_base": "1f98f", - "uc_output": "1f98f", - "uc_match": "1f98f", - "uc_greedy": "1f98f", - "shortnames": [":rhinoceros:"], - "category": "nature" - }, - ":ribbon:": { - "uc_base": "1f380", - "uc_output": "1f380", - "uc_match": "1f380", - "uc_greedy": "1f380", - "shortnames": [], - "category": "objects" - }, - ":rice:": { - "uc_base": "1f35a", - "uc_output": "1f35a", - "uc_match": "1f35a", - "uc_greedy": "1f35a", - "shortnames": [], - "category": "food" - }, - ":rice_ball:": { - "uc_base": "1f359", - "uc_output": "1f359", - "uc_match": "1f359", - "uc_greedy": "1f359", - "shortnames": [], - "category": "food" - }, - ":rice_cracker:": { - "uc_base": "1f358", - "uc_output": "1f358", - "uc_match": "1f358", - "uc_greedy": "1f358", - "shortnames": [], - "category": "food" - }, - ":rice_scene:": { - "uc_base": "1f391", - "uc_output": "1f391", - "uc_match": "1f391", - "uc_greedy": "1f391", - "shortnames": [], - "category": "travel" - }, - ":right_facing_fist:": { - "uc_base": "1f91c", - "uc_output": "1f91c", - "uc_match": "1f91c", - "uc_greedy": "1f91c", - "shortnames": [":right_fist:"], - "category": "people" - }, - ":ring:": { - "uc_base": "1f48d", - "uc_output": "1f48d", - "uc_match": "1f48d", - "uc_greedy": "1f48d", - "shortnames": [], - "category": "people" - }, - ":robot:": { - "uc_base": "1f916", - "uc_output": "1f916", - "uc_match": "1f916", - "uc_greedy": "1f916", - "shortnames": [":robot_face:"], - "category": "people" - }, - ":rocket:": { - "uc_base": "1f680", - "uc_output": "1f680", - "uc_match": "1f680", - "uc_greedy": "1f680", - "shortnames": [], - "category": "travel" - }, - ":rofl:": { - "uc_base": "1f923", - "uc_output": "1f923", - "uc_match": "1f923", - "uc_greedy": "1f923", - "shortnames": [":rolling_on_the_floor_laughing:"], - "category": "people" - }, - ":roller_coaster:": { - "uc_base": "1f3a2", - "uc_output": "1f3a2", - "uc_match": "1f3a2", - "uc_greedy": "1f3a2", - "shortnames": [], - "category": "travel" - }, - ":rolling_eyes:": { - "uc_base": "1f644", - "uc_output": "1f644", - "uc_match": "1f644", - "uc_greedy": "1f644", - "shortnames": [":face_with_rolling_eyes:"], - "category": "people" - }, - ":rooster:": { - "uc_base": "1f413", - "uc_output": "1f413", - "uc_match": "1f413", - "uc_greedy": "1f413", - "shortnames": [], - "category": "nature" - }, - ":rose:": { - "uc_base": "1f339", - "uc_output": "1f339", - "uc_match": "1f339", - "uc_greedy": "1f339", - "shortnames": [], - "category": "nature" - }, - ":rosette:": { - "uc_base": "1f3f5", - "uc_output": "1f3f5", - "uc_match": "1f3f5-fe0f", - "uc_greedy": "1f3f5-fe0f", - "shortnames": [], - "category": "activity" - }, - ":rotating_light:": { - "uc_base": "1f6a8", - "uc_output": "1f6a8", - "uc_match": "1f6a8", - "uc_greedy": "1f6a8", - "shortnames": [], - "category": "travel" - }, - ":round_pushpin:": { - "uc_base": "1f4cd", - "uc_output": "1f4cd", - "uc_match": "1f4cd", - "uc_greedy": "1f4cd", - "shortnames": [], - "category": "objects" - }, - ":rugby_football:": { - "uc_base": "1f3c9", - "uc_output": "1f3c9", - "uc_match": "1f3c9", - "uc_greedy": "1f3c9", - "shortnames": [], - "category": "activity" - }, - ":running_shirt_with_sash:": { - "uc_base": "1f3bd", - "uc_output": "1f3bd", - "uc_match": "1f3bd", - "uc_greedy": "1f3bd", - "shortnames": [], - "category": "activity" - }, - ":sa:": { - "uc_base": "1f202", - "uc_output": "1f202", - "uc_match": "1f202-fe0f", - "uc_greedy": "1f202-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":sake:": { - "uc_base": "1f376", - "uc_output": "1f376", - "uc_match": "1f376", - "uc_greedy": "1f376", - "shortnames": [], - "category": "food" - }, - ":salad:": { - "uc_base": "1f957", - "uc_output": "1f957", - "uc_match": "1f957", - "uc_greedy": "1f957", - "shortnames": [":green_salad:"], - "category": "food" - }, - ":sandal:": { - "uc_base": "1f461", - "uc_output": "1f461", - "uc_match": "1f461", - "uc_greedy": "1f461", - "shortnames": [], - "category": "people" - }, - ":sandwich:": { - "uc_base": "1f96a", - "uc_output": "1f96a", - "uc_match": "1f96a", - "uc_greedy": "1f96a", - "shortnames": [], - "category": "food" - }, - ":santa:": { - "uc_base": "1f385", - "uc_output": "1f385", - "uc_match": "1f385", - "uc_greedy": "1f385", - "shortnames": [], - "category": "people" - }, - ":satellite:": { - "uc_base": "1f4e1", - "uc_output": "1f4e1", - "uc_match": "1f4e1", - "uc_greedy": "1f4e1", - "shortnames": [], - "category": "objects" - }, - ":satellite_orbital:": { - "uc_base": "1f6f0", - "uc_output": "1f6f0", - "uc_match": "1f6f0-fe0f", - "uc_greedy": "1f6f0-fe0f", - "shortnames": [], - "category": "travel" - }, - ":sauropod:": { - "uc_base": "1f995", - "uc_output": "1f995", - "uc_match": "1f995", - "uc_greedy": "1f995", - "shortnames": [], - "category": "nature" - }, - ":saxophone:": { - "uc_base": "1f3b7", - "uc_output": "1f3b7", - "uc_match": "1f3b7", - "uc_greedy": "1f3b7", - "shortnames": [], - "category": "activity" - }, - ":scarf:": { - "uc_base": "1f9e3", - "uc_output": "1f9e3", - "uc_match": "1f9e3", - "uc_greedy": "1f9e3", - "shortnames": [], - "category": "people" - }, - ":school:": { - "uc_base": "1f3eb", - "uc_output": "1f3eb", - "uc_match": "1f3eb", - "uc_greedy": "1f3eb", - "shortnames": [], - "category": "travel" - }, - ":school_satchel:": { - "uc_base": "1f392", - "uc_output": "1f392", - "uc_match": "1f392", - "uc_greedy": "1f392", - "shortnames": [], - "category": "people" - }, - ":scooter:": { - "uc_base": "1f6f4", - "uc_output": "1f6f4", - "uc_match": "1f6f4", - "uc_greedy": "1f6f4", - "shortnames": [], - "category": "travel" - }, - ":scorpion:": { - "uc_base": "1f982", - "uc_output": "1f982", - "uc_match": "1f982", - "uc_greedy": "1f982", - "shortnames": [], - "category": "nature" - }, - ":scream:": { - "uc_base": "1f631", - "uc_output": "1f631", - "uc_match": "1f631", - "uc_greedy": "1f631", - "shortnames": [], - "category": "people" - }, - ":scream_cat:": { - "uc_base": "1f640", - "uc_output": "1f640", - "uc_match": "1f640", - "uc_greedy": "1f640", - "shortnames": [], - "category": "people" - }, - ":scroll:": { - "uc_base": "1f4dc", - "uc_output": "1f4dc", - "uc_match": "1f4dc", - "uc_greedy": "1f4dc", - "shortnames": [], - "category": "objects" - }, - ":seat:": { - "uc_base": "1f4ba", - "uc_output": "1f4ba", - "uc_match": "1f4ba", - "uc_greedy": "1f4ba", - "shortnames": [], - "category": "travel" - }, - ":second_place:": { - "uc_base": "1f948", - "uc_output": "1f948", - "uc_match": "1f948", - "uc_greedy": "1f948", - "shortnames": [":second_place_medal:"], - "category": "activity" - }, - ":see_no_evil:": { - "uc_base": "1f648", - "uc_output": "1f648", - "uc_match": "1f648", - "uc_greedy": "1f648", - "shortnames": [], - "category": "nature" - }, - ":seedling:": { - "uc_base": "1f331", - "uc_output": "1f331", - "uc_match": "1f331", - "uc_greedy": "1f331", - "shortnames": [], - "category": "nature" - }, - ":selfie:": { - "uc_base": "1f933", - "uc_output": "1f933", - "uc_match": "1f933", - "uc_greedy": "1f933", - "shortnames": [], - "category": "people" - }, - ":shallow_pan_of_food:": { - "uc_base": "1f958", - "uc_output": "1f958", - "uc_match": "1f958", - "uc_greedy": "1f958", - "shortnames": [":paella:"], - "category": "food" - }, - ":shark:": { - "uc_base": "1f988", - "uc_output": "1f988", - "uc_match": "1f988", - "uc_greedy": "1f988", - "shortnames": [], - "category": "nature" - }, - ":shaved_ice:": { - "uc_base": "1f367", - "uc_output": "1f367", - "uc_match": "1f367", - "uc_greedy": "1f367", - "shortnames": [], - "category": "food" - }, - ":sheep:": { - "uc_base": "1f411", - "uc_output": "1f411", - "uc_match": "1f411", - "uc_greedy": "1f411", - "shortnames": [], - "category": "nature" - }, - ":shell:": { - "uc_base": "1f41a", - "uc_output": "1f41a", - "uc_match": "1f41a", - "uc_greedy": "1f41a", - "shortnames": [], - "category": "nature" - }, - ":shield:": { - "uc_base": "1f6e1", - "uc_output": "1f6e1", - "uc_match": "1f6e1-fe0f", - "uc_greedy": "1f6e1-fe0f", - "shortnames": [], - "category": "objects" - }, - ":ship:": { - "uc_base": "1f6a2", - "uc_output": "1f6a2", - "uc_match": "1f6a2", - "uc_greedy": "1f6a2", - "shortnames": [], - "category": "travel" - }, - ":shirt:": { - "uc_base": "1f455", - "uc_output": "1f455", - "uc_match": "1f455", - "uc_greedy": "1f455", - "shortnames": [], - "category": "people" - }, - ":shopping_bags:": { - "uc_base": "1f6cd", - "uc_output": "1f6cd", - "uc_match": "1f6cd-fe0f", - "uc_greedy": "1f6cd-fe0f", - "shortnames": [], - "category": "objects" - }, - ":shopping_cart:": { - "uc_base": "1f6d2", - "uc_output": "1f6d2", - "uc_match": "1f6d2", - "uc_greedy": "1f6d2", - "shortnames": [":shopping_trolley:"], - "category": "objects" - }, - ":shower:": { - "uc_base": "1f6bf", - "uc_output": "1f6bf", - "uc_match": "1f6bf", - "uc_greedy": "1f6bf", - "shortnames": [], - "category": "objects" - }, - ":shrimp:": { - "uc_base": "1f990", - "uc_output": "1f990", - "uc_match": "1f990", - "uc_greedy": "1f990", - "shortnames": [], - "category": "nature" - }, - ":shushing_face:": { - "uc_base": "1f92b", - "uc_output": "1f92b", - "uc_match": "1f92b", - "uc_greedy": "1f92b", - "shortnames": [], - "category": "people" - }, - ":signal_strength:": { - "uc_base": "1f4f6", - "uc_output": "1f4f6", - "uc_match": "1f4f6", - "uc_greedy": "1f4f6", - "shortnames": [], - "category": "symbols" - }, - ":six_pointed_star:": { - "uc_base": "1f52f", - "uc_output": "1f52f", - "uc_match": "1f52f", - "uc_greedy": "1f52f", - "shortnames": [], - "category": "symbols" - }, - ":ski:": { - "uc_base": "1f3bf", - "uc_output": "1f3bf", - "uc_match": "1f3bf", - "uc_greedy": "1f3bf", - "shortnames": [], - "category": "activity" - }, - ":skull:": { - "uc_base": "1f480", - "uc_output": "1f480", - "uc_match": "1f480", - "uc_greedy": "1f480", - "shortnames": [":skeleton:"], - "category": "people" - }, - ":sled:": { - "uc_base": "1f6f7", - "uc_output": "1f6f7", - "uc_match": "1f6f7", - "uc_greedy": "1f6f7", - "shortnames": [], - "category": "activity" - }, - ":sleeping:": { - "uc_base": "1f634", - "uc_output": "1f634", - "uc_match": "1f634", - "uc_greedy": "1f634", - "shortnames": [], - "category": "people" - }, - ":sleeping_accommodation:": { - "uc_base": "1f6cc", - "uc_output": "1f6cc", - "uc_match": "1f6cc", - "uc_greedy": "1f6cc", - "shortnames": [], - "category": "objects" - }, - ":sleepy:": { - "uc_base": "1f62a", - "uc_output": "1f62a", - "uc_match": "1f62a", - "uc_greedy": "1f62a", - "shortnames": [], - "category": "people" - }, - ":slight_frown:": { - "uc_base": "1f641", - "uc_output": "1f641", - "uc_match": "1f641", - "uc_greedy": "1f641", - "shortnames": [":slightly_frowning_face:"], - "category": "people" - }, - ":slight_smile:": { - "uc_base": "1f642", - "uc_output": "1f642", - "uc_match": "1f642", - "uc_greedy": "1f642", - "shortnames": [":slightly_smiling_face:"], - "category": "people" - }, - ":slot_machine:": { - "uc_base": "1f3b0", - "uc_output": "1f3b0", - "uc_match": "1f3b0", - "uc_greedy": "1f3b0", - "shortnames": [], - "category": "activity" - }, - ":small_blue_diamond:": { - "uc_base": "1f539", - "uc_output": "1f539", - "uc_match": "1f539", - "uc_greedy": "1f539", - "shortnames": [], - "category": "symbols" - }, - ":small_orange_diamond:": { - "uc_base": "1f538", - "uc_output": "1f538", - "uc_match": "1f538", - "uc_greedy": "1f538", - "shortnames": [], - "category": "symbols" - }, - ":small_red_triangle:": { - "uc_base": "1f53a", - "uc_output": "1f53a", - "uc_match": "1f53a", - "uc_greedy": "1f53a", - "shortnames": [], - "category": "symbols" - }, - ":small_red_triangle_down:": { - "uc_base": "1f53b", - "uc_output": "1f53b", - "uc_match": "1f53b", - "uc_greedy": "1f53b", - "shortnames": [], - "category": "symbols" - }, - ":smile:": { - "uc_base": "1f604", - "uc_output": "1f604", - "uc_match": "1f604", - "uc_greedy": "1f604", - "shortnames": [], - "category": "people" - }, - ":smile_cat:": { - "uc_base": "1f638", - "uc_output": "1f638", - "uc_match": "1f638", - "uc_greedy": "1f638", - "shortnames": [], - "category": "people" - }, - ":smiley:": { - "uc_base": "1f603", - "uc_output": "1f603", - "uc_match": "1f603", - "uc_greedy": "1f603", - "shortnames": [], - "category": "people" - }, - ":smiley_cat:": { - "uc_base": "1f63a", - "uc_output": "1f63a", - "uc_match": "1f63a", - "uc_greedy": "1f63a", - "shortnames": [], - "category": "people" - }, - ":smiling_imp:": { - "uc_base": "1f608", - "uc_output": "1f608", - "uc_match": "1f608", - "uc_greedy": "1f608", - "shortnames": [], - "category": "people" - }, - ":smirk:": { - "uc_base": "1f60f", - "uc_output": "1f60f", - "uc_match": "1f60f", - "uc_greedy": "1f60f", - "shortnames": [], - "category": "people" - }, - ":smirk_cat:": { - "uc_base": "1f63c", - "uc_output": "1f63c", - "uc_match": "1f63c", - "uc_greedy": "1f63c", - "shortnames": [], - "category": "people" - }, - ":smoking:": { - "uc_base": "1f6ac", - "uc_output": "1f6ac", - "uc_match": "1f6ac", - "uc_greedy": "1f6ac", - "shortnames": [], - "category": "objects" - }, - ":snail:": { - "uc_base": "1f40c", - "uc_output": "1f40c", - "uc_match": "1f40c", - "uc_greedy": "1f40c", - "shortnames": [], - "category": "nature" - }, - ":snake:": { - "uc_base": "1f40d", - "uc_output": "1f40d", - "uc_match": "1f40d", - "uc_greedy": "1f40d", - "shortnames": [], - "category": "nature" - }, - ":sneezing_face:": { - "uc_base": "1f927", - "uc_output": "1f927", - "uc_match": "1f927", - "uc_greedy": "1f927", - "shortnames": [":sneeze:"], - "category": "people" - }, - ":snowboarder:": { - "uc_base": "1f3c2", - "uc_output": "1f3c2", - "uc_match": "1f3c2-fe0f", - "uc_greedy": "1f3c2-fe0f", - "shortnames": [], - "category": "activity" - }, - ":sob:": { - "uc_base": "1f62d", - "uc_output": "1f62d", - "uc_match": "1f62d", - "uc_greedy": "1f62d", - "shortnames": [], - "category": "people" - }, - ":socks:": { - "uc_base": "1f9e6", - "uc_output": "1f9e6", - "uc_match": "1f9e6", - "uc_greedy": "1f9e6", - "shortnames": [], - "category": "people" - }, - ":soon:": { - "uc_base": "1f51c", - "uc_output": "1f51c", - "uc_match": "1f51c", - "uc_greedy": "1f51c", - "shortnames": [], - "category": "symbols" - }, - ":sos:": { - "uc_base": "1f198", - "uc_output": "1f198", - "uc_match": "1f198", - "uc_greedy": "1f198", - "shortnames": [], - "category": "symbols" - }, - ":sound:": { - "uc_base": "1f509", - "uc_output": "1f509", - "uc_match": "1f509", - "uc_greedy": "1f509", - "shortnames": [], - "category": "symbols" - }, - ":space_invader:": { - "uc_base": "1f47e", - "uc_output": "1f47e", - "uc_match": "1f47e", - "uc_greedy": "1f47e", - "shortnames": [], - "category": "people" - }, - ":spaghetti:": { - "uc_base": "1f35d", - "uc_output": "1f35d", - "uc_match": "1f35d", - "uc_greedy": "1f35d", - "shortnames": [], - "category": "food" - }, - ":sparkler:": { - "uc_base": "1f387", - "uc_output": "1f387", - "uc_match": "1f387", - "uc_greedy": "1f387", - "shortnames": [], - "category": "travel" - }, - ":sparkling_heart:": { - "uc_base": "1f496", - "uc_output": "1f496", - "uc_match": "1f496", - "uc_greedy": "1f496", - "shortnames": [], - "category": "symbols" - }, - ":speak_no_evil:": { - "uc_base": "1f64a", - "uc_output": "1f64a", - "uc_match": "1f64a", - "uc_greedy": "1f64a", - "shortnames": [], - "category": "nature" - }, - ":speaker:": { - "uc_base": "1f508", - "uc_output": "1f508", - "uc_match": "1f508-fe0f", - "uc_greedy": "1f508-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":speaking_head:": { - "uc_base": "1f5e3", - "uc_output": "1f5e3", - "uc_match": "1f5e3-fe0f", - "uc_greedy": "1f5e3-fe0f", - "shortnames": [":speaking_head_in_silhouette:"], - "category": "people" - }, - ":speech_balloon:": { - "uc_base": "1f4ac", - "uc_output": "1f4ac", - "uc_match": "1f4ac", - "uc_greedy": "1f4ac", - "shortnames": [], - "category": "symbols" - }, - ":speech_left:": { - "uc_base": "1f5e8", - "uc_output": "1f5e8", - "uc_match": "1f5e8-fe0f", - "uc_greedy": "1f5e8-fe0f", - "shortnames": [":left_speech_bubble:"], - "category": "symbols" - }, - ":speedboat:": { - "uc_base": "1f6a4", - "uc_output": "1f6a4", - "uc_match": "1f6a4", - "uc_greedy": "1f6a4", - "shortnames": [], - "category": "travel" - }, - ":spider:": { - "uc_base": "1f577", - "uc_output": "1f577", - "uc_match": "1f577-fe0f", - "uc_greedy": "1f577-fe0f", - "shortnames": [], - "category": "nature" - }, - ":spider_web:": { - "uc_base": "1f578", - "uc_output": "1f578", - "uc_match": "1f578-fe0f", - "uc_greedy": "1f578-fe0f", - "shortnames": [], - "category": "nature" - }, - ":spoon:": { - "uc_base": "1f944", - "uc_output": "1f944", - "uc_match": "1f944", - "uc_greedy": "1f944", - "shortnames": [], - "category": "food" - }, - ":squid:": { - "uc_base": "1f991", - "uc_output": "1f991", - "uc_match": "1f991", - "uc_greedy": "1f991", - "shortnames": [], - "category": "nature" - }, - ":stadium:": { - "uc_base": "1f3df", - "uc_output": "1f3df", - "uc_match": "1f3df-fe0f", - "uc_greedy": "1f3df-fe0f", - "shortnames": [], - "category": "travel" - }, - ":star2:": { - "uc_base": "1f31f", - "uc_output": "1f31f", - "uc_match": "1f31f", - "uc_greedy": "1f31f", - "shortnames": [], - "category": "nature" - }, - ":star_struck:": { - "uc_base": "1f929", - "uc_output": "1f929", - "uc_match": "1f929", - "uc_greedy": "1f929", - "shortnames": [], - "category": "people" - }, - ":stars:": { - "uc_base": "1f320", - "uc_output": "1f320", - "uc_match": "1f320", - "uc_greedy": "1f320", - "shortnames": [], - "category": "travel" - }, - ":station:": { - "uc_base": "1f689", - "uc_output": "1f689", - "uc_match": "1f689", - "uc_greedy": "1f689", - "shortnames": [], - "category": "travel" - }, - ":statue_of_liberty:": { - "uc_base": "1f5fd", - "uc_output": "1f5fd", - "uc_match": "1f5fd", - "uc_greedy": "1f5fd", - "shortnames": [], - "category": "travel" - }, - ":steam_locomotive:": { - "uc_base": "1f682", - "uc_output": "1f682", - "uc_match": "1f682", - "uc_greedy": "1f682", - "shortnames": [], - "category": "travel" - }, - ":stew:": { - "uc_base": "1f372", - "uc_output": "1f372", - "uc_match": "1f372", - "uc_greedy": "1f372", - "shortnames": [], - "category": "food" - }, - ":straight_ruler:": { - "uc_base": "1f4cf", - "uc_output": "1f4cf", - "uc_match": "1f4cf", - "uc_greedy": "1f4cf", - "shortnames": [], - "category": "objects" - }, - ":strawberry:": { - "uc_base": "1f353", - "uc_output": "1f353", - "uc_match": "1f353", - "uc_greedy": "1f353", - "shortnames": [], - "category": "food" - }, - ":stuck_out_tongue:": { - "uc_base": "1f61b", - "uc_output": "1f61b", - "uc_match": "1f61b", - "uc_greedy": "1f61b", - "shortnames": [], - "category": "people" - }, - ":stuck_out_tongue_closed_eyes:": { - "uc_base": "1f61d", - "uc_output": "1f61d", - "uc_match": "1f61d", - "uc_greedy": "1f61d", - "shortnames": [], - "category": "people" - }, - ":stuck_out_tongue_winking_eye:": { - "uc_base": "1f61c", - "uc_output": "1f61c", - "uc_match": "1f61c", - "uc_greedy": "1f61c", - "shortnames": [], - "category": "people" - }, - ":stuffed_flatbread:": { - "uc_base": "1f959", - "uc_output": "1f959", - "uc_match": "1f959", - "uc_greedy": "1f959", - "shortnames": [":stuffed_pita:"], - "category": "food" - }, - ":sun_with_face:": { - "uc_base": "1f31e", - "uc_output": "1f31e", - "uc_match": "1f31e", - "uc_greedy": "1f31e", - "shortnames": [], - "category": "nature" - }, - ":sunflower:": { - "uc_base": "1f33b", - "uc_output": "1f33b", - "uc_match": "1f33b", - "uc_greedy": "1f33b", - "shortnames": [], - "category": "nature" - }, - ":sunglasses:": { - "uc_base": "1f60e", - "uc_output": "1f60e", - "uc_match": "1f60e", - "uc_greedy": "1f60e", - "shortnames": [], - "category": "people" - }, - ":sunrise:": { - "uc_base": "1f305", - "uc_output": "1f305", - "uc_match": "1f305", - "uc_greedy": "1f305", - "shortnames": [], - "category": "travel" - }, - ":sunrise_over_mountains:": { - "uc_base": "1f304", - "uc_output": "1f304", - "uc_match": "1f304", - "uc_greedy": "1f304", - "shortnames": [], - "category": "travel" - }, - ":sushi:": { - "uc_base": "1f363", - "uc_output": "1f363", - "uc_match": "1f363", - "uc_greedy": "1f363", - "shortnames": [], - "category": "food" - }, - ":suspension_railway:": { - "uc_base": "1f69f", - "uc_output": "1f69f", - "uc_match": "1f69f", - "uc_greedy": "1f69f", - "shortnames": [], - "category": "travel" - }, - ":sweat:": { - "uc_base": "1f613", - "uc_output": "1f613", - "uc_match": "1f613", - "uc_greedy": "1f613", - "shortnames": [], - "category": "people" - }, - ":sweat_drops:": { - "uc_base": "1f4a6", - "uc_output": "1f4a6", - "uc_match": "1f4a6", - "uc_greedy": "1f4a6", - "shortnames": [], - "category": "nature" - }, - ":sweat_smile:": { - "uc_base": "1f605", - "uc_output": "1f605", - "uc_match": "1f605", - "uc_greedy": "1f605", - "shortnames": [], - "category": "people" - }, - ":sweet_potato:": { - "uc_base": "1f360", - "uc_output": "1f360", - "uc_match": "1f360", - "uc_greedy": "1f360", - "shortnames": [], - "category": "food" - }, - ":symbols:": { - "uc_base": "1f523", - "uc_output": "1f523", - "uc_match": "1f523", - "uc_greedy": "1f523", - "shortnames": [], - "category": "symbols" - }, - ":synagogue:": { - "uc_base": "1f54d", - "uc_output": "1f54d", - "uc_match": "1f54d", - "uc_greedy": "1f54d", - "shortnames": [], - "category": "travel" - }, - ":syringe:": { - "uc_base": "1f489", - "uc_output": "1f489", - "uc_match": "1f489", - "uc_greedy": "1f489", - "shortnames": [], - "category": "objects" - }, - ":t_rex:": { - "uc_base": "1f996", - "uc_output": "1f996", - "uc_match": "1f996", - "uc_greedy": "1f996", - "shortnames": [], - "category": "nature" - }, - ":taco:": { - "uc_base": "1f32e", - "uc_output": "1f32e", - "uc_match": "1f32e", - "uc_greedy": "1f32e", - "shortnames": [], - "category": "food" - }, - ":tada:": { - "uc_base": "1f389", - "uc_output": "1f389", - "uc_match": "1f389", - "uc_greedy": "1f389", - "shortnames": [], - "category": "objects" - }, - ":takeout_box:": { - "uc_base": "1f961", - "uc_output": "1f961", - "uc_match": "1f961", - "uc_greedy": "1f961", - "shortnames": [], - "category": "food" - }, - ":tanabata_tree:": { - "uc_base": "1f38b", - "uc_output": "1f38b", - "uc_match": "1f38b", - "uc_greedy": "1f38b", - "shortnames": [], - "category": "nature" - }, - ":tangerine:": { - "uc_base": "1f34a", - "uc_output": "1f34a", - "uc_match": "1f34a", - "uc_greedy": "1f34a", - "shortnames": [], - "category": "food" - }, - ":taxi:": { - "uc_base": "1f695", - "uc_output": "1f695", - "uc_match": "1f695", - "uc_greedy": "1f695", - "shortnames": [], - "category": "travel" - }, - ":tea:": { - "uc_base": "1f375", - "uc_output": "1f375", - "uc_match": "1f375", - "uc_greedy": "1f375", - "shortnames": [], - "category": "food" - }, - ":telephone_receiver:": { - "uc_base": "1f4de", - "uc_output": "1f4de", - "uc_match": "1f4de", - "uc_greedy": "1f4de", - "shortnames": [], - "category": "objects" - }, - ":telescope:": { - "uc_base": "1f52d", - "uc_output": "1f52d", - "uc_match": "1f52d", - "uc_greedy": "1f52d", - "shortnames": [], - "category": "objects" - }, - ":tennis:": { - "uc_base": "1f3be", - "uc_output": "1f3be", - "uc_match": "1f3be", - "uc_greedy": "1f3be", - "shortnames": [], - "category": "activity" - }, - ":thermometer:": { - "uc_base": "1f321", - "uc_output": "1f321", - "uc_match": "1f321-fe0f", - "uc_greedy": "1f321-fe0f", - "shortnames": [], - "category": "objects" - }, - ":thermometer_face:": { - "uc_base": "1f912", - "uc_output": "1f912", - "uc_match": "1f912", - "uc_greedy": "1f912", - "shortnames": [":face_with_thermometer:"], - "category": "people" - }, - ":thinking:": { - "uc_base": "1f914", - "uc_output": "1f914", - "uc_match": "1f914", - "uc_greedy": "1f914", - "shortnames": [":thinking_face:"], - "category": "people" - }, - ":third_place:": { - "uc_base": "1f949", - "uc_output": "1f949", - "uc_match": "1f949", - "uc_greedy": "1f949", - "shortnames": [":third_place_medal:"], - "category": "activity" - }, - ":thought_balloon:": { - "uc_base": "1f4ad", - "uc_output": "1f4ad", - "uc_match": "1f4ad", - "uc_greedy": "1f4ad", - "shortnames": [], - "category": "symbols" - }, - ":thumbsdown:": { - "uc_base": "1f44e", - "uc_output": "1f44e", - "uc_match": "1f44e-fe0f", - "uc_greedy": "1f44e-fe0f", - "shortnames": [":-1:", ":thumbdown:"], - "category": "people" - }, - ":thumbsup:": { - "uc_base": "1f44d", - "uc_output": "1f44d", - "uc_match": "1f44d-fe0f", - "uc_greedy": "1f44d-fe0f", - "shortnames": [":+1:", ":thumbup:"], - "category": "people" - }, - ":ticket:": { - "uc_base": "1f3ab", - "uc_output": "1f3ab", - "uc_match": "1f3ab", - "uc_greedy": "1f3ab", - "shortnames": [], - "category": "activity" - }, - ":tickets:": { - "uc_base": "1f39f", - "uc_output": "1f39f", - "uc_match": "1f39f-fe0f", - "uc_greedy": "1f39f-fe0f", - "shortnames": [":admission_tickets:"], - "category": "activity" - }, - ":tiger2:": { - "uc_base": "1f405", - "uc_output": "1f405", - "uc_match": "1f405", - "uc_greedy": "1f405", - "shortnames": [], - "category": "nature" - }, - ":tiger:": { - "uc_base": "1f42f", - "uc_output": "1f42f", - "uc_match": "1f42f", - "uc_greedy": "1f42f", - "shortnames": [], - "category": "nature" - }, - ":tired_face:": { - "uc_base": "1f62b", - "uc_output": "1f62b", - "uc_match": "1f62b", - "uc_greedy": "1f62b", - "shortnames": [], - "category": "people" - }, - ":toilet:": { - "uc_base": "1f6bd", - "uc_output": "1f6bd", - "uc_match": "1f6bd", - "uc_greedy": "1f6bd", - "shortnames": [], - "category": "objects" - }, - ":tokyo_tower:": { - "uc_base": "1f5fc", - "uc_output": "1f5fc", - "uc_match": "1f5fc", - "uc_greedy": "1f5fc", - "shortnames": [], - "category": "travel" - }, - ":tomato:": { - "uc_base": "1f345", - "uc_output": "1f345", - "uc_match": "1f345", - "uc_greedy": "1f345", - "shortnames": [], - "category": "food" - }, - ":tone1:": { - "uc_base": "1f3fb", - "uc_output": "1f3fb", - "uc_match": "1f3fb", - "uc_greedy": "1f3fb", - "shortnames": [], - "category": "modifier" - }, - ":tone2:": { - "uc_base": "1f3fc", - "uc_output": "1f3fc", - "uc_match": "1f3fc", - "uc_greedy": "1f3fc", - "shortnames": [], - "category": "modifier" - }, - ":tone3:": { - "uc_base": "1f3fd", - "uc_output": "1f3fd", - "uc_match": "1f3fd", - "uc_greedy": "1f3fd", - "shortnames": [], - "category": "modifier" - }, - ":tone4:": { - "uc_base": "1f3fe", - "uc_output": "1f3fe", - "uc_match": "1f3fe", - "uc_greedy": "1f3fe", - "shortnames": [], - "category": "modifier" - }, - ":tone5:": { - "uc_base": "1f3ff", - "uc_output": "1f3ff", - "uc_match": "1f3ff", - "uc_greedy": "1f3ff", - "shortnames": [], - "category": "modifier" - }, - ":tongue:": { - "uc_base": "1f445", - "uc_output": "1f445", - "uc_match": "1f445", - "uc_greedy": "1f445", - "shortnames": [], - "category": "people" - }, - ":tools:": { - "uc_base": "1f6e0", - "uc_output": "1f6e0", - "uc_match": "1f6e0-fe0f", - "uc_greedy": "1f6e0-fe0f", - "shortnames": [":hammer_and_wrench:"], - "category": "objects" - }, - ":top:": { - "uc_base": "1f51d", - "uc_output": "1f51d", - "uc_match": "1f51d", - "uc_greedy": "1f51d", - "shortnames": [], - "category": "symbols" - }, - ":tophat:": { - "uc_base": "1f3a9", - "uc_output": "1f3a9", - "uc_match": "1f3a9", - "uc_greedy": "1f3a9", - "shortnames": [], - "category": "people" - }, - ":trackball:": { - "uc_base": "1f5b2", - "uc_output": "1f5b2", - "uc_match": "1f5b2-fe0f", - "uc_greedy": "1f5b2-fe0f", - "shortnames": [], - "category": "objects" - }, - ":tractor:": { - "uc_base": "1f69c", - "uc_output": "1f69c", - "uc_match": "1f69c", - "uc_greedy": "1f69c", - "shortnames": [], - "category": "travel" - }, - ":traffic_light:": { - "uc_base": "1f6a5", - "uc_output": "1f6a5", - "uc_match": "1f6a5", - "uc_greedy": "1f6a5", - "shortnames": [], - "category": "travel" - }, - ":train2:": { - "uc_base": "1f686", - "uc_output": "1f686", - "uc_match": "1f686", - "uc_greedy": "1f686", - "shortnames": [], - "category": "travel" - }, - ":train:": { - "uc_base": "1f68b", - "uc_output": "1f68b", - "uc_match": "1f68b", - "uc_greedy": "1f68b", - "shortnames": [], - "category": "travel" - }, - ":tram:": { - "uc_base": "1f68a", - "uc_output": "1f68a", - "uc_match": "1f68a", - "uc_greedy": "1f68a", - "shortnames": [], - "category": "travel" - }, - ":triangular_flag_on_post:": { - "uc_base": "1f6a9", - "uc_output": "1f6a9", - "uc_match": "1f6a9", - "uc_greedy": "1f6a9", - "shortnames": [], - "category": "flags" - }, - ":triangular_ruler:": { - "uc_base": "1f4d0", - "uc_output": "1f4d0", - "uc_match": "1f4d0", - "uc_greedy": "1f4d0", - "shortnames": [], - "category": "objects" - }, - ":trident:": { - "uc_base": "1f531", - "uc_output": "1f531", - "uc_match": "1f531", - "uc_greedy": "1f531", - "shortnames": [], - "category": "symbols" - }, - ":triumph:": { - "uc_base": "1f624", - "uc_output": "1f624", - "uc_match": "1f624", - "uc_greedy": "1f624", - "shortnames": [], - "category": "people" - }, - ":trolleybus:": { - "uc_base": "1f68e", - "uc_output": "1f68e", - "uc_match": "1f68e", - "uc_greedy": "1f68e", - "shortnames": [], - "category": "travel" - }, - ":trophy:": { - "uc_base": "1f3c6", - "uc_output": "1f3c6", - "uc_match": "1f3c6-fe0f", - "uc_greedy": "1f3c6-fe0f", - "shortnames": [], - "category": "activity" - }, - ":tropical_drink:": { - "uc_base": "1f379", - "uc_output": "1f379", - "uc_match": "1f379", - "uc_greedy": "1f379", - "shortnames": [], - "category": "food" - }, - ":tropical_fish:": { - "uc_base": "1f420", - "uc_output": "1f420", - "uc_match": "1f420", - "uc_greedy": "1f420", - "shortnames": [], - "category": "nature" - }, - ":truck:": { - "uc_base": "1f69a", - "uc_output": "1f69a", - "uc_match": "1f69a", - "uc_greedy": "1f69a", - "shortnames": [], - "category": "travel" - }, - ":trumpet:": { - "uc_base": "1f3ba", - "uc_output": "1f3ba", - "uc_match": "1f3ba", - "uc_greedy": "1f3ba", - "shortnames": [], - "category": "activity" - }, - ":tulip:": { - "uc_base": "1f337", - "uc_output": "1f337", - "uc_match": "1f337", - "uc_greedy": "1f337", - "shortnames": [], - "category": "nature" - }, - ":tumbler_glass:": { - "uc_base": "1f943", - "uc_output": "1f943", - "uc_match": "1f943", - "uc_greedy": "1f943", - "shortnames": [":whisky:"], - "category": "food" - }, - ":turkey:": { - "uc_base": "1f983", - "uc_output": "1f983", - "uc_match": "1f983", - "uc_greedy": "1f983", - "shortnames": [], - "category": "nature" - }, - ":turtle:": { - "uc_base": "1f422", - "uc_output": "1f422", - "uc_match": "1f422", - "uc_greedy": "1f422", - "shortnames": [], - "category": "nature" - }, - ":tv:": { - "uc_base": "1f4fa", - "uc_output": "1f4fa", - "uc_match": "1f4fa-fe0f", - "uc_greedy": "1f4fa-fe0f", - "shortnames": [], - "category": "objects" - }, - ":twisted_rightwards_arrows:": { - "uc_base": "1f500", - "uc_output": "1f500", - "uc_match": "1f500", - "uc_greedy": "1f500", - "shortnames": [], - "category": "symbols" - }, - ":two_hearts:": { - "uc_base": "1f495", - "uc_output": "1f495", - "uc_match": "1f495", - "uc_greedy": "1f495", - "shortnames": [], - "category": "symbols" - }, - ":two_men_holding_hands:": { - "uc_base": "1f46c", - "uc_output": "1f46c", - "uc_match": "1f46c", - "uc_greedy": "1f46c", - "shortnames": [], - "category": "people" - }, - ":two_women_holding_hands:": { - "uc_base": "1f46d", - "uc_output": "1f46d", - "uc_match": "1f46d", - "uc_greedy": "1f46d", - "shortnames": [], - "category": "people" - }, - ":u5272:": { - "uc_base": "1f239", - "uc_output": "1f239", - "uc_match": "1f239", - "uc_greedy": "1f239", - "shortnames": [], - "category": "symbols" - }, - ":u5408:": { - "uc_base": "1f234", - "uc_output": "1f234", - "uc_match": "1f234", - "uc_greedy": "1f234", - "shortnames": [], - "category": "symbols" - }, - ":u55b6:": { - "uc_base": "1f23a", - "uc_output": "1f23a", - "uc_match": "1f23a", - "uc_greedy": "1f23a", - "shortnames": [], - "category": "symbols" - }, - ":u6307:": { - "uc_base": "1f22f", - "uc_output": "1f22f", - "uc_match": "1f22f-fe0f", - "uc_greedy": "1f22f-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":u6708:": { - "uc_base": "1f237", - "uc_output": "1f237", - "uc_match": "1f237-fe0f", - "uc_greedy": "1f237-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":u6709:": { - "uc_base": "1f236", - "uc_output": "1f236", - "uc_match": "1f236", - "uc_greedy": "1f236", - "shortnames": [], - "category": "symbols" - }, - ":u6e80:": { - "uc_base": "1f235", - "uc_output": "1f235", - "uc_match": "1f235", - "uc_greedy": "1f235", - "shortnames": [], - "category": "symbols" - }, - ":u7121:": { - "uc_base": "1f21a", - "uc_output": "1f21a", - "uc_match": "1f21a-fe0f", - "uc_greedy": "1f21a-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":u7533:": { - "uc_base": "1f238", - "uc_output": "1f238", - "uc_match": "1f238", - "uc_greedy": "1f238", - "shortnames": [], - "category": "symbols" - }, - ":u7981:": { - "uc_base": "1f232", - "uc_output": "1f232", - "uc_match": "1f232", - "uc_greedy": "1f232", - "shortnames": [], - "category": "symbols" - }, - ":u7a7a:": { - "uc_base": "1f233", - "uc_output": "1f233", - "uc_match": "1f233", - "uc_greedy": "1f233", - "shortnames": [], - "category": "symbols" - }, - ":unamused:": { - "uc_base": "1f612", - "uc_output": "1f612", - "uc_match": "1f612", - "uc_greedy": "1f612", - "shortnames": [], - "category": "people" - }, - ":underage:": { - "uc_base": "1f51e", - "uc_output": "1f51e", - "uc_match": "1f51e", - "uc_greedy": "1f51e", - "shortnames": [], - "category": "symbols" - }, - ":unicorn:": { - "uc_base": "1f984", - "uc_output": "1f984", - "uc_match": "1f984", - "uc_greedy": "1f984", - "shortnames": [":unicorn_face:"], - "category": "nature" - }, - ":unlock:": { - "uc_base": "1f513", - "uc_output": "1f513", - "uc_match": "1f513-fe0f", - "uc_greedy": "1f513-fe0f", - "shortnames": [], - "category": "objects" - }, - ":up:": { - "uc_base": "1f199", - "uc_output": "1f199", - "uc_match": "1f199", - "uc_greedy": "1f199", - "shortnames": [], - "category": "symbols" - }, - ":upside_down:": { - "uc_base": "1f643", - "uc_output": "1f643", - "uc_match": "1f643", - "uc_greedy": "1f643", - "shortnames": [":upside_down_face:"], - "category": "people" - }, - ":vampire:": { - "uc_base": "1f9db", - "uc_output": "1f9db", - "uc_match": "1f9db", - "uc_greedy": "1f9db", - "shortnames": [], - "category": "people" - }, - ":vertical_traffic_light:": { - "uc_base": "1f6a6", - "uc_output": "1f6a6", - "uc_match": "1f6a6", - "uc_greedy": "1f6a6", - "shortnames": [], - "category": "travel" - }, - ":vhs:": { - "uc_base": "1f4fc", - "uc_output": "1f4fc", - "uc_match": "1f4fc", - "uc_greedy": "1f4fc", - "shortnames": [], - "category": "objects" - }, - ":vibration_mode:": { - "uc_base": "1f4f3", - "uc_output": "1f4f3", - "uc_match": "1f4f3", - "uc_greedy": "1f4f3", - "shortnames": [], - "category": "symbols" - }, - ":video_camera:": { - "uc_base": "1f4f9", - "uc_output": "1f4f9", - "uc_match": "1f4f9-fe0f", - "uc_greedy": "1f4f9-fe0f", - "shortnames": [], - "category": "objects" - }, - ":video_game:": { - "uc_base": "1f3ae", - "uc_output": "1f3ae", - "uc_match": "1f3ae-fe0f", - "uc_greedy": "1f3ae-fe0f", - "shortnames": [], - "category": "activity" - }, - ":violin:": { - "uc_base": "1f3bb", - "uc_output": "1f3bb", - "uc_match": "1f3bb", - "uc_greedy": "1f3bb", - "shortnames": [], - "category": "activity" - }, - ":volcano:": { - "uc_base": "1f30b", - "uc_output": "1f30b", - "uc_match": "1f30b", - "uc_greedy": "1f30b", - "shortnames": [], - "category": "travel" - }, - ":volleyball:": { - "uc_base": "1f3d0", - "uc_output": "1f3d0", - "uc_match": "1f3d0", - "uc_greedy": "1f3d0", - "shortnames": [], - "category": "activity" - }, - ":vs:": { - "uc_base": "1f19a", - "uc_output": "1f19a", - "uc_match": "1f19a", - "uc_greedy": "1f19a", - "shortnames": [], - "category": "symbols" - }, - ":vulcan:": { - "uc_base": "1f596", - "uc_output": "1f596", - "uc_match": "1f596", - "uc_greedy": "1f596", - "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers:"], - "category": "people" - }, - ":waning_crescent_moon:": { - "uc_base": "1f318", - "uc_output": "1f318", - "uc_match": "1f318", - "uc_greedy": "1f318", - "shortnames": [], - "category": "nature" - }, - ":waning_gibbous_moon:": { - "uc_base": "1f316", - "uc_output": "1f316", - "uc_match": "1f316", - "uc_greedy": "1f316", - "shortnames": [], - "category": "nature" - }, - ":wastebasket:": { - "uc_base": "1f5d1", - "uc_output": "1f5d1", - "uc_match": "1f5d1-fe0f", - "uc_greedy": "1f5d1-fe0f", - "shortnames": [], - "category": "objects" - }, - ":water_buffalo:": { - "uc_base": "1f403", - "uc_output": "1f403", - "uc_match": "1f403", - "uc_greedy": "1f403", - "shortnames": [], - "category": "nature" - }, - ":watermelon:": { - "uc_base": "1f349", - "uc_output": "1f349", - "uc_match": "1f349", - "uc_greedy": "1f349", - "shortnames": [], - "category": "food" - }, - ":wave:": { - "uc_base": "1f44b", - "uc_output": "1f44b", - "uc_match": "1f44b", - "uc_greedy": "1f44b", - "shortnames": [], - "category": "people" - }, - ":waxing_crescent_moon:": { - "uc_base": "1f312", - "uc_output": "1f312", - "uc_match": "1f312", - "uc_greedy": "1f312", - "shortnames": [], - "category": "nature" - }, - ":waxing_gibbous_moon:": { - "uc_base": "1f314", - "uc_output": "1f314", - "uc_match": "1f314", - "uc_greedy": "1f314", - "shortnames": [], - "category": "nature" - }, - ":wc:": { - "uc_base": "1f6be", - "uc_output": "1f6be", - "uc_match": "1f6be", - "uc_greedy": "1f6be", - "shortnames": [], - "category": "symbols" - }, - ":weary:": { - "uc_base": "1f629", - "uc_output": "1f629", - "uc_match": "1f629", - "uc_greedy": "1f629", - "shortnames": [], - "category": "people" - }, - ":wedding:": { - "uc_base": "1f492", - "uc_output": "1f492", - "uc_match": "1f492", - "uc_greedy": "1f492", - "shortnames": [], - "category": "travel" - }, - ":whale2:": { - "uc_base": "1f40b", - "uc_output": "1f40b", - "uc_match": "1f40b", - "uc_greedy": "1f40b", - "shortnames": [], - "category": "nature" - }, - ":whale:": { - "uc_base": "1f433", - "uc_output": "1f433", - "uc_match": "1f433", - "uc_greedy": "1f433", - "shortnames": [], - "category": "nature" - }, - ":white_flower:": { - "uc_base": "1f4ae", - "uc_output": "1f4ae", - "uc_match": "1f4ae", - "uc_greedy": "1f4ae", - "shortnames": [], - "category": "symbols" - }, - ":white_square_button:": { - "uc_base": "1f533", - "uc_output": "1f533", - "uc_match": "1f533", - "uc_greedy": "1f533", - "shortnames": [], - "category": "symbols" - }, - ":white_sun_cloud:": { - "uc_base": "1f325", - "uc_output": "1f325", - "uc_match": "1f325-fe0f", - "uc_greedy": "1f325-fe0f", - "shortnames": [":white_sun_behind_cloud:"], - "category": "nature" - }, - ":white_sun_rain_cloud:": { - "uc_base": "1f326", - "uc_output": "1f326", - "uc_match": "1f326-fe0f", - "uc_greedy": "1f326-fe0f", - "shortnames": [":white_sun_behind_cloud_with_rain:"], - "category": "nature" - }, - ":white_sun_small_cloud:": { - "uc_base": "1f324", - "uc_output": "1f324", - "uc_match": "1f324-fe0f", - "uc_greedy": "1f324-fe0f", - "shortnames": [":white_sun_with_small_cloud:"], - "category": "nature" - }, - ":wilted_rose:": { - "uc_base": "1f940", - "uc_output": "1f940", - "uc_match": "1f940", - "uc_greedy": "1f940", - "shortnames": [":wilted_flower:"], - "category": "nature" - }, - ":wind_blowing_face:": { - "uc_base": "1f32c", - "uc_output": "1f32c", - "uc_match": "1f32c-fe0f", - "uc_greedy": "1f32c-fe0f", - "shortnames": [], - "category": "nature" - }, - ":wind_chime:": { - "uc_base": "1f390", - "uc_output": "1f390", - "uc_match": "1f390", - "uc_greedy": "1f390", - "shortnames": [], - "category": "objects" - }, - ":wine_glass:": { - "uc_base": "1f377", - "uc_output": "1f377", - "uc_match": "1f377", - "uc_greedy": "1f377", - "shortnames": [], - "category": "food" - }, - ":wink:": { - "uc_base": "1f609", - "uc_output": "1f609", - "uc_match": "1f609", - "uc_greedy": "1f609", - "shortnames": [], - "category": "people" - }, - ":wolf:": { - "uc_base": "1f43a", - "uc_output": "1f43a", - "uc_match": "1f43a", - "uc_greedy": "1f43a", - "shortnames": [], - "category": "nature" - }, - ":woman:": { - "uc_base": "1f469", - "uc_output": "1f469", - "uc_match": "1f469", - "uc_greedy": "1f469", - "shortnames": [], - "category": "people" - }, - ":woman_with_headscarf:": { - "uc_base": "1f9d5", - "uc_output": "1f9d5", - "uc_match": "1f9d5", - "uc_greedy": "1f9d5", - "shortnames": [], - "category": "people" - }, - ":womans_clothes:": { - "uc_base": "1f45a", - "uc_output": "1f45a", - "uc_match": "1f45a", - "uc_greedy": "1f45a", - "shortnames": [], - "category": "people" - }, - ":womans_hat:": { - "uc_base": "1f452", - "uc_output": "1f452", - "uc_match": "1f452", - "uc_greedy": "1f452", - "shortnames": [], - "category": "people" - }, - ":womens:": { - "uc_base": "1f6ba", - "uc_output": "1f6ba", - "uc_match": "1f6ba-fe0f", - "uc_greedy": "1f6ba-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":worried:": { - "uc_base": "1f61f", - "uc_output": "1f61f", - "uc_match": "1f61f", - "uc_greedy": "1f61f", - "shortnames": [], - "category": "people" - }, - ":wrench:": { - "uc_base": "1f527", - "uc_output": "1f527", - "uc_match": "1f527", - "uc_greedy": "1f527", - "shortnames": [], - "category": "objects" - }, - ":yellow_heart:": { - "uc_base": "1f49b", - "uc_output": "1f49b", - "uc_match": "1f49b", - "uc_greedy": "1f49b", - "shortnames": [], - "category": "symbols" - }, - ":yen:": { - "uc_base": "1f4b4", - "uc_output": "1f4b4", - "uc_match": "1f4b4", - "uc_greedy": "1f4b4", - "shortnames": [], - "category": "objects" - }, - ":yum:": { - "uc_base": "1f60b", - "uc_output": "1f60b", - "uc_match": "1f60b", - "uc_greedy": "1f60b", - "shortnames": [], - "category": "people" - }, - ":zebra:": { - "uc_base": "1f993", - "uc_output": "1f993", - "uc_match": "1f993", - "uc_greedy": "1f993", - "shortnames": [], - "category": "nature" - }, - ":zipper_mouth:": { - "uc_base": "1f910", - "uc_output": "1f910", - "uc_match": "1f910", - "uc_greedy": "1f910", - "shortnames": [":zipper_mouth_face:"], - "category": "people" - }, - ":zombie:": { - "uc_base": "1f9df", - "uc_output": "1f9df", - "uc_match": "1f9df", - "uc_greedy": "1f9df", - "shortnames": [], - "category": "people" - }, - ":zzz:": { - "uc_base": "1f4a4", - "uc_output": "1f4a4", - "uc_match": "1f4a4", - "uc_greedy": "1f4a4", - "shortnames": [], - "category": "symbols" - }, - ":airplane:": { - "uc_base": "2708", - "uc_output": "2708", - "uc_match": "2708-fe0f", - "uc_greedy": "2708-fe0f", - "shortnames": [], - "category": "travel" - }, - ":alarm_clock:": { - "uc_base": "23f0", - "uc_output": "23f0", - "uc_match": "23f0", - "uc_greedy": "23f0", - "shortnames": [], - "category": "objects" - }, - ":alembic:": { - "uc_base": "2697", - "uc_output": "2697", - "uc_match": "2697-fe0f", - "uc_greedy": "2697-fe0f", - "shortnames": [], - "category": "objects" - }, - ":anchor:": { - "uc_base": "2693", - "uc_output": "2693", - "uc_match": "2693-fe0f", - "uc_greedy": "2693-fe0f", - "shortnames": [], - "category": "travel" - }, - ":aquarius:": { - "uc_base": "2652", - "uc_output": "2652", - "uc_match": "2652-fe0f", - "uc_greedy": "2652-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":aries:": { - "uc_base": "2648", - "uc_output": "2648", - "uc_match": "2648-fe0f", - "uc_greedy": "2648-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":arrow_backward:": { - "uc_base": "25c0", - "uc_output": "25c0", - "uc_match": "25c0-fe0f", - "uc_greedy": "25c0", - "shortnames": [], - "category": "symbols" - }, - ":arrow_double_down:": { - "uc_base": "23ec", - "uc_output": "23ec", - "uc_match": "23ec", - "uc_greedy": "23ec", - "shortnames": [], - "category": "symbols" - }, - ":arrow_double_up:": { - "uc_base": "23eb", - "uc_output": "23eb", - "uc_match": "23eb", - "uc_greedy": "23eb", - "shortnames": [], - "category": "symbols" - }, - ":arrow_down:": { - "uc_base": "2b07", - "uc_output": "2b07", - "uc_match": "2b07-fe0f", - "uc_greedy": "2b07", - "shortnames": [], - "category": "symbols" - }, - ":arrow_forward:": { - "uc_base": "25b6", - "uc_output": "25b6", - "uc_match": "25b6-fe0f", - "uc_greedy": "25b6", - "shortnames": [], - "category": "symbols" - }, - ":arrow_heading_down:": { - "uc_base": "2935", - "uc_output": "2935", - "uc_match": "2935-fe0f", - "uc_greedy": "2935", - "shortnames": [], - "category": "symbols" - }, - ":arrow_heading_up:": { - "uc_base": "2934", - "uc_output": "2934", - "uc_match": "2934-fe0f", - "uc_greedy": "2934", - "shortnames": [], - "category": "symbols" - }, - ":arrow_left:": { - "uc_base": "2b05", - "uc_output": "2b05", - "uc_match": "2b05-fe0f", - "uc_greedy": "2b05", - "shortnames": [], - "category": "symbols" - }, - ":arrow_lower_left:": { - "uc_base": "2199", - "uc_output": "2199", - "uc_match": "2199-fe0f", - "uc_greedy": "2199", - "shortnames": [], - "category": "symbols" - }, - ":arrow_lower_right:": { - "uc_base": "2198", - "uc_output": "2198", - "uc_match": "2198-fe0f", - "uc_greedy": "2198", - "shortnames": [], - "category": "symbols" - }, - ":arrow_right:": { - "uc_base": "27a1", - "uc_output": "27a1", - "uc_match": "27a1-fe0f", - "uc_greedy": "27a1", - "shortnames": [], - "category": "symbols" - }, - ":arrow_right_hook:": { - "uc_base": "21aa", - "uc_output": "21aa", - "uc_match": "21aa-fe0f", - "uc_greedy": "21aa", - "shortnames": [], - "category": "symbols" - }, - ":arrow_up:": { - "uc_base": "2b06", - "uc_output": "2b06", - "uc_match": "2b06-fe0f", - "uc_greedy": "2b06", - "shortnames": [], - "category": "symbols" - }, - ":arrow_up_down:": { - "uc_base": "2195", - "uc_output": "2195", - "uc_match": "2195-fe0f", - "uc_greedy": "2195", - "shortnames": [], - "category": "symbols" - }, - ":arrow_upper_left:": { - "uc_base": "2196", - "uc_output": "2196", - "uc_match": "2196-fe0f", - "uc_greedy": "2196", - "shortnames": [], - "category": "symbols" - }, - ":arrow_upper_right:": { - "uc_base": "2197", - "uc_output": "2197", - "uc_match": "2197-fe0f", - "uc_greedy": "2197", - "shortnames": [], - "category": "symbols" - }, - ":atom:": { - "uc_base": "269b", - "uc_output": "269b", - "uc_match": "269b-fe0f", - "uc_greedy": "269b", - "shortnames": [":atom_symbol:"], - "category": "symbols" - }, - ":ballot_box_with_check:": { - "uc_base": "2611", - "uc_output": "2611", - "uc_match": "2611-fe0f", - "uc_greedy": "2611", - "shortnames": [], - "category": "symbols" - }, - ":bangbang:": { - "uc_base": "203c", - "uc_output": "203c", - "uc_match": "203c-fe0f", - "uc_greedy": "203c", - "shortnames": [], - "category": "symbols" - }, - ":baseball:": { - "uc_base": "26be", - "uc_output": "26be", - "uc_match": "26be-fe0f", - "uc_greedy": "26be-fe0f", - "shortnames": [], - "category": "activity" - }, - ":beach_umbrella:": { - "uc_base": "26f1", - "uc_output": "26f1", - "uc_match": "26f1-fe0f", - "uc_greedy": "26f1-fe0f", - "shortnames": [":umbrella_on_ground:"], - "category": "travel" - }, - ":biohazard:": { - "uc_base": "2623", - "uc_output": "2623", - "uc_match": "2623-fe0f", - "uc_greedy": "2623", - "shortnames": [":biohazard_sign:"], - "category": "symbols" - }, - ":black_circle:": { - "uc_base": "26ab", - "uc_output": "26ab", - "uc_match": "26ab-fe0f", - "uc_greedy": "26ab-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":black_large_square:": { - "uc_base": "2b1b", - "uc_output": "2b1b", - "uc_match": "2b1b-fe0f", - "uc_greedy": "2b1b-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":black_medium_small_square:": { - "uc_base": "25fe", - "uc_output": "25fe", - "uc_match": "25fe-fe0f", - "uc_greedy": "25fe-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":black_medium_square:": { - "uc_base": "25fc", - "uc_output": "25fc", - "uc_match": "25fc-fe0f", - "uc_greedy": "25fc", - "shortnames": [], - "category": "symbols" - }, - ":black_nib:": { - "uc_base": "2712", - "uc_output": "2712", - "uc_match": "2712-fe0f", - "uc_greedy": "2712-fe0f", - "shortnames": [], - "category": "objects" - }, - ":black_small_square:": { - "uc_base": "25aa", - "uc_output": "25aa", - "uc_match": "25aa-fe0f", - "uc_greedy": "25aa", - "shortnames": [], - "category": "symbols" - }, - ":cancer:": { - "uc_base": "264b", - "uc_output": "264b", - "uc_match": "264b-fe0f", - "uc_greedy": "264b-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":capricorn:": { - "uc_base": "2651", - "uc_output": "2651", - "uc_match": "2651-fe0f", - "uc_greedy": "2651-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":chains:": { - "uc_base": "26d3", - "uc_output": "26d3", - "uc_match": "26d3-fe0f", - "uc_greedy": "26d3-fe0f", - "shortnames": [], - "category": "objects" - }, - ":church:": { - "uc_base": "26ea", - "uc_output": "26ea", - "uc_match": "26ea-fe0f", - "uc_greedy": "26ea-fe0f", - "shortnames": [], - "category": "travel" - }, - ":cloud:": { - "uc_base": "2601", - "uc_output": "2601", - "uc_match": "2601-fe0f", - "uc_greedy": "2601-fe0f", - "shortnames": [], - "category": "nature" - }, - ":clubs:": { - "uc_base": "2663", - "uc_output": "2663", - "uc_match": "2663-fe0f", - "uc_greedy": "2663-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":coffee:": { - "uc_base": "2615", - "uc_output": "2615", - "uc_match": "2615-fe0f", - "uc_greedy": "2615-fe0f", - "shortnames": [], - "category": "food" - }, - ":coffin:": { - "uc_base": "26b0", - "uc_output": "26b0", - "uc_match": "26b0-fe0f", - "uc_greedy": "26b0-fe0f", - "shortnames": [], - "category": "objects" - }, - ":comet:": { - "uc_base": "2604", - "uc_output": "2604", - "uc_match": "2604-fe0f", - "uc_greedy": "2604-fe0f", - "shortnames": [], - "category": "nature" - }, - ":congratulations:": { - "uc_base": "3297", - "uc_output": "3297", - "uc_match": "3297-fe0f", - "uc_greedy": "3297-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":copyright:": { - "uc_base": "00a9", - "uc_output": "00a9", - "uc_match": "00a9-fe0f", - "uc_greedy": "00a9", - "shortnames": [], - "category": "symbols" - }, - ":cross:": { - "uc_base": "271d", - "uc_output": "271d", - "uc_match": "271d-fe0f", - "uc_greedy": "271d", - "shortnames": [":latin_cross:"], - "category": "symbols" - }, - ":crossed_swords:": { - "uc_base": "2694", - "uc_output": "2694", - "uc_match": "2694-fe0f", - "uc_greedy": "2694-fe0f", - "shortnames": [], - "category": "objects" - }, - ":curly_loop:": { - "uc_base": "27b0", - "uc_output": "27b0", - "uc_match": "27b0", - "uc_greedy": "27b0", - "shortnames": [], - "category": "symbols" - }, - ":diamonds:": { - "uc_base": "2666", - "uc_output": "2666", - "uc_match": "2666-fe0f", - "uc_greedy": "2666-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":eight_pointed_black_star:": { - "uc_base": "2734", - "uc_output": "2734", - "uc_match": "2734-fe0f", - "uc_greedy": "2734-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":eight_spoked_asterisk:": { - "uc_base": "2733", - "uc_output": "2733", - "uc_match": "2733-fe0f", - "uc_greedy": "2733-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":eject:": { - "uc_base": "23cf", - "uc_output": "23cf", - "uc_match": "23cf-fe0f", - "uc_greedy": "23cf", - "shortnames": [":eject_symbol:"], - "category": "symbols" - }, - ":envelope:": { - "uc_base": "2709", - "uc_output": "2709", - "uc_match": "2709-fe0f", - "uc_greedy": "2709-fe0f", - "shortnames": [], - "category": "objects" - }, - ":exclamation:": { - "uc_base": "2757", - "uc_output": "2757", - "uc_match": "2757-fe0f", - "uc_greedy": "2757-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":fast_forward:": { - "uc_base": "23e9", - "uc_output": "23e9", - "uc_match": "23e9-fe0f", - "uc_greedy": "23e9-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":female_sign:": { - "uc_base": "2640", - "uc_output": "2640", - "uc_match": "2640-fe0f", - "uc_greedy": "2640", - "shortnames": [], - "category": "symbols" - }, - ":ferry:": { - "uc_base": "26f4", - "uc_output": "26f4", - "uc_match": "26f4-fe0f", - "uc_greedy": "26f4-fe0f", - "shortnames": [], - "category": "travel" - }, - ":fist:": { - "uc_base": "270a", - "uc_output": "270a", - "uc_match": "270a", - "uc_greedy": "270a", - "shortnames": [], - "category": "people" - }, - ":fleur-de-lis:": { - "uc_base": "269c", - "uc_output": "269c", - "uc_match": "269c-fe0f", - "uc_greedy": "269c-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":fountain:": { - "uc_base": "26f2", - "uc_output": "26f2", - "uc_match": "26f2-fe0f", - "uc_greedy": "26f2-fe0f", - "shortnames": [], - "category": "travel" - }, - ":frowning2:": { - "uc_base": "2639", - "uc_output": "2639", - "uc_match": "2639-fe0f", - "uc_greedy": "2639-fe0f", - "shortnames": [":white_frowning_face:"], - "category": "people" - }, - ":fuelpump:": { - "uc_base": "26fd", - "uc_output": "26fd", - "uc_match": "26fd-fe0f", - "uc_greedy": "26fd-fe0f", - "shortnames": [], - "category": "travel" - }, - ":gear:": { - "uc_base": "2699", - "uc_output": "2699", - "uc_match": "2699-fe0f", - "uc_greedy": "2699-fe0f", - "shortnames": [], - "category": "objects" - }, - ":gemini:": { - "uc_base": "264a", - "uc_output": "264a", - "uc_match": "264a-fe0f", - "uc_greedy": "264a-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":golf:": { - "uc_base": "26f3", - "uc_output": "26f3", - "uc_match": "26f3-fe0f", - "uc_greedy": "26f3-fe0f", - "shortnames": [], - "category": "activity" - }, - ":grey_exclamation:": { - "uc_base": "2755", - "uc_output": "2755", - "uc_match": "2755", - "uc_greedy": "2755", - "shortnames": [], - "category": "symbols" - }, - ":grey_question:": { - "uc_base": "2754", - "uc_output": "2754", - "uc_match": "2754", - "uc_greedy": "2754", - "shortnames": [], - "category": "symbols" - }, - ":hammer_pick:": { - "uc_base": "2692", - "uc_output": "2692", - "uc_match": "2692-fe0f", - "uc_greedy": "2692-fe0f", - "shortnames": [":hammer_and_pick:"], - "category": "objects" - }, - ":heart:": { - "uc_base": "2764", - "uc_output": "2764", - "uc_match": "2764-fe0f", - "uc_greedy": "2764-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":heart_exclamation:": { - "uc_base": "2763", - "uc_output": "2763", - "uc_match": "2763-fe0f", - "uc_greedy": "2763-fe0f", - "shortnames": [":heavy_heart_exclamation_mark_ornament:"], - "category": "symbols" - }, - ":hearts:": { - "uc_base": "2665", - "uc_output": "2665", - "uc_match": "2665-fe0f", - "uc_greedy": "2665-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":heavy_check_mark:": { - "uc_base": "2714", - "uc_output": "2714", - "uc_match": "2714-fe0f", - "uc_greedy": "2714", - "shortnames": [], - "category": "symbols" - }, - ":heavy_division_sign:": { - "uc_base": "2797", - "uc_output": "2797", - "uc_match": "2797", - "uc_greedy": "2797", - "shortnames": [], - "category": "symbols" - }, - ":heavy_minus_sign:": { - "uc_base": "2796", - "uc_output": "2796", - "uc_match": "2796", - "uc_greedy": "2796", - "shortnames": [], - "category": "symbols" - }, - ":heavy_multiplication_x:": { - "uc_base": "2716", - "uc_output": "2716", - "uc_match": "2716-fe0f", - "uc_greedy": "2716", - "shortnames": [], - "category": "symbols" - }, - ":heavy_plus_sign:": { - "uc_base": "2795", - "uc_output": "2795", - "uc_match": "2795", - "uc_greedy": "2795", - "shortnames": [], - "category": "symbols" - }, - ":helmet_with_cross:": { - "uc_base": "26d1", - "uc_output": "26d1", - "uc_match": "26d1-fe0f", - "uc_greedy": "26d1-fe0f", - "shortnames": [":helmet_with_white_cross:"], - "category": "people" - }, - ":hotsprings:": { - "uc_base": "2668", - "uc_output": "2668", - "uc_match": "2668-fe0f", - "uc_greedy": "2668-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":hourglass:": { - "uc_base": "231b", - "uc_output": "231b", - "uc_match": "231b-fe0f", - "uc_greedy": "231b-fe0f", - "shortnames": [], - "category": "objects" - }, - ":hourglass_flowing_sand:": { - "uc_base": "23f3", - "uc_output": "23f3", - "uc_match": "23f3-fe0f", - "uc_greedy": "23f3-fe0f", - "shortnames": [], - "category": "objects" - }, - ":ice_skate:": { - "uc_base": "26f8", - "uc_output": "26f8", - "uc_match": "26f8-fe0f", - "uc_greedy": "26f8-fe0f", - "shortnames": [], - "category": "activity" - }, - ":information_source:": { - "uc_base": "2139", - "uc_output": "2139", - "uc_match": "2139-fe0f", - "uc_greedy": "2139", - "shortnames": [], - "category": "symbols" - }, - ":interrobang:": { - "uc_base": "2049", - "uc_output": "2049", - "uc_match": "2049-fe0f", - "uc_greedy": "2049", - "shortnames": [], - "category": "symbols" - }, - ":keyboard:": { - "uc_base": "2328", - "uc_output": "2328", - "uc_match": "2328-fe0f", - "uc_greedy": "2328-fe0f", - "shortnames": [], - "category": "objects" - }, - ":left_right_arrow:": { - "uc_base": "2194", - "uc_output": "2194", - "uc_match": "2194-fe0f", - "uc_greedy": "2194", - "shortnames": [], - "category": "symbols" - }, - ":leftwards_arrow_with_hook:": { - "uc_base": "21a9", - "uc_output": "21a9", - "uc_match": "21a9-fe0f", - "uc_greedy": "21a9", - "shortnames": [], - "category": "symbols" - }, - ":leo:": { - "uc_base": "264c", - "uc_output": "264c", - "uc_match": "264c-fe0f", - "uc_greedy": "264c-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":libra:": { - "uc_base": "264e", - "uc_output": "264e", - "uc_match": "264e-fe0f", - "uc_greedy": "264e-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":loop:": { - "uc_base": "27bf", - "uc_output": "27bf", - "uc_match": "27bf", - "uc_greedy": "27bf", - "shortnames": [], - "category": "symbols" - }, - ":m:": { - "uc_base": "24c2", - "uc_output": "24c2", - "uc_match": "24c2-fe0f", - "uc_greedy": "24c2-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":male_sign:": { - "uc_base": "2642", - "uc_output": "2642", - "uc_match": "2642-fe0f", - "uc_greedy": "2642", - "shortnames": [], - "category": "symbols" - }, - ":medical_symbol:": { - "uc_base": "2695", - "uc_output": "2695", - "uc_match": "2695-fe0f", - "uc_greedy": "2695", - "shortnames": [], - "category": "symbols" - }, - ":mountain:": { - "uc_base": "26f0", - "uc_output": "26f0", - "uc_match": "26f0-fe0f", - "uc_greedy": "26f0-fe0f", - "shortnames": [], - "category": "travel" - }, - ":negative_squared_cross_mark:": { - "uc_base": "274e", - "uc_output": "274e", - "uc_match": "274e", - "uc_greedy": "274e", - "shortnames": [], - "category": "symbols" - }, - ":no_entry:": { - "uc_base": "26d4", - "uc_output": "26d4", - "uc_match": "26d4-fe0f", - "uc_greedy": "26d4-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":o:": { - "uc_base": "2b55", - "uc_output": "2b55", - "uc_match": "2b55-fe0f", - "uc_greedy": "2b55-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":ophiuchus:": { - "uc_base": "26ce", - "uc_output": "26ce", - "uc_match": "26ce", - "uc_greedy": "26ce", - "shortnames": [], - "category": "symbols" - }, - ":orthodox_cross:": { - "uc_base": "2626", - "uc_output": "2626", - "uc_match": "2626-fe0f", - "uc_greedy": "2626", - "shortnames": [], - "category": "symbols" - }, - ":part_alternation_mark:": { - "uc_base": "303d", - "uc_output": "303d", - "uc_match": "303d-fe0f", - "uc_greedy": "303d-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":partly_sunny:": { - "uc_base": "26c5", - "uc_output": "26c5", - "uc_match": "26c5-fe0f", - "uc_greedy": "26c5-fe0f", - "shortnames": [], - "category": "nature" - }, - ":pause_button:": { - "uc_base": "23f8", - "uc_output": "23f8", - "uc_match": "23f8-fe0f", - "uc_greedy": "23f8", - "shortnames": [":double_vertical_bar:"], - "category": "symbols" - }, - ":peace:": { - "uc_base": "262e", - "uc_output": "262e", - "uc_match": "262e-fe0f", - "uc_greedy": "262e", - "shortnames": [":peace_symbol:"], - "category": "symbols" - }, - ":pencil2:": { - "uc_base": "270f", - "uc_output": "270f", - "uc_match": "270f-fe0f", - "uc_greedy": "270f-fe0f", - "shortnames": [], - "category": "objects" - }, - ":person_bouncing_ball:": { - "uc_base": "26f9", - "uc_output": "26f9", - "uc_match": "26f9-fe0f", - "uc_greedy": "26f9-fe0f", - "shortnames": [":basketball_player:", ":person_with_ball:"], - "category": "activity" - }, - ":pick:": { - "uc_base": "26cf", - "uc_output": "26cf", - "uc_match": "26cf-fe0f", - "uc_greedy": "26cf-fe0f", - "shortnames": [], - "category": "objects" - }, - ":pisces:": { - "uc_base": "2653", - "uc_output": "2653", - "uc_match": "2653-fe0f", - "uc_greedy": "2653-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":play_pause:": { - "uc_base": "23ef", - "uc_output": "23ef", - "uc_match": "23ef-fe0f", - "uc_greedy": "23ef", - "shortnames": [], - "category": "symbols" - }, - ":point_up:": { - "uc_base": "261d", - "uc_output": "261d", - "uc_match": "261d-fe0f", - "uc_greedy": "261d-fe0f", - "shortnames": [], - "category": "people" - }, - ":question:": { - "uc_base": "2753", - "uc_output": "2753", - "uc_match": "2753-fe0f", - "uc_greedy": "2753-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":radioactive:": { - "uc_base": "2622", - "uc_output": "2622", - "uc_match": "2622-fe0f", - "uc_greedy": "2622", - "shortnames": [":radioactive_sign:"], - "category": "symbols" - }, - ":raised_hand:": { - "uc_base": "270b", - "uc_output": "270b", - "uc_match": "270b", - "uc_greedy": "270b", - "shortnames": [], - "category": "people" - }, - ":record_button:": { - "uc_base": "23fa", - "uc_output": "23fa", - "uc_match": "23fa-fe0f", - "uc_greedy": "23fa", - "shortnames": [], - "category": "symbols" - }, - ":recycle:": { - "uc_base": "267b", - "uc_output": "267b", - "uc_match": "267b-fe0f", - "uc_greedy": "267b-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":registered:": { - "uc_base": "00ae", - "uc_output": "00ae", - "uc_match": "00ae-fe0f", - "uc_greedy": "00ae", - "shortnames": [], - "category": "symbols" - }, - ":relaxed:": { - "uc_base": "263a", - "uc_output": "263a", - "uc_match": "263a-fe0f", - "uc_greedy": "263a-fe0f", - "shortnames": [], - "category": "people" - }, - ":rewind:": { - "uc_base": "23ea", - "uc_output": "23ea", - "uc_match": "23ea-fe0f", - "uc_greedy": "23ea-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":sagittarius:": { - "uc_base": "2650", - "uc_output": "2650", - "uc_match": "2650-fe0f", - "uc_greedy": "2650-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":sailboat:": { - "uc_base": "26f5", - "uc_output": "26f5", - "uc_match": "26f5-fe0f", - "uc_greedy": "26f5-fe0f", - "shortnames": [], - "category": "travel" - }, - ":scales:": { - "uc_base": "2696", - "uc_output": "2696", - "uc_match": "2696-fe0f", - "uc_greedy": "2696-fe0f", - "shortnames": [], - "category": "objects" - }, - ":scissors:": { - "uc_base": "2702", - "uc_output": "2702", - "uc_match": "2702-fe0f", - "uc_greedy": "2702-fe0f", - "shortnames": [], - "category": "objects" - }, - ":scorpius:": { - "uc_base": "264f", - "uc_output": "264f", - "uc_match": "264f-fe0f", - "uc_greedy": "264f-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":secret:": { - "uc_base": "3299", - "uc_output": "3299", - "uc_match": "3299-fe0f", - "uc_greedy": "3299-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":shamrock:": { - "uc_base": "2618", - "uc_output": "2618", - "uc_match": "2618-fe0f", - "uc_greedy": "2618-fe0f", - "shortnames": [], - "category": "nature" - }, - ":shinto_shrine:": { - "uc_base": "26e9", - "uc_output": "26e9", - "uc_match": "26e9-fe0f", - "uc_greedy": "26e9-fe0f", - "shortnames": [], - "category": "travel" - }, - ":skier:": { - "uc_base": "26f7", - "uc_output": "26f7", - "uc_match": "26f7-fe0f", - "uc_greedy": "26f7-fe0f", - "shortnames": [], - "category": "activity" - }, - ":skull_crossbones:": { - "uc_base": "2620", - "uc_output": "2620", - "uc_match": "2620-fe0f", - "uc_greedy": "2620-fe0f", - "shortnames": [":skull_and_crossbones:"], - "category": "people" - }, - ":snowflake:": { - "uc_base": "2744", - "uc_output": "2744", - "uc_match": "2744-fe0f", - "uc_greedy": "2744-fe0f", - "shortnames": [], - "category": "nature" - }, - ":snowman2:": { - "uc_base": "2603", - "uc_output": "2603", - "uc_match": "2603-fe0f", - "uc_greedy": "2603-fe0f", - "shortnames": [], - "category": "nature" - }, - ":snowman:": { - "uc_base": "26c4", - "uc_output": "26c4", - "uc_match": "26c4-fe0f", - "uc_greedy": "26c4-fe0f", - "shortnames": [], - "category": "nature" - }, - ":soccer:": { - "uc_base": "26bd", - "uc_output": "26bd", - "uc_match": "26bd-fe0f", - "uc_greedy": "26bd-fe0f", - "shortnames": [], - "category": "activity" - }, - ":spades:": { - "uc_base": "2660", - "uc_output": "2660", - "uc_match": "2660-fe0f", - "uc_greedy": "2660-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":sparkle:": { - "uc_base": "2747", - "uc_output": "2747", - "uc_match": "2747-fe0f", - "uc_greedy": "2747-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":sparkles:": { - "uc_base": "2728", - "uc_output": "2728", - "uc_match": "2728", - "uc_greedy": "2728", - "shortnames": [], - "category": "nature" - }, - ":star:": { - "uc_base": "2b50", - "uc_output": "2b50", - "uc_match": "2b50-fe0f", - "uc_greedy": "2b50-fe0f", - "shortnames": [], - "category": "nature" - }, - ":star_and_crescent:": { - "uc_base": "262a", - "uc_output": "262a", - "uc_match": "262a-fe0f", - "uc_greedy": "262a", - "shortnames": [], - "category": "symbols" - }, - ":star_of_david:": { - "uc_base": "2721", - "uc_output": "2721", - "uc_match": "2721-fe0f", - "uc_greedy": "2721", - "shortnames": [], - "category": "symbols" - }, - ":stop_button:": { - "uc_base": "23f9", - "uc_output": "23f9", - "uc_match": "23f9-fe0f", - "uc_greedy": "23f9", - "shortnames": [], - "category": "symbols" - }, - ":stopwatch:": { - "uc_base": "23f1", - "uc_output": "23f1", - "uc_match": "23f1-fe0f", - "uc_greedy": "23f1-fe0f", - "shortnames": [], - "category": "objects" - }, - ":sunny:": { - "uc_base": "2600", - "uc_output": "2600", - "uc_match": "2600-fe0f", - "uc_greedy": "2600-fe0f", - "shortnames": [], - "category": "nature" - }, - ":taurus:": { - "uc_base": "2649", - "uc_output": "2649", - "uc_match": "2649-fe0f", - "uc_greedy": "2649-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":telephone:": { - "uc_base": "260e", - "uc_output": "260e", - "uc_match": "260e-fe0f", - "uc_greedy": "260e-fe0f", - "shortnames": [], - "category": "objects" - }, - ":tent:": { - "uc_base": "26fa", - "uc_output": "26fa", - "uc_match": "26fa-fe0f", - "uc_greedy": "26fa-fe0f", - "shortnames": [], - "category": "travel" - }, - ":thunder_cloud_rain:": { - "uc_base": "26c8", - "uc_output": "26c8", - "uc_match": "26c8-fe0f", - "uc_greedy": "26c8-fe0f", - "shortnames": [":thunder_cloud_and_rain:"], - "category": "nature" - }, - ":timer:": { - "uc_base": "23f2", - "uc_output": "23f2", - "uc_match": "23f2-fe0f", - "uc_greedy": "23f2-fe0f", - "shortnames": [":timer_clock:"], - "category": "objects" - }, - ":tm:": { - "uc_base": "2122", - "uc_output": "2122", - "uc_match": "2122-fe0f", - "uc_greedy": "2122", - "shortnames": [], - "category": "symbols" - }, - ":track_next:": { - "uc_base": "23ed", - "uc_output": "23ed", - "uc_match": "23ed-fe0f", - "uc_greedy": "23ed", - "shortnames": [":next_track:"], - "category": "symbols" - }, - ":track_previous:": { - "uc_base": "23ee", - "uc_output": "23ee", - "uc_match": "23ee-fe0f", - "uc_greedy": "23ee", - "shortnames": [":previous_track:"], - "category": "symbols" - }, - ":umbrella2:": { - "uc_base": "2602", - "uc_output": "2602", - "uc_match": "2602-fe0f", - "uc_greedy": "2602-fe0f", - "shortnames": [], - "category": "nature" - }, - ":umbrella:": { - "uc_base": "2614", - "uc_output": "2614", - "uc_match": "2614-fe0f", - "uc_greedy": "2614-fe0f", - "shortnames": [], - "category": "nature" - }, - ":urn:": { - "uc_base": "26b1", - "uc_output": "26b1", - "uc_match": "26b1-fe0f", - "uc_greedy": "26b1-fe0f", - "shortnames": [":funeral_urn:"], - "category": "objects" - }, - ":v:": { - "uc_base": "270c", - "uc_output": "270c", - "uc_match": "270c-fe0f", - "uc_greedy": "270c-fe0f", - "shortnames": [], - "category": "people" - }, - ":virgo:": { - "uc_base": "264d", - "uc_output": "264d", - "uc_match": "264d-fe0f", - "uc_greedy": "264d-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":warning:": { - "uc_base": "26a0", - "uc_output": "26a0", - "uc_match": "26a0-fe0f", - "uc_greedy": "26a0", - "shortnames": [], - "category": "symbols" - }, - ":watch:": { - "uc_base": "231a", - "uc_output": "231a", - "uc_match": "231a-fe0f", - "uc_greedy": "231a-fe0f", - "shortnames": [], - "category": "objects" - }, - ":wavy_dash:": { - "uc_base": "3030", - "uc_output": "3030", - "uc_match": "3030-fe0f", - "uc_greedy": "3030", - "shortnames": [], - "category": "symbols" - }, - ":wheel_of_dharma:": { - "uc_base": "2638", - "uc_output": "2638", - "uc_match": "2638-fe0f", - "uc_greedy": "2638", - "shortnames": [], - "category": "symbols" - }, - ":wheelchair:": { - "uc_base": "267f", - "uc_output": "267f", - "uc_match": "267f-fe0f", - "uc_greedy": "267f-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":white_check_mark:": { - "uc_base": "2705", - "uc_output": "2705", - "uc_match": "2705", - "uc_greedy": "2705", - "shortnames": [], - "category": "symbols" - }, - ":white_circle:": { - "uc_base": "26aa", - "uc_output": "26aa", - "uc_match": "26aa-fe0f", - "uc_greedy": "26aa-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":white_large_square:": { - "uc_base": "2b1c", - "uc_output": "2b1c", - "uc_match": "2b1c-fe0f", - "uc_greedy": "2b1c-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":white_medium_small_square:": { - "uc_base": "25fd", - "uc_output": "25fd", - "uc_match": "25fd-fe0f", - "uc_greedy": "25fd-fe0f", - "shortnames": [], - "category": "symbols" - }, - ":white_medium_square:": { - "uc_base": "25fb", - "uc_output": "25fb", - "uc_match": "25fb-fe0f", - "uc_greedy": "25fb", - "shortnames": [], - "category": "symbols" - }, - ":white_small_square:": { - "uc_base": "25ab", - "uc_output": "25ab", - "uc_match": "25ab-fe0f", - "uc_greedy": "25ab", - "shortnames": [], - "category": "symbols" - }, - ":writing_hand:": { - "uc_base": "270d", - "uc_output": "270d", - "uc_match": "270d-fe0f", - "uc_greedy": "270d-fe0f", - "shortnames": [], - "category": "people" - }, - ":x:": { - "uc_base": "274c", - "uc_output": "274c", - "uc_match": "274c", - "uc_greedy": "274c", - "shortnames": [], - "category": "symbols" - }, - ":yin_yang:": { - "uc_base": "262f", - "uc_output": "262f", - "uc_match": "262f-fe0f", - "uc_greedy": "262f", - "shortnames": [], - "category": "symbols" - }, - ":zap:": { - "uc_base": "26a1", - "uc_output": "26a1", - "uc_match": "26a1-fe0f", - "uc_greedy": "26a1-fe0f", - "shortnames": [], - "category": "nature" - } - }; - const ascii_list = { - '*\\0/*': '1f646', - '*\\O/*': '1f646', - '-___-': '1f611', - ':\'-)': '1f602', - '\':-)': '1f605', - '\':-D': '1f605', - '>:-)': '1f606', - '\':-(': '1f613', - '>:-(': '1f620', - ':\'-(': '1f622', - 'O:-)': '1f607', - '0:-3': '1f607', - '0:-)': '1f607', - '0;^)': '1f607', - 'O;-)': '1f607', - '0;-)': '1f607', - 'O:-3': '1f607', - '-__-': '1f611', - ':-Þ': '1f61b', - ':)': '1f606', - '>;)': '1f606', - '>=)': '1f606', - ';-)': '1f609', - '*-)': '1f609', - ';-]': '1f609', - ';^)': '1f609', - '\':(': '1f613', - '\'=(': '1f613', - ':-*': '1f618', - ':^*': '1f618', - '>:P': '1f61c', - 'X-P': '1f61c', - '>:[': '1f61e', - ':-(': '1f61e', - ':-[': '1f61e', - '>:(': '1f620', - ':\'(': '1f622', - ';-(': '1f622', - '>.<': '1f623', - '#-)': '1f635', - '%-)': '1f635', - 'X-)': '1f635', - '\\0/': '1f646', - '\\O/': '1f646', - '0:3': '1f607', - '0:)': '1f607', - 'O:)': '1f607', - 'O=)': '1f607', - 'O:3': '1f607', - 'B-)': '1f60e', - '8-)': '1f60e', - 'B-D': '1f60e', - '8-D': '1f60e', - '-_-': '1f611', - '>:\\': '1f615', - '>:/': '1f615', - ':-/': '1f615', - ':-.': '1f615', - ':-P': '1f61b', - ':Þ': '1f61b', - ':-b': '1f61b', - ':-O': '1f62e', - 'O_O': '1f62e', - '>:O': '1f62e', - ':-X': '1f636', - ':-#': '1f636', - ':-)': '1f642', - '(y)': '1f44d', - '<3': '2764', - ':D': '1f603', - '=D': '1f603', - ';)': '1f609', - '*)': '1f609', - ';]': '1f609', - ';D': '1f609', - ':*': '1f618', - '=*': '1f618', - ':(': '1f61e', - ':[': '1f61e', - '=(': '1f61e', - ':@': '1f620', - ';(': '1f622', - 'D:': '1f628', - ':$': '1f633', - '=$': '1f633', - '#)': '1f635', - '%)': '1f635', - 'X)': '1f635', - 'B)': '1f60e', - '8)': '1f60e', - ':/': '1f615', - ':\\': '1f615', - '=/': '1f615', - '=\\': '1f615', - ':L': '1f615', - '=L': '1f615', - ':P': '1f61b', - '=P': '1f61b', - ':b': '1f61b', - ':O': '1f62e', - ':X': '1f636', - ':#': '1f636', - '=X': '1f636', - '=#': '1f636', - ':)': '1f642', - '=]': '1f642', - '=)': '1f642', - ':]': '1f642' - }; - let shortnames = []; - - for (var emoji in emoji_list) { - if (!Object.prototype.hasOwnProperty.call(emoji_list, emoji) || emoji === '') continue; - shortnames.push(emoji.replace(/[+]/g, "\\$&")); - - for (var i = 0; i < emoji_list[emoji].shortnames.length; i++) { - shortnames.push(emoji_list[emoji].shortnames[i].replace(/[+]/g, "\\$&")); - } - } - - shortnames = shortnames.join('|'); - const SHORTNAMES_REGEX = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(" + shortnames + ")", "gi"); - const ASCII_REGEX = '(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:\'\\-\\)|\'\\:\\-\\)|\'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|\'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:\'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:\'\\)|\\:\\-D|\'\\:\\)|\'\\=\\)|\'\\:D|\'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|\'\\:\\(|\'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:\'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\:D|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\])'; - const ASCII_REPLACE_REGEX = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|((\\s|^)" + ASCII_REGEX + "(?=\\s|$|[!,.?]))", "gi"); - - function convert(unicode) { - /* For converting unicode code points and code pairs - * to their respective characters - */ - if (unicode.indexOf("-") > -1) { - const parts = [], - s = unicode.split('-'); - - for (let i = 0; i < s.length; i++) { - let part = parseInt(s[i], 16); - - if (part >= 0x10000 && part <= 0x10FFFF) { - const hi = Math.floor((part - 0x10000) / 0x400) + 0xD800; - const lo = (part - 0x10000) % 0x400 + 0xDC00; - part = String.fromCharCode(hi) + String.fromCharCode(lo); - } else { - part = String.fromCharCode(part); - } - - parts.push(part); - } - - return parts.join(''); - } - - return twemoji.default.convert.fromCodePoint(unicode); - } - - u.shortnameToUnicode = function (str) { - /* will output unicode from shortname - * useful for sending emojis back to mobile devices - */ - // Replace regular shortnames first - str = str.replace(SHORTNAMES_REGEX, shortname => { - if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emoji_list)) { - // if the shortname doesnt exist just return the entire matchhju - return shortname; - } - - const unicode = emoji_list[shortname].uc_output.toUpperCase(); - return convert(unicode); - }); // Also replace ASCII smileys - - str = str.replace(ASCII_REPLACE_REGEX, (entire, m1, m2, m3) => { - if (typeof m3 === 'undefined' || m3 === '' || !(u.unescapeHTML(m3) in ascii_list)) { - // if the ascii doesnt exist just return the entire match - return entire; - } - - m3 = u.unescapeHTML(m3); - const unicode = ascii_list[m3].toUpperCase(); - return m2 + convert(unicode); - }); - return str; - }; - - u.addEmoji = function (_converse, text) { - if (_converse.use_system_emojis) { - return u.shortnameToUnicode(text); - } else { - return twemoji.default.parse(text); - } - }; - - u.getEmojisByCategory = function (_converse) { - /* Return a dict of emojis with the categories as keys and - * lists of emojis in that category as values. - */ - if (_.isUndefined(_converse.emojis_by_category)) { - const emojis = _.values(_.mapValues(emoji_list, function (value, key, o) { - value._shortname = key; - return value; - })); - - const tones = [':tone1:', ':tone2:', ':tone3:', ':tone4:', ':tone5:']; - const excluded = [':kiss_ww:', ':kiss_mm:', ':kiss_woman_man:']; - const excluded_substrings = [':woman', ':man', ':women_', ':men_', '_man_', '_woman_', '_woman:', '_man:']; - const excluded_categories = ['modifier', 'regional']; - - const categories = _.difference(_.uniq(_.map(emojis, _.partial(_.get, _, 'category'))), excluded_categories); - - const emojis_by_category = {}; - - _.forEach(categories, cat => { - let list = _.sortBy(_.filter(emojis, ['category', cat]), ['uc_base']); - - list = _.filter(list, item => !_.includes(_.concat(tones, excluded), item._shortname) && !_.some(excluded_substrings, _.partial(_.includes, item._shortname))); - - if (cat === 'people') { - const idx = _.findIndex(list, ['uc_base', '1f600']); - - list = _.union(_.slice(list, idx), _.slice(list, 0, idx + 1)); - } else if (cat === 'activity') { - list = _.union(_.slice(list, 27 - 1), _.slice(list, 0, 27)); - } else if (cat === 'objects') { - list = _.union(_.slice(list, 24 - 1), _.slice(list, 0, 24)); - } else if (cat === 'travel') { - list = _.union(_.slice(list, 17 - 1), _.slice(list, 0, 17)); - } else if (cat === 'symbols') { - list = _.union(_.slice(list, 60 - 1), _.slice(list, 0, 60)); - } - - emojis_by_category[cat] = list; - }); - - _converse.emojis_by_category = emojis_by_category; - } - - return _converse.emojis_by_category; - }; - - u.getTonedEmojis = function (_converse) { - _converse.toned_emojis = _.uniq(_.map(_.filter(u.getEmojisByCategory(_converse).people, person => _.includes(person._shortname, '_tone')), person => person._shortname.replace(/_tone[1-5]/, ''))); - return _converse.toned_emojis; - }; - - u.getEmojiRenderer = function (_converse) { - return _converse.use_system_emojis ? u.shortnameToUnicode : _.flow(u.shortnameToUnicode, twemoji.default.parse); - }; - - return u; -}); - -/***/ }), - -/***/ "./src/headless/utils/form.js": -/*!************************************!*\ - !*** ./src/headless/utils/form.js ***! - \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"); +/* harmony import */ var backbone__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(backbone__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! es6-promise/dist/es6-promise.auto */ "./node_modules/es6-promise/dist/es6-promise.auto.js"); +/* harmony import */ var es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var strophe_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! strophe.js */ "./node_modules/strophe.js/dist/strophe.js"); +/* harmony import */ var strophe_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(strophe_js__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../lodash.noconflict */ "./src/headless/lodash.noconflict.js"); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(sizzle__WEBPACK_IMPORTED_MODULE_4__); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // This is the utilities module. @@ -100019,41 +78323,22009 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ // Licensed under the Mozilla Public License (MPLv2) // -/*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ../lodash.noconflict */ "./src/headless/lodash.noconflict.js"), __webpack_require__(/*! ./core */ "./src/headless/utils/core.js"), __webpack_require__(/*! ../templates/field.html */ "./src/headless/templates/field.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (_, u, tpl_field) { - "use strict"; +/*global escape, Uint8Array */ - u.webForm2xForm = function (field) { - /* Takes an HTML DOM and turns it into an XForm field. - * - * Parameters: - * (DOMElement) field - the field to convert - */ - let value; - if (field.getAttribute('type') === 'checkbox') { - value = field.checked && 1 || 0; - } else if (field.tagName == "TEXTAREA") { - value = _.filter(field.value.split('\n'), _.trim); - } else if (field.tagName == "SELECT") { - value = u.getSelectValues(field); + + + +const u = {}; + +u.getLongestSubstring = function (string, candidates) { + function reducer(accumulator, current_value) { + if (string.startsWith(current_value)) { + if (current_value.length > accumulator.length) { + return current_value; + } else { + return accumulator; + } } else { - value = field.value; + return accumulator; + } + } + + return candidates.reduce(reducer, ''); +}; + +u.prefixMentions = function (message) { + /* Given a message object, return its text with @ chars + * inserted before the mentioned nicknames. + */ + let text = message.get('message'); + (message.get('references') || []).sort((a, b) => b.begin - a.begin).forEach(ref => { + text = `${text.slice(0, ref.begin)}@${text.slice(ref.begin)}`; + }); + return text; +}; + +u.isValidJID = function (jid) { + return _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.compact(jid.split('@')).length === 2 && !jid.startsWith('@') && !jid.endsWith('@'); +}; + +u.isValidMUCJID = function (jid) { + return !jid.startsWith('@') && !jid.endsWith('@'); +}; + +u.isSameBareJID = function (jid1, jid2) { + return strophe_js__WEBPACK_IMPORTED_MODULE_2__["Strophe"].getBareJidFromJid(jid1).toLowerCase() === strophe_js__WEBPACK_IMPORTED_MODULE_2__["Strophe"].getBareJidFromJid(jid2).toLowerCase(); +}; + +u.getMostRecentMessage = function (model) { + const messages = model.messages.filter('message'); + return messages[messages.length - 1]; +}; + +u.isNewMessage = function (message) { + /* Given a stanza, determine whether it's a new + * message, i.e. not a MAM archived one. + */ + if (message instanceof Element) { + return !(sizzle__WEBPACK_IMPORTED_MODULE_4___default()(`result[xmlns="${strophe_js__WEBPACK_IMPORTED_MODULE_2__["Strophe"].NS.MAM}"]`, message).length && sizzle__WEBPACK_IMPORTED_MODULE_4___default()(`delay[xmlns="${strophe_js__WEBPACK_IMPORTED_MODULE_2__["Strophe"].NS.DELAY}"]`, message).length); + } else { + return !(message.get('is_delayed') && message.get('is_archived')); + } +}; + +u.isOnlyChatStateNotification = function (attrs) { + if (attrs instanceof backbone__WEBPACK_IMPORTED_MODULE_0___default.a.Model) { + attrs = attrs.attributes; + } + + return attrs['chat_state'] && !attrs['oob_url'] && !attrs['file'] && !(attrs['is_encrypted'] && attrs['plaintext']) && !attrs['message']; +}; + +u.isHeadlineMessage = function (_converse, message) { + var from_jid = message.getAttribute('from'); + + if (message.getAttribute('type') === 'headline') { + return true; + } + + const chatbox = _converse.chatboxes.get(strophe_js__WEBPACK_IMPORTED_MODULE_2__["Strophe"].getBareJidFromJid(from_jid)); + + if (chatbox && chatbox.get('type') === 'chatroom') { + return false; + } + + if (message.getAttribute('type') !== 'error' && !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isNil(from_jid) && !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.includes(from_jid, '@')) { + // Some servers (I'm looking at you Prosody) don't set the message + // type to "headline" when sending server messages. For now we + // check if an @ signal is included, and if not, we assume it's + // a headline message. + return true; + } + + return false; +}; + +u.merge = function merge(first, second) { + /* Merge the second object into the first one. + */ + for (var k in second) { + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isObject(first[k])) { + merge(first[k], second[k]); + } else { + first[k] = second[k]; + } + } +}; + +u.applyUserSettings = function applyUserSettings(context, settings, user_settings) { + /* Configuration settings might be nested objects. We only want to + * add settings which are whitelisted. + */ + for (var k in settings) { + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isUndefined(user_settings[k])) { + continue; } - return u.stringToNode(tpl_field({ - 'name': field.getAttribute('name'), - 'value': value - })); - }; + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isObject(settings[k]) && !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.isArray(settings[k])) { + applyUserSettings(context[k], settings[k], user_settings[k]); + } else { + context[k] = user_settings[k]; + } + } +}; - return u; -}); +u.stringToNode = function (s) { + /* Converts an HTML string into a DOM Node. + * Expects that the HTML string has only one top-level element, + * i.e. not multiple ones. + * + * Parameters: + * (String) s - The HTML string + */ + var div = document.createElement('div'); + div.innerHTML = s; + return div.firstElementChild; +}; + +u.getOuterWidth = function (el, include_margin = false) { + var width = el.offsetWidth; + + if (!include_margin) { + return width; + } + + var style = window.getComputedStyle(el); + width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); + return width; +}; + +u.stringToElement = function (s) { + /* Converts an HTML string into a DOM element. + * Expects that the HTML string has only one top-level element, + * i.e. not multiple ones. + * + * Parameters: + * (String) s - The HTML string + */ + var div = document.createElement('div'); + div.innerHTML = s; + return div.firstElementChild; +}; + +u.matchesSelector = function (el, selector) { + /* Checks whether the DOM element matches the given selector. + * + * Parameters: + * (DOMElement) el - The DOM element + * (String) selector - The selector + */ + return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); +}; + +u.queryChildren = function (el, selector) { + /* Returns a list of children of the DOM element that match the + * selector. + * + * Parameters: + * (DOMElement) el - the DOM element + * (String) selector - the selector they should be matched + * against. + */ + return _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.filter(el.childNodes, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.partial(u.matchesSelector, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a, selector)); +}; + +u.contains = function (attr, query) { + return function (item) { + if (typeof attr === 'object') { + var value = false; + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.forEach(attr, function (a) { + value = value || _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.includes(item.get(a).toLowerCase(), query.toLowerCase()); + }); + + return value; + } else if (typeof attr === 'string') { + return _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.includes(item.get(attr).toLowerCase(), query.toLowerCase()); + } else { + throw new TypeError('contains: wrong attribute type. Must be string or array.'); + } + }; +}; + +u.isOfType = function (type, item) { + return item.get('type') == type; +}; + +u.isInstance = function (type, item) { + return item instanceof type; +}; + +u.getAttribute = function (key, item) { + return item.get(key); +}; + +u.contains.not = function (attr, query) { + return function (item) { + return !u.contains(attr, query)(item); + }; +}; + +u.rootContains = function (root, el) { + // The document element does not have the contains method in IE. + if (root === document && !root.contains) { + return document.head.contains(el) || document.body.contains(el); + } + + return root.contains ? root.contains(el) : window.HTMLElement.prototype.contains.call(root, el); +}; + +u.createFragmentFromText = function (markup) { + /* Returns a DocumentFragment containing DOM nodes based on the + * passed-in markup text. + */ + // http://stackoverflow.com/questions/9334645/create-node-from-markup-string + var frag = document.createDocumentFragment(), + tmp = document.createElement('body'), + child; + tmp.innerHTML = markup; // Append elements in a loop to a DocumentFragment, so that the + // browser does not re-render the document for each node. + + while (child = tmp.firstChild) { + // eslint-disable-line no-cond-assign + frag.appendChild(child); + } + + return frag; +}; + +u.isPersistableModel = function (model) { + return model.collection && model.collection.browserStorage; +}; + +u.getResolveablePromise = function () { + /* Returns a promise object on which `resolve` or `reject` can be + * called. + */ + const wrapper = {}; + const promise = new es6_promise_dist_es6_promise_auto__WEBPACK_IMPORTED_MODULE_1___default.a((resolve, reject) => { + wrapper.resolve = resolve; + wrapper.reject = reject; + }); + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.assign(promise, wrapper); + + return promise; +}; + +u.interpolate = function (string, o) { + return string.replace(/{{{([^{}]*)}}}/g, (a, b) => { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); +}; + +u.onMultipleEvents = function (events = [], callback) { + /* Call the callback once all the events have been triggered + * + * Parameters: + * (Array) events: An array of objects, with keys `object` and + * `event`, representing the event name and the object it's + * triggered upon. + * (Function) callback: The function to call once all events have + * been triggered. + */ + let triggered = []; + + function handler(result) { + triggered.push(result); + + if (events.length === triggered.length) { + callback(triggered); + triggered = []; + } + } + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.each(events, map => map.object.on(map.event, handler)); +}; + +u.safeSave = function (model, attributes) { + if (u.isPersistableModel(model)) { + model.save(attributes); + } else { + model.set(attributes); + } +}; + +u.siblingIndex = function (el) { + /* eslint-disable no-cond-assign */ + for (var i = 0; el = el.previousElementSibling; i++); + + return i; +}; + +u.getCurrentWord = function (input) { + const cursor = input.selectionEnd || undefined; + return _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.last(input.value.slice(0, cursor).split(' ')); +}; + +u.replaceCurrentWord = function (input, new_value) { + const cursor = input.selectionEnd || undefined, + current_word = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_3___default.a.last(input.value.slice(0, cursor).split(' ')), + value = input.value; + + input.value = value.slice(0, cursor - current_word.length) + `${new_value} ` + value.slice(cursor); + input.selectionEnd = cursor - current_word.length + new_value.length + 1; +}; + +u.isVisible = function (el) { + if (u.hasClass('hidden', el)) { + return false; + } // XXX: Taken from jQuery's "visible" implementation + + + return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0; +}; + +u.triggerEvent = function (el, name, type = "Event", bubbles = true, cancelable = true) { + const evt = document.createEvent(type); + evt.initEvent(name, bubbles, cancelable); + el.dispatchEvent(evt); +}; + +u.geoUriToHttp = function (text, geouri_replacement) { + const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g; + return text.replace(regex, geouri_replacement); +}; + +u.httpToGeoUri = function (text, _converse) { + const replacement = 'geo:$1,$2'; + return text.replace(_converse.geouri_regex, replacement); +}; + +u.getSelectValues = function (select) { + const result = []; + const options = select && select.options; + + for (var i = 0, iLen = options.length; i < iLen; i++) { + const opt = options[i]; + + if (opt.selected) { + result.push(opt.value || opt.text); + } + } + + return result; +}; + +u.formatFingerprint = function (fp) { + fp = fp.replace(/^05/, ''); + const arr = []; + + for (let i = 1; i < 8; i++) { + const idx = i * 8 + i - 1; + fp = fp.slice(0, idx) + ' ' + fp.slice(idx); + } + + return fp; +}; + +u.appendArrayBuffer = function (buffer1, buffer2) { + const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); + tmp.set(new Uint8Array(buffer1), 0); + tmp.set(new Uint8Array(buffer2), buffer1.byteLength); + return tmp.buffer; +}; + +u.arrayBufferToHex = function (ab) { + // https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex#40031979 + return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join(''); +}; + +u.arrayBufferToString = function (ab) { + return new TextDecoder("utf-8").decode(ab); +}; + +u.stringToArrayBuffer = function (string) { + const bytes = new TextEncoder("utf-8").encode(string); + return bytes.buffer; +}; + +u.arrayBufferToBase64 = function (ab) { + return btoa(new Uint8Array(ab).reduce((data, byte) => data + String.fromCharCode(byte), '')); +}; + +u.base64ToArrayBuffer = function (b64) { + const binary_string = window.atob(b64), + len = binary_string.length, + bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + + return bytes.buffer; +}; + +u.getRandomInt = function (max) { + return Math.floor(Math.random() * Math.floor(max)); +}; + +u.putCurserAtEnd = function (textarea) { + if (textarea !== document.activeElement) { + textarea.focus(); + } // Double the length because Opera is inconsistent about whether a carriage return is one character or two. + + + const len = textarea.value.length * 2; // Timeout seems to be required for Blink + + setTimeout(() => textarea.setSelectionRange(len, len), 1); // Scroll to the bottom, in case we're in a tall textarea + // (Necessary for Firefox and Chrome) + + this.scrollTop = 999999; +}; + +u.getUniqueId = function () { + return 'xxxxxxxx-xxxx'.replace(/[x]/g, function (c) { + var r = Math.random() * 16 | 0, + v = c === 'x' ? r : r & 0x3 | 0x8; + return v.toString(16); + }); +}; + +/* harmony default export */ __webpack_exports__["default"] = (u); + +/***/ }), + +/***/ "./src/headless/utils/emoji.js": +/*!*************************************!*\ + !*** ./src/headless/utils/emoji.js ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var twemoji__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! twemoji */ "./node_modules/twemoji/2/esm.js"); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../lodash.noconflict */ "./src/headless/lodash.noconflict.js"); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./core */ "./src/headless/utils/core.js"); + + + +const emoji_list = { + ":kiss_mm:": { + "uc_base": "1f468-2764-1f48b-1f468", + "uc_output": "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", + "uc_match": "1f468-2764-fe0f-1f48b-1f468", + "uc_greedy": "1f468-2764-1f48b-1f468", + "shortnames": [":couplekiss_mm:"], + "category": "people" + }, + ":kiss_woman_man:": { + "uc_base": "1f469-2764-1f48b-1f468", + "uc_output": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f468", + "uc_match": "1f469-2764-fe0f-1f48b-1f468", + "uc_greedy": "1f469-2764-1f48b-1f468", + "shortnames": [], + "category": "people" + }, + ":kiss_ww:": { + "uc_base": "1f469-2764-1f48b-1f469", + "uc_output": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", + "uc_match": "1f469-2764-fe0f-1f48b-1f469", + "uc_greedy": "1f469-2764-1f48b-1f469", + "shortnames": [":couplekiss_ww:"], + "category": "people" + }, + ":england:": { + "uc_base": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", + "uc_output": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", + "uc_match": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", + "uc_greedy": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", + "shortnames": [], + "category": "flags" + }, + ":scotland:": { + "uc_base": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", + "uc_output": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", + "uc_match": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", + "uc_greedy": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", + "shortnames": [], + "category": "flags" + }, + ":wales:": { + "uc_base": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", + "uc_output": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", + "uc_match": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", + "uc_greedy": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", + "shortnames": [], + "category": "flags" + }, + ":family_mmbb:": { + "uc_base": "1f468-1f468-1f466-1f466", + "uc_output": "1f468-200d-1f468-200d-1f466-200d-1f466", + "uc_match": "1f468-1f468-1f466-1f466", + "uc_greedy": "1f468-1f468-1f466-1f466", + "shortnames": [], + "category": "people" + }, + ":family_mmgb:": { + "uc_base": "1f468-1f468-1f467-1f466", + "uc_output": "1f468-200d-1f468-200d-1f467-200d-1f466", + "uc_match": "1f468-1f468-1f467-1f466", + "uc_greedy": "1f468-1f468-1f467-1f466", + "shortnames": [], + "category": "people" + }, + ":family_mmgg:": { + "uc_base": "1f468-1f468-1f467-1f467", + "uc_output": "1f468-200d-1f468-200d-1f467-200d-1f467", + "uc_match": "1f468-1f468-1f467-1f467", + "uc_greedy": "1f468-1f468-1f467-1f467", + "shortnames": [], + "category": "people" + }, + ":family_mwbb:": { + "uc_base": "1f468-1f469-1f466-1f466", + "uc_output": "1f468-200d-1f469-200d-1f466-200d-1f466", + "uc_match": "1f468-1f469-1f466-1f466", + "uc_greedy": "1f468-1f469-1f466-1f466", + "shortnames": [], + "category": "people" + }, + ":family_mwgb:": { + "uc_base": "1f468-1f469-1f467-1f466", + "uc_output": "1f468-200d-1f469-200d-1f467-200d-1f466", + "uc_match": "1f468-1f469-1f467-1f466", + "uc_greedy": "1f468-1f469-1f467-1f466", + "shortnames": [], + "category": "people" + }, + ":family_mwgg:": { + "uc_base": "1f468-1f469-1f467-1f467", + "uc_output": "1f468-200d-1f469-200d-1f467-200d-1f467", + "uc_match": "1f468-1f469-1f467-1f467", + "uc_greedy": "1f468-1f469-1f467-1f467", + "shortnames": [], + "category": "people" + }, + ":family_wwbb:": { + "uc_base": "1f469-1f469-1f466-1f466", + "uc_output": "1f469-200d-1f469-200d-1f466-200d-1f466", + "uc_match": "1f469-1f469-1f466-1f466", + "uc_greedy": "1f469-1f469-1f466-1f466", + "shortnames": [], + "category": "people" + }, + ":family_wwgb:": { + "uc_base": "1f469-1f469-1f467-1f466", + "uc_output": "1f469-200d-1f469-200d-1f467-200d-1f466", + "uc_match": "1f469-1f469-1f467-1f466", + "uc_greedy": "1f469-1f469-1f467-1f466", + "shortnames": [], + "category": "people" + }, + ":family_wwgg:": { + "uc_base": "1f469-1f469-1f467-1f467", + "uc_output": "1f469-200d-1f469-200d-1f467-200d-1f467", + "uc_match": "1f469-1f469-1f467-1f467", + "uc_greedy": "1f469-1f469-1f467-1f467", + "shortnames": [], + "category": "people" + }, + ":couple_mm:": { + "uc_base": "1f468-2764-1f468", + "uc_output": "1f468-200d-2764-fe0f-200d-1f468", + "uc_match": "1f468-2764-fe0f-1f468", + "uc_greedy": "1f468-2764-1f468", + "shortnames": [":couple_with_heart_mm:"], + "category": "people" + }, + ":couple_with_heart_woman_man:": { + "uc_base": "1f469-2764-1f468", + "uc_output": "1f469-200d-2764-fe0f-200d-1f468", + "uc_match": "1f469-2764-fe0f-1f468", + "uc_greedy": "1f469-2764-1f468", + "shortnames": [], + "category": "people" + }, + ":couple_ww:": { + "uc_base": "1f469-2764-1f469", + "uc_output": "1f469-200d-2764-fe0f-200d-1f469", + "uc_match": "1f469-2764-fe0f-1f469", + "uc_greedy": "1f469-2764-1f469", + "shortnames": [":couple_with_heart_ww:"], + "category": "people" + }, + ":family_man_boy_boy:": { + "uc_base": "1f468-1f466-1f466", + "uc_output": "1f468-200d-1f466-200d-1f466", + "uc_match": "1f468-1f466-1f466", + "uc_greedy": "1f468-1f466-1f466", + "shortnames": [], + "category": "people" + }, + ":family_man_girl_boy:": { + "uc_base": "1f468-1f467-1f466", + "uc_output": "1f468-200d-1f467-200d-1f466", + "uc_match": "1f468-1f467-1f466", + "uc_greedy": "1f468-1f467-1f466", + "shortnames": [], + "category": "people" + }, + ":family_man_girl_girl:": { + "uc_base": "1f468-1f467-1f467", + "uc_output": "1f468-200d-1f467-200d-1f467", + "uc_match": "1f468-1f467-1f467", + "uc_greedy": "1f468-1f467-1f467", + "shortnames": [], + "category": "people" + }, + ":family_man_woman_boy:": { + "uc_base": "1f468-1f469-1f466", + "uc_output": "1f468-200d-1f469-200d-1f466", + "uc_match": "1f468-1f469-1f466", + "uc_greedy": "1f468-1f469-1f466", + "shortnames": [], + "category": "people" + }, + ":family_mmb:": { + "uc_base": "1f468-1f468-1f466", + "uc_output": "1f468-200d-1f468-200d-1f466", + "uc_match": "1f468-1f468-1f466", + "uc_greedy": "1f468-1f468-1f466", + "shortnames": [], + "category": "people" + }, + ":family_mmg:": { + "uc_base": "1f468-1f468-1f467", + "uc_output": "1f468-200d-1f468-200d-1f467", + "uc_match": "1f468-1f468-1f467", + "uc_greedy": "1f468-1f468-1f467", + "shortnames": [], + "category": "people" + }, + ":family_mwg:": { + "uc_base": "1f468-1f469-1f467", + "uc_output": "1f468-200d-1f469-200d-1f467", + "uc_match": "1f468-1f469-1f467", + "uc_greedy": "1f468-1f469-1f467", + "shortnames": [], + "category": "people" + }, + ":family_woman_boy_boy:": { + "uc_base": "1f469-1f466-1f466", + "uc_output": "1f469-200d-1f466-200d-1f466", + "uc_match": "1f469-1f466-1f466", + "uc_greedy": "1f469-1f466-1f466", + "shortnames": [], + "category": "people" + }, + ":family_woman_girl_boy:": { + "uc_base": "1f469-1f467-1f466", + "uc_output": "1f469-200d-1f467-200d-1f466", + "uc_match": "1f469-1f467-1f466", + "uc_greedy": "1f469-1f467-1f466", + "shortnames": [], + "category": "people" + }, + ":family_woman_girl_girl:": { + "uc_base": "1f469-1f467-1f467", + "uc_output": "1f469-200d-1f467-200d-1f467", + "uc_match": "1f469-1f467-1f467", + "uc_greedy": "1f469-1f467-1f467", + "shortnames": [], + "category": "people" + }, + ":family_wwb:": { + "uc_base": "1f469-1f469-1f466", + "uc_output": "1f469-200d-1f469-200d-1f466", + "uc_match": "1f469-1f469-1f466", + "uc_greedy": "1f469-1f469-1f466", + "shortnames": [], + "category": "people" + }, + ":family_wwg:": { + "uc_base": "1f469-1f469-1f467", + "uc_output": "1f469-200d-1f469-200d-1f467", + "uc_match": "1f469-1f469-1f467", + "uc_greedy": "1f469-1f469-1f467", + "shortnames": [], + "category": "people" + }, + ":blond-haired_man_tone1:": { + "uc_base": "1f471-1f3fb-2642", + "uc_output": "1f471-1f3fb-200d-2642-fe0f", + "uc_match": "1f471-1f3fb-2642-fe0f", + "uc_greedy": "1f471-1f3fb-2642", + "shortnames": [":blond-haired_man_light_skin_tone:"], + "category": "people" + }, + ":blond-haired_man_tone2:": { + "uc_base": "1f471-1f3fc-2642", + "uc_output": "1f471-1f3fc-200d-2642-fe0f", + "uc_match": "1f471-1f3fc-2642-fe0f", + "uc_greedy": "1f471-1f3fc-2642", + "shortnames": [":blond-haired_man_medium_light_skin_tone:"], + "category": "people" + }, + ":blond-haired_man_tone3:": { + "uc_base": "1f471-1f3fd-2642", + "uc_output": "1f471-1f3fd-200d-2642-fe0f", + "uc_match": "1f471-1f3fd-2642-fe0f", + "uc_greedy": "1f471-1f3fd-2642", + "shortnames": [":blond-haired_man_medium_skin_tone:"], + "category": "people" + }, + ":blond-haired_man_tone4:": { + "uc_base": "1f471-1f3fe-2642", + "uc_output": "1f471-1f3fe-200d-2642-fe0f", + "uc_match": "1f471-1f3fe-2642-fe0f", + "uc_greedy": "1f471-1f3fe-2642", + "shortnames": [":blond-haired_man_medium_dark_skin_tone:"], + "category": "people" + }, + ":blond-haired_man_tone5:": { + "uc_base": "1f471-1f3ff-2642", + "uc_output": "1f471-1f3ff-200d-2642-fe0f", + "uc_match": "1f471-1f3ff-2642-fe0f", + "uc_greedy": "1f471-1f3ff-2642", + "shortnames": [":blond-haired_man_dark_skin_tone:"], + "category": "people" + }, + ":blond-haired_woman_tone1:": { + "uc_base": "1f471-1f3fb-2640", + "uc_output": "1f471-1f3fb-200d-2640-fe0f", + "uc_match": "1f471-1f3fb-2640-fe0f", + "uc_greedy": "1f471-1f3fb-2640", + "shortnames": [":blond-haired_woman_light_skin_tone:"], + "category": "people" + }, + ":blond-haired_woman_tone2:": { + "uc_base": "1f471-1f3fc-2640", + "uc_output": "1f471-1f3fc-200d-2640-fe0f", + "uc_match": "1f471-1f3fc-2640-fe0f", + "uc_greedy": "1f471-1f3fc-2640", + "shortnames": [":blond-haired_woman_medium_light_skin_tone:"], + "category": "people" + }, + ":blond-haired_woman_tone3:": { + "uc_base": "1f471-1f3fd-2640", + "uc_output": "1f471-1f3fd-200d-2640-fe0f", + "uc_match": "1f471-1f3fd-2640-fe0f", + "uc_greedy": "1f471-1f3fd-2640", + "shortnames": [":blond-haired_woman_medium_skin_tone:"], + "category": "people" + }, + ":blond-haired_woman_tone4:": { + "uc_base": "1f471-1f3fe-2640", + "uc_output": "1f471-1f3fe-200d-2640-fe0f", + "uc_match": "1f471-1f3fe-2640-fe0f", + "uc_greedy": "1f471-1f3fe-2640", + "shortnames": [":blond-haired_woman_medium_dark_skin_tone:"], + "category": "people" + }, + ":blond-haired_woman_tone5:": { + "uc_base": "1f471-1f3ff-2640", + "uc_output": "1f471-1f3ff-200d-2640-fe0f", + "uc_match": "1f471-1f3ff-2640-fe0f", + "uc_greedy": "1f471-1f3ff-2640", + "shortnames": [":blond-haired_woman_dark_skin_tone:"], + "category": "people" + }, + ":eye_in_speech_bubble:": { + "uc_base": "1f441-1f5e8", + "uc_output": "1f441-fe0f-200d-1f5e8-fe0f", + "uc_match": "1f441-fe0f-200d-1f5e8", + "uc_greedy": "1f441-1f5e8", + "shortnames": [], + "category": "symbols" + }, + ":man_biking_tone1:": { + "uc_base": "1f6b4-1f3fb-2642", + "uc_output": "1f6b4-1f3fb-200d-2642-fe0f", + "uc_match": "1f6b4-1f3fb-2642-fe0f", + "uc_greedy": "1f6b4-1f3fb-2642", + "shortnames": [":man_biking_light_skin_tone:"], + "category": "activity" + }, + ":man_biking_tone2:": { + "uc_base": "1f6b4-1f3fc-2642", + "uc_output": "1f6b4-1f3fc-200d-2642-fe0f", + "uc_match": "1f6b4-1f3fc-2642-fe0f", + "uc_greedy": "1f6b4-1f3fc-2642", + "shortnames": [":man_biking_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_biking_tone3:": { + "uc_base": "1f6b4-1f3fd-2642", + "uc_output": "1f6b4-1f3fd-200d-2642-fe0f", + "uc_match": "1f6b4-1f3fd-2642-fe0f", + "uc_greedy": "1f6b4-1f3fd-2642", + "shortnames": [":man_biking_medium_skin_tone:"], + "category": "activity" + }, + ":man_biking_tone4:": { + "uc_base": "1f6b4-1f3fe-2642", + "uc_output": "1f6b4-1f3fe-200d-2642-fe0f", + "uc_match": "1f6b4-1f3fe-2642-fe0f", + "uc_greedy": "1f6b4-1f3fe-2642", + "shortnames": [":man_biking_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_biking_tone5:": { + "uc_base": "1f6b4-1f3ff-2642", + "uc_output": "1f6b4-1f3ff-200d-2642-fe0f", + "uc_match": "1f6b4-1f3ff-2642-fe0f", + "uc_greedy": "1f6b4-1f3ff-2642", + "shortnames": [":man_biking_dark_skin_tone:"], + "category": "activity" + }, + ":man_bowing_tone1:": { + "uc_base": "1f647-1f3fb-2642", + "uc_output": "1f647-1f3fb-200d-2642-fe0f", + "uc_match": "1f647-1f3fb-2642-fe0f", + "uc_greedy": "1f647-1f3fb-2642", + "shortnames": [":man_bowing_light_skin_tone:"], + "category": "people" + }, + ":man_bowing_tone2:": { + "uc_base": "1f647-1f3fc-2642", + "uc_output": "1f647-1f3fc-200d-2642-fe0f", + "uc_match": "1f647-1f3fc-2642-fe0f", + "uc_greedy": "1f647-1f3fc-2642", + "shortnames": [":man_bowing_medium_light_skin_tone:"], + "category": "people" + }, + ":man_bowing_tone3:": { + "uc_base": "1f647-1f3fd-2642", + "uc_output": "1f647-1f3fd-200d-2642-fe0f", + "uc_match": "1f647-1f3fd-2642-fe0f", + "uc_greedy": "1f647-1f3fd-2642", + "shortnames": [":man_bowing_medium_skin_tone:"], + "category": "people" + }, + ":man_bowing_tone4:": { + "uc_base": "1f647-1f3fe-2642", + "uc_output": "1f647-1f3fe-200d-2642-fe0f", + "uc_match": "1f647-1f3fe-2642-fe0f", + "uc_greedy": "1f647-1f3fe-2642", + "shortnames": [":man_bowing_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_bowing_tone5:": { + "uc_base": "1f647-1f3ff-2642", + "uc_output": "1f647-1f3ff-200d-2642-fe0f", + "uc_match": "1f647-1f3ff-2642-fe0f", + "uc_greedy": "1f647-1f3ff-2642", + "shortnames": [":man_bowing_dark_skin_tone:"], + "category": "people" + }, + ":man_cartwheeling_tone1:": { + "uc_base": "1f938-1f3fb-2642", + "uc_output": "1f938-1f3fb-200d-2642-fe0f", + "uc_match": "1f938-1f3fb-2642-fe0f", + "uc_greedy": "1f938-1f3fb-2642", + "shortnames": [":man_cartwheeling_light_skin_tone:"], + "category": "activity" + }, + ":man_cartwheeling_tone2:": { + "uc_base": "1f938-1f3fc-2642", + "uc_output": "1f938-1f3fc-200d-2642-fe0f", + "uc_match": "1f938-1f3fc-2642-fe0f", + "uc_greedy": "1f938-1f3fc-2642", + "shortnames": [":man_cartwheeling_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_cartwheeling_tone3:": { + "uc_base": "1f938-1f3fd-2642", + "uc_output": "1f938-1f3fd-200d-2642-fe0f", + "uc_match": "1f938-1f3fd-2642-fe0f", + "uc_greedy": "1f938-1f3fd-2642", + "shortnames": [":man_cartwheeling_medium_skin_tone:"], + "category": "activity" + }, + ":man_cartwheeling_tone4:": { + "uc_base": "1f938-1f3fe-2642", + "uc_output": "1f938-1f3fe-200d-2642-fe0f", + "uc_match": "1f938-1f3fe-2642-fe0f", + "uc_greedy": "1f938-1f3fe-2642", + "shortnames": [":man_cartwheeling_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_cartwheeling_tone5:": { + "uc_base": "1f938-1f3ff-2642", + "uc_output": "1f938-1f3ff-200d-2642-fe0f", + "uc_match": "1f938-1f3ff-2642-fe0f", + "uc_greedy": "1f938-1f3ff-2642", + "shortnames": [":man_cartwheeling_dark_skin_tone:"], + "category": "activity" + }, + ":man_climbing_tone1:": { + "uc_base": "1f9d7-1f3fb-2642", + "uc_output": "1f9d7-1f3fb-200d-2642-fe0f", + "uc_match": "1f9d7-1f3fb-2642-fe0f", + "uc_greedy": "1f9d7-1f3fb-2642", + "shortnames": [":man_climbing_light_skin_tone:"], + "category": "activity" + }, + ":man_climbing_tone2:": { + "uc_base": "1f9d7-1f3fc-2642", + "uc_output": "1f9d7-1f3fc-200d-2642-fe0f", + "uc_match": "1f9d7-1f3fc-2642-fe0f", + "uc_greedy": "1f9d7-1f3fc-2642", + "shortnames": [":man_climbing_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_climbing_tone3:": { + "uc_base": "1f9d7-1f3fd-2642", + "uc_output": "1f9d7-1f3fd-200d-2642-fe0f", + "uc_match": "1f9d7-1f3fd-2642-fe0f", + "uc_greedy": "1f9d7-1f3fd-2642", + "shortnames": [":man_climbing_medium_skin_tone:"], + "category": "activity" + }, + ":man_climbing_tone4:": { + "uc_base": "1f9d7-1f3fe-2642", + "uc_output": "1f9d7-1f3fe-200d-2642-fe0f", + "uc_match": "1f9d7-1f3fe-2642-fe0f", + "uc_greedy": "1f9d7-1f3fe-2642", + "shortnames": [":man_climbing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_climbing_tone5:": { + "uc_base": "1f9d7-1f3ff-2642", + "uc_output": "1f9d7-1f3ff-200d-2642-fe0f", + "uc_match": "1f9d7-1f3ff-2642-fe0f", + "uc_greedy": "1f9d7-1f3ff-2642", + "shortnames": [":man_climbing_dark_skin_tone:"], + "category": "activity" + }, + ":man_construction_worker_tone1:": { + "uc_base": "1f477-1f3fb-2642", + "uc_output": "1f477-1f3fb-200d-2642-fe0f", + "uc_match": "1f477-1f3fb-2642-fe0f", + "uc_greedy": "1f477-1f3fb-2642", + "shortnames": [":man_construction_worker_light_skin_tone:"], + "category": "people" + }, + ":man_construction_worker_tone2:": { + "uc_base": "1f477-1f3fc-2642", + "uc_output": "1f477-1f3fc-200d-2642-fe0f", + "uc_match": "1f477-1f3fc-2642-fe0f", + "uc_greedy": "1f477-1f3fc-2642", + "shortnames": [":man_construction_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":man_construction_worker_tone3:": { + "uc_base": "1f477-1f3fd-2642", + "uc_output": "1f477-1f3fd-200d-2642-fe0f", + "uc_match": "1f477-1f3fd-2642-fe0f", + "uc_greedy": "1f477-1f3fd-2642", + "shortnames": [":man_construction_worker_medium_skin_tone:"], + "category": "people" + }, + ":man_construction_worker_tone4:": { + "uc_base": "1f477-1f3fe-2642", + "uc_output": "1f477-1f3fe-200d-2642-fe0f", + "uc_match": "1f477-1f3fe-2642-fe0f", + "uc_greedy": "1f477-1f3fe-2642", + "shortnames": [":man_construction_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_construction_worker_tone5:": { + "uc_base": "1f477-1f3ff-2642", + "uc_output": "1f477-1f3ff-200d-2642-fe0f", + "uc_match": "1f477-1f3ff-2642-fe0f", + "uc_greedy": "1f477-1f3ff-2642", + "shortnames": [":man_construction_worker_dark_skin_tone:"], + "category": "people" + }, + ":man_detective_tone1:": { + "uc_base": "1f575-1f3fb-2642", + "uc_output": "1f575-1f3fb-200d-2642-fe0f", + "uc_match": "1f575-fe0f-1f3fb-2642-fe0f", + "uc_greedy": "1f575-1f3fb-2642", + "shortnames": [":man_detective_light_skin_tone:"], + "category": "people" + }, + ":man_detective_tone2:": { + "uc_base": "1f575-1f3fc-2642", + "uc_output": "1f575-1f3fc-200d-2642-fe0f", + "uc_match": "1f575-fe0f-1f3fc-2642-fe0f", + "uc_greedy": "1f575-1f3fc-2642", + "shortnames": [":man_detective_medium_light_skin_tone:"], + "category": "people" + }, + ":man_detective_tone3:": { + "uc_base": "1f575-1f3fd-2642", + "uc_output": "1f575-1f3fd-200d-2642-fe0f", + "uc_match": "1f575-fe0f-1f3fd-2642-fe0f", + "uc_greedy": "1f575-1f3fd-2642", + "shortnames": [":man_detective_medium_skin_tone:"], + "category": "people" + }, + ":man_detective_tone4:": { + "uc_base": "1f575-1f3fe-2642", + "uc_output": "1f575-1f3fe-200d-2642-fe0f", + "uc_match": "1f575-fe0f-1f3fe-2642-fe0f", + "uc_greedy": "1f575-1f3fe-2642", + "shortnames": [":man_detective_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_detective_tone5:": { + "uc_base": "1f575-1f3ff-2642", + "uc_output": "1f575-1f3ff-200d-2642-fe0f", + "uc_match": "1f575-fe0f-1f3ff-2642-fe0f", + "uc_greedy": "1f575-1f3ff-2642", + "shortnames": [":man_detective_dark_skin_tone:"], + "category": "people" + }, + ":man_elf_tone1:": { + "uc_base": "1f9dd-1f3fb-2642", + "uc_output": "1f9dd-1f3fb-200d-2642-fe0f", + "uc_match": "1f9dd-1f3fb-2642-fe0f", + "uc_greedy": "1f9dd-1f3fb-2642", + "shortnames": [":man_elf_light_skin_tone:"], + "category": "people" + }, + ":man_elf_tone2:": { + "uc_base": "1f9dd-1f3fc-2642", + "uc_output": "1f9dd-1f3fc-200d-2642-fe0f", + "uc_match": "1f9dd-1f3fc-2642-fe0f", + "uc_greedy": "1f9dd-1f3fc-2642", + "shortnames": [":man_elf_medium_light_skin_tone:"], + "category": "people" + }, + ":man_elf_tone3:": { + "uc_base": "1f9dd-1f3fd-2642", + "uc_output": "1f9dd-1f3fd-200d-2642-fe0f", + "uc_match": "1f9dd-1f3fd-2642-fe0f", + "uc_greedy": "1f9dd-1f3fd-2642", + "shortnames": [":man_elf_medium_skin_tone:"], + "category": "people" + }, + ":man_elf_tone4:": { + "uc_base": "1f9dd-1f3fe-2642", + "uc_output": "1f9dd-1f3fe-200d-2642-fe0f", + "uc_match": "1f9dd-1f3fe-2642-fe0f", + "uc_greedy": "1f9dd-1f3fe-2642", + "shortnames": [":man_elf_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_elf_tone5:": { + "uc_base": "1f9dd-1f3ff-2642", + "uc_output": "1f9dd-1f3ff-200d-2642-fe0f", + "uc_match": "1f9dd-1f3ff-2642-fe0f", + "uc_greedy": "1f9dd-1f3ff-2642", + "shortnames": [":man_elf_dark_skin_tone:"], + "category": "people" + }, + ":man_facepalming_tone1:": { + "uc_base": "1f926-1f3fb-2642", + "uc_output": "1f926-1f3fb-200d-2642-fe0f", + "uc_match": "1f926-1f3fb-2642-fe0f", + "uc_greedy": "1f926-1f3fb-2642", + "shortnames": [":man_facepalming_light_skin_tone:"], + "category": "people" + }, + ":man_facepalming_tone2:": { + "uc_base": "1f926-1f3fc-2642", + "uc_output": "1f926-1f3fc-200d-2642-fe0f", + "uc_match": "1f926-1f3fc-2642-fe0f", + "uc_greedy": "1f926-1f3fc-2642", + "shortnames": [":man_facepalming_medium_light_skin_tone:"], + "category": "people" + }, + ":man_facepalming_tone3:": { + "uc_base": "1f926-1f3fd-2642", + "uc_output": "1f926-1f3fd-200d-2642-fe0f", + "uc_match": "1f926-1f3fd-2642-fe0f", + "uc_greedy": "1f926-1f3fd-2642", + "shortnames": [":man_facepalming_medium_skin_tone:"], + "category": "people" + }, + ":man_facepalming_tone4:": { + "uc_base": "1f926-1f3fe-2642", + "uc_output": "1f926-1f3fe-200d-2642-fe0f", + "uc_match": "1f926-1f3fe-2642-fe0f", + "uc_greedy": "1f926-1f3fe-2642", + "shortnames": [":man_facepalming_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_facepalming_tone5:": { + "uc_base": "1f926-1f3ff-2642", + "uc_output": "1f926-1f3ff-200d-2642-fe0f", + "uc_match": "1f926-1f3ff-2642-fe0f", + "uc_greedy": "1f926-1f3ff-2642", + "shortnames": [":man_facepalming_dark_skin_tone:"], + "category": "people" + }, + ":man_fairy_tone1:": { + "uc_base": "1f9da-1f3fb-2642", + "uc_output": "1f9da-1f3fb-200d-2642-fe0f", + "uc_match": "1f9da-1f3fb-2642-fe0f", + "uc_greedy": "1f9da-1f3fb-2642", + "shortnames": [":man_fairy_light_skin_tone:"], + "category": "people" + }, + ":man_fairy_tone2:": { + "uc_base": "1f9da-1f3fc-2642", + "uc_output": "1f9da-1f3fc-200d-2642-fe0f", + "uc_match": "1f9da-1f3fc-2642-fe0f", + "uc_greedy": "1f9da-1f3fc-2642", + "shortnames": [":man_fairy_medium_light_skin_tone:"], + "category": "people" + }, + ":man_fairy_tone3:": { + "uc_base": "1f9da-1f3fd-2642", + "uc_output": "1f9da-1f3fd-200d-2642-fe0f", + "uc_match": "1f9da-1f3fd-2642-fe0f", + "uc_greedy": "1f9da-1f3fd-2642", + "shortnames": [":man_fairy_medium_skin_tone:"], + "category": "people" + }, + ":man_fairy_tone4:": { + "uc_base": "1f9da-1f3fe-2642", + "uc_output": "1f9da-1f3fe-200d-2642-fe0f", + "uc_match": "1f9da-1f3fe-2642-fe0f", + "uc_greedy": "1f9da-1f3fe-2642", + "shortnames": [":man_fairy_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_fairy_tone5:": { + "uc_base": "1f9da-1f3ff-2642", + "uc_output": "1f9da-1f3ff-200d-2642-fe0f", + "uc_match": "1f9da-1f3ff-2642-fe0f", + "uc_greedy": "1f9da-1f3ff-2642", + "shortnames": [":man_fairy_dark_skin_tone:"], + "category": "people" + }, + ":man_frowning_tone1:": { + "uc_base": "1f64d-1f3fb-2642", + "uc_output": "1f64d-1f3fb-200d-2642-fe0f", + "uc_match": "1f64d-1f3fb-2642-fe0f", + "uc_greedy": "1f64d-1f3fb-2642", + "shortnames": [":man_frowning_light_skin_tone:"], + "category": "people" + }, + ":man_frowning_tone2:": { + "uc_base": "1f64d-1f3fc-2642", + "uc_output": "1f64d-1f3fc-200d-2642-fe0f", + "uc_match": "1f64d-1f3fc-2642-fe0f", + "uc_greedy": "1f64d-1f3fc-2642", + "shortnames": [":man_frowning_medium_light_skin_tone:"], + "category": "people" + }, + ":man_frowning_tone3:": { + "uc_base": "1f64d-1f3fd-2642", + "uc_output": "1f64d-1f3fd-200d-2642-fe0f", + "uc_match": "1f64d-1f3fd-2642-fe0f", + "uc_greedy": "1f64d-1f3fd-2642", + "shortnames": [":man_frowning_medium_skin_tone:"], + "category": "people" + }, + ":man_frowning_tone4:": { + "uc_base": "1f64d-1f3fe-2642", + "uc_output": "1f64d-1f3fe-200d-2642-fe0f", + "uc_match": "1f64d-1f3fe-2642-fe0f", + "uc_greedy": "1f64d-1f3fe-2642", + "shortnames": [":man_frowning_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_frowning_tone5:": { + "uc_base": "1f64d-1f3ff-2642", + "uc_output": "1f64d-1f3ff-200d-2642-fe0f", + "uc_match": "1f64d-1f3ff-2642-fe0f", + "uc_greedy": "1f64d-1f3ff-2642", + "shortnames": [":man_frowning_dark_skin_tone:"], + "category": "people" + }, + ":man_gesturing_no_tone1:": { + "uc_base": "1f645-1f3fb-2642", + "uc_output": "1f645-1f3fb-200d-2642-fe0f", + "uc_match": "1f645-1f3fb-2642-fe0f", + "uc_greedy": "1f645-1f3fb-2642", + "shortnames": [":man_gesturing_no_light_skin_tone:"], + "category": "people" + }, + ":man_gesturing_no_tone2:": { + "uc_base": "1f645-1f3fc-2642", + "uc_output": "1f645-1f3fc-200d-2642-fe0f", + "uc_match": "1f645-1f3fc-2642-fe0f", + "uc_greedy": "1f645-1f3fc-2642", + "shortnames": [":man_gesturing_no_medium_light_skin_tone:"], + "category": "people" + }, + ":man_gesturing_no_tone3:": { + "uc_base": "1f645-1f3fd-2642", + "uc_output": "1f645-1f3fd-200d-2642-fe0f", + "uc_match": "1f645-1f3fd-2642-fe0f", + "uc_greedy": "1f645-1f3fd-2642", + "shortnames": [":man_gesturing_no_medium_skin_tone:"], + "category": "people" + }, + ":man_gesturing_no_tone4:": { + "uc_base": "1f645-1f3fe-2642", + "uc_output": "1f645-1f3fe-200d-2642-fe0f", + "uc_match": "1f645-1f3fe-2642-fe0f", + "uc_greedy": "1f645-1f3fe-2642", + "shortnames": [":man_gesturing_no_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_gesturing_no_tone5:": { + "uc_base": "1f645-1f3ff-2642", + "uc_output": "1f645-1f3ff-200d-2642-fe0f", + "uc_match": "1f645-1f3ff-2642-fe0f", + "uc_greedy": "1f645-1f3ff-2642", + "shortnames": [":man_gesturing_no_dark_skin_tone:"], + "category": "people" + }, + ":man_gesturing_ok_tone1:": { + "uc_base": "1f646-1f3fb-2642", + "uc_output": "1f646-1f3fb-200d-2642-fe0f", + "uc_match": "1f646-1f3fb-2642-fe0f", + "uc_greedy": "1f646-1f3fb-2642", + "shortnames": [":man_gesturing_ok_light_skin_tone:"], + "category": "people" + }, + ":man_gesturing_ok_tone2:": { + "uc_base": "1f646-1f3fc-2642", + "uc_output": "1f646-1f3fc-200d-2642-fe0f", + "uc_match": "1f646-1f3fc-2642-fe0f", + "uc_greedy": "1f646-1f3fc-2642", + "shortnames": [":man_gesturing_ok_medium_light_skin_tone:"], + "category": "people" + }, + ":man_gesturing_ok_tone3:": { + "uc_base": "1f646-1f3fd-2642", + "uc_output": "1f646-1f3fd-200d-2642-fe0f", + "uc_match": "1f646-1f3fd-2642-fe0f", + "uc_greedy": "1f646-1f3fd-2642", + "shortnames": [":man_gesturing_ok_medium_skin_tone:"], + "category": "people" + }, + ":man_gesturing_ok_tone4:": { + "uc_base": "1f646-1f3fe-2642", + "uc_output": "1f646-1f3fe-200d-2642-fe0f", + "uc_match": "1f646-1f3fe-2642-fe0f", + "uc_greedy": "1f646-1f3fe-2642", + "shortnames": [":man_gesturing_ok_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_gesturing_ok_tone5:": { + "uc_base": "1f646-1f3ff-2642", + "uc_output": "1f646-1f3ff-200d-2642-fe0f", + "uc_match": "1f646-1f3ff-2642-fe0f", + "uc_greedy": "1f646-1f3ff-2642", + "shortnames": [":man_gesturing_ok_dark_skin_tone:"], + "category": "people" + }, + ":man_getting_face_massage_tone1:": { + "uc_base": "1f486-1f3fb-2642", + "uc_output": "1f486-1f3fb-200d-2642-fe0f", + "uc_match": "1f486-1f3fb-2642-fe0f", + "uc_greedy": "1f486-1f3fb-2642", + "shortnames": [":man_getting_face_massage_light_skin_tone:"], + "category": "people" + }, + ":man_getting_face_massage_tone2:": { + "uc_base": "1f486-1f3fc-2642", + "uc_output": "1f486-1f3fc-200d-2642-fe0f", + "uc_match": "1f486-1f3fc-2642-fe0f", + "uc_greedy": "1f486-1f3fc-2642", + "shortnames": [":man_getting_face_massage_medium_light_skin_tone:"], + "category": "people" + }, + ":man_getting_face_massage_tone3:": { + "uc_base": "1f486-1f3fd-2642", + "uc_output": "1f486-1f3fd-200d-2642-fe0f", + "uc_match": "1f486-1f3fd-2642-fe0f", + "uc_greedy": "1f486-1f3fd-2642", + "shortnames": [":man_getting_face_massage_medium_skin_tone:"], + "category": "people" + }, + ":man_getting_face_massage_tone4:": { + "uc_base": "1f486-1f3fe-2642", + "uc_output": "1f486-1f3fe-200d-2642-fe0f", + "uc_match": "1f486-1f3fe-2642-fe0f", + "uc_greedy": "1f486-1f3fe-2642", + "shortnames": [":man_getting_face_massage_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_getting_face_massage_tone5:": { + "uc_base": "1f486-1f3ff-2642", + "uc_output": "1f486-1f3ff-200d-2642-fe0f", + "uc_match": "1f486-1f3ff-2642-fe0f", + "uc_greedy": "1f486-1f3ff-2642", + "shortnames": [":man_getting_face_massage_dark_skin_tone:"], + "category": "people" + }, + ":man_getting_haircut_tone1:": { + "uc_base": "1f487-1f3fb-2642", + "uc_output": "1f487-1f3fb-200d-2642-fe0f", + "uc_match": "1f487-1f3fb-2642-fe0f", + "uc_greedy": "1f487-1f3fb-2642", + "shortnames": [":man_getting_haircut_light_skin_tone:"], + "category": "people" + }, + ":man_getting_haircut_tone2:": { + "uc_base": "1f487-1f3fc-2642", + "uc_output": "1f487-1f3fc-200d-2642-fe0f", + "uc_match": "1f487-1f3fc-2642-fe0f", + "uc_greedy": "1f487-1f3fc-2642", + "shortnames": [":man_getting_haircut_medium_light_skin_tone:"], + "category": "people" + }, + ":man_getting_haircut_tone3:": { + "uc_base": "1f487-1f3fd-2642", + "uc_output": "1f487-1f3fd-200d-2642-fe0f", + "uc_match": "1f487-1f3fd-2642-fe0f", + "uc_greedy": "1f487-1f3fd-2642", + "shortnames": [":man_getting_haircut_medium_skin_tone:"], + "category": "people" + }, + ":man_getting_haircut_tone4:": { + "uc_base": "1f487-1f3fe-2642", + "uc_output": "1f487-1f3fe-200d-2642-fe0f", + "uc_match": "1f487-1f3fe-2642-fe0f", + "uc_greedy": "1f487-1f3fe-2642", + "shortnames": [":man_getting_haircut_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_getting_haircut_tone5:": { + "uc_base": "1f487-1f3ff-2642", + "uc_output": "1f487-1f3ff-200d-2642-fe0f", + "uc_match": "1f487-1f3ff-2642-fe0f", + "uc_greedy": "1f487-1f3ff-2642", + "shortnames": [":man_getting_haircut_dark_skin_tone:"], + "category": "people" + }, + ":man_golfing_tone1:": { + "uc_base": "1f3cc-1f3fb-2642", + "uc_output": "1f3cc-1f3fb-200d-2642-fe0f", + "uc_match": "1f3cc-fe0f-1f3fb-2642-fe0f", + "uc_greedy": "1f3cc-1f3fb-2642", + "shortnames": [":man_golfing_light_skin_tone:"], + "category": "activity" + }, + ":man_golfing_tone2:": { + "uc_base": "1f3cc-1f3fc-2642", + "uc_output": "1f3cc-1f3fc-200d-2642-fe0f", + "uc_match": "1f3cc-fe0f-1f3fc-2642-fe0f", + "uc_greedy": "1f3cc-1f3fc-2642", + "shortnames": [":man_golfing_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_golfing_tone3:": { + "uc_base": "1f3cc-1f3fd-2642", + "uc_output": "1f3cc-1f3fd-200d-2642-fe0f", + "uc_match": "1f3cc-fe0f-1f3fd-2642-fe0f", + "uc_greedy": "1f3cc-1f3fd-2642", + "shortnames": [":man_golfing_medium_skin_tone:"], + "category": "activity" + }, + ":man_golfing_tone4:": { + "uc_base": "1f3cc-1f3fe-2642", + "uc_output": "1f3cc-1f3fe-200d-2642-fe0f", + "uc_match": "1f3cc-fe0f-1f3fe-2642-fe0f", + "uc_greedy": "1f3cc-1f3fe-2642", + "shortnames": [":man_golfing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_golfing_tone5:": { + "uc_base": "1f3cc-1f3ff-2642", + "uc_output": "1f3cc-1f3ff-200d-2642-fe0f", + "uc_match": "1f3cc-fe0f-1f3ff-2642-fe0f", + "uc_greedy": "1f3cc-1f3ff-2642", + "shortnames": [":man_golfing_dark_skin_tone:"], + "category": "activity" + }, + ":man_guard_tone1:": { + "uc_base": "1f482-1f3fb-2642", + "uc_output": "1f482-1f3fb-200d-2642-fe0f", + "uc_match": "1f482-1f3fb-2642-fe0f", + "uc_greedy": "1f482-1f3fb-2642", + "shortnames": [":man_guard_light_skin_tone:"], + "category": "people" + }, + ":man_guard_tone2:": { + "uc_base": "1f482-1f3fc-2642", + "uc_output": "1f482-1f3fc-200d-2642-fe0f", + "uc_match": "1f482-1f3fc-2642-fe0f", + "uc_greedy": "1f482-1f3fc-2642", + "shortnames": [":man_guard_medium_light_skin_tone:"], + "category": "people" + }, + ":man_guard_tone3:": { + "uc_base": "1f482-1f3fd-2642", + "uc_output": "1f482-1f3fd-200d-2642-fe0f", + "uc_match": "1f482-1f3fd-2642-fe0f", + "uc_greedy": "1f482-1f3fd-2642", + "shortnames": [":man_guard_medium_skin_tone:"], + "category": "people" + }, + ":man_guard_tone4:": { + "uc_base": "1f482-1f3fe-2642", + "uc_output": "1f482-1f3fe-200d-2642-fe0f", + "uc_match": "1f482-1f3fe-2642-fe0f", + "uc_greedy": "1f482-1f3fe-2642", + "shortnames": [":man_guard_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_guard_tone5:": { + "uc_base": "1f482-1f3ff-2642", + "uc_output": "1f482-1f3ff-200d-2642-fe0f", + "uc_match": "1f482-1f3ff-2642-fe0f", + "uc_greedy": "1f482-1f3ff-2642", + "shortnames": [":man_guard_dark_skin_tone:"], + "category": "people" + }, + ":man_health_worker_tone1:": { + "uc_base": "1f468-1f3fb-2695", + "uc_output": "1f468-1f3fb-200d-2695-fe0f", + "uc_match": "1f468-1f3fb-2695-fe0f", + "uc_greedy": "1f468-1f3fb-2695", + "shortnames": [":man_health_worker_light_skin_tone:"], + "category": "people" + }, + ":man_health_worker_tone2:": { + "uc_base": "1f468-1f3fc-2695", + "uc_output": "1f468-1f3fc-200d-2695-fe0f", + "uc_match": "1f468-1f3fc-2695-fe0f", + "uc_greedy": "1f468-1f3fc-2695", + "shortnames": [":man_health_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":man_health_worker_tone3:": { + "uc_base": "1f468-1f3fd-2695", + "uc_output": "1f468-1f3fd-200d-2695-fe0f", + "uc_match": "1f468-1f3fd-2695-fe0f", + "uc_greedy": "1f468-1f3fd-2695", + "shortnames": [":man_health_worker_medium_skin_tone:"], + "category": "people" + }, + ":man_health_worker_tone4:": { + "uc_base": "1f468-1f3fe-2695", + "uc_output": "1f468-1f3fe-200d-2695-fe0f", + "uc_match": "1f468-1f3fe-2695-fe0f", + "uc_greedy": "1f468-1f3fe-2695", + "shortnames": [":man_health_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_health_worker_tone5:": { + "uc_base": "1f468-1f3ff-2695", + "uc_output": "1f468-1f3ff-200d-2695-fe0f", + "uc_match": "1f468-1f3ff-2695-fe0f", + "uc_greedy": "1f468-1f3ff-2695", + "shortnames": [":man_health_worker_dark_skin_tone:"], + "category": "people" + }, + ":man_in_lotus_position_tone1:": { + "uc_base": "1f9d8-1f3fb-2642", + "uc_output": "1f9d8-1f3fb-200d-2642-fe0f", + "uc_match": "1f9d8-1f3fb-2642-fe0f", + "uc_greedy": "1f9d8-1f3fb-2642", + "shortnames": [":man_in_lotus_position_light_skin_tone:"], + "category": "activity" + }, + ":man_in_lotus_position_tone2:": { + "uc_base": "1f9d8-1f3fc-2642", + "uc_output": "1f9d8-1f3fc-200d-2642-fe0f", + "uc_match": "1f9d8-1f3fc-2642-fe0f", + "uc_greedy": "1f9d8-1f3fc-2642", + "shortnames": [":man_in_lotus_position_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_in_lotus_position_tone3:": { + "uc_base": "1f9d8-1f3fd-2642", + "uc_output": "1f9d8-1f3fd-200d-2642-fe0f", + "uc_match": "1f9d8-1f3fd-2642-fe0f", + "uc_greedy": "1f9d8-1f3fd-2642", + "shortnames": [":man_in_lotus_position_medium_skin_tone:"], + "category": "activity" + }, + ":man_in_lotus_position_tone4:": { + "uc_base": "1f9d8-1f3fe-2642", + "uc_output": "1f9d8-1f3fe-200d-2642-fe0f", + "uc_match": "1f9d8-1f3fe-2642-fe0f", + "uc_greedy": "1f9d8-1f3fe-2642", + "shortnames": [":man_in_lotus_position_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_in_lotus_position_tone5:": { + "uc_base": "1f9d8-1f3ff-2642", + "uc_output": "1f9d8-1f3ff-200d-2642-fe0f", + "uc_match": "1f9d8-1f3ff-2642-fe0f", + "uc_greedy": "1f9d8-1f3ff-2642", + "shortnames": [":man_in_lotus_position_dark_skin_tone:"], + "category": "activity" + }, + ":man_in_steamy_room_tone1:": { + "uc_base": "1f9d6-1f3fb-2642", + "uc_output": "1f9d6-1f3fb-200d-2642-fe0f", + "uc_match": "1f9d6-1f3fb-2642-fe0f", + "uc_greedy": "1f9d6-1f3fb-2642", + "shortnames": [":man_in_steamy_room_light_skin_tone:"], + "category": "people" + }, + ":man_in_steamy_room_tone2:": { + "uc_base": "1f9d6-1f3fc-2642", + "uc_output": "1f9d6-1f3fc-200d-2642-fe0f", + "uc_match": "1f9d6-1f3fc-2642-fe0f", + "uc_greedy": "1f9d6-1f3fc-2642", + "shortnames": [":man_in_steamy_room_medium_light_skin_tone:"], + "category": "people" + }, + ":man_in_steamy_room_tone3:": { + "uc_base": "1f9d6-1f3fd-2642", + "uc_output": "1f9d6-1f3fd-200d-2642-fe0f", + "uc_match": "1f9d6-1f3fd-2642-fe0f", + "uc_greedy": "1f9d6-1f3fd-2642", + "shortnames": [":man_in_steamy_room_medium_skin_tone:"], + "category": "people" + }, + ":man_in_steamy_room_tone4:": { + "uc_base": "1f9d6-1f3fe-2642", + "uc_output": "1f9d6-1f3fe-200d-2642-fe0f", + "uc_match": "1f9d6-1f3fe-2642-fe0f", + "uc_greedy": "1f9d6-1f3fe-2642", + "shortnames": [":man_in_steamy_room_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_in_steamy_room_tone5:": { + "uc_base": "1f9d6-1f3ff-2642", + "uc_output": "1f9d6-1f3ff-200d-2642-fe0f", + "uc_match": "1f9d6-1f3ff-2642-fe0f", + "uc_greedy": "1f9d6-1f3ff-2642", + "shortnames": [":man_in_steamy_room_dark_skin_tone:"], + "category": "people" + }, + ":man_judge_tone1:": { + "uc_base": "1f468-1f3fb-2696", + "uc_output": "1f468-1f3fb-200d-2696-fe0f", + "uc_match": "1f468-1f3fb-2696-fe0f", + "uc_greedy": "1f468-1f3fb-2696", + "shortnames": [":man_judge_light_skin_tone:"], + "category": "people" + }, + ":man_judge_tone2:": { + "uc_base": "1f468-1f3fc-2696", + "uc_output": "1f468-1f3fc-200d-2696-fe0f", + "uc_match": "1f468-1f3fc-2696-fe0f", + "uc_greedy": "1f468-1f3fc-2696", + "shortnames": [":man_judge_medium_light_skin_tone:"], + "category": "people" + }, + ":man_judge_tone3:": { + "uc_base": "1f468-1f3fd-2696", + "uc_output": "1f468-1f3fd-200d-2696-fe0f", + "uc_match": "1f468-1f3fd-2696-fe0f", + "uc_greedy": "1f468-1f3fd-2696", + "shortnames": [":man_judge_medium_skin_tone:"], + "category": "people" + }, + ":man_judge_tone4:": { + "uc_base": "1f468-1f3fe-2696", + "uc_output": "1f468-1f3fe-200d-2696-fe0f", + "uc_match": "1f468-1f3fe-2696-fe0f", + "uc_greedy": "1f468-1f3fe-2696", + "shortnames": [":man_judge_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_judge_tone5:": { + "uc_base": "1f468-1f3ff-2696", + "uc_output": "1f468-1f3ff-200d-2696-fe0f", + "uc_match": "1f468-1f3ff-2696-fe0f", + "uc_greedy": "1f468-1f3ff-2696", + "shortnames": [":man_judge_dark_skin_tone:"], + "category": "people" + }, + ":man_juggling_tone1:": { + "uc_base": "1f939-1f3fb-2642", + "uc_output": "1f939-1f3fb-200d-2642-fe0f", + "uc_match": "1f939-1f3fb-2642-fe0f", + "uc_greedy": "1f939-1f3fb-2642", + "shortnames": [":man_juggling_light_skin_tone:"], + "category": "activity" + }, + ":man_juggling_tone2:": { + "uc_base": "1f939-1f3fc-2642", + "uc_output": "1f939-1f3fc-200d-2642-fe0f", + "uc_match": "1f939-1f3fc-2642-fe0f", + "uc_greedy": "1f939-1f3fc-2642", + "shortnames": [":man_juggling_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_juggling_tone3:": { + "uc_base": "1f939-1f3fd-2642", + "uc_output": "1f939-1f3fd-200d-2642-fe0f", + "uc_match": "1f939-1f3fd-2642-fe0f", + "uc_greedy": "1f939-1f3fd-2642", + "shortnames": [":man_juggling_medium_skin_tone:"], + "category": "activity" + }, + ":man_juggling_tone4:": { + "uc_base": "1f939-1f3fe-2642", + "uc_output": "1f939-1f3fe-200d-2642-fe0f", + "uc_match": "1f939-1f3fe-2642-fe0f", + "uc_greedy": "1f939-1f3fe-2642", + "shortnames": [":man_juggling_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_juggling_tone5:": { + "uc_base": "1f939-1f3ff-2642", + "uc_output": "1f939-1f3ff-200d-2642-fe0f", + "uc_match": "1f939-1f3ff-2642-fe0f", + "uc_greedy": "1f939-1f3ff-2642", + "shortnames": [":man_juggling_dark_skin_tone:"], + "category": "activity" + }, + ":man_lifting_weights_tone1:": { + "uc_base": "1f3cb-1f3fb-2642", + "uc_output": "1f3cb-1f3fb-200d-2642-fe0f", + "uc_match": "1f3cb-fe0f-1f3fb-2642-fe0f", + "uc_greedy": "1f3cb-1f3fb-2642", + "shortnames": [":man_lifting_weights_light_skin_tone:"], + "category": "activity" + }, + ":man_lifting_weights_tone2:": { + "uc_base": "1f3cb-1f3fc-2642", + "uc_output": "1f3cb-1f3fc-200d-2642-fe0f", + "uc_match": "1f3cb-fe0f-1f3fc-2642-fe0f", + "uc_greedy": "1f3cb-1f3fc-2642", + "shortnames": [":man_lifting_weights_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_lifting_weights_tone3:": { + "uc_base": "1f3cb-1f3fd-2642", + "uc_output": "1f3cb-1f3fd-200d-2642-fe0f", + "uc_match": "1f3cb-fe0f-1f3fd-2642-fe0f", + "uc_greedy": "1f3cb-1f3fd-2642", + "shortnames": [":man_lifting_weights_medium_skin_tone:"], + "category": "activity" + }, + ":man_lifting_weights_tone4:": { + "uc_base": "1f3cb-1f3fe-2642", + "uc_output": "1f3cb-1f3fe-200d-2642-fe0f", + "uc_match": "1f3cb-fe0f-1f3fe-2642-fe0f", + "uc_greedy": "1f3cb-1f3fe-2642", + "shortnames": [":man_lifting_weights_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_lifting_weights_tone5:": { + "uc_base": "1f3cb-1f3ff-2642", + "uc_output": "1f3cb-1f3ff-200d-2642-fe0f", + "uc_match": "1f3cb-fe0f-1f3ff-2642-fe0f", + "uc_greedy": "1f3cb-1f3ff-2642", + "shortnames": [":man_lifting_weights_dark_skin_tone:"], + "category": "activity" + }, + ":man_mage_tone1:": { + "uc_base": "1f9d9-1f3fb-2642", + "uc_output": "1f9d9-1f3fb-200d-2642-fe0f", + "uc_match": "1f9d9-1f3fb-2642-fe0f", + "uc_greedy": "1f9d9-1f3fb-2642", + "shortnames": [":man_mage_light_skin_tone:"], + "category": "people" + }, + ":man_mage_tone2:": { + "uc_base": "1f9d9-1f3fc-2642", + "uc_output": "1f9d9-1f3fc-200d-2642-fe0f", + "uc_match": "1f9d9-1f3fc-2642-fe0f", + "uc_greedy": "1f9d9-1f3fc-2642", + "shortnames": [":man_mage_medium_light_skin_tone:"], + "category": "people" + }, + ":man_mage_tone3:": { + "uc_base": "1f9d9-1f3fd-2642", + "uc_output": "1f9d9-1f3fd-200d-2642-fe0f", + "uc_match": "1f9d9-1f3fd-2642-fe0f", + "uc_greedy": "1f9d9-1f3fd-2642", + "shortnames": [":man_mage_medium_skin_tone:"], + "category": "people" + }, + ":man_mage_tone4:": { + "uc_base": "1f9d9-1f3fe-2642", + "uc_output": "1f9d9-1f3fe-200d-2642-fe0f", + "uc_match": "1f9d9-1f3fe-2642-fe0f", + "uc_greedy": "1f9d9-1f3fe-2642", + "shortnames": [":man_mage_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_mage_tone5:": { + "uc_base": "1f9d9-1f3ff-2642", + "uc_output": "1f9d9-1f3ff-200d-2642-fe0f", + "uc_match": "1f9d9-1f3ff-2642-fe0f", + "uc_greedy": "1f9d9-1f3ff-2642", + "shortnames": [":man_mage_dark_skin_tone:"], + "category": "people" + }, + ":man_mountain_biking_tone1:": { + "uc_base": "1f6b5-1f3fb-2642", + "uc_output": "1f6b5-1f3fb-200d-2642-fe0f", + "uc_match": "1f6b5-1f3fb-2642-fe0f", + "uc_greedy": "1f6b5-1f3fb-2642", + "shortnames": [":man_mountain_biking_light_skin_tone:"], + "category": "activity" + }, + ":man_mountain_biking_tone2:": { + "uc_base": "1f6b5-1f3fc-2642", + "uc_output": "1f6b5-1f3fc-200d-2642-fe0f", + "uc_match": "1f6b5-1f3fc-2642-fe0f", + "uc_greedy": "1f6b5-1f3fc-2642", + "shortnames": [":man_mountain_biking_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_mountain_biking_tone3:": { + "uc_base": "1f6b5-1f3fd-2642", + "uc_output": "1f6b5-1f3fd-200d-2642-fe0f", + "uc_match": "1f6b5-1f3fd-2642-fe0f", + "uc_greedy": "1f6b5-1f3fd-2642", + "shortnames": [":man_mountain_biking_medium_skin_tone:"], + "category": "activity" + }, + ":man_mountain_biking_tone4:": { + "uc_base": "1f6b5-1f3fe-2642", + "uc_output": "1f6b5-1f3fe-200d-2642-fe0f", + "uc_match": "1f6b5-1f3fe-2642-fe0f", + "uc_greedy": "1f6b5-1f3fe-2642", + "shortnames": [":man_mountain_biking_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_mountain_biking_tone5:": { + "uc_base": "1f6b5-1f3ff-2642", + "uc_output": "1f6b5-1f3ff-200d-2642-fe0f", + "uc_match": "1f6b5-1f3ff-2642-fe0f", + "uc_greedy": "1f6b5-1f3ff-2642", + "shortnames": [":man_mountain_biking_dark_skin_tone:"], + "category": "activity" + }, + ":man_pilot_tone1:": { + "uc_base": "1f468-1f3fb-2708", + "uc_output": "1f468-1f3fb-200d-2708-fe0f", + "uc_match": "1f468-1f3fb-2708-fe0f", + "uc_greedy": "1f468-1f3fb-2708", + "shortnames": [":man_pilot_light_skin_tone:"], + "category": "people" + }, + ":man_pilot_tone2:": { + "uc_base": "1f468-1f3fc-2708", + "uc_output": "1f468-1f3fc-200d-2708-fe0f", + "uc_match": "1f468-1f3fc-2708-fe0f", + "uc_greedy": "1f468-1f3fc-2708", + "shortnames": [":man_pilot_medium_light_skin_tone:"], + "category": "people" + }, + ":man_pilot_tone3:": { + "uc_base": "1f468-1f3fd-2708", + "uc_output": "1f468-1f3fd-200d-2708-fe0f", + "uc_match": "1f468-1f3fd-2708-fe0f", + "uc_greedy": "1f468-1f3fd-2708", + "shortnames": [":man_pilot_medium_skin_tone:"], + "category": "people" + }, + ":man_pilot_tone4:": { + "uc_base": "1f468-1f3fe-2708", + "uc_output": "1f468-1f3fe-200d-2708-fe0f", + "uc_match": "1f468-1f3fe-2708-fe0f", + "uc_greedy": "1f468-1f3fe-2708", + "shortnames": [":man_pilot_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_pilot_tone5:": { + "uc_base": "1f468-1f3ff-2708", + "uc_output": "1f468-1f3ff-200d-2708-fe0f", + "uc_match": "1f468-1f3ff-2708-fe0f", + "uc_greedy": "1f468-1f3ff-2708", + "shortnames": [":man_pilot_dark_skin_tone:"], + "category": "people" + }, + ":man_playing_handball_tone1:": { + "uc_base": "1f93e-1f3fb-2642", + "uc_output": "1f93e-1f3fb-200d-2642-fe0f", + "uc_match": "1f93e-1f3fb-2642-fe0f", + "uc_greedy": "1f93e-1f3fb-2642", + "shortnames": [":man_playing_handball_light_skin_tone:"], + "category": "activity" + }, + ":man_playing_handball_tone2:": { + "uc_base": "1f93e-1f3fc-2642", + "uc_output": "1f93e-1f3fc-200d-2642-fe0f", + "uc_match": "1f93e-1f3fc-2642-fe0f", + "uc_greedy": "1f93e-1f3fc-2642", + "shortnames": [":man_playing_handball_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_playing_handball_tone3:": { + "uc_base": "1f93e-1f3fd-2642", + "uc_output": "1f93e-1f3fd-200d-2642-fe0f", + "uc_match": "1f93e-1f3fd-2642-fe0f", + "uc_greedy": "1f93e-1f3fd-2642", + "shortnames": [":man_playing_handball_medium_skin_tone:"], + "category": "activity" + }, + ":man_playing_handball_tone4:": { + "uc_base": "1f93e-1f3fe-2642", + "uc_output": "1f93e-1f3fe-200d-2642-fe0f", + "uc_match": "1f93e-1f3fe-2642-fe0f", + "uc_greedy": "1f93e-1f3fe-2642", + "shortnames": [":man_playing_handball_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_playing_handball_tone5:": { + "uc_base": "1f93e-1f3ff-2642", + "uc_output": "1f93e-1f3ff-200d-2642-fe0f", + "uc_match": "1f93e-1f3ff-2642-fe0f", + "uc_greedy": "1f93e-1f3ff-2642", + "shortnames": [":man_playing_handball_dark_skin_tone:"], + "category": "activity" + }, + ":man_playing_water_polo_tone1:": { + "uc_base": "1f93d-1f3fb-2642", + "uc_output": "1f93d-1f3fb-200d-2642-fe0f", + "uc_match": "1f93d-1f3fb-2642-fe0f", + "uc_greedy": "1f93d-1f3fb-2642", + "shortnames": [":man_playing_water_polo_light_skin_tone:"], + "category": "activity" + }, + ":man_playing_water_polo_tone2:": { + "uc_base": "1f93d-1f3fc-2642", + "uc_output": "1f93d-1f3fc-200d-2642-fe0f", + "uc_match": "1f93d-1f3fc-2642-fe0f", + "uc_greedy": "1f93d-1f3fc-2642", + "shortnames": [":man_playing_water_polo_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_playing_water_polo_tone3:": { + "uc_base": "1f93d-1f3fd-2642", + "uc_output": "1f93d-1f3fd-200d-2642-fe0f", + "uc_match": "1f93d-1f3fd-2642-fe0f", + "uc_greedy": "1f93d-1f3fd-2642", + "shortnames": [":man_playing_water_polo_medium_skin_tone:"], + "category": "activity" + }, + ":man_playing_water_polo_tone4:": { + "uc_base": "1f93d-1f3fe-2642", + "uc_output": "1f93d-1f3fe-200d-2642-fe0f", + "uc_match": "1f93d-1f3fe-2642-fe0f", + "uc_greedy": "1f93d-1f3fe-2642", + "shortnames": [":man_playing_water_polo_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_playing_water_polo_tone5:": { + "uc_base": "1f93d-1f3ff-2642", + "uc_output": "1f93d-1f3ff-200d-2642-fe0f", + "uc_match": "1f93d-1f3ff-2642-fe0f", + "uc_greedy": "1f93d-1f3ff-2642", + "shortnames": [":man_playing_water_polo_dark_skin_tone:"], + "category": "activity" + }, + ":man_police_officer_tone1:": { + "uc_base": "1f46e-1f3fb-2642", + "uc_output": "1f46e-1f3fb-200d-2642-fe0f", + "uc_match": "1f46e-1f3fb-2642-fe0f", + "uc_greedy": "1f46e-1f3fb-2642", + "shortnames": [":man_police_officer_light_skin_tone:"], + "category": "people" + }, + ":man_police_officer_tone2:": { + "uc_base": "1f46e-1f3fc-2642", + "uc_output": "1f46e-1f3fc-200d-2642-fe0f", + "uc_match": "1f46e-1f3fc-2642-fe0f", + "uc_greedy": "1f46e-1f3fc-2642", + "shortnames": [":man_police_officer_medium_light_skin_tone:"], + "category": "people" + }, + ":man_police_officer_tone3:": { + "uc_base": "1f46e-1f3fd-2642", + "uc_output": "1f46e-1f3fd-200d-2642-fe0f", + "uc_match": "1f46e-1f3fd-2642-fe0f", + "uc_greedy": "1f46e-1f3fd-2642", + "shortnames": [":man_police_officer_medium_skin_tone:"], + "category": "people" + }, + ":man_police_officer_tone4:": { + "uc_base": "1f46e-1f3fe-2642", + "uc_output": "1f46e-1f3fe-200d-2642-fe0f", + "uc_match": "1f46e-1f3fe-2642-fe0f", + "uc_greedy": "1f46e-1f3fe-2642", + "shortnames": [":man_police_officer_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_police_officer_tone5:": { + "uc_base": "1f46e-1f3ff-2642", + "uc_output": "1f46e-1f3ff-200d-2642-fe0f", + "uc_match": "1f46e-1f3ff-2642-fe0f", + "uc_greedy": "1f46e-1f3ff-2642", + "shortnames": [":man_police_officer_dark_skin_tone:"], + "category": "people" + }, + ":man_pouting_tone1:": { + "uc_base": "1f64e-1f3fb-2642", + "uc_output": "1f64e-1f3fb-200d-2642-fe0f", + "uc_match": "1f64e-1f3fb-2642-fe0f", + "uc_greedy": "1f64e-1f3fb-2642", + "shortnames": [":man_pouting_light_skin_tone:"], + "category": "people" + }, + ":man_pouting_tone2:": { + "uc_base": "1f64e-1f3fc-2642", + "uc_output": "1f64e-1f3fc-200d-2642-fe0f", + "uc_match": "1f64e-1f3fc-2642-fe0f", + "uc_greedy": "1f64e-1f3fc-2642", + "shortnames": [":man_pouting_medium_light_skin_tone:"], + "category": "people" + }, + ":man_pouting_tone3:": { + "uc_base": "1f64e-1f3fd-2642", + "uc_output": "1f64e-1f3fd-200d-2642-fe0f", + "uc_match": "1f64e-1f3fd-2642-fe0f", + "uc_greedy": "1f64e-1f3fd-2642", + "shortnames": [":man_pouting_medium_skin_tone:"], + "category": "people" + }, + ":man_pouting_tone4:": { + "uc_base": "1f64e-1f3fe-2642", + "uc_output": "1f64e-1f3fe-200d-2642-fe0f", + "uc_match": "1f64e-1f3fe-2642-fe0f", + "uc_greedy": "1f64e-1f3fe-2642", + "shortnames": [":man_pouting_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_pouting_tone5:": { + "uc_base": "1f64e-1f3ff-2642", + "uc_output": "1f64e-1f3ff-200d-2642-fe0f", + "uc_match": "1f64e-1f3ff-2642-fe0f", + "uc_greedy": "1f64e-1f3ff-2642", + "shortnames": [":man_pouting_dark_skin_tone:"], + "category": "people" + }, + ":man_raising_hand_tone1:": { + "uc_base": "1f64b-1f3fb-2642", + "uc_output": "1f64b-1f3fb-200d-2642-fe0f", + "uc_match": "1f64b-1f3fb-2642-fe0f", + "uc_greedy": "1f64b-1f3fb-2642", + "shortnames": [":man_raising_hand_light_skin_tone:"], + "category": "people" + }, + ":man_raising_hand_tone2:": { + "uc_base": "1f64b-1f3fc-2642", + "uc_output": "1f64b-1f3fc-200d-2642-fe0f", + "uc_match": "1f64b-1f3fc-2642-fe0f", + "uc_greedy": "1f64b-1f3fc-2642", + "shortnames": [":man_raising_hand_medium_light_skin_tone:"], + "category": "people" + }, + ":man_raising_hand_tone3:": { + "uc_base": "1f64b-1f3fd-2642", + "uc_output": "1f64b-1f3fd-200d-2642-fe0f", + "uc_match": "1f64b-1f3fd-2642-fe0f", + "uc_greedy": "1f64b-1f3fd-2642", + "shortnames": [":man_raising_hand_medium_skin_tone:"], + "category": "people" + }, + ":man_raising_hand_tone4:": { + "uc_base": "1f64b-1f3fe-2642", + "uc_output": "1f64b-1f3fe-200d-2642-fe0f", + "uc_match": "1f64b-1f3fe-2642-fe0f", + "uc_greedy": "1f64b-1f3fe-2642", + "shortnames": [":man_raising_hand_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_raising_hand_tone5:": { + "uc_base": "1f64b-1f3ff-2642", + "uc_output": "1f64b-1f3ff-200d-2642-fe0f", + "uc_match": "1f64b-1f3ff-2642-fe0f", + "uc_greedy": "1f64b-1f3ff-2642", + "shortnames": [":man_raising_hand_dark_skin_tone:"], + "category": "people" + }, + ":man_rowing_boat_tone1:": { + "uc_base": "1f6a3-1f3fb-2642", + "uc_output": "1f6a3-1f3fb-200d-2642-fe0f", + "uc_match": "1f6a3-1f3fb-2642-fe0f", + "uc_greedy": "1f6a3-1f3fb-2642", + "shortnames": [":man_rowing_boat_light_skin_tone:"], + "category": "activity" + }, + ":man_rowing_boat_tone2:": { + "uc_base": "1f6a3-1f3fc-2642", + "uc_output": "1f6a3-1f3fc-200d-2642-fe0f", + "uc_match": "1f6a3-1f3fc-2642-fe0f", + "uc_greedy": "1f6a3-1f3fc-2642", + "shortnames": [":man_rowing_boat_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_rowing_boat_tone3:": { + "uc_base": "1f6a3-1f3fd-2642", + "uc_output": "1f6a3-1f3fd-200d-2642-fe0f", + "uc_match": "1f6a3-1f3fd-2642-fe0f", + "uc_greedy": "1f6a3-1f3fd-2642", + "shortnames": [":man_rowing_boat_medium_skin_tone:"], + "category": "activity" + }, + ":man_rowing_boat_tone4:": { + "uc_base": "1f6a3-1f3fe-2642", + "uc_output": "1f6a3-1f3fe-200d-2642-fe0f", + "uc_match": "1f6a3-1f3fe-2642-fe0f", + "uc_greedy": "1f6a3-1f3fe-2642", + "shortnames": [":man_rowing_boat_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_rowing_boat_tone5:": { + "uc_base": "1f6a3-1f3ff-2642", + "uc_output": "1f6a3-1f3ff-200d-2642-fe0f", + "uc_match": "1f6a3-1f3ff-2642-fe0f", + "uc_greedy": "1f6a3-1f3ff-2642", + "shortnames": [":man_rowing_boat_dark_skin_tone:"], + "category": "activity" + }, + ":man_running_tone1:": { + "uc_base": "1f3c3-1f3fb-2642", + "uc_output": "1f3c3-1f3fb-200d-2642-fe0f", + "uc_match": "1f3c3-1f3fb-2642-fe0f", + "uc_greedy": "1f3c3-1f3fb-2642", + "shortnames": [":man_running_light_skin_tone:"], + "category": "people" + }, + ":man_running_tone2:": { + "uc_base": "1f3c3-1f3fc-2642", + "uc_output": "1f3c3-1f3fc-200d-2642-fe0f", + "uc_match": "1f3c3-1f3fc-2642-fe0f", + "uc_greedy": "1f3c3-1f3fc-2642", + "shortnames": [":man_running_medium_light_skin_tone:"], + "category": "people" + }, + ":man_running_tone3:": { + "uc_base": "1f3c3-1f3fd-2642", + "uc_output": "1f3c3-1f3fd-200d-2642-fe0f", + "uc_match": "1f3c3-1f3fd-2642-fe0f", + "uc_greedy": "1f3c3-1f3fd-2642", + "shortnames": [":man_running_medium_skin_tone:"], + "category": "people" + }, + ":man_running_tone4:": { + "uc_base": "1f3c3-1f3fe-2642", + "uc_output": "1f3c3-1f3fe-200d-2642-fe0f", + "uc_match": "1f3c3-1f3fe-2642-fe0f", + "uc_greedy": "1f3c3-1f3fe-2642", + "shortnames": [":man_running_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_running_tone5:": { + "uc_base": "1f3c3-1f3ff-2642", + "uc_output": "1f3c3-1f3ff-200d-2642-fe0f", + "uc_match": "1f3c3-1f3ff-2642-fe0f", + "uc_greedy": "1f3c3-1f3ff-2642", + "shortnames": [":man_running_dark_skin_tone:"], + "category": "people" + }, + ":man_shrugging_tone1:": { + "uc_base": "1f937-1f3fb-2642", + "uc_output": "1f937-1f3fb-200d-2642-fe0f", + "uc_match": "1f937-1f3fb-2642-fe0f", + "uc_greedy": "1f937-1f3fb-2642", + "shortnames": [":man_shrugging_light_skin_tone:"], + "category": "people" + }, + ":man_shrugging_tone2:": { + "uc_base": "1f937-1f3fc-2642", + "uc_output": "1f937-1f3fc-200d-2642-fe0f", + "uc_match": "1f937-1f3fc-2642-fe0f", + "uc_greedy": "1f937-1f3fc-2642", + "shortnames": [":man_shrugging_medium_light_skin_tone:"], + "category": "people" + }, + ":man_shrugging_tone3:": { + "uc_base": "1f937-1f3fd-2642", + "uc_output": "1f937-1f3fd-200d-2642-fe0f", + "uc_match": "1f937-1f3fd-2642-fe0f", + "uc_greedy": "1f937-1f3fd-2642", + "shortnames": [":man_shrugging_medium_skin_tone:"], + "category": "people" + }, + ":man_shrugging_tone4:": { + "uc_base": "1f937-1f3fe-2642", + "uc_output": "1f937-1f3fe-200d-2642-fe0f", + "uc_match": "1f937-1f3fe-2642-fe0f", + "uc_greedy": "1f937-1f3fe-2642", + "shortnames": [":man_shrugging_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_shrugging_tone5:": { + "uc_base": "1f937-1f3ff-2642", + "uc_output": "1f937-1f3ff-200d-2642-fe0f", + "uc_match": "1f937-1f3ff-2642-fe0f", + "uc_greedy": "1f937-1f3ff-2642", + "shortnames": [":man_shrugging_dark_skin_tone:"], + "category": "people" + }, + ":man_surfing_tone1:": { + "uc_base": "1f3c4-1f3fb-2642", + "uc_output": "1f3c4-1f3fb-200d-2642-fe0f", + "uc_match": "1f3c4-1f3fb-2642-fe0f", + "uc_greedy": "1f3c4-1f3fb-2642", + "shortnames": [":man_surfing_light_skin_tone:"], + "category": "activity" + }, + ":man_surfing_tone2:": { + "uc_base": "1f3c4-1f3fc-2642", + "uc_output": "1f3c4-1f3fc-200d-2642-fe0f", + "uc_match": "1f3c4-1f3fc-2642-fe0f", + "uc_greedy": "1f3c4-1f3fc-2642", + "shortnames": [":man_surfing_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_surfing_tone3:": { + "uc_base": "1f3c4-1f3fd-2642", + "uc_output": "1f3c4-1f3fd-200d-2642-fe0f", + "uc_match": "1f3c4-1f3fd-2642-fe0f", + "uc_greedy": "1f3c4-1f3fd-2642", + "shortnames": [":man_surfing_medium_skin_tone:"], + "category": "activity" + }, + ":man_surfing_tone4:": { + "uc_base": "1f3c4-1f3fe-2642", + "uc_output": "1f3c4-1f3fe-200d-2642-fe0f", + "uc_match": "1f3c4-1f3fe-2642-fe0f", + "uc_greedy": "1f3c4-1f3fe-2642", + "shortnames": [":man_surfing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_surfing_tone5:": { + "uc_base": "1f3c4-1f3ff-2642", + "uc_output": "1f3c4-1f3ff-200d-2642-fe0f", + "uc_match": "1f3c4-1f3ff-2642-fe0f", + "uc_greedy": "1f3c4-1f3ff-2642", + "shortnames": [":man_surfing_dark_skin_tone:"], + "category": "activity" + }, + ":man_swimming_tone1:": { + "uc_base": "1f3ca-1f3fb-2642", + "uc_output": "1f3ca-1f3fb-200d-2642-fe0f", + "uc_match": "1f3ca-1f3fb-2642-fe0f", + "uc_greedy": "1f3ca-1f3fb-2642", + "shortnames": [":man_swimming_light_skin_tone:"], + "category": "activity" + }, + ":man_swimming_tone2:": { + "uc_base": "1f3ca-1f3fc-2642", + "uc_output": "1f3ca-1f3fc-200d-2642-fe0f", + "uc_match": "1f3ca-1f3fc-2642-fe0f", + "uc_greedy": "1f3ca-1f3fc-2642", + "shortnames": [":man_swimming_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_swimming_tone3:": { + "uc_base": "1f3ca-1f3fd-2642", + "uc_output": "1f3ca-1f3fd-200d-2642-fe0f", + "uc_match": "1f3ca-1f3fd-2642-fe0f", + "uc_greedy": "1f3ca-1f3fd-2642", + "shortnames": [":man_swimming_medium_skin_tone:"], + "category": "activity" + }, + ":man_swimming_tone4:": { + "uc_base": "1f3ca-1f3fe-2642", + "uc_output": "1f3ca-1f3fe-200d-2642-fe0f", + "uc_match": "1f3ca-1f3fe-2642-fe0f", + "uc_greedy": "1f3ca-1f3fe-2642", + "shortnames": [":man_swimming_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_swimming_tone5:": { + "uc_base": "1f3ca-1f3ff-2642", + "uc_output": "1f3ca-1f3ff-200d-2642-fe0f", + "uc_match": "1f3ca-1f3ff-2642-fe0f", + "uc_greedy": "1f3ca-1f3ff-2642", + "shortnames": [":man_swimming_dark_skin_tone:"], + "category": "activity" + }, + ":man_tipping_hand_tone1:": { + "uc_base": "1f481-1f3fb-2642", + "uc_output": "1f481-1f3fb-200d-2642-fe0f", + "uc_match": "1f481-1f3fb-2642-fe0f", + "uc_greedy": "1f481-1f3fb-2642", + "shortnames": [":man_tipping_hand_light_skin_tone:"], + "category": "people" + }, + ":man_tipping_hand_tone2:": { + "uc_base": "1f481-1f3fc-2642", + "uc_output": "1f481-1f3fc-200d-2642-fe0f", + "uc_match": "1f481-1f3fc-2642-fe0f", + "uc_greedy": "1f481-1f3fc-2642", + "shortnames": [":man_tipping_hand_medium_light_skin_tone:"], + "category": "people" + }, + ":man_tipping_hand_tone3:": { + "uc_base": "1f481-1f3fd-2642", + "uc_output": "1f481-1f3fd-200d-2642-fe0f", + "uc_match": "1f481-1f3fd-2642-fe0f", + "uc_greedy": "1f481-1f3fd-2642", + "shortnames": [":man_tipping_hand_medium_skin_tone:"], + "category": "people" + }, + ":man_tipping_hand_tone4:": { + "uc_base": "1f481-1f3fe-2642", + "uc_output": "1f481-1f3fe-200d-2642-fe0f", + "uc_match": "1f481-1f3fe-2642-fe0f", + "uc_greedy": "1f481-1f3fe-2642", + "shortnames": [":man_tipping_hand_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_tipping_hand_tone5:": { + "uc_base": "1f481-1f3ff-2642", + "uc_output": "1f481-1f3ff-200d-2642-fe0f", + "uc_match": "1f481-1f3ff-2642-fe0f", + "uc_greedy": "1f481-1f3ff-2642", + "shortnames": [":man_tipping_hand_dark_skin_tone:"], + "category": "people" + }, + ":man_vampire_tone1:": { + "uc_base": "1f9db-1f3fb-2642", + "uc_output": "1f9db-1f3fb-200d-2642-fe0f", + "uc_match": "1f9db-1f3fb-2642-fe0f", + "uc_greedy": "1f9db-1f3fb-2642", + "shortnames": [":man_vampire_light_skin_tone:"], + "category": "people" + }, + ":man_vampire_tone2:": { + "uc_base": "1f9db-1f3fc-2642", + "uc_output": "1f9db-1f3fc-200d-2642-fe0f", + "uc_match": "1f9db-1f3fc-2642-fe0f", + "uc_greedy": "1f9db-1f3fc-2642", + "shortnames": [":man_vampire_medium_light_skin_tone:"], + "category": "people" + }, + ":man_vampire_tone3:": { + "uc_base": "1f9db-1f3fd-2642", + "uc_output": "1f9db-1f3fd-200d-2642-fe0f", + "uc_match": "1f9db-1f3fd-2642-fe0f", + "uc_greedy": "1f9db-1f3fd-2642", + "shortnames": [":man_vampire_medium_skin_tone:"], + "category": "people" + }, + ":man_vampire_tone4:": { + "uc_base": "1f9db-1f3fe-2642", + "uc_output": "1f9db-1f3fe-200d-2642-fe0f", + "uc_match": "1f9db-1f3fe-2642-fe0f", + "uc_greedy": "1f9db-1f3fe-2642", + "shortnames": [":man_vampire_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_vampire_tone5:": { + "uc_base": "1f9db-1f3ff-2642", + "uc_output": "1f9db-1f3ff-200d-2642-fe0f", + "uc_match": "1f9db-1f3ff-2642-fe0f", + "uc_greedy": "1f9db-1f3ff-2642", + "shortnames": [":man_vampire_dark_skin_tone:"], + "category": "people" + }, + ":man_walking_tone1:": { + "uc_base": "1f6b6-1f3fb-2642", + "uc_output": "1f6b6-1f3fb-200d-2642-fe0f", + "uc_match": "1f6b6-1f3fb-2642-fe0f", + "uc_greedy": "1f6b6-1f3fb-2642", + "shortnames": [":man_walking_light_skin_tone:"], + "category": "people" + }, + ":man_walking_tone2:": { + "uc_base": "1f6b6-1f3fc-2642", + "uc_output": "1f6b6-1f3fc-200d-2642-fe0f", + "uc_match": "1f6b6-1f3fc-2642-fe0f", + "uc_greedy": "1f6b6-1f3fc-2642", + "shortnames": [":man_walking_medium_light_skin_tone:"], + "category": "people" + }, + ":man_walking_tone3:": { + "uc_base": "1f6b6-1f3fd-2642", + "uc_output": "1f6b6-1f3fd-200d-2642-fe0f", + "uc_match": "1f6b6-1f3fd-2642-fe0f", + "uc_greedy": "1f6b6-1f3fd-2642", + "shortnames": [":man_walking_medium_skin_tone:"], + "category": "people" + }, + ":man_walking_tone4:": { + "uc_base": "1f6b6-1f3fe-2642", + "uc_output": "1f6b6-1f3fe-200d-2642-fe0f", + "uc_match": "1f6b6-1f3fe-2642-fe0f", + "uc_greedy": "1f6b6-1f3fe-2642", + "shortnames": [":man_walking_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_walking_tone5:": { + "uc_base": "1f6b6-1f3ff-2642", + "uc_output": "1f6b6-1f3ff-200d-2642-fe0f", + "uc_match": "1f6b6-1f3ff-2642-fe0f", + "uc_greedy": "1f6b6-1f3ff-2642", + "shortnames": [":man_walking_dark_skin_tone:"], + "category": "people" + }, + ":man_wearing_turban_tone1:": { + "uc_base": "1f473-1f3fb-2642", + "uc_output": "1f473-1f3fb-200d-2642-fe0f", + "uc_match": "1f473-1f3fb-2642-fe0f", + "uc_greedy": "1f473-1f3fb-2642", + "shortnames": [":man_wearing_turban_light_skin_tone:"], + "category": "people" + }, + ":man_wearing_turban_tone2:": { + "uc_base": "1f473-1f3fc-2642", + "uc_output": "1f473-1f3fc-200d-2642-fe0f", + "uc_match": "1f473-1f3fc-2642-fe0f", + "uc_greedy": "1f473-1f3fc-2642", + "shortnames": [":man_wearing_turban_medium_light_skin_tone:"], + "category": "people" + }, + ":man_wearing_turban_tone3:": { + "uc_base": "1f473-1f3fd-2642", + "uc_output": "1f473-1f3fd-200d-2642-fe0f", + "uc_match": "1f473-1f3fd-2642-fe0f", + "uc_greedy": "1f473-1f3fd-2642", + "shortnames": [":man_wearing_turban_medium_skin_tone:"], + "category": "people" + }, + ":man_wearing_turban_tone4:": { + "uc_base": "1f473-1f3fe-2642", + "uc_output": "1f473-1f3fe-200d-2642-fe0f", + "uc_match": "1f473-1f3fe-2642-fe0f", + "uc_greedy": "1f473-1f3fe-2642", + "shortnames": [":man_wearing_turban_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_wearing_turban_tone5:": { + "uc_base": "1f473-1f3ff-2642", + "uc_output": "1f473-1f3ff-200d-2642-fe0f", + "uc_match": "1f473-1f3ff-2642-fe0f", + "uc_greedy": "1f473-1f3ff-2642", + "shortnames": [":man_wearing_turban_dark_skin_tone:"], + "category": "people" + }, + ":mermaid_tone1:": { + "uc_base": "1f9dc-1f3fb-2640", + "uc_output": "1f9dc-1f3fb-200d-2640-fe0f", + "uc_match": "1f9dc-1f3fb-2640-fe0f", + "uc_greedy": "1f9dc-1f3fb-2640", + "shortnames": [":mermaid_light_skin_tone:"], + "category": "people" + }, + ":mermaid_tone2:": { + "uc_base": "1f9dc-1f3fc-2640", + "uc_output": "1f9dc-1f3fc-200d-2640-fe0f", + "uc_match": "1f9dc-1f3fc-2640-fe0f", + "uc_greedy": "1f9dc-1f3fc-2640", + "shortnames": [":mermaid_medium_light_skin_tone:"], + "category": "people" + }, + ":mermaid_tone3:": { + "uc_base": "1f9dc-1f3fd-2640", + "uc_output": "1f9dc-1f3fd-200d-2640-fe0f", + "uc_match": "1f9dc-1f3fd-2640-fe0f", + "uc_greedy": "1f9dc-1f3fd-2640", + "shortnames": [":mermaid_medium_skin_tone:"], + "category": "people" + }, + ":mermaid_tone4:": { + "uc_base": "1f9dc-1f3fe-2640", + "uc_output": "1f9dc-1f3fe-200d-2640-fe0f", + "uc_match": "1f9dc-1f3fe-2640-fe0f", + "uc_greedy": "1f9dc-1f3fe-2640", + "shortnames": [":mermaid_medium_dark_skin_tone:"], + "category": "people" + }, + ":mermaid_tone5:": { + "uc_base": "1f9dc-1f3ff-2640", + "uc_output": "1f9dc-1f3ff-200d-2640-fe0f", + "uc_match": "1f9dc-1f3ff-2640-fe0f", + "uc_greedy": "1f9dc-1f3ff-2640", + "shortnames": [":mermaid_dark_skin_tone:"], + "category": "people" + }, + ":merman_tone1:": { + "uc_base": "1f9dc-1f3fb-2642", + "uc_output": "1f9dc-1f3fb-200d-2642-fe0f", + "uc_match": "1f9dc-1f3fb-2642-fe0f", + "uc_greedy": "1f9dc-1f3fb-2642", + "shortnames": [":merman_light_skin_tone:"], + "category": "people" + }, + ":merman_tone2:": { + "uc_base": "1f9dc-1f3fc-2642", + "uc_output": "1f9dc-1f3fc-200d-2642-fe0f", + "uc_match": "1f9dc-1f3fc-2642-fe0f", + "uc_greedy": "1f9dc-1f3fc-2642", + "shortnames": [":merman_medium_light_skin_tone:"], + "category": "people" + }, + ":merman_tone3:": { + "uc_base": "1f9dc-1f3fd-2642", + "uc_output": "1f9dc-1f3fd-200d-2642-fe0f", + "uc_match": "1f9dc-1f3fd-2642-fe0f", + "uc_greedy": "1f9dc-1f3fd-2642", + "shortnames": [":merman_medium_skin_tone:"], + "category": "people" + }, + ":merman_tone4:": { + "uc_base": "1f9dc-1f3fe-2642", + "uc_output": "1f9dc-1f3fe-200d-2642-fe0f", + "uc_match": "1f9dc-1f3fe-2642-fe0f", + "uc_greedy": "1f9dc-1f3fe-2642", + "shortnames": [":merman_medium_dark_skin_tone:"], + "category": "people" + }, + ":merman_tone5:": { + "uc_base": "1f9dc-1f3ff-2642", + "uc_output": "1f9dc-1f3ff-200d-2642-fe0f", + "uc_match": "1f9dc-1f3ff-2642-fe0f", + "uc_greedy": "1f9dc-1f3ff-2642", + "shortnames": [":merman_dark_skin_tone:"], + "category": "people" + }, + ":woman_biking_tone1:": { + "uc_base": "1f6b4-1f3fb-2640", + "uc_output": "1f6b4-1f3fb-200d-2640-fe0f", + "uc_match": "1f6b4-1f3fb-2640-fe0f", + "uc_greedy": "1f6b4-1f3fb-2640", + "shortnames": [":woman_biking_light_skin_tone:"], + "category": "activity" + }, + ":woman_biking_tone2:": { + "uc_base": "1f6b4-1f3fc-2640", + "uc_output": "1f6b4-1f3fc-200d-2640-fe0f", + "uc_match": "1f6b4-1f3fc-2640-fe0f", + "uc_greedy": "1f6b4-1f3fc-2640", + "shortnames": [":woman_biking_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_biking_tone3:": { + "uc_base": "1f6b4-1f3fd-2640", + "uc_output": "1f6b4-1f3fd-200d-2640-fe0f", + "uc_match": "1f6b4-1f3fd-2640-fe0f", + "uc_greedy": "1f6b4-1f3fd-2640", + "shortnames": [":woman_biking_medium_skin_tone:"], + "category": "activity" + }, + ":woman_biking_tone4:": { + "uc_base": "1f6b4-1f3fe-2640", + "uc_output": "1f6b4-1f3fe-200d-2640-fe0f", + "uc_match": "1f6b4-1f3fe-2640-fe0f", + "uc_greedy": "1f6b4-1f3fe-2640", + "shortnames": [":woman_biking_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_biking_tone5:": { + "uc_base": "1f6b4-1f3ff-2640", + "uc_output": "1f6b4-1f3ff-200d-2640-fe0f", + "uc_match": "1f6b4-1f3ff-2640-fe0f", + "uc_greedy": "1f6b4-1f3ff-2640", + "shortnames": [":woman_biking_dark_skin_tone:"], + "category": "activity" + }, + ":woman_bowing_tone1:": { + "uc_base": "1f647-1f3fb-2640", + "uc_output": "1f647-1f3fb-200d-2640-fe0f", + "uc_match": "1f647-1f3fb-2640-fe0f", + "uc_greedy": "1f647-1f3fb-2640", + "shortnames": [":woman_bowing_light_skin_tone:"], + "category": "people" + }, + ":woman_bowing_tone2:": { + "uc_base": "1f647-1f3fc-2640", + "uc_output": "1f647-1f3fc-200d-2640-fe0f", + "uc_match": "1f647-1f3fc-2640-fe0f", + "uc_greedy": "1f647-1f3fc-2640", + "shortnames": [":woman_bowing_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_bowing_tone3:": { + "uc_base": "1f647-1f3fd-2640", + "uc_output": "1f647-1f3fd-200d-2640-fe0f", + "uc_match": "1f647-1f3fd-2640-fe0f", + "uc_greedy": "1f647-1f3fd-2640", + "shortnames": [":woman_bowing_medium_skin_tone:"], + "category": "people" + }, + ":woman_bowing_tone4:": { + "uc_base": "1f647-1f3fe-2640", + "uc_output": "1f647-1f3fe-200d-2640-fe0f", + "uc_match": "1f647-1f3fe-2640-fe0f", + "uc_greedy": "1f647-1f3fe-2640", + "shortnames": [":woman_bowing_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_bowing_tone5:": { + "uc_base": "1f647-1f3ff-2640", + "uc_output": "1f647-1f3ff-200d-2640-fe0f", + "uc_match": "1f647-1f3ff-2640-fe0f", + "uc_greedy": "1f647-1f3ff-2640", + "shortnames": [":woman_bowing_dark_skin_tone:"], + "category": "people" + }, + ":woman_cartwheeling_tone1:": { + "uc_base": "1f938-1f3fb-2640", + "uc_output": "1f938-1f3fb-200d-2640-fe0f", + "uc_match": "1f938-1f3fb-2640-fe0f", + "uc_greedy": "1f938-1f3fb-2640", + "shortnames": [":woman_cartwheeling_light_skin_tone:"], + "category": "activity" + }, + ":woman_cartwheeling_tone2:": { + "uc_base": "1f938-1f3fc-2640", + "uc_output": "1f938-1f3fc-200d-2640-fe0f", + "uc_match": "1f938-1f3fc-2640-fe0f", + "uc_greedy": "1f938-1f3fc-2640", + "shortnames": [":woman_cartwheeling_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_cartwheeling_tone3:": { + "uc_base": "1f938-1f3fd-2640", + "uc_output": "1f938-1f3fd-200d-2640-fe0f", + "uc_match": "1f938-1f3fd-2640-fe0f", + "uc_greedy": "1f938-1f3fd-2640", + "shortnames": [":woman_cartwheeling_medium_skin_tone:"], + "category": "activity" + }, + ":woman_cartwheeling_tone4:": { + "uc_base": "1f938-1f3fe-2640", + "uc_output": "1f938-1f3fe-200d-2640-fe0f", + "uc_match": "1f938-1f3fe-2640-fe0f", + "uc_greedy": "1f938-1f3fe-2640", + "shortnames": [":woman_cartwheeling_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_cartwheeling_tone5:": { + "uc_base": "1f938-1f3ff-2640", + "uc_output": "1f938-1f3ff-200d-2640-fe0f", + "uc_match": "1f938-1f3ff-2640-fe0f", + "uc_greedy": "1f938-1f3ff-2640", + "shortnames": [":woman_cartwheeling_dark_skin_tone:"], + "category": "activity" + }, + ":woman_climbing_tone1:": { + "uc_base": "1f9d7-1f3fb-2640", + "uc_output": "1f9d7-1f3fb-200d-2640-fe0f", + "uc_match": "1f9d7-1f3fb-2640-fe0f", + "uc_greedy": "1f9d7-1f3fb-2640", + "shortnames": [":woman_climbing_light_skin_tone:"], + "category": "activity" + }, + ":woman_climbing_tone2:": { + "uc_base": "1f9d7-1f3fc-2640", + "uc_output": "1f9d7-1f3fc-200d-2640-fe0f", + "uc_match": "1f9d7-1f3fc-2640-fe0f", + "uc_greedy": "1f9d7-1f3fc-2640", + "shortnames": [":woman_climbing_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_climbing_tone3:": { + "uc_base": "1f9d7-1f3fd-2640", + "uc_output": "1f9d7-1f3fd-200d-2640-fe0f", + "uc_match": "1f9d7-1f3fd-2640-fe0f", + "uc_greedy": "1f9d7-1f3fd-2640", + "shortnames": [":woman_climbing_medium_skin_tone:"], + "category": "activity" + }, + ":woman_climbing_tone4:": { + "uc_base": "1f9d7-1f3fe-2640", + "uc_output": "1f9d7-1f3fe-200d-2640-fe0f", + "uc_match": "1f9d7-1f3fe-2640-fe0f", + "uc_greedy": "1f9d7-1f3fe-2640", + "shortnames": [":woman_climbing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_climbing_tone5:": { + "uc_base": "1f9d7-1f3ff-2640", + "uc_output": "1f9d7-1f3ff-200d-2640-fe0f", + "uc_match": "1f9d7-1f3ff-2640-fe0f", + "uc_greedy": "1f9d7-1f3ff-2640", + "shortnames": [":woman_climbing_dark_skin_tone:"], + "category": "activity" + }, + ":woman_construction_worker_tone1:": { + "uc_base": "1f477-1f3fb-2640", + "uc_output": "1f477-1f3fb-200d-2640-fe0f", + "uc_match": "1f477-1f3fb-2640-fe0f", + "uc_greedy": "1f477-1f3fb-2640", + "shortnames": [":woman_construction_worker_light_skin_tone:"], + "category": "people" + }, + ":woman_construction_worker_tone2:": { + "uc_base": "1f477-1f3fc-2640", + "uc_output": "1f477-1f3fc-200d-2640-fe0f", + "uc_match": "1f477-1f3fc-2640-fe0f", + "uc_greedy": "1f477-1f3fc-2640", + "shortnames": [":woman_construction_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_construction_worker_tone3:": { + "uc_base": "1f477-1f3fd-2640", + "uc_output": "1f477-1f3fd-200d-2640-fe0f", + "uc_match": "1f477-1f3fd-2640-fe0f", + "uc_greedy": "1f477-1f3fd-2640", + "shortnames": [":woman_construction_worker_medium_skin_tone:"], + "category": "people" + }, + ":woman_construction_worker_tone4:": { + "uc_base": "1f477-1f3fe-2640", + "uc_output": "1f477-1f3fe-200d-2640-fe0f", + "uc_match": "1f477-1f3fe-2640-fe0f", + "uc_greedy": "1f477-1f3fe-2640", + "shortnames": [":woman_construction_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_construction_worker_tone5:": { + "uc_base": "1f477-1f3ff-2640", + "uc_output": "1f477-1f3ff-200d-2640-fe0f", + "uc_match": "1f477-1f3ff-2640-fe0f", + "uc_greedy": "1f477-1f3ff-2640", + "shortnames": [":woman_construction_worker_dark_skin_tone:"], + "category": "people" + }, + ":woman_detective_tone1:": { + "uc_base": "1f575-1f3fb-2640", + "uc_output": "1f575-1f3fb-200d-2640-fe0f", + "uc_match": "1f575-fe0f-1f3fb-2640-fe0f", + "uc_greedy": "1f575-1f3fb-2640", + "shortnames": [":woman_detective_light_skin_tone:"], + "category": "people" + }, + ":woman_detective_tone2:": { + "uc_base": "1f575-1f3fc-2640", + "uc_output": "1f575-1f3fc-200d-2640-fe0f", + "uc_match": "1f575-fe0f-1f3fc-2640-fe0f", + "uc_greedy": "1f575-1f3fc-2640", + "shortnames": [":woman_detective_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_detective_tone3:": { + "uc_base": "1f575-1f3fd-2640", + "uc_output": "1f575-1f3fd-200d-2640-fe0f", + "uc_match": "1f575-fe0f-1f3fd-2640-fe0f", + "uc_greedy": "1f575-1f3fd-2640", + "shortnames": [":woman_detective_medium_skin_tone:"], + "category": "people" + }, + ":woman_detective_tone4:": { + "uc_base": "1f575-1f3fe-2640", + "uc_output": "1f575-1f3fe-200d-2640-fe0f", + "uc_match": "1f575-fe0f-1f3fe-2640-fe0f", + "uc_greedy": "1f575-1f3fe-2640", + "shortnames": [":woman_detective_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_detective_tone5:": { + "uc_base": "1f575-1f3ff-2640", + "uc_output": "1f575-1f3ff-200d-2640-fe0f", + "uc_match": "1f575-fe0f-1f3ff-2640-fe0f", + "uc_greedy": "1f575-1f3ff-2640", + "shortnames": [":woman_detective_dark_skin_tone:"], + "category": "people" + }, + ":woman_elf_tone1:": { + "uc_base": "1f9dd-1f3fb-2640", + "uc_output": "1f9dd-1f3fb-200d-2640-fe0f", + "uc_match": "1f9dd-1f3fb-2640-fe0f", + "uc_greedy": "1f9dd-1f3fb-2640", + "shortnames": [":woman_elf_light_skin_tone:"], + "category": "people" + }, + ":woman_elf_tone2:": { + "uc_base": "1f9dd-1f3fc-2640", + "uc_output": "1f9dd-1f3fc-200d-2640-fe0f", + "uc_match": "1f9dd-1f3fc-2640-fe0f", + "uc_greedy": "1f9dd-1f3fc-2640", + "shortnames": [":woman_elf_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_elf_tone3:": { + "uc_base": "1f9dd-1f3fd-2640", + "uc_output": "1f9dd-1f3fd-200d-2640-fe0f", + "uc_match": "1f9dd-1f3fd-2640-fe0f", + "uc_greedy": "1f9dd-1f3fd-2640", + "shortnames": [":woman_elf_medium_skin_tone:"], + "category": "people" + }, + ":woman_elf_tone4:": { + "uc_base": "1f9dd-1f3fe-2640", + "uc_output": "1f9dd-1f3fe-200d-2640-fe0f", + "uc_match": "1f9dd-1f3fe-2640-fe0f", + "uc_greedy": "1f9dd-1f3fe-2640", + "shortnames": [":woman_elf_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_elf_tone5:": { + "uc_base": "1f9dd-1f3ff-2640", + "uc_output": "1f9dd-1f3ff-200d-2640-fe0f", + "uc_match": "1f9dd-1f3ff-2640-fe0f", + "uc_greedy": "1f9dd-1f3ff-2640", + "shortnames": [":woman_elf_dark_skin_tone:"], + "category": "people" + }, + ":woman_facepalming_tone1:": { + "uc_base": "1f926-1f3fb-2640", + "uc_output": "1f926-1f3fb-200d-2640-fe0f", + "uc_match": "1f926-1f3fb-2640-fe0f", + "uc_greedy": "1f926-1f3fb-2640", + "shortnames": [":woman_facepalming_light_skin_tone:"], + "category": "people" + }, + ":woman_facepalming_tone2:": { + "uc_base": "1f926-1f3fc-2640", + "uc_output": "1f926-1f3fc-200d-2640-fe0f", + "uc_match": "1f926-1f3fc-2640-fe0f", + "uc_greedy": "1f926-1f3fc-2640", + "shortnames": [":woman_facepalming_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_facepalming_tone3:": { + "uc_base": "1f926-1f3fd-2640", + "uc_output": "1f926-1f3fd-200d-2640-fe0f", + "uc_match": "1f926-1f3fd-2640-fe0f", + "uc_greedy": "1f926-1f3fd-2640", + "shortnames": [":woman_facepalming_medium_skin_tone:"], + "category": "people" + }, + ":woman_facepalming_tone4:": { + "uc_base": "1f926-1f3fe-2640", + "uc_output": "1f926-1f3fe-200d-2640-fe0f", + "uc_match": "1f926-1f3fe-2640-fe0f", + "uc_greedy": "1f926-1f3fe-2640", + "shortnames": [":woman_facepalming_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_facepalming_tone5:": { + "uc_base": "1f926-1f3ff-2640", + "uc_output": "1f926-1f3ff-200d-2640-fe0f", + "uc_match": "1f926-1f3ff-2640-fe0f", + "uc_greedy": "1f926-1f3ff-2640", + "shortnames": [":woman_facepalming_dark_skin_tone:"], + "category": "people" + }, + ":woman_fairy_tone1:": { + "uc_base": "1f9da-1f3fb-2640", + "uc_output": "1f9da-1f3fb-200d-2640-fe0f", + "uc_match": "1f9da-1f3fb-2640-fe0f", + "uc_greedy": "1f9da-1f3fb-2640", + "shortnames": [":woman_fairy_light_skin_tone:"], + "category": "people" + }, + ":woman_fairy_tone2:": { + "uc_base": "1f9da-1f3fc-2640", + "uc_output": "1f9da-1f3fc-200d-2640-fe0f", + "uc_match": "1f9da-1f3fc-2640-fe0f", + "uc_greedy": "1f9da-1f3fc-2640", + "shortnames": [":woman_fairy_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_fairy_tone3:": { + "uc_base": "1f9da-1f3fd-2640", + "uc_output": "1f9da-1f3fd-200d-2640-fe0f", + "uc_match": "1f9da-1f3fd-2640-fe0f", + "uc_greedy": "1f9da-1f3fd-2640", + "shortnames": [":woman_fairy_medium_skin_tone:"], + "category": "people" + }, + ":woman_fairy_tone4:": { + "uc_base": "1f9da-1f3fe-2640", + "uc_output": "1f9da-1f3fe-200d-2640-fe0f", + "uc_match": "1f9da-1f3fe-2640-fe0f", + "uc_greedy": "1f9da-1f3fe-2640", + "shortnames": [":woman_fairy_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_fairy_tone5:": { + "uc_base": "1f9da-1f3ff-2640", + "uc_output": "1f9da-1f3ff-200d-2640-fe0f", + "uc_match": "1f9da-1f3ff-2640-fe0f", + "uc_greedy": "1f9da-1f3ff-2640", + "shortnames": [":woman_fairy_dark_skin_tone:"], + "category": "people" + }, + ":woman_frowning_tone1:": { + "uc_base": "1f64d-1f3fb-2640", + "uc_output": "1f64d-1f3fb-200d-2640-fe0f", + "uc_match": "1f64d-1f3fb-2640-fe0f", + "uc_greedy": "1f64d-1f3fb-2640", + "shortnames": [":woman_frowning_light_skin_tone:"], + "category": "people" + }, + ":woman_frowning_tone2:": { + "uc_base": "1f64d-1f3fc-2640", + "uc_output": "1f64d-1f3fc-200d-2640-fe0f", + "uc_match": "1f64d-1f3fc-2640-fe0f", + "uc_greedy": "1f64d-1f3fc-2640", + "shortnames": [":woman_frowning_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_frowning_tone3:": { + "uc_base": "1f64d-1f3fd-2640", + "uc_output": "1f64d-1f3fd-200d-2640-fe0f", + "uc_match": "1f64d-1f3fd-2640-fe0f", + "uc_greedy": "1f64d-1f3fd-2640", + "shortnames": [":woman_frowning_medium_skin_tone:"], + "category": "people" + }, + ":woman_frowning_tone4:": { + "uc_base": "1f64d-1f3fe-2640", + "uc_output": "1f64d-1f3fe-200d-2640-fe0f", + "uc_match": "1f64d-1f3fe-2640-fe0f", + "uc_greedy": "1f64d-1f3fe-2640", + "shortnames": [":woman_frowning_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_frowning_tone5:": { + "uc_base": "1f64d-1f3ff-2640", + "uc_output": "1f64d-1f3ff-200d-2640-fe0f", + "uc_match": "1f64d-1f3ff-2640-fe0f", + "uc_greedy": "1f64d-1f3ff-2640", + "shortnames": [":woman_frowning_dark_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_no_tone1:": { + "uc_base": "1f645-1f3fb-2640", + "uc_output": "1f645-1f3fb-200d-2640-fe0f", + "uc_match": "1f645-1f3fb-2640-fe0f", + "uc_greedy": "1f645-1f3fb-2640", + "shortnames": [":woman_gesturing_no_light_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_no_tone2:": { + "uc_base": "1f645-1f3fc-2640", + "uc_output": "1f645-1f3fc-200d-2640-fe0f", + "uc_match": "1f645-1f3fc-2640-fe0f", + "uc_greedy": "1f645-1f3fc-2640", + "shortnames": [":woman_gesturing_no_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_no_tone3:": { + "uc_base": "1f645-1f3fd-2640", + "uc_output": "1f645-1f3fd-200d-2640-fe0f", + "uc_match": "1f645-1f3fd-2640-fe0f", + "uc_greedy": "1f645-1f3fd-2640", + "shortnames": [":woman_gesturing_no_medium_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_no_tone4:": { + "uc_base": "1f645-1f3fe-2640", + "uc_output": "1f645-1f3fe-200d-2640-fe0f", + "uc_match": "1f645-1f3fe-2640-fe0f", + "uc_greedy": "1f645-1f3fe-2640", + "shortnames": [":woman_gesturing_no_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_no_tone5:": { + "uc_base": "1f645-1f3ff-2640", + "uc_output": "1f645-1f3ff-200d-2640-fe0f", + "uc_match": "1f645-1f3ff-2640-fe0f", + "uc_greedy": "1f645-1f3ff-2640", + "shortnames": [":woman_gesturing_no_dark_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_ok_tone1:": { + "uc_base": "1f646-1f3fb-2640", + "uc_output": "1f646-1f3fb-200d-2640-fe0f", + "uc_match": "1f646-1f3fb-2640-fe0f", + "uc_greedy": "1f646-1f3fb-2640", + "shortnames": [":woman_gesturing_ok_light_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_ok_tone2:": { + "uc_base": "1f646-1f3fc-2640", + "uc_output": "1f646-1f3fc-200d-2640-fe0f", + "uc_match": "1f646-1f3fc-2640-fe0f", + "uc_greedy": "1f646-1f3fc-2640", + "shortnames": [":woman_gesturing_ok_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_ok_tone3:": { + "uc_base": "1f646-1f3fd-2640", + "uc_output": "1f646-1f3fd-200d-2640-fe0f", + "uc_match": "1f646-1f3fd-2640-fe0f", + "uc_greedy": "1f646-1f3fd-2640", + "shortnames": [":woman_gesturing_ok_medium_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_ok_tone4:": { + "uc_base": "1f646-1f3fe-2640", + "uc_output": "1f646-1f3fe-200d-2640-fe0f", + "uc_match": "1f646-1f3fe-2640-fe0f", + "uc_greedy": "1f646-1f3fe-2640", + "shortnames": [":woman_gesturing_ok_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_gesturing_ok_tone5:": { + "uc_base": "1f646-1f3ff-2640", + "uc_output": "1f646-1f3ff-200d-2640-fe0f", + "uc_match": "1f646-1f3ff-2640-fe0f", + "uc_greedy": "1f646-1f3ff-2640", + "shortnames": [":woman_gesturing_ok_dark_skin_tone:"], + "category": "people" + }, + ":woman_getting_face_massage_tone1:": { + "uc_base": "1f486-1f3fb-2640", + "uc_output": "1f486-1f3fb-200d-2640-fe0f", + "uc_match": "1f486-1f3fb-2640-fe0f", + "uc_greedy": "1f486-1f3fb-2640", + "shortnames": [":woman_getting_face_massage_light_skin_tone:"], + "category": "people" + }, + ":woman_getting_face_massage_tone2:": { + "uc_base": "1f486-1f3fc-2640", + "uc_output": "1f486-1f3fc-200d-2640-fe0f", + "uc_match": "1f486-1f3fc-2640-fe0f", + "uc_greedy": "1f486-1f3fc-2640", + "shortnames": [":woman_getting_face_massage_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_getting_face_massage_tone3:": { + "uc_base": "1f486-1f3fd-2640", + "uc_output": "1f486-1f3fd-200d-2640-fe0f", + "uc_match": "1f486-1f3fd-2640-fe0f", + "uc_greedy": "1f486-1f3fd-2640", + "shortnames": [":woman_getting_face_massage_medium_skin_tone:"], + "category": "people" + }, + ":woman_getting_face_massage_tone4:": { + "uc_base": "1f486-1f3fe-2640", + "uc_output": "1f486-1f3fe-200d-2640-fe0f", + "uc_match": "1f486-1f3fe-2640-fe0f", + "uc_greedy": "1f486-1f3fe-2640", + "shortnames": [":woman_getting_face_massage_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_getting_face_massage_tone5:": { + "uc_base": "1f486-1f3ff-2640", + "uc_output": "1f486-1f3ff-200d-2640-fe0f", + "uc_match": "1f486-1f3ff-2640-fe0f", + "uc_greedy": "1f486-1f3ff-2640", + "shortnames": [":woman_getting_face_massage_dark_skin_tone:"], + "category": "people" + }, + ":woman_getting_haircut_tone1:": { + "uc_base": "1f487-1f3fb-2640", + "uc_output": "1f487-1f3fb-200d-2640-fe0f", + "uc_match": "1f487-1f3fb-2640-fe0f", + "uc_greedy": "1f487-1f3fb-2640", + "shortnames": [":woman_getting_haircut_light_skin_tone:"], + "category": "people" + }, + ":woman_getting_haircut_tone2:": { + "uc_base": "1f487-1f3fc-2640", + "uc_output": "1f487-1f3fc-200d-2640-fe0f", + "uc_match": "1f487-1f3fc-2640-fe0f", + "uc_greedy": "1f487-1f3fc-2640", + "shortnames": [":woman_getting_haircut_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_getting_haircut_tone3:": { + "uc_base": "1f487-1f3fd-2640", + "uc_output": "1f487-1f3fd-200d-2640-fe0f", + "uc_match": "1f487-1f3fd-2640-fe0f", + "uc_greedy": "1f487-1f3fd-2640", + "shortnames": [":woman_getting_haircut_medium_skin_tone:"], + "category": "people" + }, + ":woman_getting_haircut_tone4:": { + "uc_base": "1f487-1f3fe-2640", + "uc_output": "1f487-1f3fe-200d-2640-fe0f", + "uc_match": "1f487-1f3fe-2640-fe0f", + "uc_greedy": "1f487-1f3fe-2640", + "shortnames": [":woman_getting_haircut_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_getting_haircut_tone5:": { + "uc_base": "1f487-1f3ff-2640", + "uc_output": "1f487-1f3ff-200d-2640-fe0f", + "uc_match": "1f487-1f3ff-2640-fe0f", + "uc_greedy": "1f487-1f3ff-2640", + "shortnames": [":woman_getting_haircut_dark_skin_tone:"], + "category": "people" + }, + ":woman_golfing_tone1:": { + "uc_base": "1f3cc-1f3fb-2640", + "uc_output": "1f3cc-1f3fb-200d-2640-fe0f", + "uc_match": "1f3cc-fe0f-1f3fb-2640-fe0f", + "uc_greedy": "1f3cc-1f3fb-2640", + "shortnames": [":woman_golfing_light_skin_tone:"], + "category": "activity" + }, + ":woman_golfing_tone2:": { + "uc_base": "1f3cc-1f3fc-2640", + "uc_output": "1f3cc-1f3fc-200d-2640-fe0f", + "uc_match": "1f3cc-fe0f-1f3fc-2640-fe0f", + "uc_greedy": "1f3cc-1f3fc-2640", + "shortnames": [":woman_golfing_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_golfing_tone3:": { + "uc_base": "1f3cc-1f3fd-2640", + "uc_output": "1f3cc-1f3fd-200d-2640-fe0f", + "uc_match": "1f3cc-fe0f-1f3fd-2640-fe0f", + "uc_greedy": "1f3cc-1f3fd-2640", + "shortnames": [":woman_golfing_medium_skin_tone:"], + "category": "activity" + }, + ":woman_golfing_tone4:": { + "uc_base": "1f3cc-1f3fe-2640", + "uc_output": "1f3cc-1f3fe-200d-2640-fe0f", + "uc_match": "1f3cc-fe0f-1f3fe-2640-fe0f", + "uc_greedy": "1f3cc-1f3fe-2640", + "shortnames": [":woman_golfing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_golfing_tone5:": { + "uc_base": "1f3cc-1f3ff-2640", + "uc_output": "1f3cc-1f3ff-200d-2640-fe0f", + "uc_match": "1f3cc-fe0f-1f3ff-2640-fe0f", + "uc_greedy": "1f3cc-1f3ff-2640", + "shortnames": [":woman_golfing_dark_skin_tone:"], + "category": "activity" + }, + ":woman_guard_tone1:": { + "uc_base": "1f482-1f3fb-2640", + "uc_output": "1f482-1f3fb-200d-2640-fe0f", + "uc_match": "1f482-1f3fb-2640-fe0f", + "uc_greedy": "1f482-1f3fb-2640", + "shortnames": [":woman_guard_light_skin_tone:"], + "category": "people" + }, + ":woman_guard_tone2:": { + "uc_base": "1f482-1f3fc-2640", + "uc_output": "1f482-1f3fc-200d-2640-fe0f", + "uc_match": "1f482-1f3fc-2640-fe0f", + "uc_greedy": "1f482-1f3fc-2640", + "shortnames": [":woman_guard_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_guard_tone3:": { + "uc_base": "1f482-1f3fd-2640", + "uc_output": "1f482-1f3fd-200d-2640-fe0f", + "uc_match": "1f482-1f3fd-2640-fe0f", + "uc_greedy": "1f482-1f3fd-2640", + "shortnames": [":woman_guard_medium_skin_tone:"], + "category": "people" + }, + ":woman_guard_tone4:": { + "uc_base": "1f482-1f3fe-2640", + "uc_output": "1f482-1f3fe-200d-2640-fe0f", + "uc_match": "1f482-1f3fe-2640-fe0f", + "uc_greedy": "1f482-1f3fe-2640", + "shortnames": [":woman_guard_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_guard_tone5:": { + "uc_base": "1f482-1f3ff-2640", + "uc_output": "1f482-1f3ff-200d-2640-fe0f", + "uc_match": "1f482-1f3ff-2640-fe0f", + "uc_greedy": "1f482-1f3ff-2640", + "shortnames": [":woman_guard_dark_skin_tone:"], + "category": "people" + }, + ":woman_health_worker_tone1:": { + "uc_base": "1f469-1f3fb-2695", + "uc_output": "1f469-1f3fb-200d-2695-fe0f", + "uc_match": "1f469-1f3fb-2695-fe0f", + "uc_greedy": "1f469-1f3fb-2695", + "shortnames": [":woman_health_worker_light_skin_tone:"], + "category": "people" + }, + ":woman_health_worker_tone2:": { + "uc_base": "1f469-1f3fc-2695", + "uc_output": "1f469-1f3fc-200d-2695-fe0f", + "uc_match": "1f469-1f3fc-2695-fe0f", + "uc_greedy": "1f469-1f3fc-2695", + "shortnames": [":woman_health_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_health_worker_tone3:": { + "uc_base": "1f469-1f3fd-2695", + "uc_output": "1f469-1f3fd-200d-2695-fe0f", + "uc_match": "1f469-1f3fd-2695-fe0f", + "uc_greedy": "1f469-1f3fd-2695", + "shortnames": [":woman_health_worker_medium_skin_tone:"], + "category": "people" + }, + ":woman_health_worker_tone4:": { + "uc_base": "1f469-1f3fe-2695", + "uc_output": "1f469-1f3fe-200d-2695-fe0f", + "uc_match": "1f469-1f3fe-2695-fe0f", + "uc_greedy": "1f469-1f3fe-2695", + "shortnames": [":woman_health_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_health_worker_tone5:": { + "uc_base": "1f469-1f3ff-2695", + "uc_output": "1f469-1f3ff-200d-2695-fe0f", + "uc_match": "1f469-1f3ff-2695-fe0f", + "uc_greedy": "1f469-1f3ff-2695", + "shortnames": [":woman_health_worker_dark_skin_tone:"], + "category": "people" + }, + ":woman_in_lotus_position_tone1:": { + "uc_base": "1f9d8-1f3fb-2640", + "uc_output": "1f9d8-1f3fb-200d-2640-fe0f", + "uc_match": "1f9d8-1f3fb-2640-fe0f", + "uc_greedy": "1f9d8-1f3fb-2640", + "shortnames": [":woman_in_lotus_position_light_skin_tone:"], + "category": "activity" + }, + ":woman_in_lotus_position_tone2:": { + "uc_base": "1f9d8-1f3fc-2640", + "uc_output": "1f9d8-1f3fc-200d-2640-fe0f", + "uc_match": "1f9d8-1f3fc-2640-fe0f", + "uc_greedy": "1f9d8-1f3fc-2640", + "shortnames": [":woman_in_lotus_position_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_in_lotus_position_tone3:": { + "uc_base": "1f9d8-1f3fd-2640", + "uc_output": "1f9d8-1f3fd-200d-2640-fe0f", + "uc_match": "1f9d8-1f3fd-2640-fe0f", + "uc_greedy": "1f9d8-1f3fd-2640", + "shortnames": [":woman_in_lotus_position_medium_skin_tone:"], + "category": "activity" + }, + ":woman_in_lotus_position_tone4:": { + "uc_base": "1f9d8-1f3fe-2640", + "uc_output": "1f9d8-1f3fe-200d-2640-fe0f", + "uc_match": "1f9d8-1f3fe-2640-fe0f", + "uc_greedy": "1f9d8-1f3fe-2640", + "shortnames": [":woman_in_lotus_position_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_in_lotus_position_tone5:": { + "uc_base": "1f9d8-1f3ff-2640", + "uc_output": "1f9d8-1f3ff-200d-2640-fe0f", + "uc_match": "1f9d8-1f3ff-2640-fe0f", + "uc_greedy": "1f9d8-1f3ff-2640", + "shortnames": [":woman_in_lotus_position_dark_skin_tone:"], + "category": "activity" + }, + ":woman_in_steamy_room_tone1:": { + "uc_base": "1f9d6-1f3fb-2640", + "uc_output": "1f9d6-1f3fb-200d-2640-fe0f", + "uc_match": "1f9d6-1f3fb-2640-fe0f", + "uc_greedy": "1f9d6-1f3fb-2640", + "shortnames": [":woman_in_steamy_room_light_skin_tone:"], + "category": "people" + }, + ":woman_in_steamy_room_tone2:": { + "uc_base": "1f9d6-1f3fc-2640", + "uc_output": "1f9d6-1f3fc-200d-2640-fe0f", + "uc_match": "1f9d6-1f3fc-2640-fe0f", + "uc_greedy": "1f9d6-1f3fc-2640", + "shortnames": [":woman_in_steamy_room_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_in_steamy_room_tone3:": { + "uc_base": "1f9d6-1f3fd-2640", + "uc_output": "1f9d6-1f3fd-200d-2640-fe0f", + "uc_match": "1f9d6-1f3fd-2640-fe0f", + "uc_greedy": "1f9d6-1f3fd-2640", + "shortnames": [":woman_in_steamy_room_medium_skin_tone:"], + "category": "people" + }, + ":woman_in_steamy_room_tone4:": { + "uc_base": "1f9d6-1f3fe-2640", + "uc_output": "1f9d6-1f3fe-200d-2640-fe0f", + "uc_match": "1f9d6-1f3fe-2640-fe0f", + "uc_greedy": "1f9d6-1f3fe-2640", + "shortnames": [":woman_in_steamy_room_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_in_steamy_room_tone5:": { + "uc_base": "1f9d6-1f3ff-2640", + "uc_output": "1f9d6-1f3ff-200d-2640-fe0f", + "uc_match": "1f9d6-1f3ff-2640-fe0f", + "uc_greedy": "1f9d6-1f3ff-2640", + "shortnames": [":woman_in_steamy_room_dark_skin_tone:"], + "category": "people" + }, + ":woman_judge_tone1:": { + "uc_base": "1f469-1f3fb-2696", + "uc_output": "1f469-1f3fb-200d-2696-fe0f", + "uc_match": "1f469-1f3fb-2696-fe0f", + "uc_greedy": "1f469-1f3fb-2696", + "shortnames": [":woman_judge_light_skin_tone:"], + "category": "people" + }, + ":woman_judge_tone2:": { + "uc_base": "1f469-1f3fc-2696", + "uc_output": "1f469-1f3fc-200d-2696-fe0f", + "uc_match": "1f469-1f3fc-2696-fe0f", + "uc_greedy": "1f469-1f3fc-2696", + "shortnames": [":woman_judge_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_judge_tone3:": { + "uc_base": "1f469-1f3fd-2696", + "uc_output": "1f469-1f3fd-200d-2696-fe0f", + "uc_match": "1f469-1f3fd-2696-fe0f", + "uc_greedy": "1f469-1f3fd-2696", + "shortnames": [":woman_judge_medium_skin_tone:"], + "category": "people" + }, + ":woman_judge_tone4:": { + "uc_base": "1f469-1f3fe-2696", + "uc_output": "1f469-1f3fe-200d-2696-fe0f", + "uc_match": "1f469-1f3fe-2696-fe0f", + "uc_greedy": "1f469-1f3fe-2696", + "shortnames": [":woman_judge_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_judge_tone5:": { + "uc_base": "1f469-1f3ff-2696", + "uc_output": "1f469-1f3ff-200d-2696-fe0f", + "uc_match": "1f469-1f3ff-2696-fe0f", + "uc_greedy": "1f469-1f3ff-2696", + "shortnames": [":woman_judge_dark_skin_tone:"], + "category": "people" + }, + ":woman_juggling_tone1:": { + "uc_base": "1f939-1f3fb-2640", + "uc_output": "1f939-1f3fb-200d-2640-fe0f", + "uc_match": "1f939-1f3fb-2640-fe0f", + "uc_greedy": "1f939-1f3fb-2640", + "shortnames": [":woman_juggling_light_skin_tone:"], + "category": "activity" + }, + ":woman_juggling_tone2:": { + "uc_base": "1f939-1f3fc-2640", + "uc_output": "1f939-1f3fc-200d-2640-fe0f", + "uc_match": "1f939-1f3fc-2640-fe0f", + "uc_greedy": "1f939-1f3fc-2640", + "shortnames": [":woman_juggling_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_juggling_tone3:": { + "uc_base": "1f939-1f3fd-2640", + "uc_output": "1f939-1f3fd-200d-2640-fe0f", + "uc_match": "1f939-1f3fd-2640-fe0f", + "uc_greedy": "1f939-1f3fd-2640", + "shortnames": [":woman_juggling_medium_skin_tone:"], + "category": "activity" + }, + ":woman_juggling_tone4:": { + "uc_base": "1f939-1f3fe-2640", + "uc_output": "1f939-1f3fe-200d-2640-fe0f", + "uc_match": "1f939-1f3fe-2640-fe0f", + "uc_greedy": "1f939-1f3fe-2640", + "shortnames": [":woman_juggling_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_juggling_tone5:": { + "uc_base": "1f939-1f3ff-2640", + "uc_output": "1f939-1f3ff-200d-2640-fe0f", + "uc_match": "1f939-1f3ff-2640-fe0f", + "uc_greedy": "1f939-1f3ff-2640", + "shortnames": [":woman_juggling_dark_skin_tone:"], + "category": "activity" + }, + ":woman_lifting_weights_tone1:": { + "uc_base": "1f3cb-1f3fb-2640", + "uc_output": "1f3cb-1f3fb-200d-2640-fe0f", + "uc_match": "1f3cb-fe0f-1f3fb-2640-fe0f", + "uc_greedy": "1f3cb-1f3fb-2640", + "shortnames": [":woman_lifting_weights_light_skin_tone:"], + "category": "activity" + }, + ":woman_lifting_weights_tone2:": { + "uc_base": "1f3cb-1f3fc-2640", + "uc_output": "1f3cb-1f3fc-200d-2640-fe0f", + "uc_match": "1f3cb-fe0f-1f3fc-2640-fe0f", + "uc_greedy": "1f3cb-1f3fc-2640", + "shortnames": [":woman_lifting_weights_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_lifting_weights_tone3:": { + "uc_base": "1f3cb-1f3fd-2640", + "uc_output": "1f3cb-1f3fd-200d-2640-fe0f", + "uc_match": "1f3cb-fe0f-1f3fd-2640-fe0f", + "uc_greedy": "1f3cb-1f3fd-2640", + "shortnames": [":woman_lifting_weights_medium_skin_tone:"], + "category": "activity" + }, + ":woman_lifting_weights_tone4:": { + "uc_base": "1f3cb-1f3fe-2640", + "uc_output": "1f3cb-1f3fe-200d-2640-fe0f", + "uc_match": "1f3cb-fe0f-1f3fe-2640-fe0f", + "uc_greedy": "1f3cb-1f3fe-2640", + "shortnames": [":woman_lifting_weights_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_lifting_weights_tone5:": { + "uc_base": "1f3cb-1f3ff-2640", + "uc_output": "1f3cb-1f3ff-200d-2640-fe0f", + "uc_match": "1f3cb-fe0f-1f3ff-2640-fe0f", + "uc_greedy": "1f3cb-1f3ff-2640", + "shortnames": [":woman_lifting_weights_dark_skin_tone:"], + "category": "activity" + }, + ":woman_mage_tone1:": { + "uc_base": "1f9d9-1f3fb-2640", + "uc_output": "1f9d9-1f3fb-200d-2640-fe0f", + "uc_match": "1f9d9-1f3fb-2640-fe0f", + "uc_greedy": "1f9d9-1f3fb-2640", + "shortnames": [":woman_mage_light_skin_tone:"], + "category": "people" + }, + ":woman_mage_tone2:": { + "uc_base": "1f9d9-1f3fc-2640", + "uc_output": "1f9d9-1f3fc-200d-2640-fe0f", + "uc_match": "1f9d9-1f3fc-2640-fe0f", + "uc_greedy": "1f9d9-1f3fc-2640", + "shortnames": [":woman_mage_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_mage_tone3:": { + "uc_base": "1f9d9-1f3fd-2640", + "uc_output": "1f9d9-1f3fd-200d-2640-fe0f", + "uc_match": "1f9d9-1f3fd-2640-fe0f", + "uc_greedy": "1f9d9-1f3fd-2640", + "shortnames": [":woman_mage_medium_skin_tone:"], + "category": "people" + }, + ":woman_mage_tone4:": { + "uc_base": "1f9d9-1f3fe-2640", + "uc_output": "1f9d9-1f3fe-200d-2640-fe0f", + "uc_match": "1f9d9-1f3fe-2640-fe0f", + "uc_greedy": "1f9d9-1f3fe-2640", + "shortnames": [":woman_mage_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_mage_tone5:": { + "uc_base": "1f9d9-1f3ff-2640", + "uc_output": "1f9d9-1f3ff-200d-2640-fe0f", + "uc_match": "1f9d9-1f3ff-2640-fe0f", + "uc_greedy": "1f9d9-1f3ff-2640", + "shortnames": [":woman_mage_dark_skin_tone:"], + "category": "people" + }, + ":woman_mountain_biking_tone1:": { + "uc_base": "1f6b5-1f3fb-2640", + "uc_output": "1f6b5-1f3fb-200d-2640-fe0f", + "uc_match": "1f6b5-1f3fb-2640-fe0f", + "uc_greedy": "1f6b5-1f3fb-2640", + "shortnames": [":woman_mountain_biking_light_skin_tone:"], + "category": "activity" + }, + ":woman_mountain_biking_tone2:": { + "uc_base": "1f6b5-1f3fc-2640", + "uc_output": "1f6b5-1f3fc-200d-2640-fe0f", + "uc_match": "1f6b5-1f3fc-2640-fe0f", + "uc_greedy": "1f6b5-1f3fc-2640", + "shortnames": [":woman_mountain_biking_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_mountain_biking_tone3:": { + "uc_base": "1f6b5-1f3fd-2640", + "uc_output": "1f6b5-1f3fd-200d-2640-fe0f", + "uc_match": "1f6b5-1f3fd-2640-fe0f", + "uc_greedy": "1f6b5-1f3fd-2640", + "shortnames": [":woman_mountain_biking_medium_skin_tone:"], + "category": "activity" + }, + ":woman_mountain_biking_tone4:": { + "uc_base": "1f6b5-1f3fe-2640", + "uc_output": "1f6b5-1f3fe-200d-2640-fe0f", + "uc_match": "1f6b5-1f3fe-2640-fe0f", + "uc_greedy": "1f6b5-1f3fe-2640", + "shortnames": [":woman_mountain_biking_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_mountain_biking_tone5:": { + "uc_base": "1f6b5-1f3ff-2640", + "uc_output": "1f6b5-1f3ff-200d-2640-fe0f", + "uc_match": "1f6b5-1f3ff-2640-fe0f", + "uc_greedy": "1f6b5-1f3ff-2640", + "shortnames": [":woman_mountain_biking_dark_skin_tone:"], + "category": "activity" + }, + ":woman_pilot_tone1:": { + "uc_base": "1f469-1f3fb-2708", + "uc_output": "1f469-1f3fb-200d-2708-fe0f", + "uc_match": "1f469-1f3fb-2708-fe0f", + "uc_greedy": "1f469-1f3fb-2708", + "shortnames": [":woman_pilot_light_skin_tone:"], + "category": "people" + }, + ":woman_pilot_tone2:": { + "uc_base": "1f469-1f3fc-2708", + "uc_output": "1f469-1f3fc-200d-2708-fe0f", + "uc_match": "1f469-1f3fc-2708-fe0f", + "uc_greedy": "1f469-1f3fc-2708", + "shortnames": [":woman_pilot_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_pilot_tone3:": { + "uc_base": "1f469-1f3fd-2708", + "uc_output": "1f469-1f3fd-200d-2708-fe0f", + "uc_match": "1f469-1f3fd-2708-fe0f", + "uc_greedy": "1f469-1f3fd-2708", + "shortnames": [":woman_pilot_medium_skin_tone:"], + "category": "people" + }, + ":woman_pilot_tone4:": { + "uc_base": "1f469-1f3fe-2708", + "uc_output": "1f469-1f3fe-200d-2708-fe0f", + "uc_match": "1f469-1f3fe-2708-fe0f", + "uc_greedy": "1f469-1f3fe-2708", + "shortnames": [":woman_pilot_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_pilot_tone5:": { + "uc_base": "1f469-1f3ff-2708", + "uc_output": "1f469-1f3ff-200d-2708-fe0f", + "uc_match": "1f469-1f3ff-2708-fe0f", + "uc_greedy": "1f469-1f3ff-2708", + "shortnames": [":woman_pilot_dark_skin_tone:"], + "category": "people" + }, + ":woman_playing_handball_tone1:": { + "uc_base": "1f93e-1f3fb-2640", + "uc_output": "1f93e-1f3fb-200d-2640-fe0f", + "uc_match": "1f93e-1f3fb-2640-fe0f", + "uc_greedy": "1f93e-1f3fb-2640", + "shortnames": [":woman_playing_handball_light_skin_tone:"], + "category": "activity" + }, + ":woman_playing_handball_tone2:": { + "uc_base": "1f93e-1f3fc-2640", + "uc_output": "1f93e-1f3fc-200d-2640-fe0f", + "uc_match": "1f93e-1f3fc-2640-fe0f", + "uc_greedy": "1f93e-1f3fc-2640", + "shortnames": [":woman_playing_handball_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_playing_handball_tone3:": { + "uc_base": "1f93e-1f3fd-2640", + "uc_output": "1f93e-1f3fd-200d-2640-fe0f", + "uc_match": "1f93e-1f3fd-2640-fe0f", + "uc_greedy": "1f93e-1f3fd-2640", + "shortnames": [":woman_playing_handball_medium_skin_tone:"], + "category": "activity" + }, + ":woman_playing_handball_tone4:": { + "uc_base": "1f93e-1f3fe-2640", + "uc_output": "1f93e-1f3fe-200d-2640-fe0f", + "uc_match": "1f93e-1f3fe-2640-fe0f", + "uc_greedy": "1f93e-1f3fe-2640", + "shortnames": [":woman_playing_handball_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_playing_handball_tone5:": { + "uc_base": "1f93e-1f3ff-2640", + "uc_output": "1f93e-1f3ff-200d-2640-fe0f", + "uc_match": "1f93e-1f3ff-2640-fe0f", + "uc_greedy": "1f93e-1f3ff-2640", + "shortnames": [":woman_playing_handball_dark_skin_tone:"], + "category": "activity" + }, + ":woman_playing_water_polo_tone1:": { + "uc_base": "1f93d-1f3fb-2640", + "uc_output": "1f93d-1f3fb-200d-2640-fe0f", + "uc_match": "1f93d-1f3fb-2640-fe0f", + "uc_greedy": "1f93d-1f3fb-2640", + "shortnames": [":woman_playing_water_polo_light_skin_tone:"], + "category": "activity" + }, + ":woman_playing_water_polo_tone2:": { + "uc_base": "1f93d-1f3fc-2640", + "uc_output": "1f93d-1f3fc-200d-2640-fe0f", + "uc_match": "1f93d-1f3fc-2640-fe0f", + "uc_greedy": "1f93d-1f3fc-2640", + "shortnames": [":woman_playing_water_polo_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_playing_water_polo_tone3:": { + "uc_base": "1f93d-1f3fd-2640", + "uc_output": "1f93d-1f3fd-200d-2640-fe0f", + "uc_match": "1f93d-1f3fd-2640-fe0f", + "uc_greedy": "1f93d-1f3fd-2640", + "shortnames": [":woman_playing_water_polo_medium_skin_tone:"], + "category": "activity" + }, + ":woman_playing_water_polo_tone4:": { + "uc_base": "1f93d-1f3fe-2640", + "uc_output": "1f93d-1f3fe-200d-2640-fe0f", + "uc_match": "1f93d-1f3fe-2640-fe0f", + "uc_greedy": "1f93d-1f3fe-2640", + "shortnames": [":woman_playing_water_polo_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_playing_water_polo_tone5:": { + "uc_base": "1f93d-1f3ff-2640", + "uc_output": "1f93d-1f3ff-200d-2640-fe0f", + "uc_match": "1f93d-1f3ff-2640-fe0f", + "uc_greedy": "1f93d-1f3ff-2640", + "shortnames": [":woman_playing_water_polo_dark_skin_tone:"], + "category": "activity" + }, + ":woman_police_officer_tone1:": { + "uc_base": "1f46e-1f3fb-2640", + "uc_output": "1f46e-1f3fb-200d-2640-fe0f", + "uc_match": "1f46e-1f3fb-2640-fe0f", + "uc_greedy": "1f46e-1f3fb-2640", + "shortnames": [":woman_police_officer_light_skin_tone:"], + "category": "people" + }, + ":woman_police_officer_tone2:": { + "uc_base": "1f46e-1f3fc-2640", + "uc_output": "1f46e-1f3fc-200d-2640-fe0f", + "uc_match": "1f46e-1f3fc-2640-fe0f", + "uc_greedy": "1f46e-1f3fc-2640", + "shortnames": [":woman_police_officer_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_police_officer_tone3:": { + "uc_base": "1f46e-1f3fd-2640", + "uc_output": "1f46e-1f3fd-200d-2640-fe0f", + "uc_match": "1f46e-1f3fd-2640-fe0f", + "uc_greedy": "1f46e-1f3fd-2640", + "shortnames": [":woman_police_officer_medium_skin_tone:"], + "category": "people" + }, + ":woman_police_officer_tone4:": { + "uc_base": "1f46e-1f3fe-2640", + "uc_output": "1f46e-1f3fe-200d-2640-fe0f", + "uc_match": "1f46e-1f3fe-2640-fe0f", + "uc_greedy": "1f46e-1f3fe-2640", + "shortnames": [":woman_police_officer_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_police_officer_tone5:": { + "uc_base": "1f46e-1f3ff-2640", + "uc_output": "1f46e-1f3ff-200d-2640-fe0f", + "uc_match": "1f46e-1f3ff-2640-fe0f", + "uc_greedy": "1f46e-1f3ff-2640", + "shortnames": [":woman_police_officer_dark_skin_tone:"], + "category": "people" + }, + ":woman_pouting_tone1:": { + "uc_base": "1f64e-1f3fb-2640", + "uc_output": "1f64e-1f3fb-200d-2640-fe0f", + "uc_match": "1f64e-1f3fb-2640-fe0f", + "uc_greedy": "1f64e-1f3fb-2640", + "shortnames": [":woman_pouting_light_skin_tone:"], + "category": "people" + }, + ":woman_pouting_tone2:": { + "uc_base": "1f64e-1f3fc-2640", + "uc_output": "1f64e-1f3fc-200d-2640-fe0f", + "uc_match": "1f64e-1f3fc-2640-fe0f", + "uc_greedy": "1f64e-1f3fc-2640", + "shortnames": [":woman_pouting_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_pouting_tone3:": { + "uc_base": "1f64e-1f3fd-2640", + "uc_output": "1f64e-1f3fd-200d-2640-fe0f", + "uc_match": "1f64e-1f3fd-2640-fe0f", + "uc_greedy": "1f64e-1f3fd-2640", + "shortnames": [":woman_pouting_medium_skin_tone:"], + "category": "people" + }, + ":woman_pouting_tone4:": { + "uc_base": "1f64e-1f3fe-2640", + "uc_output": "1f64e-1f3fe-200d-2640-fe0f", + "uc_match": "1f64e-1f3fe-2640-fe0f", + "uc_greedy": "1f64e-1f3fe-2640", + "shortnames": [":woman_pouting_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_pouting_tone5:": { + "uc_base": "1f64e-1f3ff-2640", + "uc_output": "1f64e-1f3ff-200d-2640-fe0f", + "uc_match": "1f64e-1f3ff-2640-fe0f", + "uc_greedy": "1f64e-1f3ff-2640", + "shortnames": [":woman_pouting_dark_skin_tone:"], + "category": "people" + }, + ":woman_raising_hand_tone1:": { + "uc_base": "1f64b-1f3fb-2640", + "uc_output": "1f64b-1f3fb-200d-2640-fe0f", + "uc_match": "1f64b-1f3fb-2640-fe0f", + "uc_greedy": "1f64b-1f3fb-2640", + "shortnames": [":woman_raising_hand_light_skin_tone:"], + "category": "people" + }, + ":woman_raising_hand_tone2:": { + "uc_base": "1f64b-1f3fc-2640", + "uc_output": "1f64b-1f3fc-200d-2640-fe0f", + "uc_match": "1f64b-1f3fc-2640-fe0f", + "uc_greedy": "1f64b-1f3fc-2640", + "shortnames": [":woman_raising_hand_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_raising_hand_tone3:": { + "uc_base": "1f64b-1f3fd-2640", + "uc_output": "1f64b-1f3fd-200d-2640-fe0f", + "uc_match": "1f64b-1f3fd-2640-fe0f", + "uc_greedy": "1f64b-1f3fd-2640", + "shortnames": [":woman_raising_hand_medium_skin_tone:"], + "category": "people" + }, + ":woman_raising_hand_tone4:": { + "uc_base": "1f64b-1f3fe-2640", + "uc_output": "1f64b-1f3fe-200d-2640-fe0f", + "uc_match": "1f64b-1f3fe-2640-fe0f", + "uc_greedy": "1f64b-1f3fe-2640", + "shortnames": [":woman_raising_hand_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_raising_hand_tone5:": { + "uc_base": "1f64b-1f3ff-2640", + "uc_output": "1f64b-1f3ff-200d-2640-fe0f", + "uc_match": "1f64b-1f3ff-2640-fe0f", + "uc_greedy": "1f64b-1f3ff-2640", + "shortnames": [":woman_raising_hand_dark_skin_tone:"], + "category": "people" + }, + ":woman_rowing_boat_tone1:": { + "uc_base": "1f6a3-1f3fb-2640", + "uc_output": "1f6a3-1f3fb-200d-2640-fe0f", + "uc_match": "1f6a3-1f3fb-2640-fe0f", + "uc_greedy": "1f6a3-1f3fb-2640", + "shortnames": [":woman_rowing_boat_light_skin_tone:"], + "category": "activity" + }, + ":woman_rowing_boat_tone2:": { + "uc_base": "1f6a3-1f3fc-2640", + "uc_output": "1f6a3-1f3fc-200d-2640-fe0f", + "uc_match": "1f6a3-1f3fc-2640-fe0f", + "uc_greedy": "1f6a3-1f3fc-2640", + "shortnames": [":woman_rowing_boat_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_rowing_boat_tone3:": { + "uc_base": "1f6a3-1f3fd-2640", + "uc_output": "1f6a3-1f3fd-200d-2640-fe0f", + "uc_match": "1f6a3-1f3fd-2640-fe0f", + "uc_greedy": "1f6a3-1f3fd-2640", + "shortnames": [":woman_rowing_boat_medium_skin_tone:"], + "category": "activity" + }, + ":woman_rowing_boat_tone4:": { + "uc_base": "1f6a3-1f3fe-2640", + "uc_output": "1f6a3-1f3fe-200d-2640-fe0f", + "uc_match": "1f6a3-1f3fe-2640-fe0f", + "uc_greedy": "1f6a3-1f3fe-2640", + "shortnames": [":woman_rowing_boat_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_rowing_boat_tone5:": { + "uc_base": "1f6a3-1f3ff-2640", + "uc_output": "1f6a3-1f3ff-200d-2640-fe0f", + "uc_match": "1f6a3-1f3ff-2640-fe0f", + "uc_greedy": "1f6a3-1f3ff-2640", + "shortnames": [":woman_rowing_boat_dark_skin_tone:"], + "category": "activity" + }, + ":woman_running_tone1:": { + "uc_base": "1f3c3-1f3fb-2640", + "uc_output": "1f3c3-1f3fb-200d-2640-fe0f", + "uc_match": "1f3c3-1f3fb-2640-fe0f", + "uc_greedy": "1f3c3-1f3fb-2640", + "shortnames": [":woman_running_light_skin_tone:"], + "category": "people" + }, + ":woman_running_tone2:": { + "uc_base": "1f3c3-1f3fc-2640", + "uc_output": "1f3c3-1f3fc-200d-2640-fe0f", + "uc_match": "1f3c3-1f3fc-2640-fe0f", + "uc_greedy": "1f3c3-1f3fc-2640", + "shortnames": [":woman_running_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_running_tone3:": { + "uc_base": "1f3c3-1f3fd-2640", + "uc_output": "1f3c3-1f3fd-200d-2640-fe0f", + "uc_match": "1f3c3-1f3fd-2640-fe0f", + "uc_greedy": "1f3c3-1f3fd-2640", + "shortnames": [":woman_running_medium_skin_tone:"], + "category": "people" + }, + ":woman_running_tone4:": { + "uc_base": "1f3c3-1f3fe-2640", + "uc_output": "1f3c3-1f3fe-200d-2640-fe0f", + "uc_match": "1f3c3-1f3fe-2640-fe0f", + "uc_greedy": "1f3c3-1f3fe-2640", + "shortnames": [":woman_running_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_running_tone5:": { + "uc_base": "1f3c3-1f3ff-2640", + "uc_output": "1f3c3-1f3ff-200d-2640-fe0f", + "uc_match": "1f3c3-1f3ff-2640-fe0f", + "uc_greedy": "1f3c3-1f3ff-2640", + "shortnames": [":woman_running_dark_skin_tone:"], + "category": "people" + }, + ":woman_shrugging_tone1:": { + "uc_base": "1f937-1f3fb-2640", + "uc_output": "1f937-1f3fb-200d-2640-fe0f", + "uc_match": "1f937-1f3fb-2640-fe0f", + "uc_greedy": "1f937-1f3fb-2640", + "shortnames": [":woman_shrugging_light_skin_tone:"], + "category": "people" + }, + ":woman_shrugging_tone2:": { + "uc_base": "1f937-1f3fc-2640", + "uc_output": "1f937-1f3fc-200d-2640-fe0f", + "uc_match": "1f937-1f3fc-2640-fe0f", + "uc_greedy": "1f937-1f3fc-2640", + "shortnames": [":woman_shrugging_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_shrugging_tone3:": { + "uc_base": "1f937-1f3fd-2640", + "uc_output": "1f937-1f3fd-200d-2640-fe0f", + "uc_match": "1f937-1f3fd-2640-fe0f", + "uc_greedy": "1f937-1f3fd-2640", + "shortnames": [":woman_shrugging_medium_skin_tone:"], + "category": "people" + }, + ":woman_shrugging_tone4:": { + "uc_base": "1f937-1f3fe-2640", + "uc_output": "1f937-1f3fe-200d-2640-fe0f", + "uc_match": "1f937-1f3fe-2640-fe0f", + "uc_greedy": "1f937-1f3fe-2640", + "shortnames": [":woman_shrugging_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_shrugging_tone5:": { + "uc_base": "1f937-1f3ff-2640", + "uc_output": "1f937-1f3ff-200d-2640-fe0f", + "uc_match": "1f937-1f3ff-2640-fe0f", + "uc_greedy": "1f937-1f3ff-2640", + "shortnames": [":woman_shrugging_dark_skin_tone:"], + "category": "people" + }, + ":woman_surfing_tone1:": { + "uc_base": "1f3c4-1f3fb-2640", + "uc_output": "1f3c4-1f3fb-200d-2640-fe0f", + "uc_match": "1f3c4-1f3fb-2640-fe0f", + "uc_greedy": "1f3c4-1f3fb-2640", + "shortnames": [":woman_surfing_light_skin_tone:"], + "category": "activity" + }, + ":woman_surfing_tone2:": { + "uc_base": "1f3c4-1f3fc-2640", + "uc_output": "1f3c4-1f3fc-200d-2640-fe0f", + "uc_match": "1f3c4-1f3fc-2640-fe0f", + "uc_greedy": "1f3c4-1f3fc-2640", + "shortnames": [":woman_surfing_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_surfing_tone3:": { + "uc_base": "1f3c4-1f3fd-2640", + "uc_output": "1f3c4-1f3fd-200d-2640-fe0f", + "uc_match": "1f3c4-1f3fd-2640-fe0f", + "uc_greedy": "1f3c4-1f3fd-2640", + "shortnames": [":woman_surfing_medium_skin_tone:"], + "category": "activity" + }, + ":woman_surfing_tone4:": { + "uc_base": "1f3c4-1f3fe-2640", + "uc_output": "1f3c4-1f3fe-200d-2640-fe0f", + "uc_match": "1f3c4-1f3fe-2640-fe0f", + "uc_greedy": "1f3c4-1f3fe-2640", + "shortnames": [":woman_surfing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_surfing_tone5:": { + "uc_base": "1f3c4-1f3ff-2640", + "uc_output": "1f3c4-1f3ff-200d-2640-fe0f", + "uc_match": "1f3c4-1f3ff-2640-fe0f", + "uc_greedy": "1f3c4-1f3ff-2640", + "shortnames": [":woman_surfing_dark_skin_tone:"], + "category": "activity" + }, + ":woman_swimming_tone1:": { + "uc_base": "1f3ca-1f3fb-2640", + "uc_output": "1f3ca-1f3fb-200d-2640-fe0f", + "uc_match": "1f3ca-1f3fb-2640-fe0f", + "uc_greedy": "1f3ca-1f3fb-2640", + "shortnames": [":woman_swimming_light_skin_tone:"], + "category": "activity" + }, + ":woman_swimming_tone2:": { + "uc_base": "1f3ca-1f3fc-2640", + "uc_output": "1f3ca-1f3fc-200d-2640-fe0f", + "uc_match": "1f3ca-1f3fc-2640-fe0f", + "uc_greedy": "1f3ca-1f3fc-2640", + "shortnames": [":woman_swimming_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_swimming_tone3:": { + "uc_base": "1f3ca-1f3fd-2640", + "uc_output": "1f3ca-1f3fd-200d-2640-fe0f", + "uc_match": "1f3ca-1f3fd-2640-fe0f", + "uc_greedy": "1f3ca-1f3fd-2640", + "shortnames": [":woman_swimming_medium_skin_tone:"], + "category": "activity" + }, + ":woman_swimming_tone4:": { + "uc_base": "1f3ca-1f3fe-2640", + "uc_output": "1f3ca-1f3fe-200d-2640-fe0f", + "uc_match": "1f3ca-1f3fe-2640-fe0f", + "uc_greedy": "1f3ca-1f3fe-2640", + "shortnames": [":woman_swimming_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_swimming_tone5:": { + "uc_base": "1f3ca-1f3ff-2640", + "uc_output": "1f3ca-1f3ff-200d-2640-fe0f", + "uc_match": "1f3ca-1f3ff-2640-fe0f", + "uc_greedy": "1f3ca-1f3ff-2640", + "shortnames": [":woman_swimming_dark_skin_tone:"], + "category": "activity" + }, + ":woman_tipping_hand_tone1:": { + "uc_base": "1f481-1f3fb-2640", + "uc_output": "1f481-1f3fb-200d-2640-fe0f", + "uc_match": "1f481-1f3fb-2640-fe0f", + "uc_greedy": "1f481-1f3fb-2640", + "shortnames": [":woman_tipping_hand_light_skin_tone:"], + "category": "people" + }, + ":woman_tipping_hand_tone2:": { + "uc_base": "1f481-1f3fc-2640", + "uc_output": "1f481-1f3fc-200d-2640-fe0f", + "uc_match": "1f481-1f3fc-2640-fe0f", + "uc_greedy": "1f481-1f3fc-2640", + "shortnames": [":woman_tipping_hand_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_tipping_hand_tone3:": { + "uc_base": "1f481-1f3fd-2640", + "uc_output": "1f481-1f3fd-200d-2640-fe0f", + "uc_match": "1f481-1f3fd-2640-fe0f", + "uc_greedy": "1f481-1f3fd-2640", + "shortnames": [":woman_tipping_hand_medium_skin_tone:"], + "category": "people" + }, + ":woman_tipping_hand_tone4:": { + "uc_base": "1f481-1f3fe-2640", + "uc_output": "1f481-1f3fe-200d-2640-fe0f", + "uc_match": "1f481-1f3fe-2640-fe0f", + "uc_greedy": "1f481-1f3fe-2640", + "shortnames": [":woman_tipping_hand_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_tipping_hand_tone5:": { + "uc_base": "1f481-1f3ff-2640", + "uc_output": "1f481-1f3ff-200d-2640-fe0f", + "uc_match": "1f481-1f3ff-2640-fe0f", + "uc_greedy": "1f481-1f3ff-2640", + "shortnames": [":woman_tipping_hand_dark_skin_tone:"], + "category": "people" + }, + ":woman_vampire_tone1:": { + "uc_base": "1f9db-1f3fb-2640", + "uc_output": "1f9db-1f3fb-200d-2640-fe0f", + "uc_match": "1f9db-1f3fb-2640-fe0f", + "uc_greedy": "1f9db-1f3fb-2640", + "shortnames": [":woman_vampire_light_skin_tone:"], + "category": "people" + }, + ":woman_vampire_tone2:": { + "uc_base": "1f9db-1f3fc-2640", + "uc_output": "1f9db-1f3fc-200d-2640-fe0f", + "uc_match": "1f9db-1f3fc-2640-fe0f", + "uc_greedy": "1f9db-1f3fc-2640", + "shortnames": [":woman_vampire_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_vampire_tone3:": { + "uc_base": "1f9db-1f3fd-2640", + "uc_output": "1f9db-1f3fd-200d-2640-fe0f", + "uc_match": "1f9db-1f3fd-2640-fe0f", + "uc_greedy": "1f9db-1f3fd-2640", + "shortnames": [":woman_vampire_medium_skin_tone:"], + "category": "people" + }, + ":woman_vampire_tone4:": { + "uc_base": "1f9db-1f3fe-2640", + "uc_output": "1f9db-1f3fe-200d-2640-fe0f", + "uc_match": "1f9db-1f3fe-2640-fe0f", + "uc_greedy": "1f9db-1f3fe-2640", + "shortnames": [":woman_vampire_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_vampire_tone5:": { + "uc_base": "1f9db-1f3ff-2640", + "uc_output": "1f9db-1f3ff-200d-2640-fe0f", + "uc_match": "1f9db-1f3ff-2640-fe0f", + "uc_greedy": "1f9db-1f3ff-2640", + "shortnames": [":woman_vampire_dark_skin_tone:"], + "category": "people" + }, + ":woman_walking_tone1:": { + "uc_base": "1f6b6-1f3fb-2640", + "uc_output": "1f6b6-1f3fb-200d-2640-fe0f", + "uc_match": "1f6b6-1f3fb-2640-fe0f", + "uc_greedy": "1f6b6-1f3fb-2640", + "shortnames": [":woman_walking_light_skin_tone:"], + "category": "people" + }, + ":woman_walking_tone2:": { + "uc_base": "1f6b6-1f3fc-2640", + "uc_output": "1f6b6-1f3fc-200d-2640-fe0f", + "uc_match": "1f6b6-1f3fc-2640-fe0f", + "uc_greedy": "1f6b6-1f3fc-2640", + "shortnames": [":woman_walking_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_walking_tone3:": { + "uc_base": "1f6b6-1f3fd-2640", + "uc_output": "1f6b6-1f3fd-200d-2640-fe0f", + "uc_match": "1f6b6-1f3fd-2640-fe0f", + "uc_greedy": "1f6b6-1f3fd-2640", + "shortnames": [":woman_walking_medium_skin_tone:"], + "category": "people" + }, + ":woman_walking_tone4:": { + "uc_base": "1f6b6-1f3fe-2640", + "uc_output": "1f6b6-1f3fe-200d-2640-fe0f", + "uc_match": "1f6b6-1f3fe-2640-fe0f", + "uc_greedy": "1f6b6-1f3fe-2640", + "shortnames": [":woman_walking_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_walking_tone5:": { + "uc_base": "1f6b6-1f3ff-2640", + "uc_output": "1f6b6-1f3ff-200d-2640-fe0f", + "uc_match": "1f6b6-1f3ff-2640-fe0f", + "uc_greedy": "1f6b6-1f3ff-2640", + "shortnames": [":woman_walking_dark_skin_tone:"], + "category": "people" + }, + ":woman_wearing_turban_tone1:": { + "uc_base": "1f473-1f3fb-2640", + "uc_output": "1f473-1f3fb-200d-2640-fe0f", + "uc_match": "1f473-1f3fb-2640-fe0f", + "uc_greedy": "1f473-1f3fb-2640", + "shortnames": [":woman_wearing_turban_light_skin_tone:"], + "category": "people" + }, + ":woman_wearing_turban_tone2:": { + "uc_base": "1f473-1f3fc-2640", + "uc_output": "1f473-1f3fc-200d-2640-fe0f", + "uc_match": "1f473-1f3fc-2640-fe0f", + "uc_greedy": "1f473-1f3fc-2640", + "shortnames": [":woman_wearing_turban_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_wearing_turban_tone3:": { + "uc_base": "1f473-1f3fd-2640", + "uc_output": "1f473-1f3fd-200d-2640-fe0f", + "uc_match": "1f473-1f3fd-2640-fe0f", + "uc_greedy": "1f473-1f3fd-2640", + "shortnames": [":woman_wearing_turban_medium_skin_tone:"], + "category": "people" + }, + ":woman_wearing_turban_tone4:": { + "uc_base": "1f473-1f3fe-2640", + "uc_output": "1f473-1f3fe-200d-2640-fe0f", + "uc_match": "1f473-1f3fe-2640-fe0f", + "uc_greedy": "1f473-1f3fe-2640", + "shortnames": [":woman_wearing_turban_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_wearing_turban_tone5:": { + "uc_base": "1f473-1f3ff-2640", + "uc_output": "1f473-1f3ff-200d-2640-fe0f", + "uc_match": "1f473-1f3ff-2640-fe0f", + "uc_greedy": "1f473-1f3ff-2640", + "shortnames": [":woman_wearing_turban_dark_skin_tone:"], + "category": "people" + }, + ":man_bouncing_ball_tone1:": { + "uc_base": "26f9-1f3fb-2642", + "uc_output": "26f9-1f3fb-200d-2642-fe0f", + "uc_match": "26f9-fe0f-1f3fb-2642-fe0f", + "uc_greedy": "26f9-1f3fb-2642", + "shortnames": [":man_bouncing_ball_light_skin_tone:"], + "category": "activity" + }, + ":man_bouncing_ball_tone2:": { + "uc_base": "26f9-1f3fc-2642", + "uc_output": "26f9-1f3fc-200d-2642-fe0f", + "uc_match": "26f9-fe0f-1f3fc-2642-fe0f", + "uc_greedy": "26f9-1f3fc-2642", + "shortnames": [":man_bouncing_ball_medium_light_skin_tone:"], + "category": "activity" + }, + ":man_bouncing_ball_tone3:": { + "uc_base": "26f9-1f3fd-2642", + "uc_output": "26f9-1f3fd-200d-2642-fe0f", + "uc_match": "26f9-fe0f-1f3fd-2642-fe0f", + "uc_greedy": "26f9-1f3fd-2642", + "shortnames": [":man_bouncing_ball_medium_skin_tone:"], + "category": "activity" + }, + ":man_bouncing_ball_tone4:": { + "uc_base": "26f9-1f3fe-2642", + "uc_output": "26f9-1f3fe-200d-2642-fe0f", + "uc_match": "26f9-fe0f-1f3fe-2642-fe0f", + "uc_greedy": "26f9-1f3fe-2642", + "shortnames": [":man_bouncing_ball_medium_dark_skin_tone:"], + "category": "activity" + }, + ":man_bouncing_ball_tone5:": { + "uc_base": "26f9-1f3ff-2642", + "uc_output": "26f9-1f3ff-200d-2642-fe0f", + "uc_match": "26f9-fe0f-1f3ff-2642-fe0f", + "uc_greedy": "26f9-1f3ff-2642", + "shortnames": [":man_bouncing_ball_dark_skin_tone:"], + "category": "activity" + }, + ":man_detective:": { + "uc_base": "1f575-2642", + "uc_output": "1f575-fe0f-200d-2642-fe0f", + "uc_match": "1f575-fe0f-200d-2642", + "uc_greedy": "1f575-2642", + "shortnames": [], + "category": "people" + }, + ":man_golfing:": { + "uc_base": "1f3cc-2642", + "uc_output": "1f3cc-fe0f-200d-2642-fe0f", + "uc_match": "1f3cc-fe0f-200d-2642", + "uc_greedy": "1f3cc-2642", + "shortnames": [], + "category": "activity" + }, + ":man_lifting_weights:": { + "uc_base": "1f3cb-2642", + "uc_output": "1f3cb-fe0f-200d-2642-fe0f", + "uc_match": "1f3cb-fe0f-200d-2642", + "uc_greedy": "1f3cb-2642", + "shortnames": [], + "category": "activity" + }, + ":woman_bouncing_ball_tone1:": { + "uc_base": "26f9-1f3fb-2640", + "uc_output": "26f9-1f3fb-200d-2640-fe0f", + "uc_match": "26f9-fe0f-1f3fb-2640-fe0f", + "uc_greedy": "26f9-1f3fb-2640", + "shortnames": [":woman_bouncing_ball_light_skin_tone:"], + "category": "activity" + }, + ":woman_bouncing_ball_tone2:": { + "uc_base": "26f9-1f3fc-2640", + "uc_output": "26f9-1f3fc-200d-2640-fe0f", + "uc_match": "26f9-fe0f-1f3fc-2640-fe0f", + "uc_greedy": "26f9-1f3fc-2640", + "shortnames": [":woman_bouncing_ball_medium_light_skin_tone:"], + "category": "activity" + }, + ":woman_bouncing_ball_tone3:": { + "uc_base": "26f9-1f3fd-2640", + "uc_output": "26f9-1f3fd-200d-2640-fe0f", + "uc_match": "26f9-fe0f-1f3fd-2640-fe0f", + "uc_greedy": "26f9-1f3fd-2640", + "shortnames": [":woman_bouncing_ball_medium_skin_tone:"], + "category": "activity" + }, + ":woman_bouncing_ball_tone4:": { + "uc_base": "26f9-1f3fe-2640", + "uc_output": "26f9-1f3fe-200d-2640-fe0f", + "uc_match": "26f9-fe0f-1f3fe-2640-fe0f", + "uc_greedy": "26f9-1f3fe-2640", + "shortnames": [":woman_bouncing_ball_medium_dark_skin_tone:"], + "category": "activity" + }, + ":woman_bouncing_ball_tone5:": { + "uc_base": "26f9-1f3ff-2640", + "uc_output": "26f9-1f3ff-200d-2640-fe0f", + "uc_match": "26f9-fe0f-1f3ff-2640-fe0f", + "uc_greedy": "26f9-1f3ff-2640", + "shortnames": [":woman_bouncing_ball_dark_skin_tone:"], + "category": "activity" + }, + ":woman_detective:": { + "uc_base": "1f575-2640", + "uc_output": "1f575-fe0f-200d-2640-fe0f", + "uc_match": "1f575-fe0f-200d-2640", + "uc_greedy": "1f575-2640", + "shortnames": [], + "category": "people" + }, + ":woman_golfing:": { + "uc_base": "1f3cc-2640", + "uc_output": "1f3cc-fe0f-200d-2640-fe0f", + "uc_match": "1f3cc-fe0f-200d-2640", + "uc_greedy": "1f3cc-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_lifting_weights:": { + "uc_base": "1f3cb-2640", + "uc_output": "1f3cb-fe0f-200d-2640-fe0f", + "uc_match": "1f3cb-fe0f-200d-2640", + "uc_greedy": "1f3cb-2640", + "shortnames": [], + "category": "activity" + }, + ":man_bouncing_ball:": { + "uc_base": "26f9-2642", + "uc_output": "26f9-fe0f-200d-2642-fe0f", + "uc_match": "26f9-fe0f-200d-2642", + "uc_greedy": "26f9-2642", + "shortnames": [], + "category": "activity" + }, + ":woman_bouncing_ball:": { + "uc_base": "26f9-2640", + "uc_output": "26f9-fe0f-200d-2640-fe0f", + "uc_match": "26f9-fe0f-200d-2640", + "uc_greedy": "26f9-2640", + "shortnames": [], + "category": "activity" + }, + ":man_artist_tone1:": { + "uc_base": "1f468-1f3fb-1f3a8", + "uc_output": "1f468-1f3fb-200d-1f3a8", + "uc_match": "1f468-1f3fb-1f3a8", + "uc_greedy": "1f468-1f3fb-1f3a8", + "shortnames": [":man_artist_light_skin_tone:"], + "category": "people" + }, + ":man_artist_tone2:": { + "uc_base": "1f468-1f3fc-1f3a8", + "uc_output": "1f468-1f3fc-200d-1f3a8", + "uc_match": "1f468-1f3fc-1f3a8", + "uc_greedy": "1f468-1f3fc-1f3a8", + "shortnames": [":man_artist_medium_light_skin_tone:"], + "category": "people" + }, + ":man_artist_tone3:": { + "uc_base": "1f468-1f3fd-1f3a8", + "uc_output": "1f468-1f3fd-200d-1f3a8", + "uc_match": "1f468-1f3fd-1f3a8", + "uc_greedy": "1f468-1f3fd-1f3a8", + "shortnames": [":man_artist_medium_skin_tone:"], + "category": "people" + }, + ":man_artist_tone4:": { + "uc_base": "1f468-1f3fe-1f3a8", + "uc_output": "1f468-1f3fe-200d-1f3a8", + "uc_match": "1f468-1f3fe-1f3a8", + "uc_greedy": "1f468-1f3fe-1f3a8", + "shortnames": [":man_artist_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_artist_tone5:": { + "uc_base": "1f468-1f3ff-1f3a8", + "uc_output": "1f468-1f3ff-200d-1f3a8", + "uc_match": "1f468-1f3ff-1f3a8", + "uc_greedy": "1f468-1f3ff-1f3a8", + "shortnames": [":man_artist_dark_skin_tone:"], + "category": "people" + }, + ":man_astronaut_tone1:": { + "uc_base": "1f468-1f3fb-1f680", + "uc_output": "1f468-1f3fb-200d-1f680", + "uc_match": "1f468-1f3fb-1f680", + "uc_greedy": "1f468-1f3fb-1f680", + "shortnames": [":man_astronaut_light_skin_tone:"], + "category": "people" + }, + ":man_astronaut_tone2:": { + "uc_base": "1f468-1f3fc-1f680", + "uc_output": "1f468-1f3fc-200d-1f680", + "uc_match": "1f468-1f3fc-1f680", + "uc_greedy": "1f468-1f3fc-1f680", + "shortnames": [":man_astronaut_medium_light_skin_tone:"], + "category": "people" + }, + ":man_astronaut_tone3:": { + "uc_base": "1f468-1f3fd-1f680", + "uc_output": "1f468-1f3fd-200d-1f680", + "uc_match": "1f468-1f3fd-1f680", + "uc_greedy": "1f468-1f3fd-1f680", + "shortnames": [":man_astronaut_medium_skin_tone:"], + "category": "people" + }, + ":man_astronaut_tone4:": { + "uc_base": "1f468-1f3fe-1f680", + "uc_output": "1f468-1f3fe-200d-1f680", + "uc_match": "1f468-1f3fe-1f680", + "uc_greedy": "1f468-1f3fe-1f680", + "shortnames": [":man_astronaut_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_astronaut_tone5:": { + "uc_base": "1f468-1f3ff-1f680", + "uc_output": "1f468-1f3ff-200d-1f680", + "uc_match": "1f468-1f3ff-1f680", + "uc_greedy": "1f468-1f3ff-1f680", + "shortnames": [":man_astronaut_dark_skin_tone:"], + "category": "people" + }, + ":man_cook_tone1:": { + "uc_base": "1f468-1f3fb-1f373", + "uc_output": "1f468-1f3fb-200d-1f373", + "uc_match": "1f468-1f3fb-1f373", + "uc_greedy": "1f468-1f3fb-1f373", + "shortnames": [":man_cook_light_skin_tone:"], + "category": "people" + }, + ":man_cook_tone2:": { + "uc_base": "1f468-1f3fc-1f373", + "uc_output": "1f468-1f3fc-200d-1f373", + "uc_match": "1f468-1f3fc-1f373", + "uc_greedy": "1f468-1f3fc-1f373", + "shortnames": [":man_cook_medium_light_skin_tone:"], + "category": "people" + }, + ":man_cook_tone3:": { + "uc_base": "1f468-1f3fd-1f373", + "uc_output": "1f468-1f3fd-200d-1f373", + "uc_match": "1f468-1f3fd-1f373", + "uc_greedy": "1f468-1f3fd-1f373", + "shortnames": [":man_cook_medium_skin_tone:"], + "category": "people" + }, + ":man_cook_tone4:": { + "uc_base": "1f468-1f3fe-1f373", + "uc_output": "1f468-1f3fe-200d-1f373", + "uc_match": "1f468-1f3fe-1f373", + "uc_greedy": "1f468-1f3fe-1f373", + "shortnames": [":man_cook_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_cook_tone5:": { + "uc_base": "1f468-1f3ff-1f373", + "uc_output": "1f468-1f3ff-200d-1f373", + "uc_match": "1f468-1f3ff-1f373", + "uc_greedy": "1f468-1f3ff-1f373", + "shortnames": [":man_cook_dark_skin_tone:"], + "category": "people" + }, + ":man_factory_worker_tone1:": { + "uc_base": "1f468-1f3fb-1f3ed", + "uc_output": "1f468-1f3fb-200d-1f3ed", + "uc_match": "1f468-1f3fb-1f3ed", + "uc_greedy": "1f468-1f3fb-1f3ed", + "shortnames": [":man_factory_worker_light_skin_tone:"], + "category": "people" + }, + ":man_factory_worker_tone2:": { + "uc_base": "1f468-1f3fc-1f3ed", + "uc_output": "1f468-1f3fc-200d-1f3ed", + "uc_match": "1f468-1f3fc-1f3ed", + "uc_greedy": "1f468-1f3fc-1f3ed", + "shortnames": [":man_factory_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":man_factory_worker_tone3:": { + "uc_base": "1f468-1f3fd-1f3ed", + "uc_output": "1f468-1f3fd-200d-1f3ed", + "uc_match": "1f468-1f3fd-1f3ed", + "uc_greedy": "1f468-1f3fd-1f3ed", + "shortnames": [":man_factory_worker_medium_skin_tone:"], + "category": "people" + }, + ":man_factory_worker_tone4:": { + "uc_base": "1f468-1f3fe-1f3ed", + "uc_output": "1f468-1f3fe-200d-1f3ed", + "uc_match": "1f468-1f3fe-1f3ed", + "uc_greedy": "1f468-1f3fe-1f3ed", + "shortnames": [":man_factory_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_factory_worker_tone5:": { + "uc_base": "1f468-1f3ff-1f3ed", + "uc_output": "1f468-1f3ff-200d-1f3ed", + "uc_match": "1f468-1f3ff-1f3ed", + "uc_greedy": "1f468-1f3ff-1f3ed", + "shortnames": [":man_factory_worker_dark_skin_tone:"], + "category": "people" + }, + ":man_farmer_tone1:": { + "uc_base": "1f468-1f3fb-1f33e", + "uc_output": "1f468-1f3fb-200d-1f33e", + "uc_match": "1f468-1f3fb-1f33e", + "uc_greedy": "1f468-1f3fb-1f33e", + "shortnames": [":man_farmer_light_skin_tone:"], + "category": "people" + }, + ":man_farmer_tone2:": { + "uc_base": "1f468-1f3fc-1f33e", + "uc_output": "1f468-1f3fc-200d-1f33e", + "uc_match": "1f468-1f3fc-1f33e", + "uc_greedy": "1f468-1f3fc-1f33e", + "shortnames": [":man_farmer_medium_light_skin_tone:"], + "category": "people" + }, + ":man_farmer_tone3:": { + "uc_base": "1f468-1f3fd-1f33e", + "uc_output": "1f468-1f3fd-200d-1f33e", + "uc_match": "1f468-1f3fd-1f33e", + "uc_greedy": "1f468-1f3fd-1f33e", + "shortnames": [":man_farmer_medium_skin_tone:"], + "category": "people" + }, + ":man_farmer_tone4:": { + "uc_base": "1f468-1f3fe-1f33e", + "uc_output": "1f468-1f3fe-200d-1f33e", + "uc_match": "1f468-1f3fe-1f33e", + "uc_greedy": "1f468-1f3fe-1f33e", + "shortnames": [":man_farmer_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_farmer_tone5:": { + "uc_base": "1f468-1f3ff-1f33e", + "uc_output": "1f468-1f3ff-200d-1f33e", + "uc_match": "1f468-1f3ff-1f33e", + "uc_greedy": "1f468-1f3ff-1f33e", + "shortnames": [":man_farmer_dark_skin_tone:"], + "category": "people" + }, + ":man_firefighter_tone1:": { + "uc_base": "1f468-1f3fb-1f692", + "uc_output": "1f468-1f3fb-200d-1f692", + "uc_match": "1f468-1f3fb-1f692", + "uc_greedy": "1f468-1f3fb-1f692", + "shortnames": [":man_firefighter_light_skin_tone:"], + "category": "people" + }, + ":man_firefighter_tone2:": { + "uc_base": "1f468-1f3fc-1f692", + "uc_output": "1f468-1f3fc-200d-1f692", + "uc_match": "1f468-1f3fc-1f692", + "uc_greedy": "1f468-1f3fc-1f692", + "shortnames": [":man_firefighter_medium_light_skin_tone:"], + "category": "people" + }, + ":man_firefighter_tone3:": { + "uc_base": "1f468-1f3fd-1f692", + "uc_output": "1f468-1f3fd-200d-1f692", + "uc_match": "1f468-1f3fd-1f692", + "uc_greedy": "1f468-1f3fd-1f692", + "shortnames": [":man_firefighter_medium_skin_tone:"], + "category": "people" + }, + ":man_firefighter_tone4:": { + "uc_base": "1f468-1f3fe-1f692", + "uc_output": "1f468-1f3fe-200d-1f692", + "uc_match": "1f468-1f3fe-1f692", + "uc_greedy": "1f468-1f3fe-1f692", + "shortnames": [":man_firefighter_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_firefighter_tone5:": { + "uc_base": "1f468-1f3ff-1f692", + "uc_output": "1f468-1f3ff-200d-1f692", + "uc_match": "1f468-1f3ff-1f692", + "uc_greedy": "1f468-1f3ff-1f692", + "shortnames": [":man_firefighter_dark_skin_tone:"], + "category": "people" + }, + ":man_mechanic_tone1:": { + "uc_base": "1f468-1f3fb-1f527", + "uc_output": "1f468-1f3fb-200d-1f527", + "uc_match": "1f468-1f3fb-1f527", + "uc_greedy": "1f468-1f3fb-1f527", + "shortnames": [":man_mechanic_light_skin_tone:"], + "category": "people" + }, + ":man_mechanic_tone2:": { + "uc_base": "1f468-1f3fc-1f527", + "uc_output": "1f468-1f3fc-200d-1f527", + "uc_match": "1f468-1f3fc-1f527", + "uc_greedy": "1f468-1f3fc-1f527", + "shortnames": [":man_mechanic_medium_light_skin_tone:"], + "category": "people" + }, + ":man_mechanic_tone3:": { + "uc_base": "1f468-1f3fd-1f527", + "uc_output": "1f468-1f3fd-200d-1f527", + "uc_match": "1f468-1f3fd-1f527", + "uc_greedy": "1f468-1f3fd-1f527", + "shortnames": [":man_mechanic_medium_skin_tone:"], + "category": "people" + }, + ":man_mechanic_tone4:": { + "uc_base": "1f468-1f3fe-1f527", + "uc_output": "1f468-1f3fe-200d-1f527", + "uc_match": "1f468-1f3fe-1f527", + "uc_greedy": "1f468-1f3fe-1f527", + "shortnames": [":man_mechanic_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_mechanic_tone5:": { + "uc_base": "1f468-1f3ff-1f527", + "uc_output": "1f468-1f3ff-200d-1f527", + "uc_match": "1f468-1f3ff-1f527", + "uc_greedy": "1f468-1f3ff-1f527", + "shortnames": [":man_mechanic_dark_skin_tone:"], + "category": "people" + }, + ":man_office_worker_tone1:": { + "uc_base": "1f468-1f3fb-1f4bc", + "uc_output": "1f468-1f3fb-200d-1f4bc", + "uc_match": "1f468-1f3fb-1f4bc", + "uc_greedy": "1f468-1f3fb-1f4bc", + "shortnames": [":man_office_worker_light_skin_tone:"], + "category": "people" + }, + ":man_office_worker_tone2:": { + "uc_base": "1f468-1f3fc-1f4bc", + "uc_output": "1f468-1f3fc-200d-1f4bc", + "uc_match": "1f468-1f3fc-1f4bc", + "uc_greedy": "1f468-1f3fc-1f4bc", + "shortnames": [":man_office_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":man_office_worker_tone3:": { + "uc_base": "1f468-1f3fd-1f4bc", + "uc_output": "1f468-1f3fd-200d-1f4bc", + "uc_match": "1f468-1f3fd-1f4bc", + "uc_greedy": "1f468-1f3fd-1f4bc", + "shortnames": [":man_office_worker_medium_skin_tone:"], + "category": "people" + }, + ":man_office_worker_tone4:": { + "uc_base": "1f468-1f3fe-1f4bc", + "uc_output": "1f468-1f3fe-200d-1f4bc", + "uc_match": "1f468-1f3fe-1f4bc", + "uc_greedy": "1f468-1f3fe-1f4bc", + "shortnames": [":man_office_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_office_worker_tone5:": { + "uc_base": "1f468-1f3ff-1f4bc", + "uc_output": "1f468-1f3ff-200d-1f4bc", + "uc_match": "1f468-1f3ff-1f4bc", + "uc_greedy": "1f468-1f3ff-1f4bc", + "shortnames": [":man_office_worker_dark_skin_tone:"], + "category": "people" + }, + ":man_scientist_tone1:": { + "uc_base": "1f468-1f3fb-1f52c", + "uc_output": "1f468-1f3fb-200d-1f52c", + "uc_match": "1f468-1f3fb-1f52c", + "uc_greedy": "1f468-1f3fb-1f52c", + "shortnames": [":man_scientist_light_skin_tone:"], + "category": "people" + }, + ":man_scientist_tone2:": { + "uc_base": "1f468-1f3fc-1f52c", + "uc_output": "1f468-1f3fc-200d-1f52c", + "uc_match": "1f468-1f3fc-1f52c", + "uc_greedy": "1f468-1f3fc-1f52c", + "shortnames": [":man_scientist_medium_light_skin_tone:"], + "category": "people" + }, + ":man_scientist_tone3:": { + "uc_base": "1f468-1f3fd-1f52c", + "uc_output": "1f468-1f3fd-200d-1f52c", + "uc_match": "1f468-1f3fd-1f52c", + "uc_greedy": "1f468-1f3fd-1f52c", + "shortnames": [":man_scientist_medium_skin_tone:"], + "category": "people" + }, + ":man_scientist_tone4:": { + "uc_base": "1f468-1f3fe-1f52c", + "uc_output": "1f468-1f3fe-200d-1f52c", + "uc_match": "1f468-1f3fe-1f52c", + "uc_greedy": "1f468-1f3fe-1f52c", + "shortnames": [":man_scientist_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_scientist_tone5:": { + "uc_base": "1f468-1f3ff-1f52c", + "uc_output": "1f468-1f3ff-200d-1f52c", + "uc_match": "1f468-1f3ff-1f52c", + "uc_greedy": "1f468-1f3ff-1f52c", + "shortnames": [":man_scientist_dark_skin_tone:"], + "category": "people" + }, + ":man_singer_tone1:": { + "uc_base": "1f468-1f3fb-1f3a4", + "uc_output": "1f468-1f3fb-200d-1f3a4", + "uc_match": "1f468-1f3fb-1f3a4", + "uc_greedy": "1f468-1f3fb-1f3a4", + "shortnames": [":man_singer_light_skin_tone:"], + "category": "people" + }, + ":man_singer_tone2:": { + "uc_base": "1f468-1f3fc-1f3a4", + "uc_output": "1f468-1f3fc-200d-1f3a4", + "uc_match": "1f468-1f3fc-1f3a4", + "uc_greedy": "1f468-1f3fc-1f3a4", + "shortnames": [":man_singer_medium_light_skin_tone:"], + "category": "people" + }, + ":man_singer_tone3:": { + "uc_base": "1f468-1f3fd-1f3a4", + "uc_output": "1f468-1f3fd-200d-1f3a4", + "uc_match": "1f468-1f3fd-1f3a4", + "uc_greedy": "1f468-1f3fd-1f3a4", + "shortnames": [":man_singer_medium_skin_tone:"], + "category": "people" + }, + ":man_singer_tone4:": { + "uc_base": "1f468-1f3fe-1f3a4", + "uc_output": "1f468-1f3fe-200d-1f3a4", + "uc_match": "1f468-1f3fe-1f3a4", + "uc_greedy": "1f468-1f3fe-1f3a4", + "shortnames": [":man_singer_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_singer_tone5:": { + "uc_base": "1f468-1f3ff-1f3a4", + "uc_output": "1f468-1f3ff-200d-1f3a4", + "uc_match": "1f468-1f3ff-1f3a4", + "uc_greedy": "1f468-1f3ff-1f3a4", + "shortnames": [":man_singer_dark_skin_tone:"], + "category": "people" + }, + ":man_student_tone1:": { + "uc_base": "1f468-1f3fb-1f393", + "uc_output": "1f468-1f3fb-200d-1f393", + "uc_match": "1f468-1f3fb-1f393", + "uc_greedy": "1f468-1f3fb-1f393", + "shortnames": [":man_student_light_skin_tone:"], + "category": "people" + }, + ":man_student_tone2:": { + "uc_base": "1f468-1f3fc-1f393", + "uc_output": "1f468-1f3fc-200d-1f393", + "uc_match": "1f468-1f3fc-1f393", + "uc_greedy": "1f468-1f3fc-1f393", + "shortnames": [":man_student_medium_light_skin_tone:"], + "category": "people" + }, + ":man_student_tone3:": { + "uc_base": "1f468-1f3fd-1f393", + "uc_output": "1f468-1f3fd-200d-1f393", + "uc_match": "1f468-1f3fd-1f393", + "uc_greedy": "1f468-1f3fd-1f393", + "shortnames": [":man_student_medium_skin_tone:"], + "category": "people" + }, + ":man_student_tone4:": { + "uc_base": "1f468-1f3fe-1f393", + "uc_output": "1f468-1f3fe-200d-1f393", + "uc_match": "1f468-1f3fe-1f393", + "uc_greedy": "1f468-1f3fe-1f393", + "shortnames": [":man_student_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_student_tone5:": { + "uc_base": "1f468-1f3ff-1f393", + "uc_output": "1f468-1f3ff-200d-1f393", + "uc_match": "1f468-1f3ff-1f393", + "uc_greedy": "1f468-1f3ff-1f393", + "shortnames": [":man_student_dark_skin_tone:"], + "category": "people" + }, + ":man_teacher_tone1:": { + "uc_base": "1f468-1f3fb-1f3eb", + "uc_output": "1f468-1f3fb-200d-1f3eb", + "uc_match": "1f468-1f3fb-1f3eb", + "uc_greedy": "1f468-1f3fb-1f3eb", + "shortnames": [":man_teacher_light_skin_tone:"], + "category": "people" + }, + ":man_teacher_tone2:": { + "uc_base": "1f468-1f3fc-1f3eb", + "uc_output": "1f468-1f3fc-200d-1f3eb", + "uc_match": "1f468-1f3fc-1f3eb", + "uc_greedy": "1f468-1f3fc-1f3eb", + "shortnames": [":man_teacher_medium_light_skin_tone:"], + "category": "people" + }, + ":man_teacher_tone3:": { + "uc_base": "1f468-1f3fd-1f3eb", + "uc_output": "1f468-1f3fd-200d-1f3eb", + "uc_match": "1f468-1f3fd-1f3eb", + "uc_greedy": "1f468-1f3fd-1f3eb", + "shortnames": [":man_teacher_medium_skin_tone:"], + "category": "people" + }, + ":man_teacher_tone4:": { + "uc_base": "1f468-1f3fe-1f3eb", + "uc_output": "1f468-1f3fe-200d-1f3eb", + "uc_match": "1f468-1f3fe-1f3eb", + "uc_greedy": "1f468-1f3fe-1f3eb", + "shortnames": [":man_teacher_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_teacher_tone5:": { + "uc_base": "1f468-1f3ff-1f3eb", + "uc_output": "1f468-1f3ff-200d-1f3eb", + "uc_match": "1f468-1f3ff-1f3eb", + "uc_greedy": "1f468-1f3ff-1f3eb", + "shortnames": [":man_teacher_dark_skin_tone:"], + "category": "people" + }, + ":man_technologist_tone1:": { + "uc_base": "1f468-1f3fb-1f4bb", + "uc_output": "1f468-1f3fb-200d-1f4bb", + "uc_match": "1f468-1f3fb-1f4bb", + "uc_greedy": "1f468-1f3fb-1f4bb", + "shortnames": [":man_technologist_light_skin_tone:"], + "category": "people" + }, + ":man_technologist_tone2:": { + "uc_base": "1f468-1f3fc-1f4bb", + "uc_output": "1f468-1f3fc-200d-1f4bb", + "uc_match": "1f468-1f3fc-1f4bb", + "uc_greedy": "1f468-1f3fc-1f4bb", + "shortnames": [":man_technologist_medium_light_skin_tone:"], + "category": "people" + }, + ":man_technologist_tone3:": { + "uc_base": "1f468-1f3fd-1f4bb", + "uc_output": "1f468-1f3fd-200d-1f4bb", + "uc_match": "1f468-1f3fd-1f4bb", + "uc_greedy": "1f468-1f3fd-1f4bb", + "shortnames": [":man_technologist_medium_skin_tone:"], + "category": "people" + }, + ":man_technologist_tone4:": { + "uc_base": "1f468-1f3fe-1f4bb", + "uc_output": "1f468-1f3fe-200d-1f4bb", + "uc_match": "1f468-1f3fe-1f4bb", + "uc_greedy": "1f468-1f3fe-1f4bb", + "shortnames": [":man_technologist_medium_dark_skin_tone:"], + "category": "people" + }, + ":man_technologist_tone5:": { + "uc_base": "1f468-1f3ff-1f4bb", + "uc_output": "1f468-1f3ff-200d-1f4bb", + "uc_match": "1f468-1f3ff-1f4bb", + "uc_greedy": "1f468-1f3ff-1f4bb", + "shortnames": [":man_technologist_dark_skin_tone:"], + "category": "people" + }, + ":woman_artist_tone1:": { + "uc_base": "1f469-1f3fb-1f3a8", + "uc_output": "1f469-1f3fb-200d-1f3a8", + "uc_match": "1f469-1f3fb-1f3a8", + "uc_greedy": "1f469-1f3fb-1f3a8", + "shortnames": [":woman_artist_light_skin_tone:"], + "category": "people" + }, + ":woman_artist_tone2:": { + "uc_base": "1f469-1f3fc-1f3a8", + "uc_output": "1f469-1f3fc-200d-1f3a8", + "uc_match": "1f469-1f3fc-1f3a8", + "uc_greedy": "1f469-1f3fc-1f3a8", + "shortnames": [":woman_artist_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_artist_tone3:": { + "uc_base": "1f469-1f3fd-1f3a8", + "uc_output": "1f469-1f3fd-200d-1f3a8", + "uc_match": "1f469-1f3fd-1f3a8", + "uc_greedy": "1f469-1f3fd-1f3a8", + "shortnames": [":woman_artist_medium_skin_tone:"], + "category": "people" + }, + ":woman_artist_tone4:": { + "uc_base": "1f469-1f3fe-1f3a8", + "uc_output": "1f469-1f3fe-200d-1f3a8", + "uc_match": "1f469-1f3fe-1f3a8", + "uc_greedy": "1f469-1f3fe-1f3a8", + "shortnames": [":woman_artist_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_artist_tone5:": { + "uc_base": "1f469-1f3ff-1f3a8", + "uc_output": "1f469-1f3ff-200d-1f3a8", + "uc_match": "1f469-1f3ff-1f3a8", + "uc_greedy": "1f469-1f3ff-1f3a8", + "shortnames": [":woman_artist_dark_skin_tone:"], + "category": "people" + }, + ":woman_astronaut_tone1:": { + "uc_base": "1f469-1f3fb-1f680", + "uc_output": "1f469-1f3fb-200d-1f680", + "uc_match": "1f469-1f3fb-1f680", + "uc_greedy": "1f469-1f3fb-1f680", + "shortnames": [":woman_astronaut_light_skin_tone:"], + "category": "people" + }, + ":woman_astronaut_tone2:": { + "uc_base": "1f469-1f3fc-1f680", + "uc_output": "1f469-1f3fc-200d-1f680", + "uc_match": "1f469-1f3fc-1f680", + "uc_greedy": "1f469-1f3fc-1f680", + "shortnames": [":woman_astronaut_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_astronaut_tone3:": { + "uc_base": "1f469-1f3fd-1f680", + "uc_output": "1f469-1f3fd-200d-1f680", + "uc_match": "1f469-1f3fd-1f680", + "uc_greedy": "1f469-1f3fd-1f680", + "shortnames": [":woman_astronaut_medium_skin_tone:"], + "category": "people" + }, + ":woman_astronaut_tone4:": { + "uc_base": "1f469-1f3fe-1f680", + "uc_output": "1f469-1f3fe-200d-1f680", + "uc_match": "1f469-1f3fe-1f680", + "uc_greedy": "1f469-1f3fe-1f680", + "shortnames": [":woman_astronaut_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_astronaut_tone5:": { + "uc_base": "1f469-1f3ff-1f680", + "uc_output": "1f469-1f3ff-200d-1f680", + "uc_match": "1f469-1f3ff-1f680", + "uc_greedy": "1f469-1f3ff-1f680", + "shortnames": [":woman_astronaut_dark_skin_tone:"], + "category": "people" + }, + ":woman_cook_tone1:": { + "uc_base": "1f469-1f3fb-1f373", + "uc_output": "1f469-1f3fb-200d-1f373", + "uc_match": "1f469-1f3fb-1f373", + "uc_greedy": "1f469-1f3fb-1f373", + "shortnames": [":woman_cook_light_skin_tone:"], + "category": "people" + }, + ":woman_cook_tone2:": { + "uc_base": "1f469-1f3fc-1f373", + "uc_output": "1f469-1f3fc-200d-1f373", + "uc_match": "1f469-1f3fc-1f373", + "uc_greedy": "1f469-1f3fc-1f373", + "shortnames": [":woman_cook_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_cook_tone3:": { + "uc_base": "1f469-1f3fd-1f373", + "uc_output": "1f469-1f3fd-200d-1f373", + "uc_match": "1f469-1f3fd-1f373", + "uc_greedy": "1f469-1f3fd-1f373", + "shortnames": [":woman_cook_medium_skin_tone:"], + "category": "people" + }, + ":woman_cook_tone4:": { + "uc_base": "1f469-1f3fe-1f373", + "uc_output": "1f469-1f3fe-200d-1f373", + "uc_match": "1f469-1f3fe-1f373", + "uc_greedy": "1f469-1f3fe-1f373", + "shortnames": [":woman_cook_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_cook_tone5:": { + "uc_base": "1f469-1f3ff-1f373", + "uc_output": "1f469-1f3ff-200d-1f373", + "uc_match": "1f469-1f3ff-1f373", + "uc_greedy": "1f469-1f3ff-1f373", + "shortnames": [":woman_cook_dark_skin_tone:"], + "category": "people" + }, + ":woman_factory_worker_tone1:": { + "uc_base": "1f469-1f3fb-1f3ed", + "uc_output": "1f469-1f3fb-200d-1f3ed", + "uc_match": "1f469-1f3fb-1f3ed", + "uc_greedy": "1f469-1f3fb-1f3ed", + "shortnames": [":woman_factory_worker_light_skin_tone:"], + "category": "people" + }, + ":woman_factory_worker_tone2:": { + "uc_base": "1f469-1f3fc-1f3ed", + "uc_output": "1f469-1f3fc-200d-1f3ed", + "uc_match": "1f469-1f3fc-1f3ed", + "uc_greedy": "1f469-1f3fc-1f3ed", + "shortnames": [":woman_factory_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_factory_worker_tone3:": { + "uc_base": "1f469-1f3fd-1f3ed", + "uc_output": "1f469-1f3fd-200d-1f3ed", + "uc_match": "1f469-1f3fd-1f3ed", + "uc_greedy": "1f469-1f3fd-1f3ed", + "shortnames": [":woman_factory_worker_medium_skin_tone:"], + "category": "people" + }, + ":woman_factory_worker_tone4:": { + "uc_base": "1f469-1f3fe-1f3ed", + "uc_output": "1f469-1f3fe-200d-1f3ed", + "uc_match": "1f469-1f3fe-1f3ed", + "uc_greedy": "1f469-1f3fe-1f3ed", + "shortnames": [":woman_factory_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_factory_worker_tone5:": { + "uc_base": "1f469-1f3ff-1f3ed", + "uc_output": "1f469-1f3ff-200d-1f3ed", + "uc_match": "1f469-1f3ff-1f3ed", + "uc_greedy": "1f469-1f3ff-1f3ed", + "shortnames": [":woman_factory_worker_dark_skin_tone:"], + "category": "people" + }, + ":woman_farmer_tone1:": { + "uc_base": "1f469-1f3fb-1f33e", + "uc_output": "1f469-1f3fb-200d-1f33e", + "uc_match": "1f469-1f3fb-1f33e", + "uc_greedy": "1f469-1f3fb-1f33e", + "shortnames": [":woman_farmer_light_skin_tone:"], + "category": "people" + }, + ":woman_farmer_tone2:": { + "uc_base": "1f469-1f3fc-1f33e", + "uc_output": "1f469-1f3fc-200d-1f33e", + "uc_match": "1f469-1f3fc-1f33e", + "uc_greedy": "1f469-1f3fc-1f33e", + "shortnames": [":woman_farmer_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_farmer_tone3:": { + "uc_base": "1f469-1f3fd-1f33e", + "uc_output": "1f469-1f3fd-200d-1f33e", + "uc_match": "1f469-1f3fd-1f33e", + "uc_greedy": "1f469-1f3fd-1f33e", + "shortnames": [":woman_farmer_medium_skin_tone:"], + "category": "people" + }, + ":woman_farmer_tone4:": { + "uc_base": "1f469-1f3fe-1f33e", + "uc_output": "1f469-1f3fe-200d-1f33e", + "uc_match": "1f469-1f3fe-1f33e", + "uc_greedy": "1f469-1f3fe-1f33e", + "shortnames": [":woman_farmer_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_farmer_tone5:": { + "uc_base": "1f469-1f3ff-1f33e", + "uc_output": "1f469-1f3ff-200d-1f33e", + "uc_match": "1f469-1f3ff-1f33e", + "uc_greedy": "1f469-1f3ff-1f33e", + "shortnames": [":woman_farmer_dark_skin_tone:"], + "category": "people" + }, + ":woman_firefighter_tone1:": { + "uc_base": "1f469-1f3fb-1f692", + "uc_output": "1f469-1f3fb-200d-1f692", + "uc_match": "1f469-1f3fb-1f692", + "uc_greedy": "1f469-1f3fb-1f692", + "shortnames": [":woman_firefighter_light_skin_tone:"], + "category": "people" + }, + ":woman_firefighter_tone2:": { + "uc_base": "1f469-1f3fc-1f692", + "uc_output": "1f469-1f3fc-200d-1f692", + "uc_match": "1f469-1f3fc-1f692", + "uc_greedy": "1f469-1f3fc-1f692", + "shortnames": [":woman_firefighter_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_firefighter_tone3:": { + "uc_base": "1f469-1f3fd-1f692", + "uc_output": "1f469-1f3fd-200d-1f692", + "uc_match": "1f469-1f3fd-1f692", + "uc_greedy": "1f469-1f3fd-1f692", + "shortnames": [":woman_firefighter_medium_skin_tone:"], + "category": "people" + }, + ":woman_firefighter_tone4:": { + "uc_base": "1f469-1f3fe-1f692", + "uc_output": "1f469-1f3fe-200d-1f692", + "uc_match": "1f469-1f3fe-1f692", + "uc_greedy": "1f469-1f3fe-1f692", + "shortnames": [":woman_firefighter_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_firefighter_tone5:": { + "uc_base": "1f469-1f3ff-1f692", + "uc_output": "1f469-1f3ff-200d-1f692", + "uc_match": "1f469-1f3ff-1f692", + "uc_greedy": "1f469-1f3ff-1f692", + "shortnames": [":woman_firefighter_dark_skin_tone:"], + "category": "people" + }, + ":woman_mechanic_tone1:": { + "uc_base": "1f469-1f3fb-1f527", + "uc_output": "1f469-1f3fb-200d-1f527", + "uc_match": "1f469-1f3fb-1f527", + "uc_greedy": "1f469-1f3fb-1f527", + "shortnames": [":woman_mechanic_light_skin_tone:"], + "category": "people" + }, + ":woman_mechanic_tone2:": { + "uc_base": "1f469-1f3fc-1f527", + "uc_output": "1f469-1f3fc-200d-1f527", + "uc_match": "1f469-1f3fc-1f527", + "uc_greedy": "1f469-1f3fc-1f527", + "shortnames": [":woman_mechanic_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_mechanic_tone3:": { + "uc_base": "1f469-1f3fd-1f527", + "uc_output": "1f469-1f3fd-200d-1f527", + "uc_match": "1f469-1f3fd-1f527", + "uc_greedy": "1f469-1f3fd-1f527", + "shortnames": [":woman_mechanic_medium_skin_tone:"], + "category": "people" + }, + ":woman_mechanic_tone4:": { + "uc_base": "1f469-1f3fe-1f527", + "uc_output": "1f469-1f3fe-200d-1f527", + "uc_match": "1f469-1f3fe-1f527", + "uc_greedy": "1f469-1f3fe-1f527", + "shortnames": [":woman_mechanic_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_mechanic_tone5:": { + "uc_base": "1f469-1f3ff-1f527", + "uc_output": "1f469-1f3ff-200d-1f527", + "uc_match": "1f469-1f3ff-1f527", + "uc_greedy": "1f469-1f3ff-1f527", + "shortnames": [":woman_mechanic_dark_skin_tone:"], + "category": "people" + }, + ":woman_office_worker_tone1:": { + "uc_base": "1f469-1f3fb-1f4bc", + "uc_output": "1f469-1f3fb-200d-1f4bc", + "uc_match": "1f469-1f3fb-1f4bc", + "uc_greedy": "1f469-1f3fb-1f4bc", + "shortnames": [":woman_office_worker_light_skin_tone:"], + "category": "people" + }, + ":woman_office_worker_tone2:": { + "uc_base": "1f469-1f3fc-1f4bc", + "uc_output": "1f469-1f3fc-200d-1f4bc", + "uc_match": "1f469-1f3fc-1f4bc", + "uc_greedy": "1f469-1f3fc-1f4bc", + "shortnames": [":woman_office_worker_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_office_worker_tone3:": { + "uc_base": "1f469-1f3fd-1f4bc", + "uc_output": "1f469-1f3fd-200d-1f4bc", + "uc_match": "1f469-1f3fd-1f4bc", + "uc_greedy": "1f469-1f3fd-1f4bc", + "shortnames": [":woman_office_worker_medium_skin_tone:"], + "category": "people" + }, + ":woman_office_worker_tone4:": { + "uc_base": "1f469-1f3fe-1f4bc", + "uc_output": "1f469-1f3fe-200d-1f4bc", + "uc_match": "1f469-1f3fe-1f4bc", + "uc_greedy": "1f469-1f3fe-1f4bc", + "shortnames": [":woman_office_worker_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_office_worker_tone5:": { + "uc_base": "1f469-1f3ff-1f4bc", + "uc_output": "1f469-1f3ff-200d-1f4bc", + "uc_match": "1f469-1f3ff-1f4bc", + "uc_greedy": "1f469-1f3ff-1f4bc", + "shortnames": [":woman_office_worker_dark_skin_tone:"], + "category": "people" + }, + ":woman_scientist_tone1:": { + "uc_base": "1f469-1f3fb-1f52c", + "uc_output": "1f469-1f3fb-200d-1f52c", + "uc_match": "1f469-1f3fb-1f52c", + "uc_greedy": "1f469-1f3fb-1f52c", + "shortnames": [":woman_scientist_light_skin_tone:"], + "category": "people" + }, + ":woman_scientist_tone2:": { + "uc_base": "1f469-1f3fc-1f52c", + "uc_output": "1f469-1f3fc-200d-1f52c", + "uc_match": "1f469-1f3fc-1f52c", + "uc_greedy": "1f469-1f3fc-1f52c", + "shortnames": [":woman_scientist_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_scientist_tone3:": { + "uc_base": "1f469-1f3fd-1f52c", + "uc_output": "1f469-1f3fd-200d-1f52c", + "uc_match": "1f469-1f3fd-1f52c", + "uc_greedy": "1f469-1f3fd-1f52c", + "shortnames": [":woman_scientist_medium_skin_tone:"], + "category": "people" + }, + ":woman_scientist_tone4:": { + "uc_base": "1f469-1f3fe-1f52c", + "uc_output": "1f469-1f3fe-200d-1f52c", + "uc_match": "1f469-1f3fe-1f52c", + "uc_greedy": "1f469-1f3fe-1f52c", + "shortnames": [":woman_scientist_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_scientist_tone5:": { + "uc_base": "1f469-1f3ff-1f52c", + "uc_output": "1f469-1f3ff-200d-1f52c", + "uc_match": "1f469-1f3ff-1f52c", + "uc_greedy": "1f469-1f3ff-1f52c", + "shortnames": [":woman_scientist_dark_skin_tone:"], + "category": "people" + }, + ":woman_singer_tone1:": { + "uc_base": "1f469-1f3fb-1f3a4", + "uc_output": "1f469-1f3fb-200d-1f3a4", + "uc_match": "1f469-1f3fb-1f3a4", + "uc_greedy": "1f469-1f3fb-1f3a4", + "shortnames": [":woman_singer_light_skin_tone:"], + "category": "people" + }, + ":woman_singer_tone2:": { + "uc_base": "1f469-1f3fc-1f3a4", + "uc_output": "1f469-1f3fc-200d-1f3a4", + "uc_match": "1f469-1f3fc-1f3a4", + "uc_greedy": "1f469-1f3fc-1f3a4", + "shortnames": [":woman_singer_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_singer_tone3:": { + "uc_base": "1f469-1f3fd-1f3a4", + "uc_output": "1f469-1f3fd-200d-1f3a4", + "uc_match": "1f469-1f3fd-1f3a4", + "uc_greedy": "1f469-1f3fd-1f3a4", + "shortnames": [":woman_singer_medium_skin_tone:"], + "category": "people" + }, + ":woman_singer_tone4:": { + "uc_base": "1f469-1f3fe-1f3a4", + "uc_output": "1f469-1f3fe-200d-1f3a4", + "uc_match": "1f469-1f3fe-1f3a4", + "uc_greedy": "1f469-1f3fe-1f3a4", + "shortnames": [":woman_singer_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_singer_tone5:": { + "uc_base": "1f469-1f3ff-1f3a4", + "uc_output": "1f469-1f3ff-200d-1f3a4", + "uc_match": "1f469-1f3ff-1f3a4", + "uc_greedy": "1f469-1f3ff-1f3a4", + "shortnames": [":woman_singer_dark_skin_tone:"], + "category": "people" + }, + ":woman_student_tone1:": { + "uc_base": "1f469-1f3fb-1f393", + "uc_output": "1f469-1f3fb-200d-1f393", + "uc_match": "1f469-1f3fb-1f393", + "uc_greedy": "1f469-1f3fb-1f393", + "shortnames": [":woman_student_light_skin_tone:"], + "category": "people" + }, + ":woman_student_tone2:": { + "uc_base": "1f469-1f3fc-1f393", + "uc_output": "1f469-1f3fc-200d-1f393", + "uc_match": "1f469-1f3fc-1f393", + "uc_greedy": "1f469-1f3fc-1f393", + "shortnames": [":woman_student_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_student_tone3:": { + "uc_base": "1f469-1f3fd-1f393", + "uc_output": "1f469-1f3fd-200d-1f393", + "uc_match": "1f469-1f3fd-1f393", + "uc_greedy": "1f469-1f3fd-1f393", + "shortnames": [":woman_student_medium_skin_tone:"], + "category": "people" + }, + ":woman_student_tone4:": { + "uc_base": "1f469-1f3fe-1f393", + "uc_output": "1f469-1f3fe-200d-1f393", + "uc_match": "1f469-1f3fe-1f393", + "uc_greedy": "1f469-1f3fe-1f393", + "shortnames": [":woman_student_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_student_tone5:": { + "uc_base": "1f469-1f3ff-1f393", + "uc_output": "1f469-1f3ff-200d-1f393", + "uc_match": "1f469-1f3ff-1f393", + "uc_greedy": "1f469-1f3ff-1f393", + "shortnames": [":woman_student_dark_skin_tone:"], + "category": "people" + }, + ":woman_teacher_tone1:": { + "uc_base": "1f469-1f3fb-1f3eb", + "uc_output": "1f469-1f3fb-200d-1f3eb", + "uc_match": "1f469-1f3fb-1f3eb", + "uc_greedy": "1f469-1f3fb-1f3eb", + "shortnames": [":woman_teacher_light_skin_tone:"], + "category": "people" + }, + ":woman_teacher_tone2:": { + "uc_base": "1f469-1f3fc-1f3eb", + "uc_output": "1f469-1f3fc-200d-1f3eb", + "uc_match": "1f469-1f3fc-1f3eb", + "uc_greedy": "1f469-1f3fc-1f3eb", + "shortnames": [":woman_teacher_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_teacher_tone3:": { + "uc_base": "1f469-1f3fd-1f3eb", + "uc_output": "1f469-1f3fd-200d-1f3eb", + "uc_match": "1f469-1f3fd-1f3eb", + "uc_greedy": "1f469-1f3fd-1f3eb", + "shortnames": [":woman_teacher_medium_skin_tone:"], + "category": "people" + }, + ":woman_teacher_tone4:": { + "uc_base": "1f469-1f3fe-1f3eb", + "uc_output": "1f469-1f3fe-200d-1f3eb", + "uc_match": "1f469-1f3fe-1f3eb", + "uc_greedy": "1f469-1f3fe-1f3eb", + "shortnames": [":woman_teacher_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_teacher_tone5:": { + "uc_base": "1f469-1f3ff-1f3eb", + "uc_output": "1f469-1f3ff-200d-1f3eb", + "uc_match": "1f469-1f3ff-1f3eb", + "uc_greedy": "1f469-1f3ff-1f3eb", + "shortnames": [":woman_teacher_dark_skin_tone:"], + "category": "people" + }, + ":woman_technologist_tone1:": { + "uc_base": "1f469-1f3fb-1f4bb", + "uc_output": "1f469-1f3fb-200d-1f4bb", + "uc_match": "1f469-1f3fb-1f4bb", + "uc_greedy": "1f469-1f3fb-1f4bb", + "shortnames": [":woman_technologist_light_skin_tone:"], + "category": "people" + }, + ":woman_technologist_tone2:": { + "uc_base": "1f469-1f3fc-1f4bb", + "uc_output": "1f469-1f3fc-200d-1f4bb", + "uc_match": "1f469-1f3fc-1f4bb", + "uc_greedy": "1f469-1f3fc-1f4bb", + "shortnames": [":woman_technologist_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_technologist_tone3:": { + "uc_base": "1f469-1f3fd-1f4bb", + "uc_output": "1f469-1f3fd-200d-1f4bb", + "uc_match": "1f469-1f3fd-1f4bb", + "uc_greedy": "1f469-1f3fd-1f4bb", + "shortnames": [":woman_technologist_medium_skin_tone:"], + "category": "people" + }, + ":woman_technologist_tone4:": { + "uc_base": "1f469-1f3fe-1f4bb", + "uc_output": "1f469-1f3fe-200d-1f4bb", + "uc_match": "1f469-1f3fe-1f4bb", + "uc_greedy": "1f469-1f3fe-1f4bb", + "shortnames": [":woman_technologist_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_technologist_tone5:": { + "uc_base": "1f469-1f3ff-1f4bb", + "uc_output": "1f469-1f3ff-200d-1f4bb", + "uc_match": "1f469-1f3ff-1f4bb", + "uc_greedy": "1f469-1f3ff-1f4bb", + "shortnames": [":woman_technologist_dark_skin_tone:"], + "category": "people" + }, + ":rainbow_flag:": { + "uc_base": "1f3f3-1f308", + "uc_output": "1f3f3-fe0f-200d-1f308", + "uc_match": "1f3f3-fe0f-1f308", + "uc_greedy": "1f3f3-1f308", + "shortnames": [":gay_pride_flag:"], + "category": "flags" + }, + ":blond-haired_man:": { + "uc_base": "1f471-2642", + "uc_output": "1f471-200d-2642-fe0f", + "uc_match": "1f471-2642-fe0f", + "uc_greedy": "1f471-2642", + "shortnames": [], + "category": "people" + }, + ":blond-haired_woman:": { + "uc_base": "1f471-2640", + "uc_output": "1f471-200d-2640-fe0f", + "uc_match": "1f471-2640-fe0f", + "uc_greedy": "1f471-2640", + "shortnames": [], + "category": "people" + }, + ":man_biking:": { + "uc_base": "1f6b4-2642", + "uc_output": "1f6b4-200d-2642-fe0f", + "uc_match": "1f6b4-2642-fe0f", + "uc_greedy": "1f6b4-2642", + "shortnames": [], + "category": "activity" + }, + ":man_bowing:": { + "uc_base": "1f647-2642", + "uc_output": "1f647-200d-2642-fe0f", + "uc_match": "1f647-2642-fe0f", + "uc_greedy": "1f647-2642", + "shortnames": [], + "category": "people" + }, + ":man_cartwheeling:": { + "uc_base": "1f938-2642", + "uc_output": "1f938-200d-2642-fe0f", + "uc_match": "1f938-2642-fe0f", + "uc_greedy": "1f938-2642", + "shortnames": [], + "category": "activity" + }, + ":man_climbing:": { + "uc_base": "1f9d7-2642", + "uc_output": "1f9d7-200d-2642-fe0f", + "uc_match": "1f9d7-2642-fe0f", + "uc_greedy": "1f9d7-2642", + "shortnames": [], + "category": "activity" + }, + ":man_construction_worker:": { + "uc_base": "1f477-2642", + "uc_output": "1f477-200d-2642-fe0f", + "uc_match": "1f477-2642-fe0f", + "uc_greedy": "1f477-2642", + "shortnames": [], + "category": "people" + }, + ":man_elf:": { + "uc_base": "1f9dd-2642", + "uc_output": "1f9dd-200d-2642-fe0f", + "uc_match": "1f9dd-2642-fe0f", + "uc_greedy": "1f9dd-2642", + "shortnames": [], + "category": "people" + }, + ":man_facepalming:": { + "uc_base": "1f926-2642", + "uc_output": "1f926-200d-2642-fe0f", + "uc_match": "1f926-2642-fe0f", + "uc_greedy": "1f926-2642", + "shortnames": [], + "category": "people" + }, + ":man_fairy:": { + "uc_base": "1f9da-2642", + "uc_output": "1f9da-200d-2642-fe0f", + "uc_match": "1f9da-2642-fe0f", + "uc_greedy": "1f9da-2642", + "shortnames": [], + "category": "people" + }, + ":man_frowning:": { + "uc_base": "1f64d-2642", + "uc_output": "1f64d-200d-2642-fe0f", + "uc_match": "1f64d-2642-fe0f", + "uc_greedy": "1f64d-2642", + "shortnames": [], + "category": "people" + }, + ":man_genie:": { + "uc_base": "1f9de-2642", + "uc_output": "1f9de-200d-2642-fe0f", + "uc_match": "1f9de-2642-fe0f", + "uc_greedy": "1f9de-2642", + "shortnames": [], + "category": "people" + }, + ":man_gesturing_no:": { + "uc_base": "1f645-2642", + "uc_output": "1f645-200d-2642-fe0f", + "uc_match": "1f645-2642-fe0f", + "uc_greedy": "1f645-2642", + "shortnames": [], + "category": "people" + }, + ":man_gesturing_ok:": { + "uc_base": "1f646-2642", + "uc_output": "1f646-200d-2642-fe0f", + "uc_match": "1f646-2642-fe0f", + "uc_greedy": "1f646-2642", + "shortnames": [], + "category": "people" + }, + ":man_getting_face_massage:": { + "uc_base": "1f486-2642", + "uc_output": "1f486-200d-2642-fe0f", + "uc_match": "1f486-2642-fe0f", + "uc_greedy": "1f486-2642", + "shortnames": [], + "category": "people" + }, + ":man_getting_haircut:": { + "uc_base": "1f487-2642", + "uc_output": "1f487-200d-2642-fe0f", + "uc_match": "1f487-2642-fe0f", + "uc_greedy": "1f487-2642", + "shortnames": [], + "category": "people" + }, + ":man_guard:": { + "uc_base": "1f482-2642", + "uc_output": "1f482-200d-2642-fe0f", + "uc_match": "1f482-2642-fe0f", + "uc_greedy": "1f482-2642", + "shortnames": [], + "category": "people" + }, + ":man_health_worker:": { + "uc_base": "1f468-2695", + "uc_output": "1f468-200d-2695-fe0f", + "uc_match": "1f468-2695-fe0f", + "uc_greedy": "1f468-2695", + "shortnames": [], + "category": "people" + }, + ":man_in_lotus_position:": { + "uc_base": "1f9d8-2642", + "uc_output": "1f9d8-200d-2642-fe0f", + "uc_match": "1f9d8-2642-fe0f", + "uc_greedy": "1f9d8-2642", + "shortnames": [], + "category": "activity" + }, + ":man_in_steamy_room:": { + "uc_base": "1f9d6-2642", + "uc_output": "1f9d6-200d-2642-fe0f", + "uc_match": "1f9d6-2642-fe0f", + "uc_greedy": "1f9d6-2642", + "shortnames": [], + "category": "people" + }, + ":man_judge:": { + "uc_base": "1f468-2696", + "uc_output": "1f468-200d-2696-fe0f", + "uc_match": "1f468-2696-fe0f", + "uc_greedy": "1f468-2696", + "shortnames": [], + "category": "people" + }, + ":man_juggling:": { + "uc_base": "1f939-2642", + "uc_output": "1f939-200d-2642-fe0f", + "uc_match": "1f939-2642-fe0f", + "uc_greedy": "1f939-2642", + "shortnames": [], + "category": "activity" + }, + ":man_mage:": { + "uc_base": "1f9d9-2642", + "uc_output": "1f9d9-200d-2642-fe0f", + "uc_match": "1f9d9-2642-fe0f", + "uc_greedy": "1f9d9-2642", + "shortnames": [], + "category": "people" + }, + ":man_mountain_biking:": { + "uc_base": "1f6b5-2642", + "uc_output": "1f6b5-200d-2642-fe0f", + "uc_match": "1f6b5-2642-fe0f", + "uc_greedy": "1f6b5-2642", + "shortnames": [], + "category": "activity" + }, + ":man_pilot:": { + "uc_base": "1f468-2708", + "uc_output": "1f468-200d-2708-fe0f", + "uc_match": "1f468-2708-fe0f", + "uc_greedy": "1f468-2708", + "shortnames": [], + "category": "people" + }, + ":man_playing_handball:": { + "uc_base": "1f93e-2642", + "uc_output": "1f93e-200d-2642-fe0f", + "uc_match": "1f93e-2642-fe0f", + "uc_greedy": "1f93e-2642", + "shortnames": [], + "category": "activity" + }, + ":man_playing_water_polo:": { + "uc_base": "1f93d-2642", + "uc_output": "1f93d-200d-2642-fe0f", + "uc_match": "1f93d-2642-fe0f", + "uc_greedy": "1f93d-2642", + "shortnames": [], + "category": "activity" + }, + ":man_police_officer:": { + "uc_base": "1f46e-2642", + "uc_output": "1f46e-200d-2642-fe0f", + "uc_match": "1f46e-2642-fe0f", + "uc_greedy": "1f46e-2642", + "shortnames": [], + "category": "people" + }, + ":man_pouting:": { + "uc_base": "1f64e-2642", + "uc_output": "1f64e-200d-2642-fe0f", + "uc_match": "1f64e-2642-fe0f", + "uc_greedy": "1f64e-2642", + "shortnames": [], + "category": "people" + }, + ":man_raising_hand:": { + "uc_base": "1f64b-2642", + "uc_output": "1f64b-200d-2642-fe0f", + "uc_match": "1f64b-2642-fe0f", + "uc_greedy": "1f64b-2642", + "shortnames": [], + "category": "people" + }, + ":man_rowing_boat:": { + "uc_base": "1f6a3-2642", + "uc_output": "1f6a3-200d-2642-fe0f", + "uc_match": "1f6a3-2642-fe0f", + "uc_greedy": "1f6a3-2642", + "shortnames": [], + "category": "activity" + }, + ":man_running:": { + "uc_base": "1f3c3-2642", + "uc_output": "1f3c3-200d-2642-fe0f", + "uc_match": "1f3c3-2642-fe0f", + "uc_greedy": "1f3c3-2642", + "shortnames": [], + "category": "people" + }, + ":man_shrugging:": { + "uc_base": "1f937-2642", + "uc_output": "1f937-200d-2642-fe0f", + "uc_match": "1f937-2642-fe0f", + "uc_greedy": "1f937-2642", + "shortnames": [], + "category": "people" + }, + ":man_surfing:": { + "uc_base": "1f3c4-2642", + "uc_output": "1f3c4-200d-2642-fe0f", + "uc_match": "1f3c4-2642-fe0f", + "uc_greedy": "1f3c4-2642", + "shortnames": [], + "category": "activity" + }, + ":man_swimming:": { + "uc_base": "1f3ca-2642", + "uc_output": "1f3ca-200d-2642-fe0f", + "uc_match": "1f3ca-2642-fe0f", + "uc_greedy": "1f3ca-2642", + "shortnames": [], + "category": "activity" + }, + ":man_tipping_hand:": { + "uc_base": "1f481-2642", + "uc_output": "1f481-200d-2642-fe0f", + "uc_match": "1f481-2642-fe0f", + "uc_greedy": "1f481-2642", + "shortnames": [], + "category": "people" + }, + ":man_vampire:": { + "uc_base": "1f9db-2642", + "uc_output": "1f9db-200d-2642-fe0f", + "uc_match": "1f9db-2642-fe0f", + "uc_greedy": "1f9db-2642", + "shortnames": [], + "category": "people" + }, + ":man_walking:": { + "uc_base": "1f6b6-2642", + "uc_output": "1f6b6-200d-2642-fe0f", + "uc_match": "1f6b6-2642-fe0f", + "uc_greedy": "1f6b6-2642", + "shortnames": [], + "category": "people" + }, + ":man_wearing_turban:": { + "uc_base": "1f473-2642", + "uc_output": "1f473-200d-2642-fe0f", + "uc_match": "1f473-2642-fe0f", + "uc_greedy": "1f473-2642", + "shortnames": [], + "category": "people" + }, + ":man_zombie:": { + "uc_base": "1f9df-2642", + "uc_output": "1f9df-200d-2642-fe0f", + "uc_match": "1f9df-2642-fe0f", + "uc_greedy": "1f9df-2642", + "shortnames": [], + "category": "people" + }, + ":men_with_bunny_ears_partying:": { + "uc_base": "1f46f-2642", + "uc_output": "1f46f-200d-2642-fe0f", + "uc_match": "1f46f-2642-fe0f", + "uc_greedy": "1f46f-2642", + "shortnames": [], + "category": "people" + }, + ":men_wrestling:": { + "uc_base": "1f93c-2642", + "uc_output": "1f93c-200d-2642-fe0f", + "uc_match": "1f93c-2642-fe0f", + "uc_greedy": "1f93c-2642", + "shortnames": [], + "category": "activity" + }, + ":mermaid:": { + "uc_base": "1f9dc-2640", + "uc_output": "1f9dc-200d-2640-fe0f", + "uc_match": "1f9dc-2640-fe0f", + "uc_greedy": "1f9dc-2640", + "shortnames": [], + "category": "people" + }, + ":merman:": { + "uc_base": "1f9dc-2642", + "uc_output": "1f9dc-200d-2642-fe0f", + "uc_match": "1f9dc-2642-fe0f", + "uc_greedy": "1f9dc-2642", + "shortnames": [], + "category": "people" + }, + ":woman_biking:": { + "uc_base": "1f6b4-2640", + "uc_output": "1f6b4-200d-2640-fe0f", + "uc_match": "1f6b4-2640-fe0f", + "uc_greedy": "1f6b4-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_bowing:": { + "uc_base": "1f647-2640", + "uc_output": "1f647-200d-2640-fe0f", + "uc_match": "1f647-2640-fe0f", + "uc_greedy": "1f647-2640", + "shortnames": [], + "category": "people" + }, + ":woman_cartwheeling:": { + "uc_base": "1f938-2640", + "uc_output": "1f938-200d-2640-fe0f", + "uc_match": "1f938-2640-fe0f", + "uc_greedy": "1f938-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_climbing:": { + "uc_base": "1f9d7-2640", + "uc_output": "1f9d7-200d-2640-fe0f", + "uc_match": "1f9d7-2640-fe0f", + "uc_greedy": "1f9d7-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_construction_worker:": { + "uc_base": "1f477-2640", + "uc_output": "1f477-200d-2640-fe0f", + "uc_match": "1f477-2640-fe0f", + "uc_greedy": "1f477-2640", + "shortnames": [], + "category": "people" + }, + ":woman_elf:": { + "uc_base": "1f9dd-2640", + "uc_output": "1f9dd-200d-2640-fe0f", + "uc_match": "1f9dd-2640-fe0f", + "uc_greedy": "1f9dd-2640", + "shortnames": [], + "category": "people" + }, + ":woman_facepalming:": { + "uc_base": "1f926-2640", + "uc_output": "1f926-200d-2640-fe0f", + "uc_match": "1f926-2640-fe0f", + "uc_greedy": "1f926-2640", + "shortnames": [], + "category": "people" + }, + ":woman_fairy:": { + "uc_base": "1f9da-2640", + "uc_output": "1f9da-200d-2640-fe0f", + "uc_match": "1f9da-2640-fe0f", + "uc_greedy": "1f9da-2640", + "shortnames": [], + "category": "people" + }, + ":woman_frowning:": { + "uc_base": "1f64d-2640", + "uc_output": "1f64d-200d-2640-fe0f", + "uc_match": "1f64d-2640-fe0f", + "uc_greedy": "1f64d-2640", + "shortnames": [], + "category": "people" + }, + ":woman_genie:": { + "uc_base": "1f9de-2640", + "uc_output": "1f9de-200d-2640-fe0f", + "uc_match": "1f9de-2640-fe0f", + "uc_greedy": "1f9de-2640", + "shortnames": [], + "category": "people" + }, + ":woman_gesturing_no:": { + "uc_base": "1f645-2640", + "uc_output": "1f645-200d-2640-fe0f", + "uc_match": "1f645-2640-fe0f", + "uc_greedy": "1f645-2640", + "shortnames": [], + "category": "people" + }, + ":woman_gesturing_ok:": { + "uc_base": "1f646-2640", + "uc_output": "1f646-200d-2640-fe0f", + "uc_match": "1f646-2640-fe0f", + "uc_greedy": "1f646-2640", + "shortnames": [], + "category": "people" + }, + ":woman_getting_face_massage:": { + "uc_base": "1f486-2640", + "uc_output": "1f486-200d-2640-fe0f", + "uc_match": "1f486-2640-fe0f", + "uc_greedy": "1f486-2640", + "shortnames": [], + "category": "people" + }, + ":woman_getting_haircut:": { + "uc_base": "1f487-2640", + "uc_output": "1f487-200d-2640-fe0f", + "uc_match": "1f487-2640-fe0f", + "uc_greedy": "1f487-2640", + "shortnames": [], + "category": "people" + }, + ":woman_guard:": { + "uc_base": "1f482-2640", + "uc_output": "1f482-200d-2640-fe0f", + "uc_match": "1f482-2640-fe0f", + "uc_greedy": "1f482-2640", + "shortnames": [], + "category": "people" + }, + ":woman_health_worker:": { + "uc_base": "1f469-2695", + "uc_output": "1f469-200d-2695-fe0f", + "uc_match": "1f469-2695-fe0f", + "uc_greedy": "1f469-2695", + "shortnames": [], + "category": "people" + }, + ":woman_in_lotus_position:": { + "uc_base": "1f9d8-2640", + "uc_output": "1f9d8-200d-2640-fe0f", + "uc_match": "1f9d8-2640-fe0f", + "uc_greedy": "1f9d8-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_in_steamy_room:": { + "uc_base": "1f9d6-2640", + "uc_output": "1f9d6-200d-2640-fe0f", + "uc_match": "1f9d6-2640-fe0f", + "uc_greedy": "1f9d6-2640", + "shortnames": [], + "category": "people" + }, + ":woman_judge:": { + "uc_base": "1f469-2696", + "uc_output": "1f469-200d-2696-fe0f", + "uc_match": "1f469-2696-fe0f", + "uc_greedy": "1f469-2696", + "shortnames": [], + "category": "people" + }, + ":woman_juggling:": { + "uc_base": "1f939-2640", + "uc_output": "1f939-200d-2640-fe0f", + "uc_match": "1f939-2640-fe0f", + "uc_greedy": "1f939-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_mage:": { + "uc_base": "1f9d9-2640", + "uc_output": "1f9d9-200d-2640-fe0f", + "uc_match": "1f9d9-2640-fe0f", + "uc_greedy": "1f9d9-2640", + "shortnames": [], + "category": "people" + }, + ":woman_mountain_biking:": { + "uc_base": "1f6b5-2640", + "uc_output": "1f6b5-200d-2640-fe0f", + "uc_match": "1f6b5-2640-fe0f", + "uc_greedy": "1f6b5-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_pilot:": { + "uc_base": "1f469-2708", + "uc_output": "1f469-200d-2708-fe0f", + "uc_match": "1f469-2708-fe0f", + "uc_greedy": "1f469-2708", + "shortnames": [], + "category": "people" + }, + ":woman_playing_handball:": { + "uc_base": "1f93e-2640", + "uc_output": "1f93e-200d-2640-fe0f", + "uc_match": "1f93e-2640-fe0f", + "uc_greedy": "1f93e-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_playing_water_polo:": { + "uc_base": "1f93d-2640", + "uc_output": "1f93d-200d-2640-fe0f", + "uc_match": "1f93d-2640-fe0f", + "uc_greedy": "1f93d-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_police_officer:": { + "uc_base": "1f46e-2640", + "uc_output": "1f46e-200d-2640-fe0f", + "uc_match": "1f46e-2640-fe0f", + "uc_greedy": "1f46e-2640", + "shortnames": [], + "category": "people" + }, + ":woman_pouting:": { + "uc_base": "1f64e-2640", + "uc_output": "1f64e-200d-2640-fe0f", + "uc_match": "1f64e-2640-fe0f", + "uc_greedy": "1f64e-2640", + "shortnames": [], + "category": "people" + }, + ":woman_raising_hand:": { + "uc_base": "1f64b-2640", + "uc_output": "1f64b-200d-2640-fe0f", + "uc_match": "1f64b-2640-fe0f", + "uc_greedy": "1f64b-2640", + "shortnames": [], + "category": "people" + }, + ":woman_rowing_boat:": { + "uc_base": "1f6a3-2640", + "uc_output": "1f6a3-200d-2640-fe0f", + "uc_match": "1f6a3-2640-fe0f", + "uc_greedy": "1f6a3-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_running:": { + "uc_base": "1f3c3-2640", + "uc_output": "1f3c3-200d-2640-fe0f", + "uc_match": "1f3c3-2640-fe0f", + "uc_greedy": "1f3c3-2640", + "shortnames": [], + "category": "people" + }, + ":woman_shrugging:": { + "uc_base": "1f937-2640", + "uc_output": "1f937-200d-2640-fe0f", + "uc_match": "1f937-2640-fe0f", + "uc_greedy": "1f937-2640", + "shortnames": [], + "category": "people" + }, + ":woman_surfing:": { + "uc_base": "1f3c4-2640", + "uc_output": "1f3c4-200d-2640-fe0f", + "uc_match": "1f3c4-2640-fe0f", + "uc_greedy": "1f3c4-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_swimming:": { + "uc_base": "1f3ca-2640", + "uc_output": "1f3ca-200d-2640-fe0f", + "uc_match": "1f3ca-2640-fe0f", + "uc_greedy": "1f3ca-2640", + "shortnames": [], + "category": "activity" + }, + ":woman_tipping_hand:": { + "uc_base": "1f481-2640", + "uc_output": "1f481-200d-2640-fe0f", + "uc_match": "1f481-2640-fe0f", + "uc_greedy": "1f481-2640", + "shortnames": [], + "category": "people" + }, + ":woman_vampire:": { + "uc_base": "1f9db-2640", + "uc_output": "1f9db-200d-2640-fe0f", + "uc_match": "1f9db-2640-fe0f", + "uc_greedy": "1f9db-2640", + "shortnames": [], + "category": "people" + }, + ":woman_walking:": { + "uc_base": "1f6b6-2640", + "uc_output": "1f6b6-200d-2640-fe0f", + "uc_match": "1f6b6-2640-fe0f", + "uc_greedy": "1f6b6-2640", + "shortnames": [], + "category": "people" + }, + ":woman_wearing_turban:": { + "uc_base": "1f473-2640", + "uc_output": "1f473-200d-2640-fe0f", + "uc_match": "1f473-2640-fe0f", + "uc_greedy": "1f473-2640", + "shortnames": [], + "category": "people" + }, + ":woman_zombie:": { + "uc_base": "1f9df-2640", + "uc_output": "1f9df-200d-2640-fe0f", + "uc_match": "1f9df-2640-fe0f", + "uc_greedy": "1f9df-2640", + "shortnames": [], + "category": "people" + }, + ":women_with_bunny_ears_partying:": { + "uc_base": "1f46f-2640", + "uc_output": "1f46f-200d-2640-fe0f", + "uc_match": "1f46f-2640-fe0f", + "uc_greedy": "1f46f-2640", + "shortnames": [], + "category": "people" + }, + ":women_wrestling:": { + "uc_base": "1f93c-2640", + "uc_output": "1f93c-200d-2640-fe0f", + "uc_match": "1f93c-2640-fe0f", + "uc_greedy": "1f93c-2640", + "shortnames": [], + "category": "activity" + }, + ":family_man_boy:": { + "uc_base": "1f468-1f466", + "uc_output": "1f468-200d-1f466", + "uc_match": "1f468-1f466", + "uc_greedy": "1f468-1f466", + "shortnames": [], + "category": "people" + }, + ":family_man_girl:": { + "uc_base": "1f468-1f467", + "uc_output": "1f468-200d-1f467", + "uc_match": "1f468-1f467", + "uc_greedy": "1f468-1f467", + "shortnames": [], + "category": "people" + }, + ":family_woman_boy:": { + "uc_base": "1f469-1f466", + "uc_output": "1f469-200d-1f466", + "uc_match": "1f469-1f466", + "uc_greedy": "1f469-1f466", + "shortnames": [], + "category": "people" + }, + ":family_woman_girl:": { + "uc_base": "1f469-1f467", + "uc_output": "1f469-200d-1f467", + "uc_match": "1f469-1f467", + "uc_greedy": "1f469-1f467", + "shortnames": [], + "category": "people" + }, + ":man_artist:": { + "uc_base": "1f468-1f3a8", + "uc_output": "1f468-200d-1f3a8", + "uc_match": "1f468-1f3a8", + "uc_greedy": "1f468-1f3a8", + "shortnames": [], + "category": "people" + }, + ":man_astronaut:": { + "uc_base": "1f468-1f680", + "uc_output": "1f468-200d-1f680", + "uc_match": "1f468-1f680", + "uc_greedy": "1f468-1f680", + "shortnames": [], + "category": "people" + }, + ":man_cook:": { + "uc_base": "1f468-1f373", + "uc_output": "1f468-200d-1f373", + "uc_match": "1f468-1f373", + "uc_greedy": "1f468-1f373", + "shortnames": [], + "category": "people" + }, + ":man_factory_worker:": { + "uc_base": "1f468-1f3ed", + "uc_output": "1f468-200d-1f3ed", + "uc_match": "1f468-1f3ed", + "uc_greedy": "1f468-1f3ed", + "shortnames": [], + "category": "people" + }, + ":man_farmer:": { + "uc_base": "1f468-1f33e", + "uc_output": "1f468-200d-1f33e", + "uc_match": "1f468-1f33e", + "uc_greedy": "1f468-1f33e", + "shortnames": [], + "category": "people" + }, + ":man_firefighter:": { + "uc_base": "1f468-1f692", + "uc_output": "1f468-200d-1f692", + "uc_match": "1f468-1f692", + "uc_greedy": "1f468-1f692", + "shortnames": [], + "category": "people" + }, + ":man_mechanic:": { + "uc_base": "1f468-1f527", + "uc_output": "1f468-200d-1f527", + "uc_match": "1f468-1f527", + "uc_greedy": "1f468-1f527", + "shortnames": [], + "category": "people" + }, + ":man_office_worker:": { + "uc_base": "1f468-1f4bc", + "uc_output": "1f468-200d-1f4bc", + "uc_match": "1f468-1f4bc", + "uc_greedy": "1f468-1f4bc", + "shortnames": [], + "category": "people" + }, + ":man_scientist:": { + "uc_base": "1f468-1f52c", + "uc_output": "1f468-200d-1f52c", + "uc_match": "1f468-1f52c", + "uc_greedy": "1f468-1f52c", + "shortnames": [], + "category": "people" + }, + ":man_singer:": { + "uc_base": "1f468-1f3a4", + "uc_output": "1f468-200d-1f3a4", + "uc_match": "1f468-1f3a4", + "uc_greedy": "1f468-1f3a4", + "shortnames": [], + "category": "people" + }, + ":man_student:": { + "uc_base": "1f468-1f393", + "uc_output": "1f468-200d-1f393", + "uc_match": "1f468-1f393", + "uc_greedy": "1f468-1f393", + "shortnames": [], + "category": "people" + }, + ":man_teacher:": { + "uc_base": "1f468-1f3eb", + "uc_output": "1f468-200d-1f3eb", + "uc_match": "1f468-1f3eb", + "uc_greedy": "1f468-1f3eb", + "shortnames": [], + "category": "people" + }, + ":man_technologist:": { + "uc_base": "1f468-1f4bb", + "uc_output": "1f468-200d-1f4bb", + "uc_match": "1f468-1f4bb", + "uc_greedy": "1f468-1f4bb", + "shortnames": [], + "category": "people" + }, + ":woman_artist:": { + "uc_base": "1f469-1f3a8", + "uc_output": "1f469-200d-1f3a8", + "uc_match": "1f469-1f3a8", + "uc_greedy": "1f469-1f3a8", + "shortnames": [], + "category": "people" + }, + ":woman_astronaut:": { + "uc_base": "1f469-1f680", + "uc_output": "1f469-200d-1f680", + "uc_match": "1f469-1f680", + "uc_greedy": "1f469-1f680", + "shortnames": [], + "category": "people" + }, + ":woman_cook:": { + "uc_base": "1f469-1f373", + "uc_output": "1f469-200d-1f373", + "uc_match": "1f469-1f373", + "uc_greedy": "1f469-1f373", + "shortnames": [], + "category": "people" + }, + ":woman_factory_worker:": { + "uc_base": "1f469-1f3ed", + "uc_output": "1f469-200d-1f3ed", + "uc_match": "1f469-1f3ed", + "uc_greedy": "1f469-1f3ed", + "shortnames": [], + "category": "people" + }, + ":woman_farmer:": { + "uc_base": "1f469-1f33e", + "uc_output": "1f469-200d-1f33e", + "uc_match": "1f469-1f33e", + "uc_greedy": "1f469-1f33e", + "shortnames": [], + "category": "people" + }, + ":woman_firefighter:": { + "uc_base": "1f469-1f692", + "uc_output": "1f469-200d-1f692", + "uc_match": "1f469-1f692", + "uc_greedy": "1f469-1f692", + "shortnames": [], + "category": "people" + }, + ":woman_mechanic:": { + "uc_base": "1f469-1f527", + "uc_output": "1f469-200d-1f527", + "uc_match": "1f469-1f527", + "uc_greedy": "1f469-1f527", + "shortnames": [], + "category": "people" + }, + ":woman_office_worker:": { + "uc_base": "1f469-1f4bc", + "uc_output": "1f469-200d-1f4bc", + "uc_match": "1f469-1f4bc", + "uc_greedy": "1f469-1f4bc", + "shortnames": [], + "category": "people" + }, + ":woman_scientist:": { + "uc_base": "1f469-1f52c", + "uc_output": "1f469-200d-1f52c", + "uc_match": "1f469-1f52c", + "uc_greedy": "1f469-1f52c", + "shortnames": [], + "category": "people" + }, + ":woman_singer:": { + "uc_base": "1f469-1f3a4", + "uc_output": "1f469-200d-1f3a4", + "uc_match": "1f469-1f3a4", + "uc_greedy": "1f469-1f3a4", + "shortnames": [], + "category": "people" + }, + ":woman_student:": { + "uc_base": "1f469-1f393", + "uc_output": "1f469-200d-1f393", + "uc_match": "1f469-1f393", + "uc_greedy": "1f469-1f393", + "shortnames": [], + "category": "people" + }, + ":woman_teacher:": { + "uc_base": "1f469-1f3eb", + "uc_output": "1f469-200d-1f3eb", + "uc_match": "1f469-1f3eb", + "uc_greedy": "1f469-1f3eb", + "shortnames": [], + "category": "people" + }, + ":woman_technologist:": { + "uc_base": "1f469-1f4bb", + "uc_output": "1f469-200d-1f4bb", + "uc_match": "1f469-1f4bb", + "uc_greedy": "1f469-1f4bb", + "shortnames": [], + "category": "people" + }, + ":asterisk:": { + "uc_base": "002a-20e3", + "uc_output": "002a-fe0f-20e3", + "uc_match": "002a-20e3", + "uc_greedy": "002a-20e3", + "shortnames": [":keycap_asterisk:"], + "category": "symbols" + }, + ":eight:": { + "uc_base": "0038-20e3", + "uc_output": "0038-fe0f-20e3", + "uc_match": "0038-20e3", + "uc_greedy": "0038-20e3", + "shortnames": [], + "category": "symbols" + }, + ":five:": { + "uc_base": "0035-20e3", + "uc_output": "0035-fe0f-20e3", + "uc_match": "0035-20e3", + "uc_greedy": "0035-20e3", + "shortnames": [], + "category": "symbols" + }, + ":four:": { + "uc_base": "0034-20e3", + "uc_output": "0034-fe0f-20e3", + "uc_match": "0034-20e3", + "uc_greedy": "0034-20e3", + "shortnames": [], + "category": "symbols" + }, + ":hash:": { + "uc_base": "0023-20e3", + "uc_output": "0023-fe0f-20e3", + "uc_match": "0023-20e3", + "uc_greedy": "0023-20e3", + "shortnames": [], + "category": "symbols" + }, + ":nine:": { + "uc_base": "0039-20e3", + "uc_output": "0039-fe0f-20e3", + "uc_match": "0039-20e3", + "uc_greedy": "0039-20e3", + "shortnames": [], + "category": "symbols" + }, + ":one:": { + "uc_base": "0031-20e3", + "uc_output": "0031-fe0f-20e3", + "uc_match": "0031-20e3", + "uc_greedy": "0031-20e3", + "shortnames": [], + "category": "symbols" + }, + ":seven:": { + "uc_base": "0037-20e3", + "uc_output": "0037-fe0f-20e3", + "uc_match": "0037-20e3", + "uc_greedy": "0037-20e3", + "shortnames": [], + "category": "symbols" + }, + ":six:": { + "uc_base": "0036-20e3", + "uc_output": "0036-fe0f-20e3", + "uc_match": "0036-20e3", + "uc_greedy": "0036-20e3", + "shortnames": [], + "category": "symbols" + }, + ":three:": { + "uc_base": "0033-20e3", + "uc_output": "0033-fe0f-20e3", + "uc_match": "0033-20e3", + "uc_greedy": "0033-20e3", + "shortnames": [], + "category": "symbols" + }, + ":two:": { + "uc_base": "0032-20e3", + "uc_output": "0032-fe0f-20e3", + "uc_match": "0032-20e3", + "uc_greedy": "0032-20e3", + "shortnames": [], + "category": "symbols" + }, + ":zero:": { + "uc_base": "0030-20e3", + "uc_output": "0030-fe0f-20e3", + "uc_match": "0030-20e3", + "uc_greedy": "0030-20e3", + "shortnames": [], + "category": "symbols" + }, + ":adult_tone1:": { + "uc_base": "1f9d1-1f3fb", + "uc_output": "1f9d1-1f3fb", + "uc_match": "1f9d1-1f3fb", + "uc_greedy": "1f9d1-1f3fb", + "shortnames": [":adult_light_skin_tone:"], + "category": "people" + }, + ":adult_tone2:": { + "uc_base": "1f9d1-1f3fc", + "uc_output": "1f9d1-1f3fc", + "uc_match": "1f9d1-1f3fc", + "uc_greedy": "1f9d1-1f3fc", + "shortnames": [":adult_medium_light_skin_tone:"], + "category": "people" + }, + ":adult_tone3:": { + "uc_base": "1f9d1-1f3fd", + "uc_output": "1f9d1-1f3fd", + "uc_match": "1f9d1-1f3fd", + "uc_greedy": "1f9d1-1f3fd", + "shortnames": [":adult_medium_skin_tone:"], + "category": "people" + }, + ":adult_tone4:": { + "uc_base": "1f9d1-1f3fe", + "uc_output": "1f9d1-1f3fe", + "uc_match": "1f9d1-1f3fe", + "uc_greedy": "1f9d1-1f3fe", + "shortnames": [":adult_medium_dark_skin_tone:"], + "category": "people" + }, + ":adult_tone5:": { + "uc_base": "1f9d1-1f3ff", + "uc_output": "1f9d1-1f3ff", + "uc_match": "1f9d1-1f3ff", + "uc_greedy": "1f9d1-1f3ff", + "shortnames": [":adult_dark_skin_tone:"], + "category": "people" + }, + ":angel_tone1:": { + "uc_base": "1f47c-1f3fb", + "uc_output": "1f47c-1f3fb", + "uc_match": "1f47c-1f3fb", + "uc_greedy": "1f47c-1f3fb", + "shortnames": [], + "category": "people" + }, + ":angel_tone2:": { + "uc_base": "1f47c-1f3fc", + "uc_output": "1f47c-1f3fc", + "uc_match": "1f47c-1f3fc", + "uc_greedy": "1f47c-1f3fc", + "shortnames": [], + "category": "people" + }, + ":angel_tone3:": { + "uc_base": "1f47c-1f3fd", + "uc_output": "1f47c-1f3fd", + "uc_match": "1f47c-1f3fd", + "uc_greedy": "1f47c-1f3fd", + "shortnames": [], + "category": "people" + }, + ":angel_tone4:": { + "uc_base": "1f47c-1f3fe", + "uc_output": "1f47c-1f3fe", + "uc_match": "1f47c-1f3fe", + "uc_greedy": "1f47c-1f3fe", + "shortnames": [], + "category": "people" + }, + ":angel_tone5:": { + "uc_base": "1f47c-1f3ff", + "uc_output": "1f47c-1f3ff", + "uc_match": "1f47c-1f3ff", + "uc_greedy": "1f47c-1f3ff", + "shortnames": [], + "category": "people" + }, + ":baby_tone1:": { + "uc_base": "1f476-1f3fb", + "uc_output": "1f476-1f3fb", + "uc_match": "1f476-1f3fb", + "uc_greedy": "1f476-1f3fb", + "shortnames": [], + "category": "people" + }, + ":baby_tone2:": { + "uc_base": "1f476-1f3fc", + "uc_output": "1f476-1f3fc", + "uc_match": "1f476-1f3fc", + "uc_greedy": "1f476-1f3fc", + "shortnames": [], + "category": "people" + }, + ":baby_tone3:": { + "uc_base": "1f476-1f3fd", + "uc_output": "1f476-1f3fd", + "uc_match": "1f476-1f3fd", + "uc_greedy": "1f476-1f3fd", + "shortnames": [], + "category": "people" + }, + ":baby_tone4:": { + "uc_base": "1f476-1f3fe", + "uc_output": "1f476-1f3fe", + "uc_match": "1f476-1f3fe", + "uc_greedy": "1f476-1f3fe", + "shortnames": [], + "category": "people" + }, + ":baby_tone5:": { + "uc_base": "1f476-1f3ff", + "uc_output": "1f476-1f3ff", + "uc_match": "1f476-1f3ff", + "uc_greedy": "1f476-1f3ff", + "shortnames": [], + "category": "people" + }, + ":bath_tone1:": { + "uc_base": "1f6c0-1f3fb", + "uc_output": "1f6c0-1f3fb", + "uc_match": "1f6c0-1f3fb", + "uc_greedy": "1f6c0-1f3fb", + "shortnames": [], + "category": "objects" + }, + ":bath_tone2:": { + "uc_base": "1f6c0-1f3fc", + "uc_output": "1f6c0-1f3fc", + "uc_match": "1f6c0-1f3fc", + "uc_greedy": "1f6c0-1f3fc", + "shortnames": [], + "category": "objects" + }, + ":bath_tone3:": { + "uc_base": "1f6c0-1f3fd", + "uc_output": "1f6c0-1f3fd", + "uc_match": "1f6c0-1f3fd", + "uc_greedy": "1f6c0-1f3fd", + "shortnames": [], + "category": "objects" + }, + ":bath_tone4:": { + "uc_base": "1f6c0-1f3fe", + "uc_output": "1f6c0-1f3fe", + "uc_match": "1f6c0-1f3fe", + "uc_greedy": "1f6c0-1f3fe", + "shortnames": [], + "category": "objects" + }, + ":bath_tone5:": { + "uc_base": "1f6c0-1f3ff", + "uc_output": "1f6c0-1f3ff", + "uc_match": "1f6c0-1f3ff", + "uc_greedy": "1f6c0-1f3ff", + "shortnames": [], + "category": "objects" + }, + ":bearded_person_tone1:": { + "uc_base": "1f9d4-1f3fb", + "uc_output": "1f9d4-1f3fb", + "uc_match": "1f9d4-1f3fb", + "uc_greedy": "1f9d4-1f3fb", + "shortnames": [":bearded_person_light_skin_tone:"], + "category": "people" + }, + ":bearded_person_tone2:": { + "uc_base": "1f9d4-1f3fc", + "uc_output": "1f9d4-1f3fc", + "uc_match": "1f9d4-1f3fc", + "uc_greedy": "1f9d4-1f3fc", + "shortnames": [":bearded_person_medium_light_skin_tone:"], + "category": "people" + }, + ":bearded_person_tone3:": { + "uc_base": "1f9d4-1f3fd", + "uc_output": "1f9d4-1f3fd", + "uc_match": "1f9d4-1f3fd", + "uc_greedy": "1f9d4-1f3fd", + "shortnames": [":bearded_person_medium_skin_tone:"], + "category": "people" + }, + ":bearded_person_tone4:": { + "uc_base": "1f9d4-1f3fe", + "uc_output": "1f9d4-1f3fe", + "uc_match": "1f9d4-1f3fe", + "uc_greedy": "1f9d4-1f3fe", + "shortnames": [":bearded_person_medium_dark_skin_tone:"], + "category": "people" + }, + ":bearded_person_tone5:": { + "uc_base": "1f9d4-1f3ff", + "uc_output": "1f9d4-1f3ff", + "uc_match": "1f9d4-1f3ff", + "uc_greedy": "1f9d4-1f3ff", + "shortnames": [":bearded_person_dark_skin_tone:"], + "category": "people" + }, + ":blond_haired_person_tone1:": { + "uc_base": "1f471-1f3fb", + "uc_output": "1f471-1f3fb", + "uc_match": "1f471-1f3fb", + "uc_greedy": "1f471-1f3fb", + "shortnames": [":person_with_blond_hair_tone1:"], + "category": "people" + }, + ":blond_haired_person_tone2:": { + "uc_base": "1f471-1f3fc", + "uc_output": "1f471-1f3fc", + "uc_match": "1f471-1f3fc", + "uc_greedy": "1f471-1f3fc", + "shortnames": [":person_with_blond_hair_tone2:"], + "category": "people" + }, + ":blond_haired_person_tone3:": { + "uc_base": "1f471-1f3fd", + "uc_output": "1f471-1f3fd", + "uc_match": "1f471-1f3fd", + "uc_greedy": "1f471-1f3fd", + "shortnames": [":person_with_blond_hair_tone3:"], + "category": "people" + }, + ":blond_haired_person_tone4:": { + "uc_base": "1f471-1f3fe", + "uc_output": "1f471-1f3fe", + "uc_match": "1f471-1f3fe", + "uc_greedy": "1f471-1f3fe", + "shortnames": [":person_with_blond_hair_tone4:"], + "category": "people" + }, + ":blond_haired_person_tone5:": { + "uc_base": "1f471-1f3ff", + "uc_output": "1f471-1f3ff", + "uc_match": "1f471-1f3ff", + "uc_greedy": "1f471-1f3ff", + "shortnames": [":person_with_blond_hair_tone5:"], + "category": "people" + }, + ":boy_tone1:": { + "uc_base": "1f466-1f3fb", + "uc_output": "1f466-1f3fb", + "uc_match": "1f466-1f3fb", + "uc_greedy": "1f466-1f3fb", + "shortnames": [], + "category": "people" + }, + ":boy_tone2:": { + "uc_base": "1f466-1f3fc", + "uc_output": "1f466-1f3fc", + "uc_match": "1f466-1f3fc", + "uc_greedy": "1f466-1f3fc", + "shortnames": [], + "category": "people" + }, + ":boy_tone3:": { + "uc_base": "1f466-1f3fd", + "uc_output": "1f466-1f3fd", + "uc_match": "1f466-1f3fd", + "uc_greedy": "1f466-1f3fd", + "shortnames": [], + "category": "people" + }, + ":boy_tone4:": { + "uc_base": "1f466-1f3fe", + "uc_output": "1f466-1f3fe", + "uc_match": "1f466-1f3fe", + "uc_greedy": "1f466-1f3fe", + "shortnames": [], + "category": "people" + }, + ":boy_tone5:": { + "uc_base": "1f466-1f3ff", + "uc_output": "1f466-1f3ff", + "uc_match": "1f466-1f3ff", + "uc_greedy": "1f466-1f3ff", + "shortnames": [], + "category": "people" + }, + ":breast_feeding_tone1:": { + "uc_base": "1f931-1f3fb", + "uc_output": "1f931-1f3fb", + "uc_match": "1f931-1f3fb", + "uc_greedy": "1f931-1f3fb", + "shortnames": [":breast_feeding_light_skin_tone:"], + "category": "people" + }, + ":breast_feeding_tone2:": { + "uc_base": "1f931-1f3fc", + "uc_output": "1f931-1f3fc", + "uc_match": "1f931-1f3fc", + "uc_greedy": "1f931-1f3fc", + "shortnames": [":breast_feeding_medium_light_skin_tone:"], + "category": "people" + }, + ":breast_feeding_tone3:": { + "uc_base": "1f931-1f3fd", + "uc_output": "1f931-1f3fd", + "uc_match": "1f931-1f3fd", + "uc_greedy": "1f931-1f3fd", + "shortnames": [":breast_feeding_medium_skin_tone:"], + "category": "people" + }, + ":breast_feeding_tone4:": { + "uc_base": "1f931-1f3fe", + "uc_output": "1f931-1f3fe", + "uc_match": "1f931-1f3fe", + "uc_greedy": "1f931-1f3fe", + "shortnames": [":breast_feeding_medium_dark_skin_tone:"], + "category": "people" + }, + ":breast_feeding_tone5:": { + "uc_base": "1f931-1f3ff", + "uc_output": "1f931-1f3ff", + "uc_match": "1f931-1f3ff", + "uc_greedy": "1f931-1f3ff", + "shortnames": [":breast_feeding_dark_skin_tone:"], + "category": "people" + }, + ":bride_with_veil_tone1:": { + "uc_base": "1f470-1f3fb", + "uc_output": "1f470-1f3fb", + "uc_match": "1f470-1f3fb", + "uc_greedy": "1f470-1f3fb", + "shortnames": [], + "category": "people" + }, + ":bride_with_veil_tone2:": { + "uc_base": "1f470-1f3fc", + "uc_output": "1f470-1f3fc", + "uc_match": "1f470-1f3fc", + "uc_greedy": "1f470-1f3fc", + "shortnames": [], + "category": "people" + }, + ":bride_with_veil_tone3:": { + "uc_base": "1f470-1f3fd", + "uc_output": "1f470-1f3fd", + "uc_match": "1f470-1f3fd", + "uc_greedy": "1f470-1f3fd", + "shortnames": [], + "category": "people" + }, + ":bride_with_veil_tone4:": { + "uc_base": "1f470-1f3fe", + "uc_output": "1f470-1f3fe", + "uc_match": "1f470-1f3fe", + "uc_greedy": "1f470-1f3fe", + "shortnames": [], + "category": "people" + }, + ":bride_with_veil_tone5:": { + "uc_base": "1f470-1f3ff", + "uc_output": "1f470-1f3ff", + "uc_match": "1f470-1f3ff", + "uc_greedy": "1f470-1f3ff", + "shortnames": [], + "category": "people" + }, + ":call_me_tone1:": { + "uc_base": "1f919-1f3fb", + "uc_output": "1f919-1f3fb", + "uc_match": "1f919-1f3fb", + "uc_greedy": "1f919-1f3fb", + "shortnames": [":call_me_hand_tone1:"], + "category": "people" + }, + ":call_me_tone2:": { + "uc_base": "1f919-1f3fc", + "uc_output": "1f919-1f3fc", + "uc_match": "1f919-1f3fc", + "uc_greedy": "1f919-1f3fc", + "shortnames": [":call_me_hand_tone2:"], + "category": "people" + }, + ":call_me_tone3:": { + "uc_base": "1f919-1f3fd", + "uc_output": "1f919-1f3fd", + "uc_match": "1f919-1f3fd", + "uc_greedy": "1f919-1f3fd", + "shortnames": [":call_me_hand_tone3:"], + "category": "people" + }, + ":call_me_tone4:": { + "uc_base": "1f919-1f3fe", + "uc_output": "1f919-1f3fe", + "uc_match": "1f919-1f3fe", + "uc_greedy": "1f919-1f3fe", + "shortnames": [":call_me_hand_tone4:"], + "category": "people" + }, + ":call_me_tone5:": { + "uc_base": "1f919-1f3ff", + "uc_output": "1f919-1f3ff", + "uc_match": "1f919-1f3ff", + "uc_greedy": "1f919-1f3ff", + "shortnames": [":call_me_hand_tone5:"], + "category": "people" + }, + ":child_tone1:": { + "uc_base": "1f9d2-1f3fb", + "uc_output": "1f9d2-1f3fb", + "uc_match": "1f9d2-1f3fb", + "uc_greedy": "1f9d2-1f3fb", + "shortnames": [":child_light_skin_tone:"], + "category": "people" + }, + ":child_tone2:": { + "uc_base": "1f9d2-1f3fc", + "uc_output": "1f9d2-1f3fc", + "uc_match": "1f9d2-1f3fc", + "uc_greedy": "1f9d2-1f3fc", + "shortnames": [":child_medium_light_skin_tone:"], + "category": "people" + }, + ":child_tone3:": { + "uc_base": "1f9d2-1f3fd", + "uc_output": "1f9d2-1f3fd", + "uc_match": "1f9d2-1f3fd", + "uc_greedy": "1f9d2-1f3fd", + "shortnames": [":child_medium_skin_tone:"], + "category": "people" + }, + ":child_tone4:": { + "uc_base": "1f9d2-1f3fe", + "uc_output": "1f9d2-1f3fe", + "uc_match": "1f9d2-1f3fe", + "uc_greedy": "1f9d2-1f3fe", + "shortnames": [":child_medium_dark_skin_tone:"], + "category": "people" + }, + ":child_tone5:": { + "uc_base": "1f9d2-1f3ff", + "uc_output": "1f9d2-1f3ff", + "uc_match": "1f9d2-1f3ff", + "uc_greedy": "1f9d2-1f3ff", + "shortnames": [":child_dark_skin_tone:"], + "category": "people" + }, + ":clap_tone1:": { + "uc_base": "1f44f-1f3fb", + "uc_output": "1f44f-1f3fb", + "uc_match": "1f44f-1f3fb", + "uc_greedy": "1f44f-1f3fb", + "shortnames": [], + "category": "people" + }, + ":clap_tone2:": { + "uc_base": "1f44f-1f3fc", + "uc_output": "1f44f-1f3fc", + "uc_match": "1f44f-1f3fc", + "uc_greedy": "1f44f-1f3fc", + "shortnames": [], + "category": "people" + }, + ":clap_tone3:": { + "uc_base": "1f44f-1f3fd", + "uc_output": "1f44f-1f3fd", + "uc_match": "1f44f-1f3fd", + "uc_greedy": "1f44f-1f3fd", + "shortnames": [], + "category": "people" + }, + ":clap_tone4:": { + "uc_base": "1f44f-1f3fe", + "uc_output": "1f44f-1f3fe", + "uc_match": "1f44f-1f3fe", + "uc_greedy": "1f44f-1f3fe", + "shortnames": [], + "category": "people" + }, + ":clap_tone5:": { + "uc_base": "1f44f-1f3ff", + "uc_output": "1f44f-1f3ff", + "uc_match": "1f44f-1f3ff", + "uc_greedy": "1f44f-1f3ff", + "shortnames": [], + "category": "people" + }, + ":construction_worker_tone1:": { + "uc_base": "1f477-1f3fb", + "uc_output": "1f477-1f3fb", + "uc_match": "1f477-1f3fb", + "uc_greedy": "1f477-1f3fb", + "shortnames": [], + "category": "people" + }, + ":construction_worker_tone2:": { + "uc_base": "1f477-1f3fc", + "uc_output": "1f477-1f3fc", + "uc_match": "1f477-1f3fc", + "uc_greedy": "1f477-1f3fc", + "shortnames": [], + "category": "people" + }, + ":construction_worker_tone3:": { + "uc_base": "1f477-1f3fd", + "uc_output": "1f477-1f3fd", + "uc_match": "1f477-1f3fd", + "uc_greedy": "1f477-1f3fd", + "shortnames": [], + "category": "people" + }, + ":construction_worker_tone4:": { + "uc_base": "1f477-1f3fe", + "uc_output": "1f477-1f3fe", + "uc_match": "1f477-1f3fe", + "uc_greedy": "1f477-1f3fe", + "shortnames": [], + "category": "people" + }, + ":construction_worker_tone5:": { + "uc_base": "1f477-1f3ff", + "uc_output": "1f477-1f3ff", + "uc_match": "1f477-1f3ff", + "uc_greedy": "1f477-1f3ff", + "shortnames": [], + "category": "people" + }, + ":dancer_tone1:": { + "uc_base": "1f483-1f3fb", + "uc_output": "1f483-1f3fb", + "uc_match": "1f483-1f3fb", + "uc_greedy": "1f483-1f3fb", + "shortnames": [], + "category": "people" + }, + ":dancer_tone2:": { + "uc_base": "1f483-1f3fc", + "uc_output": "1f483-1f3fc", + "uc_match": "1f483-1f3fc", + "uc_greedy": "1f483-1f3fc", + "shortnames": [], + "category": "people" + }, + ":dancer_tone3:": { + "uc_base": "1f483-1f3fd", + "uc_output": "1f483-1f3fd", + "uc_match": "1f483-1f3fd", + "uc_greedy": "1f483-1f3fd", + "shortnames": [], + "category": "people" + }, + ":dancer_tone4:": { + "uc_base": "1f483-1f3fe", + "uc_output": "1f483-1f3fe", + "uc_match": "1f483-1f3fe", + "uc_greedy": "1f483-1f3fe", + "shortnames": [], + "category": "people" + }, + ":dancer_tone5:": { + "uc_base": "1f483-1f3ff", + "uc_output": "1f483-1f3ff", + "uc_match": "1f483-1f3ff", + "uc_greedy": "1f483-1f3ff", + "shortnames": [], + "category": "people" + }, + ":detective_tone1:": { + "uc_base": "1f575-1f3fb", + "uc_output": "1f575-1f3fb", + "uc_match": "1f575-fe0f-1f3fb", + "uc_greedy": "1f575-fe0f-1f3fb", + "shortnames": [":spy_tone1:", ":sleuth_or_spy_tone1:"], + "category": "people" + }, + ":detective_tone2:": { + "uc_base": "1f575-1f3fc", + "uc_output": "1f575-1f3fc", + "uc_match": "1f575-fe0f-1f3fc", + "uc_greedy": "1f575-fe0f-1f3fc", + "shortnames": [":spy_tone2:", ":sleuth_or_spy_tone2:"], + "category": "people" + }, + ":detective_tone3:": { + "uc_base": "1f575-1f3fd", + "uc_output": "1f575-1f3fd", + "uc_match": "1f575-fe0f-1f3fd", + "uc_greedy": "1f575-fe0f-1f3fd", + "shortnames": [":spy_tone3:", ":sleuth_or_spy_tone3:"], + "category": "people" + }, + ":detective_tone4:": { + "uc_base": "1f575-1f3fe", + "uc_output": "1f575-1f3fe", + "uc_match": "1f575-fe0f-1f3fe", + "uc_greedy": "1f575-fe0f-1f3fe", + "shortnames": [":spy_tone4:", ":sleuth_or_spy_tone4:"], + "category": "people" + }, + ":detective_tone5:": { + "uc_base": "1f575-1f3ff", + "uc_output": "1f575-1f3ff", + "uc_match": "1f575-fe0f-1f3ff", + "uc_greedy": "1f575-fe0f-1f3ff", + "shortnames": [":spy_tone5:", ":sleuth_or_spy_tone5:"], + "category": "people" + }, + ":ear_tone1:": { + "uc_base": "1f442-1f3fb", + "uc_output": "1f442-1f3fb", + "uc_match": "1f442-1f3fb", + "uc_greedy": "1f442-1f3fb", + "shortnames": [], + "category": "people" + }, + ":ear_tone2:": { + "uc_base": "1f442-1f3fc", + "uc_output": "1f442-1f3fc", + "uc_match": "1f442-1f3fc", + "uc_greedy": "1f442-1f3fc", + "shortnames": [], + "category": "people" + }, + ":ear_tone3:": { + "uc_base": "1f442-1f3fd", + "uc_output": "1f442-1f3fd", + "uc_match": "1f442-1f3fd", + "uc_greedy": "1f442-1f3fd", + "shortnames": [], + "category": "people" + }, + ":ear_tone4:": { + "uc_base": "1f442-1f3fe", + "uc_output": "1f442-1f3fe", + "uc_match": "1f442-1f3fe", + "uc_greedy": "1f442-1f3fe", + "shortnames": [], + "category": "people" + }, + ":ear_tone5:": { + "uc_base": "1f442-1f3ff", + "uc_output": "1f442-1f3ff", + "uc_match": "1f442-1f3ff", + "uc_greedy": "1f442-1f3ff", + "shortnames": [], + "category": "people" + }, + ":elf_tone1:": { + "uc_base": "1f9dd-1f3fb", + "uc_output": "1f9dd-1f3fb", + "uc_match": "1f9dd-1f3fb", + "uc_greedy": "1f9dd-1f3fb", + "shortnames": [":elf_light_skin_tone:"], + "category": "people" + }, + ":elf_tone2:": { + "uc_base": "1f9dd-1f3fc", + "uc_output": "1f9dd-1f3fc", + "uc_match": "1f9dd-1f3fc", + "uc_greedy": "1f9dd-1f3fc", + "shortnames": [":elf_medium_light_skin_tone:"], + "category": "people" + }, + ":elf_tone3:": { + "uc_base": "1f9dd-1f3fd", + "uc_output": "1f9dd-1f3fd", + "uc_match": "1f9dd-1f3fd", + "uc_greedy": "1f9dd-1f3fd", + "shortnames": [":elf_medium_skin_tone:"], + "category": "people" + }, + ":elf_tone4:": { + "uc_base": "1f9dd-1f3fe", + "uc_output": "1f9dd-1f3fe", + "uc_match": "1f9dd-1f3fe", + "uc_greedy": "1f9dd-1f3fe", + "shortnames": [":elf_medium_dark_skin_tone:"], + "category": "people" + }, + ":elf_tone5:": { + "uc_base": "1f9dd-1f3ff", + "uc_output": "1f9dd-1f3ff", + "uc_match": "1f9dd-1f3ff", + "uc_greedy": "1f9dd-1f3ff", + "shortnames": [":elf_dark_skin_tone:"], + "category": "people" + }, + ":fairy_tone1:": { + "uc_base": "1f9da-1f3fb", + "uc_output": "1f9da-1f3fb", + "uc_match": "1f9da-1f3fb", + "uc_greedy": "1f9da-1f3fb", + "shortnames": [":fairy_light_skin_tone:"], + "category": "people" + }, + ":fairy_tone2:": { + "uc_base": "1f9da-1f3fc", + "uc_output": "1f9da-1f3fc", + "uc_match": "1f9da-1f3fc", + "uc_greedy": "1f9da-1f3fc", + "shortnames": [":fairy_medium_light_skin_tone:"], + "category": "people" + }, + ":fairy_tone3:": { + "uc_base": "1f9da-1f3fd", + "uc_output": "1f9da-1f3fd", + "uc_match": "1f9da-1f3fd", + "uc_greedy": "1f9da-1f3fd", + "shortnames": [":fairy_medium_skin_tone:"], + "category": "people" + }, + ":fairy_tone4:": { + "uc_base": "1f9da-1f3fe", + "uc_output": "1f9da-1f3fe", + "uc_match": "1f9da-1f3fe", + "uc_greedy": "1f9da-1f3fe", + "shortnames": [":fairy_medium_dark_skin_tone:"], + "category": "people" + }, + ":fairy_tone5:": { + "uc_base": "1f9da-1f3ff", + "uc_output": "1f9da-1f3ff", + "uc_match": "1f9da-1f3ff", + "uc_greedy": "1f9da-1f3ff", + "shortnames": [":fairy_dark_skin_tone:"], + "category": "people" + }, + ":fingers_crossed_tone1:": { + "uc_base": "1f91e-1f3fb", + "uc_output": "1f91e-1f3fb", + "uc_match": "1f91e-1f3fb", + "uc_greedy": "1f91e-1f3fb", + "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone1:"], + "category": "people" + }, + ":fingers_crossed_tone2:": { + "uc_base": "1f91e-1f3fc", + "uc_output": "1f91e-1f3fc", + "uc_match": "1f91e-1f3fc", + "uc_greedy": "1f91e-1f3fc", + "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone2:"], + "category": "people" + }, + ":fingers_crossed_tone3:": { + "uc_base": "1f91e-1f3fd", + "uc_output": "1f91e-1f3fd", + "uc_match": "1f91e-1f3fd", + "uc_greedy": "1f91e-1f3fd", + "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone3:"], + "category": "people" + }, + ":fingers_crossed_tone4:": { + "uc_base": "1f91e-1f3fe", + "uc_output": "1f91e-1f3fe", + "uc_match": "1f91e-1f3fe", + "uc_greedy": "1f91e-1f3fe", + "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone4:"], + "category": "people" + }, + ":fingers_crossed_tone5:": { + "uc_base": "1f91e-1f3ff", + "uc_output": "1f91e-1f3ff", + "uc_match": "1f91e-1f3ff", + "uc_greedy": "1f91e-1f3ff", + "shortnames": [":hand_with_index_and_middle_fingers_crossed_tone5:"], + "category": "people" + }, + ":flag_ac:": { + "uc_base": "1f1e6-1f1e8", + "uc_output": "1f1e6-1f1e8", + "uc_match": "1f1e6-1f1e8", + "uc_greedy": "1f1e6-1f1e8", + "shortnames": [":ac:"], + "category": "flags" + }, + ":flag_ad:": { + "uc_base": "1f1e6-1f1e9", + "uc_output": "1f1e6-1f1e9", + "uc_match": "1f1e6-1f1e9", + "uc_greedy": "1f1e6-1f1e9", + "shortnames": [":ad:"], + "category": "flags" + }, + ":flag_ae:": { + "uc_base": "1f1e6-1f1ea", + "uc_output": "1f1e6-1f1ea", + "uc_match": "1f1e6-1f1ea", + "uc_greedy": "1f1e6-1f1ea", + "shortnames": [":ae:"], + "category": "flags" + }, + ":flag_af:": { + "uc_base": "1f1e6-1f1eb", + "uc_output": "1f1e6-1f1eb", + "uc_match": "1f1e6-1f1eb", + "uc_greedy": "1f1e6-1f1eb", + "shortnames": [":af:"], + "category": "flags" + }, + ":flag_ag:": { + "uc_base": "1f1e6-1f1ec", + "uc_output": "1f1e6-1f1ec", + "uc_match": "1f1e6-1f1ec", + "uc_greedy": "1f1e6-1f1ec", + "shortnames": [":ag:"], + "category": "flags" + }, + ":flag_ai:": { + "uc_base": "1f1e6-1f1ee", + "uc_output": "1f1e6-1f1ee", + "uc_match": "1f1e6-1f1ee", + "uc_greedy": "1f1e6-1f1ee", + "shortnames": [":ai:"], + "category": "flags" + }, + ":flag_al:": { + "uc_base": "1f1e6-1f1f1", + "uc_output": "1f1e6-1f1f1", + "uc_match": "1f1e6-1f1f1", + "uc_greedy": "1f1e6-1f1f1", + "shortnames": [":al:"], + "category": "flags" + }, + ":flag_am:": { + "uc_base": "1f1e6-1f1f2", + "uc_output": "1f1e6-1f1f2", + "uc_match": "1f1e6-1f1f2", + "uc_greedy": "1f1e6-1f1f2", + "shortnames": [":am:"], + "category": "flags" + }, + ":flag_ao:": { + "uc_base": "1f1e6-1f1f4", + "uc_output": "1f1e6-1f1f4", + "uc_match": "1f1e6-1f1f4", + "uc_greedy": "1f1e6-1f1f4", + "shortnames": [":ao:"], + "category": "flags" + }, + ":flag_aq:": { + "uc_base": "1f1e6-1f1f6", + "uc_output": "1f1e6-1f1f6", + "uc_match": "1f1e6-1f1f6", + "uc_greedy": "1f1e6-1f1f6", + "shortnames": [":aq:"], + "category": "flags" + }, + ":flag_ar:": { + "uc_base": "1f1e6-1f1f7", + "uc_output": "1f1e6-1f1f7", + "uc_match": "1f1e6-1f1f7", + "uc_greedy": "1f1e6-1f1f7", + "shortnames": [":ar:"], + "category": "flags" + }, + ":flag_as:": { + "uc_base": "1f1e6-1f1f8", + "uc_output": "1f1e6-1f1f8", + "uc_match": "1f1e6-1f1f8", + "uc_greedy": "1f1e6-1f1f8", + "shortnames": [":as:"], + "category": "flags" + }, + ":flag_at:": { + "uc_base": "1f1e6-1f1f9", + "uc_output": "1f1e6-1f1f9", + "uc_match": "1f1e6-1f1f9", + "uc_greedy": "1f1e6-1f1f9", + "shortnames": [":at:"], + "category": "flags" + }, + ":flag_au:": { + "uc_base": "1f1e6-1f1fa", + "uc_output": "1f1e6-1f1fa", + "uc_match": "1f1e6-1f1fa", + "uc_greedy": "1f1e6-1f1fa", + "shortnames": [":au:"], + "category": "flags" + }, + ":flag_aw:": { + "uc_base": "1f1e6-1f1fc", + "uc_output": "1f1e6-1f1fc", + "uc_match": "1f1e6-1f1fc", + "uc_greedy": "1f1e6-1f1fc", + "shortnames": [":aw:"], + "category": "flags" + }, + ":flag_ax:": { + "uc_base": "1f1e6-1f1fd", + "uc_output": "1f1e6-1f1fd", + "uc_match": "1f1e6-1f1fd", + "uc_greedy": "1f1e6-1f1fd", + "shortnames": [":ax:"], + "category": "flags" + }, + ":flag_az:": { + "uc_base": "1f1e6-1f1ff", + "uc_output": "1f1e6-1f1ff", + "uc_match": "1f1e6-1f1ff", + "uc_greedy": "1f1e6-1f1ff", + "shortnames": [":az:"], + "category": "flags" + }, + ":flag_ba:": { + "uc_base": "1f1e7-1f1e6", + "uc_output": "1f1e7-1f1e6", + "uc_match": "1f1e7-1f1e6", + "uc_greedy": "1f1e7-1f1e6", + "shortnames": [":ba:"], + "category": "flags" + }, + ":flag_bb:": { + "uc_base": "1f1e7-1f1e7", + "uc_output": "1f1e7-1f1e7", + "uc_match": "1f1e7-1f1e7", + "uc_greedy": "1f1e7-1f1e7", + "shortnames": [":bb:"], + "category": "flags" + }, + ":flag_bd:": { + "uc_base": "1f1e7-1f1e9", + "uc_output": "1f1e7-1f1e9", + "uc_match": "1f1e7-1f1e9", + "uc_greedy": "1f1e7-1f1e9", + "shortnames": [":bd:"], + "category": "flags" + }, + ":flag_be:": { + "uc_base": "1f1e7-1f1ea", + "uc_output": "1f1e7-1f1ea", + "uc_match": "1f1e7-1f1ea", + "uc_greedy": "1f1e7-1f1ea", + "shortnames": [":be:"], + "category": "flags" + }, + ":flag_bf:": { + "uc_base": "1f1e7-1f1eb", + "uc_output": "1f1e7-1f1eb", + "uc_match": "1f1e7-1f1eb", + "uc_greedy": "1f1e7-1f1eb", + "shortnames": [":bf:"], + "category": "flags" + }, + ":flag_bg:": { + "uc_base": "1f1e7-1f1ec", + "uc_output": "1f1e7-1f1ec", + "uc_match": "1f1e7-1f1ec", + "uc_greedy": "1f1e7-1f1ec", + "shortnames": [":bg:"], + "category": "flags" + }, + ":flag_bh:": { + "uc_base": "1f1e7-1f1ed", + "uc_output": "1f1e7-1f1ed", + "uc_match": "1f1e7-1f1ed", + "uc_greedy": "1f1e7-1f1ed", + "shortnames": [":bh:"], + "category": "flags" + }, + ":flag_bi:": { + "uc_base": "1f1e7-1f1ee", + "uc_output": "1f1e7-1f1ee", + "uc_match": "1f1e7-1f1ee", + "uc_greedy": "1f1e7-1f1ee", + "shortnames": [":bi:"], + "category": "flags" + }, + ":flag_bj:": { + "uc_base": "1f1e7-1f1ef", + "uc_output": "1f1e7-1f1ef", + "uc_match": "1f1e7-1f1ef", + "uc_greedy": "1f1e7-1f1ef", + "shortnames": [":bj:"], + "category": "flags" + }, + ":flag_bl:": { + "uc_base": "1f1e7-1f1f1", + "uc_output": "1f1e7-1f1f1", + "uc_match": "1f1e7-1f1f1", + "uc_greedy": "1f1e7-1f1f1", + "shortnames": [":bl:"], + "category": "flags" + }, + ":flag_bm:": { + "uc_base": "1f1e7-1f1f2", + "uc_output": "1f1e7-1f1f2", + "uc_match": "1f1e7-1f1f2", + "uc_greedy": "1f1e7-1f1f2", + "shortnames": [":bm:"], + "category": "flags" + }, + ":flag_bn:": { + "uc_base": "1f1e7-1f1f3", + "uc_output": "1f1e7-1f1f3", + "uc_match": "1f1e7-1f1f3", + "uc_greedy": "1f1e7-1f1f3", + "shortnames": [":bn:"], + "category": "flags" + }, + ":flag_bo:": { + "uc_base": "1f1e7-1f1f4", + "uc_output": "1f1e7-1f1f4", + "uc_match": "1f1e7-1f1f4", + "uc_greedy": "1f1e7-1f1f4", + "shortnames": [":bo:"], + "category": "flags" + }, + ":flag_bq:": { + "uc_base": "1f1e7-1f1f6", + "uc_output": "1f1e7-1f1f6", + "uc_match": "1f1e7-1f1f6", + "uc_greedy": "1f1e7-1f1f6", + "shortnames": [":bq:"], + "category": "flags" + }, + ":flag_br:": { + "uc_base": "1f1e7-1f1f7", + "uc_output": "1f1e7-1f1f7", + "uc_match": "1f1e7-1f1f7", + "uc_greedy": "1f1e7-1f1f7", + "shortnames": [":br:"], + "category": "flags" + }, + ":flag_bs:": { + "uc_base": "1f1e7-1f1f8", + "uc_output": "1f1e7-1f1f8", + "uc_match": "1f1e7-1f1f8", + "uc_greedy": "1f1e7-1f1f8", + "shortnames": [":bs:"], + "category": "flags" + }, + ":flag_bt:": { + "uc_base": "1f1e7-1f1f9", + "uc_output": "1f1e7-1f1f9", + "uc_match": "1f1e7-1f1f9", + "uc_greedy": "1f1e7-1f1f9", + "shortnames": [":bt:"], + "category": "flags" + }, + ":flag_bv:": { + "uc_base": "1f1e7-1f1fb", + "uc_output": "1f1e7-1f1fb", + "uc_match": "1f1e7-1f1fb", + "uc_greedy": "1f1e7-1f1fb", + "shortnames": [":bv:"], + "category": "flags" + }, + ":flag_bw:": { + "uc_base": "1f1e7-1f1fc", + "uc_output": "1f1e7-1f1fc", + "uc_match": "1f1e7-1f1fc", + "uc_greedy": "1f1e7-1f1fc", + "shortnames": [":bw:"], + "category": "flags" + }, + ":flag_by:": { + "uc_base": "1f1e7-1f1fe", + "uc_output": "1f1e7-1f1fe", + "uc_match": "1f1e7-1f1fe", + "uc_greedy": "1f1e7-1f1fe", + "shortnames": [":by:"], + "category": "flags" + }, + ":flag_bz:": { + "uc_base": "1f1e7-1f1ff", + "uc_output": "1f1e7-1f1ff", + "uc_match": "1f1e7-1f1ff", + "uc_greedy": "1f1e7-1f1ff", + "shortnames": [":bz:"], + "category": "flags" + }, + ":flag_ca:": { + "uc_base": "1f1e8-1f1e6", + "uc_output": "1f1e8-1f1e6", + "uc_match": "1f1e8-1f1e6", + "uc_greedy": "1f1e8-1f1e6", + "shortnames": [":ca:"], + "category": "flags" + }, + ":flag_cc:": { + "uc_base": "1f1e8-1f1e8", + "uc_output": "1f1e8-1f1e8", + "uc_match": "1f1e8-1f1e8", + "uc_greedy": "1f1e8-1f1e8", + "shortnames": [":cc:"], + "category": "flags" + }, + ":flag_cd:": { + "uc_base": "1f1e8-1f1e9", + "uc_output": "1f1e8-1f1e9", + "uc_match": "1f1e8-1f1e9", + "uc_greedy": "1f1e8-1f1e9", + "shortnames": [":congo:"], + "category": "flags" + }, + ":flag_cf:": { + "uc_base": "1f1e8-1f1eb", + "uc_output": "1f1e8-1f1eb", + "uc_match": "1f1e8-1f1eb", + "uc_greedy": "1f1e8-1f1eb", + "shortnames": [":cf:"], + "category": "flags" + }, + ":flag_cg:": { + "uc_base": "1f1e8-1f1ec", + "uc_output": "1f1e8-1f1ec", + "uc_match": "1f1e8-1f1ec", + "uc_greedy": "1f1e8-1f1ec", + "shortnames": [":cg:"], + "category": "flags" + }, + ":flag_ch:": { + "uc_base": "1f1e8-1f1ed", + "uc_output": "1f1e8-1f1ed", + "uc_match": "1f1e8-1f1ed", + "uc_greedy": "1f1e8-1f1ed", + "shortnames": [":ch:"], + "category": "flags" + }, + ":flag_ci:": { + "uc_base": "1f1e8-1f1ee", + "uc_output": "1f1e8-1f1ee", + "uc_match": "1f1e8-1f1ee", + "uc_greedy": "1f1e8-1f1ee", + "shortnames": [":ci:"], + "category": "flags" + }, + ":flag_ck:": { + "uc_base": "1f1e8-1f1f0", + "uc_output": "1f1e8-1f1f0", + "uc_match": "1f1e8-1f1f0", + "uc_greedy": "1f1e8-1f1f0", + "shortnames": [":ck:"], + "category": "flags" + }, + ":flag_cl:": { + "uc_base": "1f1e8-1f1f1", + "uc_output": "1f1e8-1f1f1", + "uc_match": "1f1e8-1f1f1", + "uc_greedy": "1f1e8-1f1f1", + "shortnames": [":chile:"], + "category": "flags" + }, + ":flag_cm:": { + "uc_base": "1f1e8-1f1f2", + "uc_output": "1f1e8-1f1f2", + "uc_match": "1f1e8-1f1f2", + "uc_greedy": "1f1e8-1f1f2", + "shortnames": [":cm:"], + "category": "flags" + }, + ":flag_cn:": { + "uc_base": "1f1e8-1f1f3", + "uc_output": "1f1e8-1f1f3", + "uc_match": "1f1e8-1f1f3", + "uc_greedy": "1f1e8-1f1f3", + "shortnames": [":cn:"], + "category": "flags" + }, + ":flag_co:": { + "uc_base": "1f1e8-1f1f4", + "uc_output": "1f1e8-1f1f4", + "uc_match": "1f1e8-1f1f4", + "uc_greedy": "1f1e8-1f1f4", + "shortnames": [":co:"], + "category": "flags" + }, + ":flag_cp:": { + "uc_base": "1f1e8-1f1f5", + "uc_output": "1f1e8-1f1f5", + "uc_match": "1f1e8-1f1f5", + "uc_greedy": "1f1e8-1f1f5", + "shortnames": [":cp:"], + "category": "flags" + }, + ":flag_cr:": { + "uc_base": "1f1e8-1f1f7", + "uc_output": "1f1e8-1f1f7", + "uc_match": "1f1e8-1f1f7", + "uc_greedy": "1f1e8-1f1f7", + "shortnames": [":cr:"], + "category": "flags" + }, + ":flag_cu:": { + "uc_base": "1f1e8-1f1fa", + "uc_output": "1f1e8-1f1fa", + "uc_match": "1f1e8-1f1fa", + "uc_greedy": "1f1e8-1f1fa", + "shortnames": [":cu:"], + "category": "flags" + }, + ":flag_cv:": { + "uc_base": "1f1e8-1f1fb", + "uc_output": "1f1e8-1f1fb", + "uc_match": "1f1e8-1f1fb", + "uc_greedy": "1f1e8-1f1fb", + "shortnames": [":cv:"], + "category": "flags" + }, + ":flag_cw:": { + "uc_base": "1f1e8-1f1fc", + "uc_output": "1f1e8-1f1fc", + "uc_match": "1f1e8-1f1fc", + "uc_greedy": "1f1e8-1f1fc", + "shortnames": [":cw:"], + "category": "flags" + }, + ":flag_cx:": { + "uc_base": "1f1e8-1f1fd", + "uc_output": "1f1e8-1f1fd", + "uc_match": "1f1e8-1f1fd", + "uc_greedy": "1f1e8-1f1fd", + "shortnames": [":cx:"], + "category": "flags" + }, + ":flag_cy:": { + "uc_base": "1f1e8-1f1fe", + "uc_output": "1f1e8-1f1fe", + "uc_match": "1f1e8-1f1fe", + "uc_greedy": "1f1e8-1f1fe", + "shortnames": [":cy:"], + "category": "flags" + }, + ":flag_cz:": { + "uc_base": "1f1e8-1f1ff", + "uc_output": "1f1e8-1f1ff", + "uc_match": "1f1e8-1f1ff", + "uc_greedy": "1f1e8-1f1ff", + "shortnames": [":cz:"], + "category": "flags" + }, + ":flag_de:": { + "uc_base": "1f1e9-1f1ea", + "uc_output": "1f1e9-1f1ea", + "uc_match": "1f1e9-1f1ea", + "uc_greedy": "1f1e9-1f1ea", + "shortnames": [":de:"], + "category": "flags" + }, + ":flag_dg:": { + "uc_base": "1f1e9-1f1ec", + "uc_output": "1f1e9-1f1ec", + "uc_match": "1f1e9-1f1ec", + "uc_greedy": "1f1e9-1f1ec", + "shortnames": [":dg:"], + "category": "flags" + }, + ":flag_dj:": { + "uc_base": "1f1e9-1f1ef", + "uc_output": "1f1e9-1f1ef", + "uc_match": "1f1e9-1f1ef", + "uc_greedy": "1f1e9-1f1ef", + "shortnames": [":dj:"], + "category": "flags" + }, + ":flag_dk:": { + "uc_base": "1f1e9-1f1f0", + "uc_output": "1f1e9-1f1f0", + "uc_match": "1f1e9-1f1f0", + "uc_greedy": "1f1e9-1f1f0", + "shortnames": [":dk:"], + "category": "flags" + }, + ":flag_dm:": { + "uc_base": "1f1e9-1f1f2", + "uc_output": "1f1e9-1f1f2", + "uc_match": "1f1e9-1f1f2", + "uc_greedy": "1f1e9-1f1f2", + "shortnames": [":dm:"], + "category": "flags" + }, + ":flag_do:": { + "uc_base": "1f1e9-1f1f4", + "uc_output": "1f1e9-1f1f4", + "uc_match": "1f1e9-1f1f4", + "uc_greedy": "1f1e9-1f1f4", + "shortnames": [":do:"], + "category": "flags" + }, + ":flag_dz:": { + "uc_base": "1f1e9-1f1ff", + "uc_output": "1f1e9-1f1ff", + "uc_match": "1f1e9-1f1ff", + "uc_greedy": "1f1e9-1f1ff", + "shortnames": [":dz:"], + "category": "flags" + }, + ":flag_ea:": { + "uc_base": "1f1ea-1f1e6", + "uc_output": "1f1ea-1f1e6", + "uc_match": "1f1ea-1f1e6", + "uc_greedy": "1f1ea-1f1e6", + "shortnames": [":ea:"], + "category": "flags" + }, + ":flag_ec:": { + "uc_base": "1f1ea-1f1e8", + "uc_output": "1f1ea-1f1e8", + "uc_match": "1f1ea-1f1e8", + "uc_greedy": "1f1ea-1f1e8", + "shortnames": [":ec:"], + "category": "flags" + }, + ":flag_ee:": { + "uc_base": "1f1ea-1f1ea", + "uc_output": "1f1ea-1f1ea", + "uc_match": "1f1ea-1f1ea", + "uc_greedy": "1f1ea-1f1ea", + "shortnames": [":ee:"], + "category": "flags" + }, + ":flag_eg:": { + "uc_base": "1f1ea-1f1ec", + "uc_output": "1f1ea-1f1ec", + "uc_match": "1f1ea-1f1ec", + "uc_greedy": "1f1ea-1f1ec", + "shortnames": [":eg:"], + "category": "flags" + }, + ":flag_eh:": { + "uc_base": "1f1ea-1f1ed", + "uc_output": "1f1ea-1f1ed", + "uc_match": "1f1ea-1f1ed", + "uc_greedy": "1f1ea-1f1ed", + "shortnames": [":eh:"], + "category": "flags" + }, + ":flag_er:": { + "uc_base": "1f1ea-1f1f7", + "uc_output": "1f1ea-1f1f7", + "uc_match": "1f1ea-1f1f7", + "uc_greedy": "1f1ea-1f1f7", + "shortnames": [":er:"], + "category": "flags" + }, + ":flag_es:": { + "uc_base": "1f1ea-1f1f8", + "uc_output": "1f1ea-1f1f8", + "uc_match": "1f1ea-1f1f8", + "uc_greedy": "1f1ea-1f1f8", + "shortnames": [":es:"], + "category": "flags" + }, + ":flag_et:": { + "uc_base": "1f1ea-1f1f9", + "uc_output": "1f1ea-1f1f9", + "uc_match": "1f1ea-1f1f9", + "uc_greedy": "1f1ea-1f1f9", + "shortnames": [":et:"], + "category": "flags" + }, + ":flag_eu:": { + "uc_base": "1f1ea-1f1fa", + "uc_output": "1f1ea-1f1fa", + "uc_match": "1f1ea-1f1fa", + "uc_greedy": "1f1ea-1f1fa", + "shortnames": [":eu:"], + "category": "flags" + }, + ":flag_fi:": { + "uc_base": "1f1eb-1f1ee", + "uc_output": "1f1eb-1f1ee", + "uc_match": "1f1eb-1f1ee", + "uc_greedy": "1f1eb-1f1ee", + "shortnames": [":fi:"], + "category": "flags" + }, + ":flag_fj:": { + "uc_base": "1f1eb-1f1ef", + "uc_output": "1f1eb-1f1ef", + "uc_match": "1f1eb-1f1ef", + "uc_greedy": "1f1eb-1f1ef", + "shortnames": [":fj:"], + "category": "flags" + }, + ":flag_fk:": { + "uc_base": "1f1eb-1f1f0", + "uc_output": "1f1eb-1f1f0", + "uc_match": "1f1eb-1f1f0", + "uc_greedy": "1f1eb-1f1f0", + "shortnames": [":fk:"], + "category": "flags" + }, + ":flag_fm:": { + "uc_base": "1f1eb-1f1f2", + "uc_output": "1f1eb-1f1f2", + "uc_match": "1f1eb-1f1f2", + "uc_greedy": "1f1eb-1f1f2", + "shortnames": [":fm:"], + "category": "flags" + }, + ":flag_fo:": { + "uc_base": "1f1eb-1f1f4", + "uc_output": "1f1eb-1f1f4", + "uc_match": "1f1eb-1f1f4", + "uc_greedy": "1f1eb-1f1f4", + "shortnames": [":fo:"], + "category": "flags" + }, + ":flag_fr:": { + "uc_base": "1f1eb-1f1f7", + "uc_output": "1f1eb-1f1f7", + "uc_match": "1f1eb-1f1f7", + "uc_greedy": "1f1eb-1f1f7", + "shortnames": [":fr:"], + "category": "flags" + }, + ":flag_ga:": { + "uc_base": "1f1ec-1f1e6", + "uc_output": "1f1ec-1f1e6", + "uc_match": "1f1ec-1f1e6", + "uc_greedy": "1f1ec-1f1e6", + "shortnames": [":ga:"], + "category": "flags" + }, + ":flag_gb:": { + "uc_base": "1f1ec-1f1e7", + "uc_output": "1f1ec-1f1e7", + "uc_match": "1f1ec-1f1e7", + "uc_greedy": "1f1ec-1f1e7", + "shortnames": [":gb:"], + "category": "flags" + }, + ":flag_gd:": { + "uc_base": "1f1ec-1f1e9", + "uc_output": "1f1ec-1f1e9", + "uc_match": "1f1ec-1f1e9", + "uc_greedy": "1f1ec-1f1e9", + "shortnames": [":gd:"], + "category": "flags" + }, + ":flag_ge:": { + "uc_base": "1f1ec-1f1ea", + "uc_output": "1f1ec-1f1ea", + "uc_match": "1f1ec-1f1ea", + "uc_greedy": "1f1ec-1f1ea", + "shortnames": [":ge:"], + "category": "flags" + }, + ":flag_gf:": { + "uc_base": "1f1ec-1f1eb", + "uc_output": "1f1ec-1f1eb", + "uc_match": "1f1ec-1f1eb", + "uc_greedy": "1f1ec-1f1eb", + "shortnames": [":gf:"], + "category": "flags" + }, + ":flag_gg:": { + "uc_base": "1f1ec-1f1ec", + "uc_output": "1f1ec-1f1ec", + "uc_match": "1f1ec-1f1ec", + "uc_greedy": "1f1ec-1f1ec", + "shortnames": [":gg:"], + "category": "flags" + }, + ":flag_gh:": { + "uc_base": "1f1ec-1f1ed", + "uc_output": "1f1ec-1f1ed", + "uc_match": "1f1ec-1f1ed", + "uc_greedy": "1f1ec-1f1ed", + "shortnames": [":gh:"], + "category": "flags" + }, + ":flag_gi:": { + "uc_base": "1f1ec-1f1ee", + "uc_output": "1f1ec-1f1ee", + "uc_match": "1f1ec-1f1ee", + "uc_greedy": "1f1ec-1f1ee", + "shortnames": [":gi:"], + "category": "flags" + }, + ":flag_gl:": { + "uc_base": "1f1ec-1f1f1", + "uc_output": "1f1ec-1f1f1", + "uc_match": "1f1ec-1f1f1", + "uc_greedy": "1f1ec-1f1f1", + "shortnames": [":gl:"], + "category": "flags" + }, + ":flag_gm:": { + "uc_base": "1f1ec-1f1f2", + "uc_output": "1f1ec-1f1f2", + "uc_match": "1f1ec-1f1f2", + "uc_greedy": "1f1ec-1f1f2", + "shortnames": [":gm:"], + "category": "flags" + }, + ":flag_gn:": { + "uc_base": "1f1ec-1f1f3", + "uc_output": "1f1ec-1f1f3", + "uc_match": "1f1ec-1f1f3", + "uc_greedy": "1f1ec-1f1f3", + "shortnames": [":gn:"], + "category": "flags" + }, + ":flag_gp:": { + "uc_base": "1f1ec-1f1f5", + "uc_output": "1f1ec-1f1f5", + "uc_match": "1f1ec-1f1f5", + "uc_greedy": "1f1ec-1f1f5", + "shortnames": [":gp:"], + "category": "flags" + }, + ":flag_gq:": { + "uc_base": "1f1ec-1f1f6", + "uc_output": "1f1ec-1f1f6", + "uc_match": "1f1ec-1f1f6", + "uc_greedy": "1f1ec-1f1f6", + "shortnames": [":gq:"], + "category": "flags" + }, + ":flag_gr:": { + "uc_base": "1f1ec-1f1f7", + "uc_output": "1f1ec-1f1f7", + "uc_match": "1f1ec-1f1f7", + "uc_greedy": "1f1ec-1f1f7", + "shortnames": [":gr:"], + "category": "flags" + }, + ":flag_gs:": { + "uc_base": "1f1ec-1f1f8", + "uc_output": "1f1ec-1f1f8", + "uc_match": "1f1ec-1f1f8", + "uc_greedy": "1f1ec-1f1f8", + "shortnames": [":gs:"], + "category": "flags" + }, + ":flag_gt:": { + "uc_base": "1f1ec-1f1f9", + "uc_output": "1f1ec-1f1f9", + "uc_match": "1f1ec-1f1f9", + "uc_greedy": "1f1ec-1f1f9", + "shortnames": [":gt:"], + "category": "flags" + }, + ":flag_gu:": { + "uc_base": "1f1ec-1f1fa", + "uc_output": "1f1ec-1f1fa", + "uc_match": "1f1ec-1f1fa", + "uc_greedy": "1f1ec-1f1fa", + "shortnames": [":gu:"], + "category": "flags" + }, + ":flag_gw:": { + "uc_base": "1f1ec-1f1fc", + "uc_output": "1f1ec-1f1fc", + "uc_match": "1f1ec-1f1fc", + "uc_greedy": "1f1ec-1f1fc", + "shortnames": [":gw:"], + "category": "flags" + }, + ":flag_gy:": { + "uc_base": "1f1ec-1f1fe", + "uc_output": "1f1ec-1f1fe", + "uc_match": "1f1ec-1f1fe", + "uc_greedy": "1f1ec-1f1fe", + "shortnames": [":gy:"], + "category": "flags" + }, + ":flag_hk:": { + "uc_base": "1f1ed-1f1f0", + "uc_output": "1f1ed-1f1f0", + "uc_match": "1f1ed-1f1f0", + "uc_greedy": "1f1ed-1f1f0", + "shortnames": [":hk:"], + "category": "flags" + }, + ":flag_hm:": { + "uc_base": "1f1ed-1f1f2", + "uc_output": "1f1ed-1f1f2", + "uc_match": "1f1ed-1f1f2", + "uc_greedy": "1f1ed-1f1f2", + "shortnames": [":hm:"], + "category": "flags" + }, + ":flag_hn:": { + "uc_base": "1f1ed-1f1f3", + "uc_output": "1f1ed-1f1f3", + "uc_match": "1f1ed-1f1f3", + "uc_greedy": "1f1ed-1f1f3", + "shortnames": [":hn:"], + "category": "flags" + }, + ":flag_hr:": { + "uc_base": "1f1ed-1f1f7", + "uc_output": "1f1ed-1f1f7", + "uc_match": "1f1ed-1f1f7", + "uc_greedy": "1f1ed-1f1f7", + "shortnames": [":hr:"], + "category": "flags" + }, + ":flag_ht:": { + "uc_base": "1f1ed-1f1f9", + "uc_output": "1f1ed-1f1f9", + "uc_match": "1f1ed-1f1f9", + "uc_greedy": "1f1ed-1f1f9", + "shortnames": [":ht:"], + "category": "flags" + }, + ":flag_hu:": { + "uc_base": "1f1ed-1f1fa", + "uc_output": "1f1ed-1f1fa", + "uc_match": "1f1ed-1f1fa", + "uc_greedy": "1f1ed-1f1fa", + "shortnames": [":hu:"], + "category": "flags" + }, + ":flag_ic:": { + "uc_base": "1f1ee-1f1e8", + "uc_output": "1f1ee-1f1e8", + "uc_match": "1f1ee-1f1e8", + "uc_greedy": "1f1ee-1f1e8", + "shortnames": [":ic:"], + "category": "flags" + }, + ":flag_id:": { + "uc_base": "1f1ee-1f1e9", + "uc_output": "1f1ee-1f1e9", + "uc_match": "1f1ee-1f1e9", + "uc_greedy": "1f1ee-1f1e9", + "shortnames": [":indonesia:"], + "category": "flags" + }, + ":flag_ie:": { + "uc_base": "1f1ee-1f1ea", + "uc_output": "1f1ee-1f1ea", + "uc_match": "1f1ee-1f1ea", + "uc_greedy": "1f1ee-1f1ea", + "shortnames": [":ie:"], + "category": "flags" + }, + ":flag_il:": { + "uc_base": "1f1ee-1f1f1", + "uc_output": "1f1ee-1f1f1", + "uc_match": "1f1ee-1f1f1", + "uc_greedy": "1f1ee-1f1f1", + "shortnames": [":il:"], + "category": "flags" + }, + ":flag_im:": { + "uc_base": "1f1ee-1f1f2", + "uc_output": "1f1ee-1f1f2", + "uc_match": "1f1ee-1f1f2", + "uc_greedy": "1f1ee-1f1f2", + "shortnames": [":im:"], + "category": "flags" + }, + ":flag_in:": { + "uc_base": "1f1ee-1f1f3", + "uc_output": "1f1ee-1f1f3", + "uc_match": "1f1ee-1f1f3", + "uc_greedy": "1f1ee-1f1f3", + "shortnames": [":in:"], + "category": "flags" + }, + ":flag_io:": { + "uc_base": "1f1ee-1f1f4", + "uc_output": "1f1ee-1f1f4", + "uc_match": "1f1ee-1f1f4", + "uc_greedy": "1f1ee-1f1f4", + "shortnames": [":io:"], + "category": "flags" + }, + ":flag_iq:": { + "uc_base": "1f1ee-1f1f6", + "uc_output": "1f1ee-1f1f6", + "uc_match": "1f1ee-1f1f6", + "uc_greedy": "1f1ee-1f1f6", + "shortnames": [":iq:"], + "category": "flags" + }, + ":flag_ir:": { + "uc_base": "1f1ee-1f1f7", + "uc_output": "1f1ee-1f1f7", + "uc_match": "1f1ee-1f1f7", + "uc_greedy": "1f1ee-1f1f7", + "shortnames": [":ir:"], + "category": "flags" + }, + ":flag_is:": { + "uc_base": "1f1ee-1f1f8", + "uc_output": "1f1ee-1f1f8", + "uc_match": "1f1ee-1f1f8", + "uc_greedy": "1f1ee-1f1f8", + "shortnames": [":is:"], + "category": "flags" + }, + ":flag_it:": { + "uc_base": "1f1ee-1f1f9", + "uc_output": "1f1ee-1f1f9", + "uc_match": "1f1ee-1f1f9", + "uc_greedy": "1f1ee-1f1f9", + "shortnames": [":it:"], + "category": "flags" + }, + ":flag_je:": { + "uc_base": "1f1ef-1f1ea", + "uc_output": "1f1ef-1f1ea", + "uc_match": "1f1ef-1f1ea", + "uc_greedy": "1f1ef-1f1ea", + "shortnames": [":je:"], + "category": "flags" + }, + ":flag_jm:": { + "uc_base": "1f1ef-1f1f2", + "uc_output": "1f1ef-1f1f2", + "uc_match": "1f1ef-1f1f2", + "uc_greedy": "1f1ef-1f1f2", + "shortnames": [":jm:"], + "category": "flags" + }, + ":flag_jo:": { + "uc_base": "1f1ef-1f1f4", + "uc_output": "1f1ef-1f1f4", + "uc_match": "1f1ef-1f1f4", + "uc_greedy": "1f1ef-1f1f4", + "shortnames": [":jo:"], + "category": "flags" + }, + ":flag_jp:": { + "uc_base": "1f1ef-1f1f5", + "uc_output": "1f1ef-1f1f5", + "uc_match": "1f1ef-1f1f5", + "uc_greedy": "1f1ef-1f1f5", + "shortnames": [":jp:"], + "category": "flags" + }, + ":flag_ke:": { + "uc_base": "1f1f0-1f1ea", + "uc_output": "1f1f0-1f1ea", + "uc_match": "1f1f0-1f1ea", + "uc_greedy": "1f1f0-1f1ea", + "shortnames": [":ke:"], + "category": "flags" + }, + ":flag_kg:": { + "uc_base": "1f1f0-1f1ec", + "uc_output": "1f1f0-1f1ec", + "uc_match": "1f1f0-1f1ec", + "uc_greedy": "1f1f0-1f1ec", + "shortnames": [":kg:"], + "category": "flags" + }, + ":flag_kh:": { + "uc_base": "1f1f0-1f1ed", + "uc_output": "1f1f0-1f1ed", + "uc_match": "1f1f0-1f1ed", + "uc_greedy": "1f1f0-1f1ed", + "shortnames": [":kh:"], + "category": "flags" + }, + ":flag_ki:": { + "uc_base": "1f1f0-1f1ee", + "uc_output": "1f1f0-1f1ee", + "uc_match": "1f1f0-1f1ee", + "uc_greedy": "1f1f0-1f1ee", + "shortnames": [":ki:"], + "category": "flags" + }, + ":flag_km:": { + "uc_base": "1f1f0-1f1f2", + "uc_output": "1f1f0-1f1f2", + "uc_match": "1f1f0-1f1f2", + "uc_greedy": "1f1f0-1f1f2", + "shortnames": [":km:"], + "category": "flags" + }, + ":flag_kn:": { + "uc_base": "1f1f0-1f1f3", + "uc_output": "1f1f0-1f1f3", + "uc_match": "1f1f0-1f1f3", + "uc_greedy": "1f1f0-1f1f3", + "shortnames": [":kn:"], + "category": "flags" + }, + ":flag_kp:": { + "uc_base": "1f1f0-1f1f5", + "uc_output": "1f1f0-1f1f5", + "uc_match": "1f1f0-1f1f5", + "uc_greedy": "1f1f0-1f1f5", + "shortnames": [":kp:"], + "category": "flags" + }, + ":flag_kr:": { + "uc_base": "1f1f0-1f1f7", + "uc_output": "1f1f0-1f1f7", + "uc_match": "1f1f0-1f1f7", + "uc_greedy": "1f1f0-1f1f7", + "shortnames": [":kr:"], + "category": "flags" + }, + ":flag_kw:": { + "uc_base": "1f1f0-1f1fc", + "uc_output": "1f1f0-1f1fc", + "uc_match": "1f1f0-1f1fc", + "uc_greedy": "1f1f0-1f1fc", + "shortnames": [":kw:"], + "category": "flags" + }, + ":flag_ky:": { + "uc_base": "1f1f0-1f1fe", + "uc_output": "1f1f0-1f1fe", + "uc_match": "1f1f0-1f1fe", + "uc_greedy": "1f1f0-1f1fe", + "shortnames": [":ky:"], + "category": "flags" + }, + ":flag_kz:": { + "uc_base": "1f1f0-1f1ff", + "uc_output": "1f1f0-1f1ff", + "uc_match": "1f1f0-1f1ff", + "uc_greedy": "1f1f0-1f1ff", + "shortnames": [":kz:"], + "category": "flags" + }, + ":flag_la:": { + "uc_base": "1f1f1-1f1e6", + "uc_output": "1f1f1-1f1e6", + "uc_match": "1f1f1-1f1e6", + "uc_greedy": "1f1f1-1f1e6", + "shortnames": [":la:"], + "category": "flags" + }, + ":flag_lb:": { + "uc_base": "1f1f1-1f1e7", + "uc_output": "1f1f1-1f1e7", + "uc_match": "1f1f1-1f1e7", + "uc_greedy": "1f1f1-1f1e7", + "shortnames": [":lb:"], + "category": "flags" + }, + ":flag_lc:": { + "uc_base": "1f1f1-1f1e8", + "uc_output": "1f1f1-1f1e8", + "uc_match": "1f1f1-1f1e8", + "uc_greedy": "1f1f1-1f1e8", + "shortnames": [":lc:"], + "category": "flags" + }, + ":flag_li:": { + "uc_base": "1f1f1-1f1ee", + "uc_output": "1f1f1-1f1ee", + "uc_match": "1f1f1-1f1ee", + "uc_greedy": "1f1f1-1f1ee", + "shortnames": [":li:"], + "category": "flags" + }, + ":flag_lk:": { + "uc_base": "1f1f1-1f1f0", + "uc_output": "1f1f1-1f1f0", + "uc_match": "1f1f1-1f1f0", + "uc_greedy": "1f1f1-1f1f0", + "shortnames": [":lk:"], + "category": "flags" + }, + ":flag_lr:": { + "uc_base": "1f1f1-1f1f7", + "uc_output": "1f1f1-1f1f7", + "uc_match": "1f1f1-1f1f7", + "uc_greedy": "1f1f1-1f1f7", + "shortnames": [":lr:"], + "category": "flags" + }, + ":flag_ls:": { + "uc_base": "1f1f1-1f1f8", + "uc_output": "1f1f1-1f1f8", + "uc_match": "1f1f1-1f1f8", + "uc_greedy": "1f1f1-1f1f8", + "shortnames": [":ls:"], + "category": "flags" + }, + ":flag_lt:": { + "uc_base": "1f1f1-1f1f9", + "uc_output": "1f1f1-1f1f9", + "uc_match": "1f1f1-1f1f9", + "uc_greedy": "1f1f1-1f1f9", + "shortnames": [":lt:"], + "category": "flags" + }, + ":flag_lu:": { + "uc_base": "1f1f1-1f1fa", + "uc_output": "1f1f1-1f1fa", + "uc_match": "1f1f1-1f1fa", + "uc_greedy": "1f1f1-1f1fa", + "shortnames": [":lu:"], + "category": "flags" + }, + ":flag_lv:": { + "uc_base": "1f1f1-1f1fb", + "uc_output": "1f1f1-1f1fb", + "uc_match": "1f1f1-1f1fb", + "uc_greedy": "1f1f1-1f1fb", + "shortnames": [":lv:"], + "category": "flags" + }, + ":flag_ly:": { + "uc_base": "1f1f1-1f1fe", + "uc_output": "1f1f1-1f1fe", + "uc_match": "1f1f1-1f1fe", + "uc_greedy": "1f1f1-1f1fe", + "shortnames": [":ly:"], + "category": "flags" + }, + ":flag_ma:": { + "uc_base": "1f1f2-1f1e6", + "uc_output": "1f1f2-1f1e6", + "uc_match": "1f1f2-1f1e6", + "uc_greedy": "1f1f2-1f1e6", + "shortnames": [":ma:"], + "category": "flags" + }, + ":flag_mc:": { + "uc_base": "1f1f2-1f1e8", + "uc_output": "1f1f2-1f1e8", + "uc_match": "1f1f2-1f1e8", + "uc_greedy": "1f1f2-1f1e8", + "shortnames": [":mc:"], + "category": "flags" + }, + ":flag_md:": { + "uc_base": "1f1f2-1f1e9", + "uc_output": "1f1f2-1f1e9", + "uc_match": "1f1f2-1f1e9", + "uc_greedy": "1f1f2-1f1e9", + "shortnames": [":md:"], + "category": "flags" + }, + ":flag_me:": { + "uc_base": "1f1f2-1f1ea", + "uc_output": "1f1f2-1f1ea", + "uc_match": "1f1f2-1f1ea", + "uc_greedy": "1f1f2-1f1ea", + "shortnames": [":me:"], + "category": "flags" + }, + ":flag_mf:": { + "uc_base": "1f1f2-1f1eb", + "uc_output": "1f1f2-1f1eb", + "uc_match": "1f1f2-1f1eb", + "uc_greedy": "1f1f2-1f1eb", + "shortnames": [":mf:"], + "category": "flags" + }, + ":flag_mg:": { + "uc_base": "1f1f2-1f1ec", + "uc_output": "1f1f2-1f1ec", + "uc_match": "1f1f2-1f1ec", + "uc_greedy": "1f1f2-1f1ec", + "shortnames": [":mg:"], + "category": "flags" + }, + ":flag_mh:": { + "uc_base": "1f1f2-1f1ed", + "uc_output": "1f1f2-1f1ed", + "uc_match": "1f1f2-1f1ed", + "uc_greedy": "1f1f2-1f1ed", + "shortnames": [":mh:"], + "category": "flags" + }, + ":flag_mk:": { + "uc_base": "1f1f2-1f1f0", + "uc_output": "1f1f2-1f1f0", + "uc_match": "1f1f2-1f1f0", + "uc_greedy": "1f1f2-1f1f0", + "shortnames": [":mk:"], + "category": "flags" + }, + ":flag_ml:": { + "uc_base": "1f1f2-1f1f1", + "uc_output": "1f1f2-1f1f1", + "uc_match": "1f1f2-1f1f1", + "uc_greedy": "1f1f2-1f1f1", + "shortnames": [":ml:"], + "category": "flags" + }, + ":flag_mm:": { + "uc_base": "1f1f2-1f1f2", + "uc_output": "1f1f2-1f1f2", + "uc_match": "1f1f2-1f1f2", + "uc_greedy": "1f1f2-1f1f2", + "shortnames": [":mm:"], + "category": "flags" + }, + ":flag_mn:": { + "uc_base": "1f1f2-1f1f3", + "uc_output": "1f1f2-1f1f3", + "uc_match": "1f1f2-1f1f3", + "uc_greedy": "1f1f2-1f1f3", + "shortnames": [":mn:"], + "category": "flags" + }, + ":flag_mo:": { + "uc_base": "1f1f2-1f1f4", + "uc_output": "1f1f2-1f1f4", + "uc_match": "1f1f2-1f1f4", + "uc_greedy": "1f1f2-1f1f4", + "shortnames": [":mo:"], + "category": "flags" + }, + ":flag_mp:": { + "uc_base": "1f1f2-1f1f5", + "uc_output": "1f1f2-1f1f5", + "uc_match": "1f1f2-1f1f5", + "uc_greedy": "1f1f2-1f1f5", + "shortnames": [":mp:"], + "category": "flags" + }, + ":flag_mq:": { + "uc_base": "1f1f2-1f1f6", + "uc_output": "1f1f2-1f1f6", + "uc_match": "1f1f2-1f1f6", + "uc_greedy": "1f1f2-1f1f6", + "shortnames": [":mq:"], + "category": "flags" + }, + ":flag_mr:": { + "uc_base": "1f1f2-1f1f7", + "uc_output": "1f1f2-1f1f7", + "uc_match": "1f1f2-1f1f7", + "uc_greedy": "1f1f2-1f1f7", + "shortnames": [":mr:"], + "category": "flags" + }, + ":flag_ms:": { + "uc_base": "1f1f2-1f1f8", + "uc_output": "1f1f2-1f1f8", + "uc_match": "1f1f2-1f1f8", + "uc_greedy": "1f1f2-1f1f8", + "shortnames": [":ms:"], + "category": "flags" + }, + ":flag_mt:": { + "uc_base": "1f1f2-1f1f9", + "uc_output": "1f1f2-1f1f9", + "uc_match": "1f1f2-1f1f9", + "uc_greedy": "1f1f2-1f1f9", + "shortnames": [":mt:"], + "category": "flags" + }, + ":flag_mu:": { + "uc_base": "1f1f2-1f1fa", + "uc_output": "1f1f2-1f1fa", + "uc_match": "1f1f2-1f1fa", + "uc_greedy": "1f1f2-1f1fa", + "shortnames": [":mu:"], + "category": "flags" + }, + ":flag_mv:": { + "uc_base": "1f1f2-1f1fb", + "uc_output": "1f1f2-1f1fb", + "uc_match": "1f1f2-1f1fb", + "uc_greedy": "1f1f2-1f1fb", + "shortnames": [":mv:"], + "category": "flags" + }, + ":flag_mw:": { + "uc_base": "1f1f2-1f1fc", + "uc_output": "1f1f2-1f1fc", + "uc_match": "1f1f2-1f1fc", + "uc_greedy": "1f1f2-1f1fc", + "shortnames": [":mw:"], + "category": "flags" + }, + ":flag_mx:": { + "uc_base": "1f1f2-1f1fd", + "uc_output": "1f1f2-1f1fd", + "uc_match": "1f1f2-1f1fd", + "uc_greedy": "1f1f2-1f1fd", + "shortnames": [":mx:"], + "category": "flags" + }, + ":flag_my:": { + "uc_base": "1f1f2-1f1fe", + "uc_output": "1f1f2-1f1fe", + "uc_match": "1f1f2-1f1fe", + "uc_greedy": "1f1f2-1f1fe", + "shortnames": [":my:"], + "category": "flags" + }, + ":flag_mz:": { + "uc_base": "1f1f2-1f1ff", + "uc_output": "1f1f2-1f1ff", + "uc_match": "1f1f2-1f1ff", + "uc_greedy": "1f1f2-1f1ff", + "shortnames": [":mz:"], + "category": "flags" + }, + ":flag_na:": { + "uc_base": "1f1f3-1f1e6", + "uc_output": "1f1f3-1f1e6", + "uc_match": "1f1f3-1f1e6", + "uc_greedy": "1f1f3-1f1e6", + "shortnames": [":na:"], + "category": "flags" + }, + ":flag_nc:": { + "uc_base": "1f1f3-1f1e8", + "uc_output": "1f1f3-1f1e8", + "uc_match": "1f1f3-1f1e8", + "uc_greedy": "1f1f3-1f1e8", + "shortnames": [":nc:"], + "category": "flags" + }, + ":flag_ne:": { + "uc_base": "1f1f3-1f1ea", + "uc_output": "1f1f3-1f1ea", + "uc_match": "1f1f3-1f1ea", + "uc_greedy": "1f1f3-1f1ea", + "shortnames": [":ne:"], + "category": "flags" + }, + ":flag_nf:": { + "uc_base": "1f1f3-1f1eb", + "uc_output": "1f1f3-1f1eb", + "uc_match": "1f1f3-1f1eb", + "uc_greedy": "1f1f3-1f1eb", + "shortnames": [":nf:"], + "category": "flags" + }, + ":flag_ng:": { + "uc_base": "1f1f3-1f1ec", + "uc_output": "1f1f3-1f1ec", + "uc_match": "1f1f3-1f1ec", + "uc_greedy": "1f1f3-1f1ec", + "shortnames": [":nigeria:"], + "category": "flags" + }, + ":flag_ni:": { + "uc_base": "1f1f3-1f1ee", + "uc_output": "1f1f3-1f1ee", + "uc_match": "1f1f3-1f1ee", + "uc_greedy": "1f1f3-1f1ee", + "shortnames": [":ni:"], + "category": "flags" + }, + ":flag_nl:": { + "uc_base": "1f1f3-1f1f1", + "uc_output": "1f1f3-1f1f1", + "uc_match": "1f1f3-1f1f1", + "uc_greedy": "1f1f3-1f1f1", + "shortnames": [":nl:"], + "category": "flags" + }, + ":flag_no:": { + "uc_base": "1f1f3-1f1f4", + "uc_output": "1f1f3-1f1f4", + "uc_match": "1f1f3-1f1f4", + "uc_greedy": "1f1f3-1f1f4", + "shortnames": [":no:"], + "category": "flags" + }, + ":flag_np:": { + "uc_base": "1f1f3-1f1f5", + "uc_output": "1f1f3-1f1f5", + "uc_match": "1f1f3-1f1f5", + "uc_greedy": "1f1f3-1f1f5", + "shortnames": [":np:"], + "category": "flags" + }, + ":flag_nr:": { + "uc_base": "1f1f3-1f1f7", + "uc_output": "1f1f3-1f1f7", + "uc_match": "1f1f3-1f1f7", + "uc_greedy": "1f1f3-1f1f7", + "shortnames": [":nr:"], + "category": "flags" + }, + ":flag_nu:": { + "uc_base": "1f1f3-1f1fa", + "uc_output": "1f1f3-1f1fa", + "uc_match": "1f1f3-1f1fa", + "uc_greedy": "1f1f3-1f1fa", + "shortnames": [":nu:"], + "category": "flags" + }, + ":flag_nz:": { + "uc_base": "1f1f3-1f1ff", + "uc_output": "1f1f3-1f1ff", + "uc_match": "1f1f3-1f1ff", + "uc_greedy": "1f1f3-1f1ff", + "shortnames": [":nz:"], + "category": "flags" + }, + ":flag_om:": { + "uc_base": "1f1f4-1f1f2", + "uc_output": "1f1f4-1f1f2", + "uc_match": "1f1f4-1f1f2", + "uc_greedy": "1f1f4-1f1f2", + "shortnames": [":om:"], + "category": "flags" + }, + ":flag_pa:": { + "uc_base": "1f1f5-1f1e6", + "uc_output": "1f1f5-1f1e6", + "uc_match": "1f1f5-1f1e6", + "uc_greedy": "1f1f5-1f1e6", + "shortnames": [":pa:"], + "category": "flags" + }, + ":flag_pe:": { + "uc_base": "1f1f5-1f1ea", + "uc_output": "1f1f5-1f1ea", + "uc_match": "1f1f5-1f1ea", + "uc_greedy": "1f1f5-1f1ea", + "shortnames": [":pe:"], + "category": "flags" + }, + ":flag_pf:": { + "uc_base": "1f1f5-1f1eb", + "uc_output": "1f1f5-1f1eb", + "uc_match": "1f1f5-1f1eb", + "uc_greedy": "1f1f5-1f1eb", + "shortnames": [":pf:"], + "category": "flags" + }, + ":flag_pg:": { + "uc_base": "1f1f5-1f1ec", + "uc_output": "1f1f5-1f1ec", + "uc_match": "1f1f5-1f1ec", + "uc_greedy": "1f1f5-1f1ec", + "shortnames": [":pg:"], + "category": "flags" + }, + ":flag_ph:": { + "uc_base": "1f1f5-1f1ed", + "uc_output": "1f1f5-1f1ed", + "uc_match": "1f1f5-1f1ed", + "uc_greedy": "1f1f5-1f1ed", + "shortnames": [":ph:"], + "category": "flags" + }, + ":flag_pk:": { + "uc_base": "1f1f5-1f1f0", + "uc_output": "1f1f5-1f1f0", + "uc_match": "1f1f5-1f1f0", + "uc_greedy": "1f1f5-1f1f0", + "shortnames": [":pk:"], + "category": "flags" + }, + ":flag_pl:": { + "uc_base": "1f1f5-1f1f1", + "uc_output": "1f1f5-1f1f1", + "uc_match": "1f1f5-1f1f1", + "uc_greedy": "1f1f5-1f1f1", + "shortnames": [":pl:"], + "category": "flags" + }, + ":flag_pm:": { + "uc_base": "1f1f5-1f1f2", + "uc_output": "1f1f5-1f1f2", + "uc_match": "1f1f5-1f1f2", + "uc_greedy": "1f1f5-1f1f2", + "shortnames": [":pm:"], + "category": "flags" + }, + ":flag_pn:": { + "uc_base": "1f1f5-1f1f3", + "uc_output": "1f1f5-1f1f3", + "uc_match": "1f1f5-1f1f3", + "uc_greedy": "1f1f5-1f1f3", + "shortnames": [":pn:"], + "category": "flags" + }, + ":flag_pr:": { + "uc_base": "1f1f5-1f1f7", + "uc_output": "1f1f5-1f1f7", + "uc_match": "1f1f5-1f1f7", + "uc_greedy": "1f1f5-1f1f7", + "shortnames": [":pr:"], + "category": "flags" + }, + ":flag_ps:": { + "uc_base": "1f1f5-1f1f8", + "uc_output": "1f1f5-1f1f8", + "uc_match": "1f1f5-1f1f8", + "uc_greedy": "1f1f5-1f1f8", + "shortnames": [":ps:"], + "category": "flags" + }, + ":flag_pt:": { + "uc_base": "1f1f5-1f1f9", + "uc_output": "1f1f5-1f1f9", + "uc_match": "1f1f5-1f1f9", + "uc_greedy": "1f1f5-1f1f9", + "shortnames": [":pt:"], + "category": "flags" + }, + ":flag_pw:": { + "uc_base": "1f1f5-1f1fc", + "uc_output": "1f1f5-1f1fc", + "uc_match": "1f1f5-1f1fc", + "uc_greedy": "1f1f5-1f1fc", + "shortnames": [":pw:"], + "category": "flags" + }, + ":flag_py:": { + "uc_base": "1f1f5-1f1fe", + "uc_output": "1f1f5-1f1fe", + "uc_match": "1f1f5-1f1fe", + "uc_greedy": "1f1f5-1f1fe", + "shortnames": [":py:"], + "category": "flags" + }, + ":flag_qa:": { + "uc_base": "1f1f6-1f1e6", + "uc_output": "1f1f6-1f1e6", + "uc_match": "1f1f6-1f1e6", + "uc_greedy": "1f1f6-1f1e6", + "shortnames": [":qa:"], + "category": "flags" + }, + ":flag_re:": { + "uc_base": "1f1f7-1f1ea", + "uc_output": "1f1f7-1f1ea", + "uc_match": "1f1f7-1f1ea", + "uc_greedy": "1f1f7-1f1ea", + "shortnames": [":re:"], + "category": "flags" + }, + ":flag_ro:": { + "uc_base": "1f1f7-1f1f4", + "uc_output": "1f1f7-1f1f4", + "uc_match": "1f1f7-1f1f4", + "uc_greedy": "1f1f7-1f1f4", + "shortnames": [":ro:"], + "category": "flags" + }, + ":flag_rs:": { + "uc_base": "1f1f7-1f1f8", + "uc_output": "1f1f7-1f1f8", + "uc_match": "1f1f7-1f1f8", + "uc_greedy": "1f1f7-1f1f8", + "shortnames": [":rs:"], + "category": "flags" + }, + ":flag_ru:": { + "uc_base": "1f1f7-1f1fa", + "uc_output": "1f1f7-1f1fa", + "uc_match": "1f1f7-1f1fa", + "uc_greedy": "1f1f7-1f1fa", + "shortnames": [":ru:"], + "category": "flags" + }, + ":flag_rw:": { + "uc_base": "1f1f7-1f1fc", + "uc_output": "1f1f7-1f1fc", + "uc_match": "1f1f7-1f1fc", + "uc_greedy": "1f1f7-1f1fc", + "shortnames": [":rw:"], + "category": "flags" + }, + ":flag_sa:": { + "uc_base": "1f1f8-1f1e6", + "uc_output": "1f1f8-1f1e6", + "uc_match": "1f1f8-1f1e6", + "uc_greedy": "1f1f8-1f1e6", + "shortnames": [":saudiarabia:", ":saudi:"], + "category": "flags" + }, + ":flag_sb:": { + "uc_base": "1f1f8-1f1e7", + "uc_output": "1f1f8-1f1e7", + "uc_match": "1f1f8-1f1e7", + "uc_greedy": "1f1f8-1f1e7", + "shortnames": [":sb:"], + "category": "flags" + }, + ":flag_sc:": { + "uc_base": "1f1f8-1f1e8", + "uc_output": "1f1f8-1f1e8", + "uc_match": "1f1f8-1f1e8", + "uc_greedy": "1f1f8-1f1e8", + "shortnames": [":sc:"], + "category": "flags" + }, + ":flag_sd:": { + "uc_base": "1f1f8-1f1e9", + "uc_output": "1f1f8-1f1e9", + "uc_match": "1f1f8-1f1e9", + "uc_greedy": "1f1f8-1f1e9", + "shortnames": [":sd:"], + "category": "flags" + }, + ":flag_se:": { + "uc_base": "1f1f8-1f1ea", + "uc_output": "1f1f8-1f1ea", + "uc_match": "1f1f8-1f1ea", + "uc_greedy": "1f1f8-1f1ea", + "shortnames": [":se:"], + "category": "flags" + }, + ":flag_sg:": { + "uc_base": "1f1f8-1f1ec", + "uc_output": "1f1f8-1f1ec", + "uc_match": "1f1f8-1f1ec", + "uc_greedy": "1f1f8-1f1ec", + "shortnames": [":sg:"], + "category": "flags" + }, + ":flag_sh:": { + "uc_base": "1f1f8-1f1ed", + "uc_output": "1f1f8-1f1ed", + "uc_match": "1f1f8-1f1ed", + "uc_greedy": "1f1f8-1f1ed", + "shortnames": [":sh:"], + "category": "flags" + }, + ":flag_si:": { + "uc_base": "1f1f8-1f1ee", + "uc_output": "1f1f8-1f1ee", + "uc_match": "1f1f8-1f1ee", + "uc_greedy": "1f1f8-1f1ee", + "shortnames": [":si:"], + "category": "flags" + }, + ":flag_sj:": { + "uc_base": "1f1f8-1f1ef", + "uc_output": "1f1f8-1f1ef", + "uc_match": "1f1f8-1f1ef", + "uc_greedy": "1f1f8-1f1ef", + "shortnames": [":sj:"], + "category": "flags" + }, + ":flag_sk:": { + "uc_base": "1f1f8-1f1f0", + "uc_output": "1f1f8-1f1f0", + "uc_match": "1f1f8-1f1f0", + "uc_greedy": "1f1f8-1f1f0", + "shortnames": [":sk:"], + "category": "flags" + }, + ":flag_sl:": { + "uc_base": "1f1f8-1f1f1", + "uc_output": "1f1f8-1f1f1", + "uc_match": "1f1f8-1f1f1", + "uc_greedy": "1f1f8-1f1f1", + "shortnames": [":sl:"], + "category": "flags" + }, + ":flag_sm:": { + "uc_base": "1f1f8-1f1f2", + "uc_output": "1f1f8-1f1f2", + "uc_match": "1f1f8-1f1f2", + "uc_greedy": "1f1f8-1f1f2", + "shortnames": [":sm:"], + "category": "flags" + }, + ":flag_sn:": { + "uc_base": "1f1f8-1f1f3", + "uc_output": "1f1f8-1f1f3", + "uc_match": "1f1f8-1f1f3", + "uc_greedy": "1f1f8-1f1f3", + "shortnames": [":sn:"], + "category": "flags" + }, + ":flag_so:": { + "uc_base": "1f1f8-1f1f4", + "uc_output": "1f1f8-1f1f4", + "uc_match": "1f1f8-1f1f4", + "uc_greedy": "1f1f8-1f1f4", + "shortnames": [":so:"], + "category": "flags" + }, + ":flag_sr:": { + "uc_base": "1f1f8-1f1f7", + "uc_output": "1f1f8-1f1f7", + "uc_match": "1f1f8-1f1f7", + "uc_greedy": "1f1f8-1f1f7", + "shortnames": [":sr:"], + "category": "flags" + }, + ":flag_ss:": { + "uc_base": "1f1f8-1f1f8", + "uc_output": "1f1f8-1f1f8", + "uc_match": "1f1f8-1f1f8", + "uc_greedy": "1f1f8-1f1f8", + "shortnames": [":ss:"], + "category": "flags" + }, + ":flag_st:": { + "uc_base": "1f1f8-1f1f9", + "uc_output": "1f1f8-1f1f9", + "uc_match": "1f1f8-1f1f9", + "uc_greedy": "1f1f8-1f1f9", + "shortnames": [":st:"], + "category": "flags" + }, + ":flag_sv:": { + "uc_base": "1f1f8-1f1fb", + "uc_output": "1f1f8-1f1fb", + "uc_match": "1f1f8-1f1fb", + "uc_greedy": "1f1f8-1f1fb", + "shortnames": [":sv:"], + "category": "flags" + }, + ":flag_sx:": { + "uc_base": "1f1f8-1f1fd", + "uc_output": "1f1f8-1f1fd", + "uc_match": "1f1f8-1f1fd", + "uc_greedy": "1f1f8-1f1fd", + "shortnames": [":sx:"], + "category": "flags" + }, + ":flag_sy:": { + "uc_base": "1f1f8-1f1fe", + "uc_output": "1f1f8-1f1fe", + "uc_match": "1f1f8-1f1fe", + "uc_greedy": "1f1f8-1f1fe", + "shortnames": [":sy:"], + "category": "flags" + }, + ":flag_sz:": { + "uc_base": "1f1f8-1f1ff", + "uc_output": "1f1f8-1f1ff", + "uc_match": "1f1f8-1f1ff", + "uc_greedy": "1f1f8-1f1ff", + "shortnames": [":sz:"], + "category": "flags" + }, + ":flag_ta:": { + "uc_base": "1f1f9-1f1e6", + "uc_output": "1f1f9-1f1e6", + "uc_match": "1f1f9-1f1e6", + "uc_greedy": "1f1f9-1f1e6", + "shortnames": [":ta:"], + "category": "flags" + }, + ":flag_tc:": { + "uc_base": "1f1f9-1f1e8", + "uc_output": "1f1f9-1f1e8", + "uc_match": "1f1f9-1f1e8", + "uc_greedy": "1f1f9-1f1e8", + "shortnames": [":tc:"], + "category": "flags" + }, + ":flag_td:": { + "uc_base": "1f1f9-1f1e9", + "uc_output": "1f1f9-1f1e9", + "uc_match": "1f1f9-1f1e9", + "uc_greedy": "1f1f9-1f1e9", + "shortnames": [":td:"], + "category": "flags" + }, + ":flag_tf:": { + "uc_base": "1f1f9-1f1eb", + "uc_output": "1f1f9-1f1eb", + "uc_match": "1f1f9-1f1eb", + "uc_greedy": "1f1f9-1f1eb", + "shortnames": [":tf:"], + "category": "flags" + }, + ":flag_tg:": { + "uc_base": "1f1f9-1f1ec", + "uc_output": "1f1f9-1f1ec", + "uc_match": "1f1f9-1f1ec", + "uc_greedy": "1f1f9-1f1ec", + "shortnames": [":tg:"], + "category": "flags" + }, + ":flag_th:": { + "uc_base": "1f1f9-1f1ed", + "uc_output": "1f1f9-1f1ed", + "uc_match": "1f1f9-1f1ed", + "uc_greedy": "1f1f9-1f1ed", + "shortnames": [":th:"], + "category": "flags" + }, + ":flag_tj:": { + "uc_base": "1f1f9-1f1ef", + "uc_output": "1f1f9-1f1ef", + "uc_match": "1f1f9-1f1ef", + "uc_greedy": "1f1f9-1f1ef", + "shortnames": [":tj:"], + "category": "flags" + }, + ":flag_tk:": { + "uc_base": "1f1f9-1f1f0", + "uc_output": "1f1f9-1f1f0", + "uc_match": "1f1f9-1f1f0", + "uc_greedy": "1f1f9-1f1f0", + "shortnames": [":tk:"], + "category": "flags" + }, + ":flag_tl:": { + "uc_base": "1f1f9-1f1f1", + "uc_output": "1f1f9-1f1f1", + "uc_match": "1f1f9-1f1f1", + "uc_greedy": "1f1f9-1f1f1", + "shortnames": [":tl:"], + "category": "flags" + }, + ":flag_tm:": { + "uc_base": "1f1f9-1f1f2", + "uc_output": "1f1f9-1f1f2", + "uc_match": "1f1f9-1f1f2", + "uc_greedy": "1f1f9-1f1f2", + "shortnames": [":turkmenistan:"], + "category": "flags" + }, + ":flag_tn:": { + "uc_base": "1f1f9-1f1f3", + "uc_output": "1f1f9-1f1f3", + "uc_match": "1f1f9-1f1f3", + "uc_greedy": "1f1f9-1f1f3", + "shortnames": [":tn:"], + "category": "flags" + }, + ":flag_to:": { + "uc_base": "1f1f9-1f1f4", + "uc_output": "1f1f9-1f1f4", + "uc_match": "1f1f9-1f1f4", + "uc_greedy": "1f1f9-1f1f4", + "shortnames": [":to:"], + "category": "flags" + }, + ":flag_tr:": { + "uc_base": "1f1f9-1f1f7", + "uc_output": "1f1f9-1f1f7", + "uc_match": "1f1f9-1f1f7", + "uc_greedy": "1f1f9-1f1f7", + "shortnames": [":tr:"], + "category": "flags" + }, + ":flag_tt:": { + "uc_base": "1f1f9-1f1f9", + "uc_output": "1f1f9-1f1f9", + "uc_match": "1f1f9-1f1f9", + "uc_greedy": "1f1f9-1f1f9", + "shortnames": [":tt:"], + "category": "flags" + }, + ":flag_tv:": { + "uc_base": "1f1f9-1f1fb", + "uc_output": "1f1f9-1f1fb", + "uc_match": "1f1f9-1f1fb", + "uc_greedy": "1f1f9-1f1fb", + "shortnames": [":tuvalu:"], + "category": "flags" + }, + ":flag_tw:": { + "uc_base": "1f1f9-1f1fc", + "uc_output": "1f1f9-1f1fc", + "uc_match": "1f1f9-1f1fc", + "uc_greedy": "1f1f9-1f1fc", + "shortnames": [":tw:"], + "category": "flags" + }, + ":flag_tz:": { + "uc_base": "1f1f9-1f1ff", + "uc_output": "1f1f9-1f1ff", + "uc_match": "1f1f9-1f1ff", + "uc_greedy": "1f1f9-1f1ff", + "shortnames": [":tz:"], + "category": "flags" + }, + ":flag_ua:": { + "uc_base": "1f1fa-1f1e6", + "uc_output": "1f1fa-1f1e6", + "uc_match": "1f1fa-1f1e6", + "uc_greedy": "1f1fa-1f1e6", + "shortnames": [":ua:"], + "category": "flags" + }, + ":flag_ug:": { + "uc_base": "1f1fa-1f1ec", + "uc_output": "1f1fa-1f1ec", + "uc_match": "1f1fa-1f1ec", + "uc_greedy": "1f1fa-1f1ec", + "shortnames": [":ug:"], + "category": "flags" + }, + ":flag_um:": { + "uc_base": "1f1fa-1f1f2", + "uc_output": "1f1fa-1f1f2", + "uc_match": "1f1fa-1f1f2", + "uc_greedy": "1f1fa-1f1f2", + "shortnames": [":um:"], + "category": "flags" + }, + ":flag_us:": { + "uc_base": "1f1fa-1f1f8", + "uc_output": "1f1fa-1f1f8", + "uc_match": "1f1fa-1f1f8", + "uc_greedy": "1f1fa-1f1f8", + "shortnames": [":us:"], + "category": "flags" + }, + ":flag_uy:": { + "uc_base": "1f1fa-1f1fe", + "uc_output": "1f1fa-1f1fe", + "uc_match": "1f1fa-1f1fe", + "uc_greedy": "1f1fa-1f1fe", + "shortnames": [":uy:"], + "category": "flags" + }, + ":flag_uz:": { + "uc_base": "1f1fa-1f1ff", + "uc_output": "1f1fa-1f1ff", + "uc_match": "1f1fa-1f1ff", + "uc_greedy": "1f1fa-1f1ff", + "shortnames": [":uz:"], + "category": "flags" + }, + ":flag_va:": { + "uc_base": "1f1fb-1f1e6", + "uc_output": "1f1fb-1f1e6", + "uc_match": "1f1fb-1f1e6", + "uc_greedy": "1f1fb-1f1e6", + "shortnames": [":va:"], + "category": "flags" + }, + ":flag_vc:": { + "uc_base": "1f1fb-1f1e8", + "uc_output": "1f1fb-1f1e8", + "uc_match": "1f1fb-1f1e8", + "uc_greedy": "1f1fb-1f1e8", + "shortnames": [":vc:"], + "category": "flags" + }, + ":flag_ve:": { + "uc_base": "1f1fb-1f1ea", + "uc_output": "1f1fb-1f1ea", + "uc_match": "1f1fb-1f1ea", + "uc_greedy": "1f1fb-1f1ea", + "shortnames": [":ve:"], + "category": "flags" + }, + ":flag_vg:": { + "uc_base": "1f1fb-1f1ec", + "uc_output": "1f1fb-1f1ec", + "uc_match": "1f1fb-1f1ec", + "uc_greedy": "1f1fb-1f1ec", + "shortnames": [":vg:"], + "category": "flags" + }, + ":flag_vi:": { + "uc_base": "1f1fb-1f1ee", + "uc_output": "1f1fb-1f1ee", + "uc_match": "1f1fb-1f1ee", + "uc_greedy": "1f1fb-1f1ee", + "shortnames": [":vi:"], + "category": "flags" + }, + ":flag_vn:": { + "uc_base": "1f1fb-1f1f3", + "uc_output": "1f1fb-1f1f3", + "uc_match": "1f1fb-1f1f3", + "uc_greedy": "1f1fb-1f1f3", + "shortnames": [":vn:"], + "category": "flags" + }, + ":flag_vu:": { + "uc_base": "1f1fb-1f1fa", + "uc_output": "1f1fb-1f1fa", + "uc_match": "1f1fb-1f1fa", + "uc_greedy": "1f1fb-1f1fa", + "shortnames": [":vu:"], + "category": "flags" + }, + ":flag_wf:": { + "uc_base": "1f1fc-1f1eb", + "uc_output": "1f1fc-1f1eb", + "uc_match": "1f1fc-1f1eb", + "uc_greedy": "1f1fc-1f1eb", + "shortnames": [":wf:"], + "category": "flags" + }, + ":flag_ws:": { + "uc_base": "1f1fc-1f1f8", + "uc_output": "1f1fc-1f1f8", + "uc_match": "1f1fc-1f1f8", + "uc_greedy": "1f1fc-1f1f8", + "shortnames": [":ws:"], + "category": "flags" + }, + ":flag_xk:": { + "uc_base": "1f1fd-1f1f0", + "uc_output": "1f1fd-1f1f0", + "uc_match": "1f1fd-1f1f0", + "uc_greedy": "1f1fd-1f1f0", + "shortnames": [":xk:"], + "category": "flags" + }, + ":flag_ye:": { + "uc_base": "1f1fe-1f1ea", + "uc_output": "1f1fe-1f1ea", + "uc_match": "1f1fe-1f1ea", + "uc_greedy": "1f1fe-1f1ea", + "shortnames": [":ye:"], + "category": "flags" + }, + ":flag_yt:": { + "uc_base": "1f1fe-1f1f9", + "uc_output": "1f1fe-1f1f9", + "uc_match": "1f1fe-1f1f9", + "uc_greedy": "1f1fe-1f1f9", + "shortnames": [":yt:"], + "category": "flags" + }, + ":flag_za:": { + "uc_base": "1f1ff-1f1e6", + "uc_output": "1f1ff-1f1e6", + "uc_match": "1f1ff-1f1e6", + "uc_greedy": "1f1ff-1f1e6", + "shortnames": [":za:"], + "category": "flags" + }, + ":flag_zm:": { + "uc_base": "1f1ff-1f1f2", + "uc_output": "1f1ff-1f1f2", + "uc_match": "1f1ff-1f1f2", + "uc_greedy": "1f1ff-1f1f2", + "shortnames": [":zm:"], + "category": "flags" + }, + ":flag_zw:": { + "uc_base": "1f1ff-1f1fc", + "uc_output": "1f1ff-1f1fc", + "uc_match": "1f1ff-1f1fc", + "uc_greedy": "1f1ff-1f1fc", + "shortnames": [":zw:"], + "category": "flags" + }, + ":girl_tone1:": { + "uc_base": "1f467-1f3fb", + "uc_output": "1f467-1f3fb", + "uc_match": "1f467-1f3fb", + "uc_greedy": "1f467-1f3fb", + "shortnames": [], + "category": "people" + }, + ":girl_tone2:": { + "uc_base": "1f467-1f3fc", + "uc_output": "1f467-1f3fc", + "uc_match": "1f467-1f3fc", + "uc_greedy": "1f467-1f3fc", + "shortnames": [], + "category": "people" + }, + ":girl_tone3:": { + "uc_base": "1f467-1f3fd", + "uc_output": "1f467-1f3fd", + "uc_match": "1f467-1f3fd", + "uc_greedy": "1f467-1f3fd", + "shortnames": [], + "category": "people" + }, + ":girl_tone4:": { + "uc_base": "1f467-1f3fe", + "uc_output": "1f467-1f3fe", + "uc_match": "1f467-1f3fe", + "uc_greedy": "1f467-1f3fe", + "shortnames": [], + "category": "people" + }, + ":girl_tone5:": { + "uc_base": "1f467-1f3ff", + "uc_output": "1f467-1f3ff", + "uc_match": "1f467-1f3ff", + "uc_greedy": "1f467-1f3ff", + "shortnames": [], + "category": "people" + }, + ":guard_tone1:": { + "uc_base": "1f482-1f3fb", + "uc_output": "1f482-1f3fb", + "uc_match": "1f482-1f3fb", + "uc_greedy": "1f482-1f3fb", + "shortnames": [":guardsman_tone1:"], + "category": "people" + }, + ":guard_tone2:": { + "uc_base": "1f482-1f3fc", + "uc_output": "1f482-1f3fc", + "uc_match": "1f482-1f3fc", + "uc_greedy": "1f482-1f3fc", + "shortnames": [":guardsman_tone2:"], + "category": "people" + }, + ":guard_tone3:": { + "uc_base": "1f482-1f3fd", + "uc_output": "1f482-1f3fd", + "uc_match": "1f482-1f3fd", + "uc_greedy": "1f482-1f3fd", + "shortnames": [":guardsman_tone3:"], + "category": "people" + }, + ":guard_tone4:": { + "uc_base": "1f482-1f3fe", + "uc_output": "1f482-1f3fe", + "uc_match": "1f482-1f3fe", + "uc_greedy": "1f482-1f3fe", + "shortnames": [":guardsman_tone4:"], + "category": "people" + }, + ":guard_tone5:": { + "uc_base": "1f482-1f3ff", + "uc_output": "1f482-1f3ff", + "uc_match": "1f482-1f3ff", + "uc_greedy": "1f482-1f3ff", + "shortnames": [":guardsman_tone5:"], + "category": "people" + }, + ":hand_splayed_tone1:": { + "uc_base": "1f590-1f3fb", + "uc_output": "1f590-1f3fb", + "uc_match": "1f590-fe0f-1f3fb", + "uc_greedy": "1f590-fe0f-1f3fb", + "shortnames": [":raised_hand_with_fingers_splayed_tone1:"], + "category": "people" + }, + ":hand_splayed_tone2:": { + "uc_base": "1f590-1f3fc", + "uc_output": "1f590-1f3fc", + "uc_match": "1f590-fe0f-1f3fc", + "uc_greedy": "1f590-fe0f-1f3fc", + "shortnames": [":raised_hand_with_fingers_splayed_tone2:"], + "category": "people" + }, + ":hand_splayed_tone3:": { + "uc_base": "1f590-1f3fd", + "uc_output": "1f590-1f3fd", + "uc_match": "1f590-fe0f-1f3fd", + "uc_greedy": "1f590-fe0f-1f3fd", + "shortnames": [":raised_hand_with_fingers_splayed_tone3:"], + "category": "people" + }, + ":hand_splayed_tone4:": { + "uc_base": "1f590-1f3fe", + "uc_output": "1f590-1f3fe", + "uc_match": "1f590-fe0f-1f3fe", + "uc_greedy": "1f590-fe0f-1f3fe", + "shortnames": [":raised_hand_with_fingers_splayed_tone4:"], + "category": "people" + }, + ":hand_splayed_tone5:": { + "uc_base": "1f590-1f3ff", + "uc_output": "1f590-1f3ff", + "uc_match": "1f590-fe0f-1f3ff", + "uc_greedy": "1f590-fe0f-1f3ff", + "shortnames": [":raised_hand_with_fingers_splayed_tone5:"], + "category": "people" + }, + ":horse_racing_tone1:": { + "uc_base": "1f3c7-1f3fb", + "uc_output": "1f3c7-1f3fb", + "uc_match": "1f3c7-1f3fb", + "uc_greedy": "1f3c7-1f3fb", + "shortnames": [], + "category": "activity" + }, + ":horse_racing_tone2:": { + "uc_base": "1f3c7-1f3fc", + "uc_output": "1f3c7-1f3fc", + "uc_match": "1f3c7-1f3fc", + "uc_greedy": "1f3c7-1f3fc", + "shortnames": [], + "category": "activity" + }, + ":horse_racing_tone3:": { + "uc_base": "1f3c7-1f3fd", + "uc_output": "1f3c7-1f3fd", + "uc_match": "1f3c7-1f3fd", + "uc_greedy": "1f3c7-1f3fd", + "shortnames": [], + "category": "activity" + }, + ":horse_racing_tone4:": { + "uc_base": "1f3c7-1f3fe", + "uc_output": "1f3c7-1f3fe", + "uc_match": "1f3c7-1f3fe", + "uc_greedy": "1f3c7-1f3fe", + "shortnames": [], + "category": "activity" + }, + ":horse_racing_tone5:": { + "uc_base": "1f3c7-1f3ff", + "uc_output": "1f3c7-1f3ff", + "uc_match": "1f3c7-1f3ff", + "uc_greedy": "1f3c7-1f3ff", + "shortnames": [], + "category": "activity" + }, + ":left_facing_fist_tone1:": { + "uc_base": "1f91b-1f3fb", + "uc_output": "1f91b-1f3fb", + "uc_match": "1f91b-1f3fb", + "uc_greedy": "1f91b-1f3fb", + "shortnames": [":left_fist_tone1:"], + "category": "people" + }, + ":left_facing_fist_tone2:": { + "uc_base": "1f91b-1f3fc", + "uc_output": "1f91b-1f3fc", + "uc_match": "1f91b-1f3fc", + "uc_greedy": "1f91b-1f3fc", + "shortnames": [":left_fist_tone2:"], + "category": "people" + }, + ":left_facing_fist_tone3:": { + "uc_base": "1f91b-1f3fd", + "uc_output": "1f91b-1f3fd", + "uc_match": "1f91b-1f3fd", + "uc_greedy": "1f91b-1f3fd", + "shortnames": [":left_fist_tone3:"], + "category": "people" + }, + ":left_facing_fist_tone4:": { + "uc_base": "1f91b-1f3fe", + "uc_output": "1f91b-1f3fe", + "uc_match": "1f91b-1f3fe", + "uc_greedy": "1f91b-1f3fe", + "shortnames": [":left_fist_tone4:"], + "category": "people" + }, + ":left_facing_fist_tone5:": { + "uc_base": "1f91b-1f3ff", + "uc_output": "1f91b-1f3ff", + "uc_match": "1f91b-1f3ff", + "uc_greedy": "1f91b-1f3ff", + "shortnames": [":left_fist_tone5:"], + "category": "people" + }, + ":levitate_tone1:": { + "uc_base": "1f574-1f3fb", + "uc_output": "1f574-1f3fb", + "uc_match": "1f574-fe0f-1f3fb", + "uc_greedy": "1f574-fe0f-1f3fb", + "shortnames": [":man_in_business_suit_levitating_tone1:", ":man_in_business_suit_levitating_light_skin_tone:"], + "category": "people" + }, + ":levitate_tone2:": { + "uc_base": "1f574-1f3fc", + "uc_output": "1f574-1f3fc", + "uc_match": "1f574-fe0f-1f3fc", + "uc_greedy": "1f574-fe0f-1f3fc", + "shortnames": [":man_in_business_suit_levitating_tone2:", ":man_in_business_suit_levitating_medium_light_skin_tone:"], + "category": "people" + }, + ":levitate_tone3:": { + "uc_base": "1f574-1f3fd", + "uc_output": "1f574-1f3fd", + "uc_match": "1f574-fe0f-1f3fd", + "uc_greedy": "1f574-fe0f-1f3fd", + "shortnames": [":man_in_business_suit_levitating_tone3:", ":man_in_business_suit_levitating_medium_skin_tone:"], + "category": "people" + }, + ":levitate_tone4:": { + "uc_base": "1f574-1f3fe", + "uc_output": "1f574-1f3fe", + "uc_match": "1f574-fe0f-1f3fe", + "uc_greedy": "1f574-fe0f-1f3fe", + "shortnames": [":man_in_business_suit_levitating_tone4:", ":man_in_business_suit_levitating_medium_dark_skin_tone:"], + "category": "people" + }, + ":levitate_tone5:": { + "uc_base": "1f574-1f3ff", + "uc_output": "1f574-1f3ff", + "uc_match": "1f574-fe0f-1f3ff", + "uc_greedy": "1f574-fe0f-1f3ff", + "shortnames": [":man_in_business_suit_levitating_tone5:", ":man_in_business_suit_levitating_dark_skin_tone:"], + "category": "people" + }, + ":love_you_gesture_tone1:": { + "uc_base": "1f91f-1f3fb", + "uc_output": "1f91f-1f3fb", + "uc_match": "1f91f-1f3fb", + "uc_greedy": "1f91f-1f3fb", + "shortnames": [":love_you_gesture_light_skin_tone:"], + "category": "people" + }, + ":love_you_gesture_tone2:": { + "uc_base": "1f91f-1f3fc", + "uc_output": "1f91f-1f3fc", + "uc_match": "1f91f-1f3fc", + "uc_greedy": "1f91f-1f3fc", + "shortnames": [":love_you_gesture_medium_light_skin_tone:"], + "category": "people" + }, + ":love_you_gesture_tone3:": { + "uc_base": "1f91f-1f3fd", + "uc_output": "1f91f-1f3fd", + "uc_match": "1f91f-1f3fd", + "uc_greedy": "1f91f-1f3fd", + "shortnames": [":love_you_gesture_medium_skin_tone:"], + "category": "people" + }, + ":love_you_gesture_tone4:": { + "uc_base": "1f91f-1f3fe", + "uc_output": "1f91f-1f3fe", + "uc_match": "1f91f-1f3fe", + "uc_greedy": "1f91f-1f3fe", + "shortnames": [":love_you_gesture_medium_dark_skin_tone:"], + "category": "people" + }, + ":love_you_gesture_tone5:": { + "uc_base": "1f91f-1f3ff", + "uc_output": "1f91f-1f3ff", + "uc_match": "1f91f-1f3ff", + "uc_greedy": "1f91f-1f3ff", + "shortnames": [":love_you_gesture_dark_skin_tone:"], + "category": "people" + }, + ":mage_tone1:": { + "uc_base": "1f9d9-1f3fb", + "uc_output": "1f9d9-1f3fb", + "uc_match": "1f9d9-1f3fb", + "uc_greedy": "1f9d9-1f3fb", + "shortnames": [":mage_light_skin_tone:"], + "category": "people" + }, + ":mage_tone2:": { + "uc_base": "1f9d9-1f3fc", + "uc_output": "1f9d9-1f3fc", + "uc_match": "1f9d9-1f3fc", + "uc_greedy": "1f9d9-1f3fc", + "shortnames": [":mage_medium_light_skin_tone:"], + "category": "people" + }, + ":mage_tone3:": { + "uc_base": "1f9d9-1f3fd", + "uc_output": "1f9d9-1f3fd", + "uc_match": "1f9d9-1f3fd", + "uc_greedy": "1f9d9-1f3fd", + "shortnames": [":mage_medium_skin_tone:"], + "category": "people" + }, + ":mage_tone4:": { + "uc_base": "1f9d9-1f3fe", + "uc_output": "1f9d9-1f3fe", + "uc_match": "1f9d9-1f3fe", + "uc_greedy": "1f9d9-1f3fe", + "shortnames": [":mage_medium_dark_skin_tone:"], + "category": "people" + }, + ":mage_tone5:": { + "uc_base": "1f9d9-1f3ff", + "uc_output": "1f9d9-1f3ff", + "uc_match": "1f9d9-1f3ff", + "uc_greedy": "1f9d9-1f3ff", + "shortnames": [":mage_dark_skin_tone:"], + "category": "people" + }, + ":man_dancing_tone1:": { + "uc_base": "1f57a-1f3fb", + "uc_output": "1f57a-1f3fb", + "uc_match": "1f57a-1f3fb", + "uc_greedy": "1f57a-1f3fb", + "shortnames": [":male_dancer_tone1:"], + "category": "people" + }, + ":man_dancing_tone2:": { + "uc_base": "1f57a-1f3fc", + "uc_output": "1f57a-1f3fc", + "uc_match": "1f57a-1f3fc", + "uc_greedy": "1f57a-1f3fc", + "shortnames": [":male_dancer_tone2:"], + "category": "people" + }, + ":man_dancing_tone3:": { + "uc_base": "1f57a-1f3fd", + "uc_output": "1f57a-1f3fd", + "uc_match": "1f57a-1f3fd", + "uc_greedy": "1f57a-1f3fd", + "shortnames": [":male_dancer_tone3:"], + "category": "people" + }, + ":man_dancing_tone4:": { + "uc_base": "1f57a-1f3fe", + "uc_output": "1f57a-1f3fe", + "uc_match": "1f57a-1f3fe", + "uc_greedy": "1f57a-1f3fe", + "shortnames": [":male_dancer_tone4:"], + "category": "people" + }, + ":man_dancing_tone5:": { + "uc_base": "1f57a-1f3ff", + "uc_output": "1f57a-1f3ff", + "uc_match": "1f57a-1f3ff", + "uc_greedy": "1f57a-1f3ff", + "shortnames": [":male_dancer_tone5:"], + "category": "people" + }, + ":man_in_tuxedo_tone1:": { + "uc_base": "1f935-1f3fb", + "uc_output": "1f935-1f3fb", + "uc_match": "1f935-1f3fb", + "uc_greedy": "1f935-1f3fb", + "shortnames": [":tuxedo_tone1:"], + "category": "people" + }, + ":man_in_tuxedo_tone2:": { + "uc_base": "1f935-1f3fc", + "uc_output": "1f935-1f3fc", + "uc_match": "1f935-1f3fc", + "uc_greedy": "1f935-1f3fc", + "shortnames": [":tuxedo_tone2:"], + "category": "people" + }, + ":man_in_tuxedo_tone3:": { + "uc_base": "1f935-1f3fd", + "uc_output": "1f935-1f3fd", + "uc_match": "1f935-1f3fd", + "uc_greedy": "1f935-1f3fd", + "shortnames": [":tuxedo_tone3:"], + "category": "people" + }, + ":man_in_tuxedo_tone4:": { + "uc_base": "1f935-1f3fe", + "uc_output": "1f935-1f3fe", + "uc_match": "1f935-1f3fe", + "uc_greedy": "1f935-1f3fe", + "shortnames": [":tuxedo_tone4:"], + "category": "people" + }, + ":man_in_tuxedo_tone5:": { + "uc_base": "1f935-1f3ff", + "uc_output": "1f935-1f3ff", + "uc_match": "1f935-1f3ff", + "uc_greedy": "1f935-1f3ff", + "shortnames": [":tuxedo_tone5:"], + "category": "people" + }, + ":man_tone1:": { + "uc_base": "1f468-1f3fb", + "uc_output": "1f468-1f3fb", + "uc_match": "1f468-1f3fb", + "uc_greedy": "1f468-1f3fb", + "shortnames": [], + "category": "people" + }, + ":man_tone2:": { + "uc_base": "1f468-1f3fc", + "uc_output": "1f468-1f3fc", + "uc_match": "1f468-1f3fc", + "uc_greedy": "1f468-1f3fc", + "shortnames": [], + "category": "people" + }, + ":man_tone3:": { + "uc_base": "1f468-1f3fd", + "uc_output": "1f468-1f3fd", + "uc_match": "1f468-1f3fd", + "uc_greedy": "1f468-1f3fd", + "shortnames": [], + "category": "people" + }, + ":man_tone4:": { + "uc_base": "1f468-1f3fe", + "uc_output": "1f468-1f3fe", + "uc_match": "1f468-1f3fe", + "uc_greedy": "1f468-1f3fe", + "shortnames": [], + "category": "people" + }, + ":man_tone5:": { + "uc_base": "1f468-1f3ff", + "uc_output": "1f468-1f3ff", + "uc_match": "1f468-1f3ff", + "uc_greedy": "1f468-1f3ff", + "shortnames": [], + "category": "people" + }, + ":man_with_chinese_cap_tone1:": { + "uc_base": "1f472-1f3fb", + "uc_output": "1f472-1f3fb", + "uc_match": "1f472-1f3fb", + "uc_greedy": "1f472-1f3fb", + "shortnames": [":man_with_gua_pi_mao_tone1:"], + "category": "people" + }, + ":man_with_chinese_cap_tone2:": { + "uc_base": "1f472-1f3fc", + "uc_output": "1f472-1f3fc", + "uc_match": "1f472-1f3fc", + "uc_greedy": "1f472-1f3fc", + "shortnames": [":man_with_gua_pi_mao_tone2:"], + "category": "people" + }, + ":man_with_chinese_cap_tone3:": { + "uc_base": "1f472-1f3fd", + "uc_output": "1f472-1f3fd", + "uc_match": "1f472-1f3fd", + "uc_greedy": "1f472-1f3fd", + "shortnames": [":man_with_gua_pi_mao_tone3:"], + "category": "people" + }, + ":man_with_chinese_cap_tone4:": { + "uc_base": "1f472-1f3fe", + "uc_output": "1f472-1f3fe", + "uc_match": "1f472-1f3fe", + "uc_greedy": "1f472-1f3fe", + "shortnames": [":man_with_gua_pi_mao_tone4:"], + "category": "people" + }, + ":man_with_chinese_cap_tone5:": { + "uc_base": "1f472-1f3ff", + "uc_output": "1f472-1f3ff", + "uc_match": "1f472-1f3ff", + "uc_greedy": "1f472-1f3ff", + "shortnames": [":man_with_gua_pi_mao_tone5:"], + "category": "people" + }, + ":merperson_tone1:": { + "uc_base": "1f9dc-1f3fb", + "uc_output": "1f9dc-1f3fb", + "uc_match": "1f9dc-1f3fb", + "uc_greedy": "1f9dc-1f3fb", + "shortnames": [":merperson_light_skin_tone:"], + "category": "people" + }, + ":merperson_tone2:": { + "uc_base": "1f9dc-1f3fc", + "uc_output": "1f9dc-1f3fc", + "uc_match": "1f9dc-1f3fc", + "uc_greedy": "1f9dc-1f3fc", + "shortnames": [":merperson_medium_light_skin_tone:"], + "category": "people" + }, + ":merperson_tone3:": { + "uc_base": "1f9dc-1f3fd", + "uc_output": "1f9dc-1f3fd", + "uc_match": "1f9dc-1f3fd", + "uc_greedy": "1f9dc-1f3fd", + "shortnames": [":merperson_medium_skin_tone:"], + "category": "people" + }, + ":merperson_tone4:": { + "uc_base": "1f9dc-1f3fe", + "uc_output": "1f9dc-1f3fe", + "uc_match": "1f9dc-1f3fe", + "uc_greedy": "1f9dc-1f3fe", + "shortnames": [":merperson_medium_dark_skin_tone:"], + "category": "people" + }, + ":merperson_tone5:": { + "uc_base": "1f9dc-1f3ff", + "uc_output": "1f9dc-1f3ff", + "uc_match": "1f9dc-1f3ff", + "uc_greedy": "1f9dc-1f3ff", + "shortnames": [":merperson_dark_skin_tone:"], + "category": "people" + }, + ":metal_tone1:": { + "uc_base": "1f918-1f3fb", + "uc_output": "1f918-1f3fb", + "uc_match": "1f918-1f3fb", + "uc_greedy": "1f918-1f3fb", + "shortnames": [":sign_of_the_horns_tone1:"], + "category": "people" + }, + ":metal_tone2:": { + "uc_base": "1f918-1f3fc", + "uc_output": "1f918-1f3fc", + "uc_match": "1f918-1f3fc", + "uc_greedy": "1f918-1f3fc", + "shortnames": [":sign_of_the_horns_tone2:"], + "category": "people" + }, + ":metal_tone3:": { + "uc_base": "1f918-1f3fd", + "uc_output": "1f918-1f3fd", + "uc_match": "1f918-1f3fd", + "uc_greedy": "1f918-1f3fd", + "shortnames": [":sign_of_the_horns_tone3:"], + "category": "people" + }, + ":metal_tone4:": { + "uc_base": "1f918-1f3fe", + "uc_output": "1f918-1f3fe", + "uc_match": "1f918-1f3fe", + "uc_greedy": "1f918-1f3fe", + "shortnames": [":sign_of_the_horns_tone4:"], + "category": "people" + }, + ":metal_tone5:": { + "uc_base": "1f918-1f3ff", + "uc_output": "1f918-1f3ff", + "uc_match": "1f918-1f3ff", + "uc_greedy": "1f918-1f3ff", + "shortnames": [":sign_of_the_horns_tone5:"], + "category": "people" + }, + ":middle_finger_tone1:": { + "uc_base": "1f595-1f3fb", + "uc_output": "1f595-1f3fb", + "uc_match": "1f595-1f3fb", + "uc_greedy": "1f595-1f3fb", + "shortnames": [":reversed_hand_with_middle_finger_extended_tone1:"], + "category": "people" + }, + ":middle_finger_tone2:": { + "uc_base": "1f595-1f3fc", + "uc_output": "1f595-1f3fc", + "uc_match": "1f595-1f3fc", + "uc_greedy": "1f595-1f3fc", + "shortnames": [":reversed_hand_with_middle_finger_extended_tone2:"], + "category": "people" + }, + ":middle_finger_tone3:": { + "uc_base": "1f595-1f3fd", + "uc_output": "1f595-1f3fd", + "uc_match": "1f595-1f3fd", + "uc_greedy": "1f595-1f3fd", + "shortnames": [":reversed_hand_with_middle_finger_extended_tone3:"], + "category": "people" + }, + ":middle_finger_tone4:": { + "uc_base": "1f595-1f3fe", + "uc_output": "1f595-1f3fe", + "uc_match": "1f595-1f3fe", + "uc_greedy": "1f595-1f3fe", + "shortnames": [":reversed_hand_with_middle_finger_extended_tone4:"], + "category": "people" + }, + ":middle_finger_tone5:": { + "uc_base": "1f595-1f3ff", + "uc_output": "1f595-1f3ff", + "uc_match": "1f595-1f3ff", + "uc_greedy": "1f595-1f3ff", + "shortnames": [":reversed_hand_with_middle_finger_extended_tone5:"], + "category": "people" + }, + ":mrs_claus_tone1:": { + "uc_base": "1f936-1f3fb", + "uc_output": "1f936-1f3fb", + "uc_match": "1f936-1f3fb", + "uc_greedy": "1f936-1f3fb", + "shortnames": [":mother_christmas_tone1:"], + "category": "people" + }, + ":mrs_claus_tone2:": { + "uc_base": "1f936-1f3fc", + "uc_output": "1f936-1f3fc", + "uc_match": "1f936-1f3fc", + "uc_greedy": "1f936-1f3fc", + "shortnames": [":mother_christmas_tone2:"], + "category": "people" + }, + ":mrs_claus_tone3:": { + "uc_base": "1f936-1f3fd", + "uc_output": "1f936-1f3fd", + "uc_match": "1f936-1f3fd", + "uc_greedy": "1f936-1f3fd", + "shortnames": [":mother_christmas_tone3:"], + "category": "people" + }, + ":mrs_claus_tone4:": { + "uc_base": "1f936-1f3fe", + "uc_output": "1f936-1f3fe", + "uc_match": "1f936-1f3fe", + "uc_greedy": "1f936-1f3fe", + "shortnames": [":mother_christmas_tone4:"], + "category": "people" + }, + ":mrs_claus_tone5:": { + "uc_base": "1f936-1f3ff", + "uc_output": "1f936-1f3ff", + "uc_match": "1f936-1f3ff", + "uc_greedy": "1f936-1f3ff", + "shortnames": [":mother_christmas_tone5:"], + "category": "people" + }, + ":muscle_tone1:": { + "uc_base": "1f4aa-1f3fb", + "uc_output": "1f4aa-1f3fb", + "uc_match": "1f4aa-1f3fb", + "uc_greedy": "1f4aa-1f3fb", + "shortnames": [], + "category": "people" + }, + ":muscle_tone2:": { + "uc_base": "1f4aa-1f3fc", + "uc_output": "1f4aa-1f3fc", + "uc_match": "1f4aa-1f3fc", + "uc_greedy": "1f4aa-1f3fc", + "shortnames": [], + "category": "people" + }, + ":muscle_tone3:": { + "uc_base": "1f4aa-1f3fd", + "uc_output": "1f4aa-1f3fd", + "uc_match": "1f4aa-1f3fd", + "uc_greedy": "1f4aa-1f3fd", + "shortnames": [], + "category": "people" + }, + ":muscle_tone4:": { + "uc_base": "1f4aa-1f3fe", + "uc_output": "1f4aa-1f3fe", + "uc_match": "1f4aa-1f3fe", + "uc_greedy": "1f4aa-1f3fe", + "shortnames": [], + "category": "people" + }, + ":muscle_tone5:": { + "uc_base": "1f4aa-1f3ff", + "uc_output": "1f4aa-1f3ff", + "uc_match": "1f4aa-1f3ff", + "uc_greedy": "1f4aa-1f3ff", + "shortnames": [], + "category": "people" + }, + ":nail_care_tone1:": { + "uc_base": "1f485-1f3fb", + "uc_output": "1f485-1f3fb", + "uc_match": "1f485-1f3fb", + "uc_greedy": "1f485-1f3fb", + "shortnames": [], + "category": "people" + }, + ":nail_care_tone2:": { + "uc_base": "1f485-1f3fc", + "uc_output": "1f485-1f3fc", + "uc_match": "1f485-1f3fc", + "uc_greedy": "1f485-1f3fc", + "shortnames": [], + "category": "people" + }, + ":nail_care_tone3:": { + "uc_base": "1f485-1f3fd", + "uc_output": "1f485-1f3fd", + "uc_match": "1f485-1f3fd", + "uc_greedy": "1f485-1f3fd", + "shortnames": [], + "category": "people" + }, + ":nail_care_tone4:": { + "uc_base": "1f485-1f3fe", + "uc_output": "1f485-1f3fe", + "uc_match": "1f485-1f3fe", + "uc_greedy": "1f485-1f3fe", + "shortnames": [], + "category": "people" + }, + ":nail_care_tone5:": { + "uc_base": "1f485-1f3ff", + "uc_output": "1f485-1f3ff", + "uc_match": "1f485-1f3ff", + "uc_greedy": "1f485-1f3ff", + "shortnames": [], + "category": "people" + }, + ":nose_tone1:": { + "uc_base": "1f443-1f3fb", + "uc_output": "1f443-1f3fb", + "uc_match": "1f443-1f3fb", + "uc_greedy": "1f443-1f3fb", + "shortnames": [], + "category": "people" + }, + ":nose_tone2:": { + "uc_base": "1f443-1f3fc", + "uc_output": "1f443-1f3fc", + "uc_match": "1f443-1f3fc", + "uc_greedy": "1f443-1f3fc", + "shortnames": [], + "category": "people" + }, + ":nose_tone3:": { + "uc_base": "1f443-1f3fd", + "uc_output": "1f443-1f3fd", + "uc_match": "1f443-1f3fd", + "uc_greedy": "1f443-1f3fd", + "shortnames": [], + "category": "people" + }, + ":nose_tone4:": { + "uc_base": "1f443-1f3fe", + "uc_output": "1f443-1f3fe", + "uc_match": "1f443-1f3fe", + "uc_greedy": "1f443-1f3fe", + "shortnames": [], + "category": "people" + }, + ":nose_tone5:": { + "uc_base": "1f443-1f3ff", + "uc_output": "1f443-1f3ff", + "uc_match": "1f443-1f3ff", + "uc_greedy": "1f443-1f3ff", + "shortnames": [], + "category": "people" + }, + ":ok_hand_tone1:": { + "uc_base": "1f44c-1f3fb", + "uc_output": "1f44c-1f3fb", + "uc_match": "1f44c-1f3fb", + "uc_greedy": "1f44c-1f3fb", + "shortnames": [], + "category": "people" + }, + ":ok_hand_tone2:": { + "uc_base": "1f44c-1f3fc", + "uc_output": "1f44c-1f3fc", + "uc_match": "1f44c-1f3fc", + "uc_greedy": "1f44c-1f3fc", + "shortnames": [], + "category": "people" + }, + ":ok_hand_tone3:": { + "uc_base": "1f44c-1f3fd", + "uc_output": "1f44c-1f3fd", + "uc_match": "1f44c-1f3fd", + "uc_greedy": "1f44c-1f3fd", + "shortnames": [], + "category": "people" + }, + ":ok_hand_tone4:": { + "uc_base": "1f44c-1f3fe", + "uc_output": "1f44c-1f3fe", + "uc_match": "1f44c-1f3fe", + "uc_greedy": "1f44c-1f3fe", + "shortnames": [], + "category": "people" + }, + ":ok_hand_tone5:": { + "uc_base": "1f44c-1f3ff", + "uc_output": "1f44c-1f3ff", + "uc_match": "1f44c-1f3ff", + "uc_greedy": "1f44c-1f3ff", + "shortnames": [], + "category": "people" + }, + ":older_adult_tone1:": { + "uc_base": "1f9d3-1f3fb", + "uc_output": "1f9d3-1f3fb", + "uc_match": "1f9d3-1f3fb", + "uc_greedy": "1f9d3-1f3fb", + "shortnames": [":older_adult_light_skin_tone:"], + "category": "people" + }, + ":older_adult_tone2:": { + "uc_base": "1f9d3-1f3fc", + "uc_output": "1f9d3-1f3fc", + "uc_match": "1f9d3-1f3fc", + "uc_greedy": "1f9d3-1f3fc", + "shortnames": [":older_adult_medium_light_skin_tone:"], + "category": "people" + }, + ":older_adult_tone3:": { + "uc_base": "1f9d3-1f3fd", + "uc_output": "1f9d3-1f3fd", + "uc_match": "1f9d3-1f3fd", + "uc_greedy": "1f9d3-1f3fd", + "shortnames": [":older_adult_medium_skin_tone:"], + "category": "people" + }, + ":older_adult_tone4:": { + "uc_base": "1f9d3-1f3fe", + "uc_output": "1f9d3-1f3fe", + "uc_match": "1f9d3-1f3fe", + "uc_greedy": "1f9d3-1f3fe", + "shortnames": [":older_adult_medium_dark_skin_tone:"], + "category": "people" + }, + ":older_adult_tone5:": { + "uc_base": "1f9d3-1f3ff", + "uc_output": "1f9d3-1f3ff", + "uc_match": "1f9d3-1f3ff", + "uc_greedy": "1f9d3-1f3ff", + "shortnames": [":older_adult_dark_skin_tone:"], + "category": "people" + }, + ":older_man_tone1:": { + "uc_base": "1f474-1f3fb", + "uc_output": "1f474-1f3fb", + "uc_match": "1f474-1f3fb", + "uc_greedy": "1f474-1f3fb", + "shortnames": [], + "category": "people" + }, + ":older_man_tone2:": { + "uc_base": "1f474-1f3fc", + "uc_output": "1f474-1f3fc", + "uc_match": "1f474-1f3fc", + "uc_greedy": "1f474-1f3fc", + "shortnames": [], + "category": "people" + }, + ":older_man_tone3:": { + "uc_base": "1f474-1f3fd", + "uc_output": "1f474-1f3fd", + "uc_match": "1f474-1f3fd", + "uc_greedy": "1f474-1f3fd", + "shortnames": [], + "category": "people" + }, + ":older_man_tone4:": { + "uc_base": "1f474-1f3fe", + "uc_output": "1f474-1f3fe", + "uc_match": "1f474-1f3fe", + "uc_greedy": "1f474-1f3fe", + "shortnames": [], + "category": "people" + }, + ":older_man_tone5:": { + "uc_base": "1f474-1f3ff", + "uc_output": "1f474-1f3ff", + "uc_match": "1f474-1f3ff", + "uc_greedy": "1f474-1f3ff", + "shortnames": [], + "category": "people" + }, + ":older_woman_tone1:": { + "uc_base": "1f475-1f3fb", + "uc_output": "1f475-1f3fb", + "uc_match": "1f475-1f3fb", + "uc_greedy": "1f475-1f3fb", + "shortnames": [":grandma_tone1:"], + "category": "people" + }, + ":older_woman_tone2:": { + "uc_base": "1f475-1f3fc", + "uc_output": "1f475-1f3fc", + "uc_match": "1f475-1f3fc", + "uc_greedy": "1f475-1f3fc", + "shortnames": [":grandma_tone2:"], + "category": "people" + }, + ":older_woman_tone3:": { + "uc_base": "1f475-1f3fd", + "uc_output": "1f475-1f3fd", + "uc_match": "1f475-1f3fd", + "uc_greedy": "1f475-1f3fd", + "shortnames": [":grandma_tone3:"], + "category": "people" + }, + ":older_woman_tone4:": { + "uc_base": "1f475-1f3fe", + "uc_output": "1f475-1f3fe", + "uc_match": "1f475-1f3fe", + "uc_greedy": "1f475-1f3fe", + "shortnames": [":grandma_tone4:"], + "category": "people" + }, + ":older_woman_tone5:": { + "uc_base": "1f475-1f3ff", + "uc_output": "1f475-1f3ff", + "uc_match": "1f475-1f3ff", + "uc_greedy": "1f475-1f3ff", + "shortnames": [":grandma_tone5:"], + "category": "people" + }, + ":open_hands_tone1:": { + "uc_base": "1f450-1f3fb", + "uc_output": "1f450-1f3fb", + "uc_match": "1f450-1f3fb", + "uc_greedy": "1f450-1f3fb", + "shortnames": [], + "category": "people" + }, + ":open_hands_tone2:": { + "uc_base": "1f450-1f3fc", + "uc_output": "1f450-1f3fc", + "uc_match": "1f450-1f3fc", + "uc_greedy": "1f450-1f3fc", + "shortnames": [], + "category": "people" + }, + ":open_hands_tone3:": { + "uc_base": "1f450-1f3fd", + "uc_output": "1f450-1f3fd", + "uc_match": "1f450-1f3fd", + "uc_greedy": "1f450-1f3fd", + "shortnames": [], + "category": "people" + }, + ":open_hands_tone4:": { + "uc_base": "1f450-1f3fe", + "uc_output": "1f450-1f3fe", + "uc_match": "1f450-1f3fe", + "uc_greedy": "1f450-1f3fe", + "shortnames": [], + "category": "people" + }, + ":open_hands_tone5:": { + "uc_base": "1f450-1f3ff", + "uc_output": "1f450-1f3ff", + "uc_match": "1f450-1f3ff", + "uc_greedy": "1f450-1f3ff", + "shortnames": [], + "category": "people" + }, + ":palms_up_together_tone1:": { + "uc_base": "1f932-1f3fb", + "uc_output": "1f932-1f3fb", + "uc_match": "1f932-1f3fb", + "uc_greedy": "1f932-1f3fb", + "shortnames": [":palms_up_together_light_skin_tone:"], + "category": "people" + }, + ":palms_up_together_tone2:": { + "uc_base": "1f932-1f3fc", + "uc_output": "1f932-1f3fc", + "uc_match": "1f932-1f3fc", + "uc_greedy": "1f932-1f3fc", + "shortnames": [":palms_up_together_medium_light_skin_tone:"], + "category": "people" + }, + ":palms_up_together_tone3:": { + "uc_base": "1f932-1f3fd", + "uc_output": "1f932-1f3fd", + "uc_match": "1f932-1f3fd", + "uc_greedy": "1f932-1f3fd", + "shortnames": [":palms_up_together_medium_skin_tone:"], + "category": "people" + }, + ":palms_up_together_tone4:": { + "uc_base": "1f932-1f3fe", + "uc_output": "1f932-1f3fe", + "uc_match": "1f932-1f3fe", + "uc_greedy": "1f932-1f3fe", + "shortnames": [":palms_up_together_medium_dark_skin_tone:"], + "category": "people" + }, + ":palms_up_together_tone5:": { + "uc_base": "1f932-1f3ff", + "uc_output": "1f932-1f3ff", + "uc_match": "1f932-1f3ff", + "uc_greedy": "1f932-1f3ff", + "shortnames": [":palms_up_together_dark_skin_tone:"], + "category": "people" + }, + ":person_biking_tone1:": { + "uc_base": "1f6b4-1f3fb", + "uc_output": "1f6b4-1f3fb", + "uc_match": "1f6b4-1f3fb", + "uc_greedy": "1f6b4-1f3fb", + "shortnames": [":bicyclist_tone1:"], + "category": "activity" + }, + ":person_biking_tone2:": { + "uc_base": "1f6b4-1f3fc", + "uc_output": "1f6b4-1f3fc", + "uc_match": "1f6b4-1f3fc", + "uc_greedy": "1f6b4-1f3fc", + "shortnames": [":bicyclist_tone2:"], + "category": "activity" + }, + ":person_biking_tone3:": { + "uc_base": "1f6b4-1f3fd", + "uc_output": "1f6b4-1f3fd", + "uc_match": "1f6b4-1f3fd", + "uc_greedy": "1f6b4-1f3fd", + "shortnames": [":bicyclist_tone3:"], + "category": "activity" + }, + ":person_biking_tone4:": { + "uc_base": "1f6b4-1f3fe", + "uc_output": "1f6b4-1f3fe", + "uc_match": "1f6b4-1f3fe", + "uc_greedy": "1f6b4-1f3fe", + "shortnames": [":bicyclist_tone4:"], + "category": "activity" + }, + ":person_biking_tone5:": { + "uc_base": "1f6b4-1f3ff", + "uc_output": "1f6b4-1f3ff", + "uc_match": "1f6b4-1f3ff", + "uc_greedy": "1f6b4-1f3ff", + "shortnames": [":bicyclist_tone5:"], + "category": "activity" + }, + ":person_bowing_tone1:": { + "uc_base": "1f647-1f3fb", + "uc_output": "1f647-1f3fb", + "uc_match": "1f647-1f3fb", + "uc_greedy": "1f647-1f3fb", + "shortnames": [":bow_tone1:"], + "category": "people" + }, + ":person_bowing_tone2:": { + "uc_base": "1f647-1f3fc", + "uc_output": "1f647-1f3fc", + "uc_match": "1f647-1f3fc", + "uc_greedy": "1f647-1f3fc", + "shortnames": [":bow_tone2:"], + "category": "people" + }, + ":person_bowing_tone3:": { + "uc_base": "1f647-1f3fd", + "uc_output": "1f647-1f3fd", + "uc_match": "1f647-1f3fd", + "uc_greedy": "1f647-1f3fd", + "shortnames": [":bow_tone3:"], + "category": "people" + }, + ":person_bowing_tone4:": { + "uc_base": "1f647-1f3fe", + "uc_output": "1f647-1f3fe", + "uc_match": "1f647-1f3fe", + "uc_greedy": "1f647-1f3fe", + "shortnames": [":bow_tone4:"], + "category": "people" + }, + ":person_bowing_tone5:": { + "uc_base": "1f647-1f3ff", + "uc_output": "1f647-1f3ff", + "uc_match": "1f647-1f3ff", + "uc_greedy": "1f647-1f3ff", + "shortnames": [":bow_tone5:"], + "category": "people" + }, + ":person_climbing_tone1:": { + "uc_base": "1f9d7-1f3fb", + "uc_output": "1f9d7-1f3fb", + "uc_match": "1f9d7-1f3fb", + "uc_greedy": "1f9d7-1f3fb", + "shortnames": [":person_climbing_light_skin_tone:"], + "category": "activity" + }, + ":person_climbing_tone2:": { + "uc_base": "1f9d7-1f3fc", + "uc_output": "1f9d7-1f3fc", + "uc_match": "1f9d7-1f3fc", + "uc_greedy": "1f9d7-1f3fc", + "shortnames": [":person_climbing_medium_light_skin_tone:"], + "category": "activity" + }, + ":person_climbing_tone3:": { + "uc_base": "1f9d7-1f3fd", + "uc_output": "1f9d7-1f3fd", + "uc_match": "1f9d7-1f3fd", + "uc_greedy": "1f9d7-1f3fd", + "shortnames": [":person_climbing_medium_skin_tone:"], + "category": "activity" + }, + ":person_climbing_tone4:": { + "uc_base": "1f9d7-1f3fe", + "uc_output": "1f9d7-1f3fe", + "uc_match": "1f9d7-1f3fe", + "uc_greedy": "1f9d7-1f3fe", + "shortnames": [":person_climbing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":person_climbing_tone5:": { + "uc_base": "1f9d7-1f3ff", + "uc_output": "1f9d7-1f3ff", + "uc_match": "1f9d7-1f3ff", + "uc_greedy": "1f9d7-1f3ff", + "shortnames": [":person_climbing_dark_skin_tone:"], + "category": "activity" + }, + ":person_doing_cartwheel_tone1:": { + "uc_base": "1f938-1f3fb", + "uc_output": "1f938-1f3fb", + "uc_match": "1f938-1f3fb", + "uc_greedy": "1f938-1f3fb", + "shortnames": [":cartwheel_tone1:"], + "category": "activity" + }, + ":person_doing_cartwheel_tone2:": { + "uc_base": "1f938-1f3fc", + "uc_output": "1f938-1f3fc", + "uc_match": "1f938-1f3fc", + "uc_greedy": "1f938-1f3fc", + "shortnames": [":cartwheel_tone2:"], + "category": "activity" + }, + ":person_doing_cartwheel_tone3:": { + "uc_base": "1f938-1f3fd", + "uc_output": "1f938-1f3fd", + "uc_match": "1f938-1f3fd", + "uc_greedy": "1f938-1f3fd", + "shortnames": [":cartwheel_tone3:"], + "category": "activity" + }, + ":person_doing_cartwheel_tone4:": { + "uc_base": "1f938-1f3fe", + "uc_output": "1f938-1f3fe", + "uc_match": "1f938-1f3fe", + "uc_greedy": "1f938-1f3fe", + "shortnames": [":cartwheel_tone4:"], + "category": "activity" + }, + ":person_doing_cartwheel_tone5:": { + "uc_base": "1f938-1f3ff", + "uc_output": "1f938-1f3ff", + "uc_match": "1f938-1f3ff", + "uc_greedy": "1f938-1f3ff", + "shortnames": [":cartwheel_tone5:"], + "category": "activity" + }, + ":person_facepalming_tone1:": { + "uc_base": "1f926-1f3fb", + "uc_output": "1f926-1f3fb", + "uc_match": "1f926-1f3fb", + "uc_greedy": "1f926-1f3fb", + "shortnames": [":face_palm_tone1:", ":facepalm_tone1:"], + "category": "people" + }, + ":person_facepalming_tone2:": { + "uc_base": "1f926-1f3fc", + "uc_output": "1f926-1f3fc", + "uc_match": "1f926-1f3fc", + "uc_greedy": "1f926-1f3fc", + "shortnames": [":face_palm_tone2:", ":facepalm_tone2:"], + "category": "people" + }, + ":person_facepalming_tone3:": { + "uc_base": "1f926-1f3fd", + "uc_output": "1f926-1f3fd", + "uc_match": "1f926-1f3fd", + "uc_greedy": "1f926-1f3fd", + "shortnames": [":face_palm_tone3:", ":facepalm_tone3:"], + "category": "people" + }, + ":person_facepalming_tone4:": { + "uc_base": "1f926-1f3fe", + "uc_output": "1f926-1f3fe", + "uc_match": "1f926-1f3fe", + "uc_greedy": "1f926-1f3fe", + "shortnames": [":face_palm_tone4:", ":facepalm_tone4:"], + "category": "people" + }, + ":person_facepalming_tone5:": { + "uc_base": "1f926-1f3ff", + "uc_output": "1f926-1f3ff", + "uc_match": "1f926-1f3ff", + "uc_greedy": "1f926-1f3ff", + "shortnames": [":face_palm_tone5:", ":facepalm_tone5:"], + "category": "people" + }, + ":person_frowning_tone1:": { + "uc_base": "1f64d-1f3fb", + "uc_output": "1f64d-1f3fb", + "uc_match": "1f64d-1f3fb", + "uc_greedy": "1f64d-1f3fb", + "shortnames": [], + "category": "people" + }, + ":person_frowning_tone2:": { + "uc_base": "1f64d-1f3fc", + "uc_output": "1f64d-1f3fc", + "uc_match": "1f64d-1f3fc", + "uc_greedy": "1f64d-1f3fc", + "shortnames": [], + "category": "people" + }, + ":person_frowning_tone3:": { + "uc_base": "1f64d-1f3fd", + "uc_output": "1f64d-1f3fd", + "uc_match": "1f64d-1f3fd", + "uc_greedy": "1f64d-1f3fd", + "shortnames": [], + "category": "people" + }, + ":person_frowning_tone4:": { + "uc_base": "1f64d-1f3fe", + "uc_output": "1f64d-1f3fe", + "uc_match": "1f64d-1f3fe", + "uc_greedy": "1f64d-1f3fe", + "shortnames": [], + "category": "people" + }, + ":person_frowning_tone5:": { + "uc_base": "1f64d-1f3ff", + "uc_output": "1f64d-1f3ff", + "uc_match": "1f64d-1f3ff", + "uc_greedy": "1f64d-1f3ff", + "shortnames": [], + "category": "people" + }, + ":person_gesturing_no_tone1:": { + "uc_base": "1f645-1f3fb", + "uc_output": "1f645-1f3fb", + "uc_match": "1f645-1f3fb", + "uc_greedy": "1f645-1f3fb", + "shortnames": [":no_good_tone1:"], + "category": "people" + }, + ":person_gesturing_no_tone2:": { + "uc_base": "1f645-1f3fc", + "uc_output": "1f645-1f3fc", + "uc_match": "1f645-1f3fc", + "uc_greedy": "1f645-1f3fc", + "shortnames": [":no_good_tone2:"], + "category": "people" + }, + ":person_gesturing_no_tone3:": { + "uc_base": "1f645-1f3fd", + "uc_output": "1f645-1f3fd", + "uc_match": "1f645-1f3fd", + "uc_greedy": "1f645-1f3fd", + "shortnames": [":no_good_tone3:"], + "category": "people" + }, + ":person_gesturing_no_tone4:": { + "uc_base": "1f645-1f3fe", + "uc_output": "1f645-1f3fe", + "uc_match": "1f645-1f3fe", + "uc_greedy": "1f645-1f3fe", + "shortnames": [":no_good_tone4:"], + "category": "people" + }, + ":person_gesturing_no_tone5:": { + "uc_base": "1f645-1f3ff", + "uc_output": "1f645-1f3ff", + "uc_match": "1f645-1f3ff", + "uc_greedy": "1f645-1f3ff", + "shortnames": [":no_good_tone5:"], + "category": "people" + }, + ":person_gesturing_ok_tone1:": { + "uc_base": "1f646-1f3fb", + "uc_output": "1f646-1f3fb", + "uc_match": "1f646-1f3fb", + "uc_greedy": "1f646-1f3fb", + "shortnames": [":ok_woman_tone1:"], + "category": "people" + }, + ":person_gesturing_ok_tone2:": { + "uc_base": "1f646-1f3fc", + "uc_output": "1f646-1f3fc", + "uc_match": "1f646-1f3fc", + "uc_greedy": "1f646-1f3fc", + "shortnames": [":ok_woman_tone2:"], + "category": "people" + }, + ":person_gesturing_ok_tone3:": { + "uc_base": "1f646-1f3fd", + "uc_output": "1f646-1f3fd", + "uc_match": "1f646-1f3fd", + "uc_greedy": "1f646-1f3fd", + "shortnames": [":ok_woman_tone3:"], + "category": "people" + }, + ":person_gesturing_ok_tone4:": { + "uc_base": "1f646-1f3fe", + "uc_output": "1f646-1f3fe", + "uc_match": "1f646-1f3fe", + "uc_greedy": "1f646-1f3fe", + "shortnames": [":ok_woman_tone4:"], + "category": "people" + }, + ":person_gesturing_ok_tone5:": { + "uc_base": "1f646-1f3ff", + "uc_output": "1f646-1f3ff", + "uc_match": "1f646-1f3ff", + "uc_greedy": "1f646-1f3ff", + "shortnames": [":ok_woman_tone5:"], + "category": "people" + }, + ":person_getting_haircut_tone1:": { + "uc_base": "1f487-1f3fb", + "uc_output": "1f487-1f3fb", + "uc_match": "1f487-1f3fb", + "uc_greedy": "1f487-1f3fb", + "shortnames": [":haircut_tone1:"], + "category": "people" + }, + ":person_getting_haircut_tone2:": { + "uc_base": "1f487-1f3fc", + "uc_output": "1f487-1f3fc", + "uc_match": "1f487-1f3fc", + "uc_greedy": "1f487-1f3fc", + "shortnames": [":haircut_tone2:"], + "category": "people" + }, + ":person_getting_haircut_tone3:": { + "uc_base": "1f487-1f3fd", + "uc_output": "1f487-1f3fd", + "uc_match": "1f487-1f3fd", + "uc_greedy": "1f487-1f3fd", + "shortnames": [":haircut_tone3:"], + "category": "people" + }, + ":person_getting_haircut_tone4:": { + "uc_base": "1f487-1f3fe", + "uc_output": "1f487-1f3fe", + "uc_match": "1f487-1f3fe", + "uc_greedy": "1f487-1f3fe", + "shortnames": [":haircut_tone4:"], + "category": "people" + }, + ":person_getting_haircut_tone5:": { + "uc_base": "1f487-1f3ff", + "uc_output": "1f487-1f3ff", + "uc_match": "1f487-1f3ff", + "uc_greedy": "1f487-1f3ff", + "shortnames": [":haircut_tone5:"], + "category": "people" + }, + ":person_getting_massage_tone1:": { + "uc_base": "1f486-1f3fb", + "uc_output": "1f486-1f3fb", + "uc_match": "1f486-1f3fb", + "uc_greedy": "1f486-1f3fb", + "shortnames": [":massage_tone1:"], + "category": "people" + }, + ":person_getting_massage_tone2:": { + "uc_base": "1f486-1f3fc", + "uc_output": "1f486-1f3fc", + "uc_match": "1f486-1f3fc", + "uc_greedy": "1f486-1f3fc", + "shortnames": [":massage_tone2:"], + "category": "people" + }, + ":person_getting_massage_tone3:": { + "uc_base": "1f486-1f3fd", + "uc_output": "1f486-1f3fd", + "uc_match": "1f486-1f3fd", + "uc_greedy": "1f486-1f3fd", + "shortnames": [":massage_tone3:"], + "category": "people" + }, + ":person_getting_massage_tone4:": { + "uc_base": "1f486-1f3fe", + "uc_output": "1f486-1f3fe", + "uc_match": "1f486-1f3fe", + "uc_greedy": "1f486-1f3fe", + "shortnames": [":massage_tone4:"], + "category": "people" + }, + ":person_getting_massage_tone5:": { + "uc_base": "1f486-1f3ff", + "uc_output": "1f486-1f3ff", + "uc_match": "1f486-1f3ff", + "uc_greedy": "1f486-1f3ff", + "shortnames": [":massage_tone5:"], + "category": "people" + }, + ":person_golfing_tone1:": { + "uc_base": "1f3cc-1f3fb", + "uc_output": "1f3cc-1f3fb", + "uc_match": "1f3cc-fe0f-1f3fb", + "uc_greedy": "1f3cc-fe0f-1f3fb", + "shortnames": [":person_golfing_light_skin_tone:"], + "category": "activity" + }, + ":person_golfing_tone2:": { + "uc_base": "1f3cc-1f3fc", + "uc_output": "1f3cc-1f3fc", + "uc_match": "1f3cc-fe0f-1f3fc", + "uc_greedy": "1f3cc-fe0f-1f3fc", + "shortnames": [":person_golfing_medium_light_skin_tone:"], + "category": "activity" + }, + ":person_golfing_tone3:": { + "uc_base": "1f3cc-1f3fd", + "uc_output": "1f3cc-1f3fd", + "uc_match": "1f3cc-fe0f-1f3fd", + "uc_greedy": "1f3cc-fe0f-1f3fd", + "shortnames": [":person_golfing_medium_skin_tone:"], + "category": "activity" + }, + ":person_golfing_tone4:": { + "uc_base": "1f3cc-1f3fe", + "uc_output": "1f3cc-1f3fe", + "uc_match": "1f3cc-fe0f-1f3fe", + "uc_greedy": "1f3cc-fe0f-1f3fe", + "shortnames": [":person_golfing_medium_dark_skin_tone:"], + "category": "activity" + }, + ":person_golfing_tone5:": { + "uc_base": "1f3cc-1f3ff", + "uc_output": "1f3cc-1f3ff", + "uc_match": "1f3cc-fe0f-1f3ff", + "uc_greedy": "1f3cc-fe0f-1f3ff", + "shortnames": [":person_golfing_dark_skin_tone:"], + "category": "activity" + }, + ":person_in_bed_tone1:": { + "uc_base": "1f6cc-1f3fb", + "uc_output": "1f6cc-1f3fb", + "uc_match": "1f6cc-1f3fb", + "uc_greedy": "1f6cc-1f3fb", + "shortnames": [":person_in_bed_light_skin_tone:"], + "category": "objects" + }, + ":person_in_bed_tone2:": { + "uc_base": "1f6cc-1f3fc", + "uc_output": "1f6cc-1f3fc", + "uc_match": "1f6cc-1f3fc", + "uc_greedy": "1f6cc-1f3fc", + "shortnames": [":person_in_bed_medium_light_skin_tone:"], + "category": "objects" + }, + ":person_in_bed_tone3:": { + "uc_base": "1f6cc-1f3fd", + "uc_output": "1f6cc-1f3fd", + "uc_match": "1f6cc-1f3fd", + "uc_greedy": "1f6cc-1f3fd", + "shortnames": [":person_in_bed_medium_skin_tone:"], + "category": "objects" + }, + ":person_in_bed_tone4:": { + "uc_base": "1f6cc-1f3fe", + "uc_output": "1f6cc-1f3fe", + "uc_match": "1f6cc-1f3fe", + "uc_greedy": "1f6cc-1f3fe", + "shortnames": [":person_in_bed_medium_dark_skin_tone:"], + "category": "objects" + }, + ":person_in_bed_tone5:": { + "uc_base": "1f6cc-1f3ff", + "uc_output": "1f6cc-1f3ff", + "uc_match": "1f6cc-1f3ff", + "uc_greedy": "1f6cc-1f3ff", + "shortnames": [":person_in_bed_dark_skin_tone:"], + "category": "objects" + }, + ":person_in_lotus_position_tone1:": { + "uc_base": "1f9d8-1f3fb", + "uc_output": "1f9d8-1f3fb", + "uc_match": "1f9d8-1f3fb", + "uc_greedy": "1f9d8-1f3fb", + "shortnames": [":person_in_lotus_position_light_skin_tone:"], + "category": "activity" + }, + ":person_in_lotus_position_tone2:": { + "uc_base": "1f9d8-1f3fc", + "uc_output": "1f9d8-1f3fc", + "uc_match": "1f9d8-1f3fc", + "uc_greedy": "1f9d8-1f3fc", + "shortnames": [":person_in_lotus_position_medium_light_skin_tone:"], + "category": "activity" + }, + ":person_in_lotus_position_tone3:": { + "uc_base": "1f9d8-1f3fd", + "uc_output": "1f9d8-1f3fd", + "uc_match": "1f9d8-1f3fd", + "uc_greedy": "1f9d8-1f3fd", + "shortnames": [":person_in_lotus_position_medium_skin_tone:"], + "category": "activity" + }, + ":person_in_lotus_position_tone4:": { + "uc_base": "1f9d8-1f3fe", + "uc_output": "1f9d8-1f3fe", + "uc_match": "1f9d8-1f3fe", + "uc_greedy": "1f9d8-1f3fe", + "shortnames": [":person_in_lotus_position_medium_dark_skin_tone:"], + "category": "activity" + }, + ":person_in_lotus_position_tone5:": { + "uc_base": "1f9d8-1f3ff", + "uc_output": "1f9d8-1f3ff", + "uc_match": "1f9d8-1f3ff", + "uc_greedy": "1f9d8-1f3ff", + "shortnames": [":person_in_lotus_position_dark_skin_tone:"], + "category": "activity" + }, + ":person_in_steamy_room_tone1:": { + "uc_base": "1f9d6-1f3fb", + "uc_output": "1f9d6-1f3fb", + "uc_match": "1f9d6-1f3fb", + "uc_greedy": "1f9d6-1f3fb", + "shortnames": [":person_in_steamy_room_light_skin_tone:"], + "category": "people" + }, + ":person_in_steamy_room_tone2:": { + "uc_base": "1f9d6-1f3fc", + "uc_output": "1f9d6-1f3fc", + "uc_match": "1f9d6-1f3fc", + "uc_greedy": "1f9d6-1f3fc", + "shortnames": [":person_in_steamy_room_medium_light_skin_tone:"], + "category": "people" + }, + ":person_in_steamy_room_tone3:": { + "uc_base": "1f9d6-1f3fd", + "uc_output": "1f9d6-1f3fd", + "uc_match": "1f9d6-1f3fd", + "uc_greedy": "1f9d6-1f3fd", + "shortnames": [":person_in_steamy_room_medium_skin_tone:"], + "category": "people" + }, + ":person_in_steamy_room_tone4:": { + "uc_base": "1f9d6-1f3fe", + "uc_output": "1f9d6-1f3fe", + "uc_match": "1f9d6-1f3fe", + "uc_greedy": "1f9d6-1f3fe", + "shortnames": [":person_in_steamy_room_medium_dark_skin_tone:"], + "category": "people" + }, + ":person_in_steamy_room_tone5:": { + "uc_base": "1f9d6-1f3ff", + "uc_output": "1f9d6-1f3ff", + "uc_match": "1f9d6-1f3ff", + "uc_greedy": "1f9d6-1f3ff", + "shortnames": [":person_in_steamy_room_dark_skin_tone:"], + "category": "people" + }, + ":person_juggling_tone1:": { + "uc_base": "1f939-1f3fb", + "uc_output": "1f939-1f3fb", + "uc_match": "1f939-1f3fb", + "uc_greedy": "1f939-1f3fb", + "shortnames": [":juggling_tone1:", ":juggler_tone1:"], + "category": "activity" + }, + ":person_juggling_tone2:": { + "uc_base": "1f939-1f3fc", + "uc_output": "1f939-1f3fc", + "uc_match": "1f939-1f3fc", + "uc_greedy": "1f939-1f3fc", + "shortnames": [":juggling_tone2:", ":juggler_tone2:"], + "category": "activity" + }, + ":person_juggling_tone3:": { + "uc_base": "1f939-1f3fd", + "uc_output": "1f939-1f3fd", + "uc_match": "1f939-1f3fd", + "uc_greedy": "1f939-1f3fd", + "shortnames": [":juggling_tone3:", ":juggler_tone3:"], + "category": "activity" + }, + ":person_juggling_tone4:": { + "uc_base": "1f939-1f3fe", + "uc_output": "1f939-1f3fe", + "uc_match": "1f939-1f3fe", + "uc_greedy": "1f939-1f3fe", + "shortnames": [":juggling_tone4:", ":juggler_tone4:"], + "category": "activity" + }, + ":person_juggling_tone5:": { + "uc_base": "1f939-1f3ff", + "uc_output": "1f939-1f3ff", + "uc_match": "1f939-1f3ff", + "uc_greedy": "1f939-1f3ff", + "shortnames": [":juggling_tone5:", ":juggler_tone5:"], + "category": "activity" + }, + ":person_lifting_weights_tone1:": { + "uc_base": "1f3cb-1f3fb", + "uc_output": "1f3cb-1f3fb", + "uc_match": "1f3cb-fe0f-1f3fb", + "uc_greedy": "1f3cb-fe0f-1f3fb", + "shortnames": [":lifter_tone1:", ":weight_lifter_tone1:"], + "category": "activity" + }, + ":person_lifting_weights_tone2:": { + "uc_base": "1f3cb-1f3fc", + "uc_output": "1f3cb-1f3fc", + "uc_match": "1f3cb-fe0f-1f3fc", + "uc_greedy": "1f3cb-fe0f-1f3fc", + "shortnames": [":lifter_tone2:", ":weight_lifter_tone2:"], + "category": "activity" + }, + ":person_lifting_weights_tone3:": { + "uc_base": "1f3cb-1f3fd", + "uc_output": "1f3cb-1f3fd", + "uc_match": "1f3cb-fe0f-1f3fd", + "uc_greedy": "1f3cb-fe0f-1f3fd", + "shortnames": [":lifter_tone3:", ":weight_lifter_tone3:"], + "category": "activity" + }, + ":person_lifting_weights_tone4:": { + "uc_base": "1f3cb-1f3fe", + "uc_output": "1f3cb-1f3fe", + "uc_match": "1f3cb-fe0f-1f3fe", + "uc_greedy": "1f3cb-fe0f-1f3fe", + "shortnames": [":lifter_tone4:", ":weight_lifter_tone4:"], + "category": "activity" + }, + ":person_lifting_weights_tone5:": { + "uc_base": "1f3cb-1f3ff", + "uc_output": "1f3cb-1f3ff", + "uc_match": "1f3cb-fe0f-1f3ff", + "uc_greedy": "1f3cb-fe0f-1f3ff", + "shortnames": [":lifter_tone5:", ":weight_lifter_tone5:"], + "category": "activity" + }, + ":person_mountain_biking_tone1:": { + "uc_base": "1f6b5-1f3fb", + "uc_output": "1f6b5-1f3fb", + "uc_match": "1f6b5-1f3fb", + "uc_greedy": "1f6b5-1f3fb", + "shortnames": [":mountain_bicyclist_tone1:"], + "category": "activity" + }, + ":person_mountain_biking_tone2:": { + "uc_base": "1f6b5-1f3fc", + "uc_output": "1f6b5-1f3fc", + "uc_match": "1f6b5-1f3fc", + "uc_greedy": "1f6b5-1f3fc", + "shortnames": [":mountain_bicyclist_tone2:"], + "category": "activity" + }, + ":person_mountain_biking_tone3:": { + "uc_base": "1f6b5-1f3fd", + "uc_output": "1f6b5-1f3fd", + "uc_match": "1f6b5-1f3fd", + "uc_greedy": "1f6b5-1f3fd", + "shortnames": [":mountain_bicyclist_tone3:"], + "category": "activity" + }, + ":person_mountain_biking_tone4:": { + "uc_base": "1f6b5-1f3fe", + "uc_output": "1f6b5-1f3fe", + "uc_match": "1f6b5-1f3fe", + "uc_greedy": "1f6b5-1f3fe", + "shortnames": [":mountain_bicyclist_tone4:"], + "category": "activity" + }, + ":person_mountain_biking_tone5:": { + "uc_base": "1f6b5-1f3ff", + "uc_output": "1f6b5-1f3ff", + "uc_match": "1f6b5-1f3ff", + "uc_greedy": "1f6b5-1f3ff", + "shortnames": [":mountain_bicyclist_tone5:"], + "category": "activity" + }, + ":person_playing_handball_tone1:": { + "uc_base": "1f93e-1f3fb", + "uc_output": "1f93e-1f3fb", + "uc_match": "1f93e-1f3fb", + "uc_greedy": "1f93e-1f3fb", + "shortnames": [":handball_tone1:"], + "category": "activity" + }, + ":person_playing_handball_tone2:": { + "uc_base": "1f93e-1f3fc", + "uc_output": "1f93e-1f3fc", + "uc_match": "1f93e-1f3fc", + "uc_greedy": "1f93e-1f3fc", + "shortnames": [":handball_tone2:"], + "category": "activity" + }, + ":person_playing_handball_tone3:": { + "uc_base": "1f93e-1f3fd", + "uc_output": "1f93e-1f3fd", + "uc_match": "1f93e-1f3fd", + "uc_greedy": "1f93e-1f3fd", + "shortnames": [":handball_tone3:"], + "category": "activity" + }, + ":person_playing_handball_tone4:": { + "uc_base": "1f93e-1f3fe", + "uc_output": "1f93e-1f3fe", + "uc_match": "1f93e-1f3fe", + "uc_greedy": "1f93e-1f3fe", + "shortnames": [":handball_tone4:"], + "category": "activity" + }, + ":person_playing_handball_tone5:": { + "uc_base": "1f93e-1f3ff", + "uc_output": "1f93e-1f3ff", + "uc_match": "1f93e-1f3ff", + "uc_greedy": "1f93e-1f3ff", + "shortnames": [":handball_tone5:"], + "category": "activity" + }, + ":person_playing_water_polo_tone1:": { + "uc_base": "1f93d-1f3fb", + "uc_output": "1f93d-1f3fb", + "uc_match": "1f93d-1f3fb", + "uc_greedy": "1f93d-1f3fb", + "shortnames": [":water_polo_tone1:"], + "category": "activity" + }, + ":person_playing_water_polo_tone2:": { + "uc_base": "1f93d-1f3fc", + "uc_output": "1f93d-1f3fc", + "uc_match": "1f93d-1f3fc", + "uc_greedy": "1f93d-1f3fc", + "shortnames": [":water_polo_tone2:"], + "category": "activity" + }, + ":person_playing_water_polo_tone3:": { + "uc_base": "1f93d-1f3fd", + "uc_output": "1f93d-1f3fd", + "uc_match": "1f93d-1f3fd", + "uc_greedy": "1f93d-1f3fd", + "shortnames": [":water_polo_tone3:"], + "category": "activity" + }, + ":person_playing_water_polo_tone4:": { + "uc_base": "1f93d-1f3fe", + "uc_output": "1f93d-1f3fe", + "uc_match": "1f93d-1f3fe", + "uc_greedy": "1f93d-1f3fe", + "shortnames": [":water_polo_tone4:"], + "category": "activity" + }, + ":person_playing_water_polo_tone5:": { + "uc_base": "1f93d-1f3ff", + "uc_output": "1f93d-1f3ff", + "uc_match": "1f93d-1f3ff", + "uc_greedy": "1f93d-1f3ff", + "shortnames": [":water_polo_tone5:"], + "category": "activity" + }, + ":person_pouting_tone1:": { + "uc_base": "1f64e-1f3fb", + "uc_output": "1f64e-1f3fb", + "uc_match": "1f64e-1f3fb", + "uc_greedy": "1f64e-1f3fb", + "shortnames": [":person_with_pouting_face_tone1:"], + "category": "people" + }, + ":person_pouting_tone2:": { + "uc_base": "1f64e-1f3fc", + "uc_output": "1f64e-1f3fc", + "uc_match": "1f64e-1f3fc", + "uc_greedy": "1f64e-1f3fc", + "shortnames": [":person_with_pouting_face_tone2:"], + "category": "people" + }, + ":person_pouting_tone3:": { + "uc_base": "1f64e-1f3fd", + "uc_output": "1f64e-1f3fd", + "uc_match": "1f64e-1f3fd", + "uc_greedy": "1f64e-1f3fd", + "shortnames": [":person_with_pouting_face_tone3:"], + "category": "people" + }, + ":person_pouting_tone4:": { + "uc_base": "1f64e-1f3fe", + "uc_output": "1f64e-1f3fe", + "uc_match": "1f64e-1f3fe", + "uc_greedy": "1f64e-1f3fe", + "shortnames": [":person_with_pouting_face_tone4:"], + "category": "people" + }, + ":person_pouting_tone5:": { + "uc_base": "1f64e-1f3ff", + "uc_output": "1f64e-1f3ff", + "uc_match": "1f64e-1f3ff", + "uc_greedy": "1f64e-1f3ff", + "shortnames": [":person_with_pouting_face_tone5:"], + "category": "people" + }, + ":person_raising_hand_tone1:": { + "uc_base": "1f64b-1f3fb", + "uc_output": "1f64b-1f3fb", + "uc_match": "1f64b-1f3fb", + "uc_greedy": "1f64b-1f3fb", + "shortnames": [":raising_hand_tone1:"], + "category": "people" + }, + ":person_raising_hand_tone2:": { + "uc_base": "1f64b-1f3fc", + "uc_output": "1f64b-1f3fc", + "uc_match": "1f64b-1f3fc", + "uc_greedy": "1f64b-1f3fc", + "shortnames": [":raising_hand_tone2:"], + "category": "people" + }, + ":person_raising_hand_tone3:": { + "uc_base": "1f64b-1f3fd", + "uc_output": "1f64b-1f3fd", + "uc_match": "1f64b-1f3fd", + "uc_greedy": "1f64b-1f3fd", + "shortnames": [":raising_hand_tone3:"], + "category": "people" + }, + ":person_raising_hand_tone4:": { + "uc_base": "1f64b-1f3fe", + "uc_output": "1f64b-1f3fe", + "uc_match": "1f64b-1f3fe", + "uc_greedy": "1f64b-1f3fe", + "shortnames": [":raising_hand_tone4:"], + "category": "people" + }, + ":person_raising_hand_tone5:": { + "uc_base": "1f64b-1f3ff", + "uc_output": "1f64b-1f3ff", + "uc_match": "1f64b-1f3ff", + "uc_greedy": "1f64b-1f3ff", + "shortnames": [":raising_hand_tone5:"], + "category": "people" + }, + ":person_rowing_boat_tone1:": { + "uc_base": "1f6a3-1f3fb", + "uc_output": "1f6a3-1f3fb", + "uc_match": "1f6a3-1f3fb", + "uc_greedy": "1f6a3-1f3fb", + "shortnames": [":rowboat_tone1:"], + "category": "activity" + }, + ":person_rowing_boat_tone2:": { + "uc_base": "1f6a3-1f3fc", + "uc_output": "1f6a3-1f3fc", + "uc_match": "1f6a3-1f3fc", + "uc_greedy": "1f6a3-1f3fc", + "shortnames": [":rowboat_tone2:"], + "category": "activity" + }, + ":person_rowing_boat_tone3:": { + "uc_base": "1f6a3-1f3fd", + "uc_output": "1f6a3-1f3fd", + "uc_match": "1f6a3-1f3fd", + "uc_greedy": "1f6a3-1f3fd", + "shortnames": [":rowboat_tone3:"], + "category": "activity" + }, + ":person_rowing_boat_tone4:": { + "uc_base": "1f6a3-1f3fe", + "uc_output": "1f6a3-1f3fe", + "uc_match": "1f6a3-1f3fe", + "uc_greedy": "1f6a3-1f3fe", + "shortnames": [":rowboat_tone4:"], + "category": "activity" + }, + ":person_rowing_boat_tone5:": { + "uc_base": "1f6a3-1f3ff", + "uc_output": "1f6a3-1f3ff", + "uc_match": "1f6a3-1f3ff", + "uc_greedy": "1f6a3-1f3ff", + "shortnames": [":rowboat_tone5:"], + "category": "activity" + }, + ":person_running_tone1:": { + "uc_base": "1f3c3-1f3fb", + "uc_output": "1f3c3-1f3fb", + "uc_match": "1f3c3-1f3fb", + "uc_greedy": "1f3c3-1f3fb", + "shortnames": [":runner_tone1:"], + "category": "people" + }, + ":person_running_tone2:": { + "uc_base": "1f3c3-1f3fc", + "uc_output": "1f3c3-1f3fc", + "uc_match": "1f3c3-1f3fc", + "uc_greedy": "1f3c3-1f3fc", + "shortnames": [":runner_tone2:"], + "category": "people" + }, + ":person_running_tone3:": { + "uc_base": "1f3c3-1f3fd", + "uc_output": "1f3c3-1f3fd", + "uc_match": "1f3c3-1f3fd", + "uc_greedy": "1f3c3-1f3fd", + "shortnames": [":runner_tone3:"], + "category": "people" + }, + ":person_running_tone4:": { + "uc_base": "1f3c3-1f3fe", + "uc_output": "1f3c3-1f3fe", + "uc_match": "1f3c3-1f3fe", + "uc_greedy": "1f3c3-1f3fe", + "shortnames": [":runner_tone4:"], + "category": "people" + }, + ":person_running_tone5:": { + "uc_base": "1f3c3-1f3ff", + "uc_output": "1f3c3-1f3ff", + "uc_match": "1f3c3-1f3ff", + "uc_greedy": "1f3c3-1f3ff", + "shortnames": [":runner_tone5:"], + "category": "people" + }, + ":person_shrugging_tone1:": { + "uc_base": "1f937-1f3fb", + "uc_output": "1f937-1f3fb", + "uc_match": "1f937-1f3fb", + "uc_greedy": "1f937-1f3fb", + "shortnames": [":shrug_tone1:"], + "category": "people" + }, + ":person_shrugging_tone2:": { + "uc_base": "1f937-1f3fc", + "uc_output": "1f937-1f3fc", + "uc_match": "1f937-1f3fc", + "uc_greedy": "1f937-1f3fc", + "shortnames": [":shrug_tone2:"], + "category": "people" + }, + ":person_shrugging_tone3:": { + "uc_base": "1f937-1f3fd", + "uc_output": "1f937-1f3fd", + "uc_match": "1f937-1f3fd", + "uc_greedy": "1f937-1f3fd", + "shortnames": [":shrug_tone3:"], + "category": "people" + }, + ":person_shrugging_tone4:": { + "uc_base": "1f937-1f3fe", + "uc_output": "1f937-1f3fe", + "uc_match": "1f937-1f3fe", + "uc_greedy": "1f937-1f3fe", + "shortnames": [":shrug_tone4:"], + "category": "people" + }, + ":person_shrugging_tone5:": { + "uc_base": "1f937-1f3ff", + "uc_output": "1f937-1f3ff", + "uc_match": "1f937-1f3ff", + "uc_greedy": "1f937-1f3ff", + "shortnames": [":shrug_tone5:"], + "category": "people" + }, + ":person_surfing_tone1:": { + "uc_base": "1f3c4-1f3fb", + "uc_output": "1f3c4-1f3fb", + "uc_match": "1f3c4-1f3fb", + "uc_greedy": "1f3c4-1f3fb", + "shortnames": [":surfer_tone1:"], + "category": "activity" + }, + ":person_surfing_tone2:": { + "uc_base": "1f3c4-1f3fc", + "uc_output": "1f3c4-1f3fc", + "uc_match": "1f3c4-1f3fc", + "uc_greedy": "1f3c4-1f3fc", + "shortnames": [":surfer_tone2:"], + "category": "activity" + }, + ":person_surfing_tone3:": { + "uc_base": "1f3c4-1f3fd", + "uc_output": "1f3c4-1f3fd", + "uc_match": "1f3c4-1f3fd", + "uc_greedy": "1f3c4-1f3fd", + "shortnames": [":surfer_tone3:"], + "category": "activity" + }, + ":person_surfing_tone4:": { + "uc_base": "1f3c4-1f3fe", + "uc_output": "1f3c4-1f3fe", + "uc_match": "1f3c4-1f3fe", + "uc_greedy": "1f3c4-1f3fe", + "shortnames": [":surfer_tone4:"], + "category": "activity" + }, + ":person_surfing_tone5:": { + "uc_base": "1f3c4-1f3ff", + "uc_output": "1f3c4-1f3ff", + "uc_match": "1f3c4-1f3ff", + "uc_greedy": "1f3c4-1f3ff", + "shortnames": [":surfer_tone5:"], + "category": "activity" + }, + ":person_swimming_tone1:": { + "uc_base": "1f3ca-1f3fb", + "uc_output": "1f3ca-1f3fb", + "uc_match": "1f3ca-1f3fb", + "uc_greedy": "1f3ca-1f3fb", + "shortnames": [":swimmer_tone1:"], + "category": "activity" + }, + ":person_swimming_tone2:": { + "uc_base": "1f3ca-1f3fc", + "uc_output": "1f3ca-1f3fc", + "uc_match": "1f3ca-1f3fc", + "uc_greedy": "1f3ca-1f3fc", + "shortnames": [":swimmer_tone2:"], + "category": "activity" + }, + ":person_swimming_tone3:": { + "uc_base": "1f3ca-1f3fd", + "uc_output": "1f3ca-1f3fd", + "uc_match": "1f3ca-1f3fd", + "uc_greedy": "1f3ca-1f3fd", + "shortnames": [":swimmer_tone3:"], + "category": "activity" + }, + ":person_swimming_tone4:": { + "uc_base": "1f3ca-1f3fe", + "uc_output": "1f3ca-1f3fe", + "uc_match": "1f3ca-1f3fe", + "uc_greedy": "1f3ca-1f3fe", + "shortnames": [":swimmer_tone4:"], + "category": "activity" + }, + ":person_swimming_tone5:": { + "uc_base": "1f3ca-1f3ff", + "uc_output": "1f3ca-1f3ff", + "uc_match": "1f3ca-1f3ff", + "uc_greedy": "1f3ca-1f3ff", + "shortnames": [":swimmer_tone5:"], + "category": "activity" + }, + ":person_tipping_hand_tone1:": { + "uc_base": "1f481-1f3fb", + "uc_output": "1f481-1f3fb", + "uc_match": "1f481-1f3fb", + "uc_greedy": "1f481-1f3fb", + "shortnames": [":information_desk_person_tone1:"], + "category": "people" + }, + ":person_tipping_hand_tone2:": { + "uc_base": "1f481-1f3fc", + "uc_output": "1f481-1f3fc", + "uc_match": "1f481-1f3fc", + "uc_greedy": "1f481-1f3fc", + "shortnames": [":information_desk_person_tone2:"], + "category": "people" + }, + ":person_tipping_hand_tone3:": { + "uc_base": "1f481-1f3fd", + "uc_output": "1f481-1f3fd", + "uc_match": "1f481-1f3fd", + "uc_greedy": "1f481-1f3fd", + "shortnames": [":information_desk_person_tone3:"], + "category": "people" + }, + ":person_tipping_hand_tone4:": { + "uc_base": "1f481-1f3fe", + "uc_output": "1f481-1f3fe", + "uc_match": "1f481-1f3fe", + "uc_greedy": "1f481-1f3fe", + "shortnames": [":information_desk_person_tone4:"], + "category": "people" + }, + ":person_tipping_hand_tone5:": { + "uc_base": "1f481-1f3ff", + "uc_output": "1f481-1f3ff", + "uc_match": "1f481-1f3ff", + "uc_greedy": "1f481-1f3ff", + "shortnames": [":information_desk_person_tone5:"], + "category": "people" + }, + ":person_walking_tone1:": { + "uc_base": "1f6b6-1f3fb", + "uc_output": "1f6b6-1f3fb", + "uc_match": "1f6b6-1f3fb", + "uc_greedy": "1f6b6-1f3fb", + "shortnames": [":walking_tone1:"], + "category": "people" + }, + ":person_walking_tone2:": { + "uc_base": "1f6b6-1f3fc", + "uc_output": "1f6b6-1f3fc", + "uc_match": "1f6b6-1f3fc", + "uc_greedy": "1f6b6-1f3fc", + "shortnames": [":walking_tone2:"], + "category": "people" + }, + ":person_walking_tone3:": { + "uc_base": "1f6b6-1f3fd", + "uc_output": "1f6b6-1f3fd", + "uc_match": "1f6b6-1f3fd", + "uc_greedy": "1f6b6-1f3fd", + "shortnames": [":walking_tone3:"], + "category": "people" + }, + ":person_walking_tone4:": { + "uc_base": "1f6b6-1f3fe", + "uc_output": "1f6b6-1f3fe", + "uc_match": "1f6b6-1f3fe", + "uc_greedy": "1f6b6-1f3fe", + "shortnames": [":walking_tone4:"], + "category": "people" + }, + ":person_walking_tone5:": { + "uc_base": "1f6b6-1f3ff", + "uc_output": "1f6b6-1f3ff", + "uc_match": "1f6b6-1f3ff", + "uc_greedy": "1f6b6-1f3ff", + "shortnames": [":walking_tone5:"], + "category": "people" + }, + ":person_wearing_turban_tone1:": { + "uc_base": "1f473-1f3fb", + "uc_output": "1f473-1f3fb", + "uc_match": "1f473-1f3fb", + "uc_greedy": "1f473-1f3fb", + "shortnames": [":man_with_turban_tone1:"], + "category": "people" + }, + ":person_wearing_turban_tone2:": { + "uc_base": "1f473-1f3fc", + "uc_output": "1f473-1f3fc", + "uc_match": "1f473-1f3fc", + "uc_greedy": "1f473-1f3fc", + "shortnames": [":man_with_turban_tone2:"], + "category": "people" + }, + ":person_wearing_turban_tone3:": { + "uc_base": "1f473-1f3fd", + "uc_output": "1f473-1f3fd", + "uc_match": "1f473-1f3fd", + "uc_greedy": "1f473-1f3fd", + "shortnames": [":man_with_turban_tone3:"], + "category": "people" + }, + ":person_wearing_turban_tone4:": { + "uc_base": "1f473-1f3fe", + "uc_output": "1f473-1f3fe", + "uc_match": "1f473-1f3fe", + "uc_greedy": "1f473-1f3fe", + "shortnames": [":man_with_turban_tone4:"], + "category": "people" + }, + ":person_wearing_turban_tone5:": { + "uc_base": "1f473-1f3ff", + "uc_output": "1f473-1f3ff", + "uc_match": "1f473-1f3ff", + "uc_greedy": "1f473-1f3ff", + "shortnames": [":man_with_turban_tone5:"], + "category": "people" + }, + ":point_down_tone1:": { + "uc_base": "1f447-1f3fb", + "uc_output": "1f447-1f3fb", + "uc_match": "1f447-1f3fb", + "uc_greedy": "1f447-1f3fb", + "shortnames": [], + "category": "people" + }, + ":point_down_tone2:": { + "uc_base": "1f447-1f3fc", + "uc_output": "1f447-1f3fc", + "uc_match": "1f447-1f3fc", + "uc_greedy": "1f447-1f3fc", + "shortnames": [], + "category": "people" + }, + ":point_down_tone3:": { + "uc_base": "1f447-1f3fd", + "uc_output": "1f447-1f3fd", + "uc_match": "1f447-1f3fd", + "uc_greedy": "1f447-1f3fd", + "shortnames": [], + "category": "people" + }, + ":point_down_tone4:": { + "uc_base": "1f447-1f3fe", + "uc_output": "1f447-1f3fe", + "uc_match": "1f447-1f3fe", + "uc_greedy": "1f447-1f3fe", + "shortnames": [], + "category": "people" + }, + ":point_down_tone5:": { + "uc_base": "1f447-1f3ff", + "uc_output": "1f447-1f3ff", + "uc_match": "1f447-1f3ff", + "uc_greedy": "1f447-1f3ff", + "shortnames": [], + "category": "people" + }, + ":point_left_tone1:": { + "uc_base": "1f448-1f3fb", + "uc_output": "1f448-1f3fb", + "uc_match": "1f448-1f3fb", + "uc_greedy": "1f448-1f3fb", + "shortnames": [], + "category": "people" + }, + ":point_left_tone2:": { + "uc_base": "1f448-1f3fc", + "uc_output": "1f448-1f3fc", + "uc_match": "1f448-1f3fc", + "uc_greedy": "1f448-1f3fc", + "shortnames": [], + "category": "people" + }, + ":point_left_tone3:": { + "uc_base": "1f448-1f3fd", + "uc_output": "1f448-1f3fd", + "uc_match": "1f448-1f3fd", + "uc_greedy": "1f448-1f3fd", + "shortnames": [], + "category": "people" + }, + ":point_left_tone4:": { + "uc_base": "1f448-1f3fe", + "uc_output": "1f448-1f3fe", + "uc_match": "1f448-1f3fe", + "uc_greedy": "1f448-1f3fe", + "shortnames": [], + "category": "people" + }, + ":point_left_tone5:": { + "uc_base": "1f448-1f3ff", + "uc_output": "1f448-1f3ff", + "uc_match": "1f448-1f3ff", + "uc_greedy": "1f448-1f3ff", + "shortnames": [], + "category": "people" + }, + ":point_right_tone1:": { + "uc_base": "1f449-1f3fb", + "uc_output": "1f449-1f3fb", + "uc_match": "1f449-1f3fb", + "uc_greedy": "1f449-1f3fb", + "shortnames": [], + "category": "people" + }, + ":point_right_tone2:": { + "uc_base": "1f449-1f3fc", + "uc_output": "1f449-1f3fc", + "uc_match": "1f449-1f3fc", + "uc_greedy": "1f449-1f3fc", + "shortnames": [], + "category": "people" + }, + ":point_right_tone3:": { + "uc_base": "1f449-1f3fd", + "uc_output": "1f449-1f3fd", + "uc_match": "1f449-1f3fd", + "uc_greedy": "1f449-1f3fd", + "shortnames": [], + "category": "people" + }, + ":point_right_tone4:": { + "uc_base": "1f449-1f3fe", + "uc_output": "1f449-1f3fe", + "uc_match": "1f449-1f3fe", + "uc_greedy": "1f449-1f3fe", + "shortnames": [], + "category": "people" + }, + ":point_right_tone5:": { + "uc_base": "1f449-1f3ff", + "uc_output": "1f449-1f3ff", + "uc_match": "1f449-1f3ff", + "uc_greedy": "1f449-1f3ff", + "shortnames": [], + "category": "people" + }, + ":point_up_2_tone1:": { + "uc_base": "1f446-1f3fb", + "uc_output": "1f446-1f3fb", + "uc_match": "1f446-1f3fb", + "uc_greedy": "1f446-1f3fb", + "shortnames": [], + "category": "people" + }, + ":point_up_2_tone2:": { + "uc_base": "1f446-1f3fc", + "uc_output": "1f446-1f3fc", + "uc_match": "1f446-1f3fc", + "uc_greedy": "1f446-1f3fc", + "shortnames": [], + "category": "people" + }, + ":point_up_2_tone3:": { + "uc_base": "1f446-1f3fd", + "uc_output": "1f446-1f3fd", + "uc_match": "1f446-1f3fd", + "uc_greedy": "1f446-1f3fd", + "shortnames": [], + "category": "people" + }, + ":point_up_2_tone4:": { + "uc_base": "1f446-1f3fe", + "uc_output": "1f446-1f3fe", + "uc_match": "1f446-1f3fe", + "uc_greedy": "1f446-1f3fe", + "shortnames": [], + "category": "people" + }, + ":point_up_2_tone5:": { + "uc_base": "1f446-1f3ff", + "uc_output": "1f446-1f3ff", + "uc_match": "1f446-1f3ff", + "uc_greedy": "1f446-1f3ff", + "shortnames": [], + "category": "people" + }, + ":police_officer_tone1:": { + "uc_base": "1f46e-1f3fb", + "uc_output": "1f46e-1f3fb", + "uc_match": "1f46e-1f3fb", + "uc_greedy": "1f46e-1f3fb", + "shortnames": [":cop_tone1:"], + "category": "people" + }, + ":police_officer_tone2:": { + "uc_base": "1f46e-1f3fc", + "uc_output": "1f46e-1f3fc", + "uc_match": "1f46e-1f3fc", + "uc_greedy": "1f46e-1f3fc", + "shortnames": [":cop_tone2:"], + "category": "people" + }, + ":police_officer_tone3:": { + "uc_base": "1f46e-1f3fd", + "uc_output": "1f46e-1f3fd", + "uc_match": "1f46e-1f3fd", + "uc_greedy": "1f46e-1f3fd", + "shortnames": [":cop_tone3:"], + "category": "people" + }, + ":police_officer_tone4:": { + "uc_base": "1f46e-1f3fe", + "uc_output": "1f46e-1f3fe", + "uc_match": "1f46e-1f3fe", + "uc_greedy": "1f46e-1f3fe", + "shortnames": [":cop_tone4:"], + "category": "people" + }, + ":police_officer_tone5:": { + "uc_base": "1f46e-1f3ff", + "uc_output": "1f46e-1f3ff", + "uc_match": "1f46e-1f3ff", + "uc_greedy": "1f46e-1f3ff", + "shortnames": [":cop_tone5:"], + "category": "people" + }, + ":pray_tone1:": { + "uc_base": "1f64f-1f3fb", + "uc_output": "1f64f-1f3fb", + "uc_match": "1f64f-1f3fb", + "uc_greedy": "1f64f-1f3fb", + "shortnames": [], + "category": "people" + }, + ":pray_tone2:": { + "uc_base": "1f64f-1f3fc", + "uc_output": "1f64f-1f3fc", + "uc_match": "1f64f-1f3fc", + "uc_greedy": "1f64f-1f3fc", + "shortnames": [], + "category": "people" + }, + ":pray_tone3:": { + "uc_base": "1f64f-1f3fd", + "uc_output": "1f64f-1f3fd", + "uc_match": "1f64f-1f3fd", + "uc_greedy": "1f64f-1f3fd", + "shortnames": [], + "category": "people" + }, + ":pray_tone4:": { + "uc_base": "1f64f-1f3fe", + "uc_output": "1f64f-1f3fe", + "uc_match": "1f64f-1f3fe", + "uc_greedy": "1f64f-1f3fe", + "shortnames": [], + "category": "people" + }, + ":pray_tone5:": { + "uc_base": "1f64f-1f3ff", + "uc_output": "1f64f-1f3ff", + "uc_match": "1f64f-1f3ff", + "uc_greedy": "1f64f-1f3ff", + "shortnames": [], + "category": "people" + }, + ":pregnant_woman_tone1:": { + "uc_base": "1f930-1f3fb", + "uc_output": "1f930-1f3fb", + "uc_match": "1f930-1f3fb", + "uc_greedy": "1f930-1f3fb", + "shortnames": [":expecting_woman_tone1:"], + "category": "people" + }, + ":pregnant_woman_tone2:": { + "uc_base": "1f930-1f3fc", + "uc_output": "1f930-1f3fc", + "uc_match": "1f930-1f3fc", + "uc_greedy": "1f930-1f3fc", + "shortnames": [":expecting_woman_tone2:"], + "category": "people" + }, + ":pregnant_woman_tone3:": { + "uc_base": "1f930-1f3fd", + "uc_output": "1f930-1f3fd", + "uc_match": "1f930-1f3fd", + "uc_greedy": "1f930-1f3fd", + "shortnames": [":expecting_woman_tone3:"], + "category": "people" + }, + ":pregnant_woman_tone4:": { + "uc_base": "1f930-1f3fe", + "uc_output": "1f930-1f3fe", + "uc_match": "1f930-1f3fe", + "uc_greedy": "1f930-1f3fe", + "shortnames": [":expecting_woman_tone4:"], + "category": "people" + }, + ":pregnant_woman_tone5:": { + "uc_base": "1f930-1f3ff", + "uc_output": "1f930-1f3ff", + "uc_match": "1f930-1f3ff", + "uc_greedy": "1f930-1f3ff", + "shortnames": [":expecting_woman_tone5:"], + "category": "people" + }, + ":prince_tone1:": { + "uc_base": "1f934-1f3fb", + "uc_output": "1f934-1f3fb", + "uc_match": "1f934-1f3fb", + "uc_greedy": "1f934-1f3fb", + "shortnames": [], + "category": "people" + }, + ":prince_tone2:": { + "uc_base": "1f934-1f3fc", + "uc_output": "1f934-1f3fc", + "uc_match": "1f934-1f3fc", + "uc_greedy": "1f934-1f3fc", + "shortnames": [], + "category": "people" + }, + ":prince_tone3:": { + "uc_base": "1f934-1f3fd", + "uc_output": "1f934-1f3fd", + "uc_match": "1f934-1f3fd", + "uc_greedy": "1f934-1f3fd", + "shortnames": [], + "category": "people" + }, + ":prince_tone4:": { + "uc_base": "1f934-1f3fe", + "uc_output": "1f934-1f3fe", + "uc_match": "1f934-1f3fe", + "uc_greedy": "1f934-1f3fe", + "shortnames": [], + "category": "people" + }, + ":prince_tone5:": { + "uc_base": "1f934-1f3ff", + "uc_output": "1f934-1f3ff", + "uc_match": "1f934-1f3ff", + "uc_greedy": "1f934-1f3ff", + "shortnames": [], + "category": "people" + }, + ":princess_tone1:": { + "uc_base": "1f478-1f3fb", + "uc_output": "1f478-1f3fb", + "uc_match": "1f478-1f3fb", + "uc_greedy": "1f478-1f3fb", + "shortnames": [], + "category": "people" + }, + ":princess_tone2:": { + "uc_base": "1f478-1f3fc", + "uc_output": "1f478-1f3fc", + "uc_match": "1f478-1f3fc", + "uc_greedy": "1f478-1f3fc", + "shortnames": [], + "category": "people" + }, + ":princess_tone3:": { + "uc_base": "1f478-1f3fd", + "uc_output": "1f478-1f3fd", + "uc_match": "1f478-1f3fd", + "uc_greedy": "1f478-1f3fd", + "shortnames": [], + "category": "people" + }, + ":princess_tone4:": { + "uc_base": "1f478-1f3fe", + "uc_output": "1f478-1f3fe", + "uc_match": "1f478-1f3fe", + "uc_greedy": "1f478-1f3fe", + "shortnames": [], + "category": "people" + }, + ":princess_tone5:": { + "uc_base": "1f478-1f3ff", + "uc_output": "1f478-1f3ff", + "uc_match": "1f478-1f3ff", + "uc_greedy": "1f478-1f3ff", + "shortnames": [], + "category": "people" + }, + ":punch_tone1:": { + "uc_base": "1f44a-1f3fb", + "uc_output": "1f44a-1f3fb", + "uc_match": "1f44a-1f3fb", + "uc_greedy": "1f44a-1f3fb", + "shortnames": [], + "category": "people" + }, + ":punch_tone2:": { + "uc_base": "1f44a-1f3fc", + "uc_output": "1f44a-1f3fc", + "uc_match": "1f44a-1f3fc", + "uc_greedy": "1f44a-1f3fc", + "shortnames": [], + "category": "people" + }, + ":punch_tone3:": { + "uc_base": "1f44a-1f3fd", + "uc_output": "1f44a-1f3fd", + "uc_match": "1f44a-1f3fd", + "uc_greedy": "1f44a-1f3fd", + "shortnames": [], + "category": "people" + }, + ":punch_tone4:": { + "uc_base": "1f44a-1f3fe", + "uc_output": "1f44a-1f3fe", + "uc_match": "1f44a-1f3fe", + "uc_greedy": "1f44a-1f3fe", + "shortnames": [], + "category": "people" + }, + ":punch_tone5:": { + "uc_base": "1f44a-1f3ff", + "uc_output": "1f44a-1f3ff", + "uc_match": "1f44a-1f3ff", + "uc_greedy": "1f44a-1f3ff", + "shortnames": [], + "category": "people" + }, + ":raised_back_of_hand_tone1:": { + "uc_base": "1f91a-1f3fb", + "uc_output": "1f91a-1f3fb", + "uc_match": "1f91a-1f3fb", + "uc_greedy": "1f91a-1f3fb", + "shortnames": [":back_of_hand_tone1:"], + "category": "people" + }, + ":raised_back_of_hand_tone2:": { + "uc_base": "1f91a-1f3fc", + "uc_output": "1f91a-1f3fc", + "uc_match": "1f91a-1f3fc", + "uc_greedy": "1f91a-1f3fc", + "shortnames": [":back_of_hand_tone2:"], + "category": "people" + }, + ":raised_back_of_hand_tone3:": { + "uc_base": "1f91a-1f3fd", + "uc_output": "1f91a-1f3fd", + "uc_match": "1f91a-1f3fd", + "uc_greedy": "1f91a-1f3fd", + "shortnames": [":back_of_hand_tone3:"], + "category": "people" + }, + ":raised_back_of_hand_tone4:": { + "uc_base": "1f91a-1f3fe", + "uc_output": "1f91a-1f3fe", + "uc_match": "1f91a-1f3fe", + "uc_greedy": "1f91a-1f3fe", + "shortnames": [":back_of_hand_tone4:"], + "category": "people" + }, + ":raised_back_of_hand_tone5:": { + "uc_base": "1f91a-1f3ff", + "uc_output": "1f91a-1f3ff", + "uc_match": "1f91a-1f3ff", + "uc_greedy": "1f91a-1f3ff", + "shortnames": [":back_of_hand_tone5:"], + "category": "people" + }, + ":raised_hands_tone1:": { + "uc_base": "1f64c-1f3fb", + "uc_output": "1f64c-1f3fb", + "uc_match": "1f64c-1f3fb", + "uc_greedy": "1f64c-1f3fb", + "shortnames": [], + "category": "people" + }, + ":raised_hands_tone2:": { + "uc_base": "1f64c-1f3fc", + "uc_output": "1f64c-1f3fc", + "uc_match": "1f64c-1f3fc", + "uc_greedy": "1f64c-1f3fc", + "shortnames": [], + "category": "people" + }, + ":raised_hands_tone3:": { + "uc_base": "1f64c-1f3fd", + "uc_output": "1f64c-1f3fd", + "uc_match": "1f64c-1f3fd", + "uc_greedy": "1f64c-1f3fd", + "shortnames": [], + "category": "people" + }, + ":raised_hands_tone4:": { + "uc_base": "1f64c-1f3fe", + "uc_output": "1f64c-1f3fe", + "uc_match": "1f64c-1f3fe", + "uc_greedy": "1f64c-1f3fe", + "shortnames": [], + "category": "people" + }, + ":raised_hands_tone5:": { + "uc_base": "1f64c-1f3ff", + "uc_output": "1f64c-1f3ff", + "uc_match": "1f64c-1f3ff", + "uc_greedy": "1f64c-1f3ff", + "shortnames": [], + "category": "people" + }, + ":right_facing_fist_tone1:": { + "uc_base": "1f91c-1f3fb", + "uc_output": "1f91c-1f3fb", + "uc_match": "1f91c-1f3fb", + "uc_greedy": "1f91c-1f3fb", + "shortnames": [":right_fist_tone1:"], + "category": "people" + }, + ":right_facing_fist_tone2:": { + "uc_base": "1f91c-1f3fc", + "uc_output": "1f91c-1f3fc", + "uc_match": "1f91c-1f3fc", + "uc_greedy": "1f91c-1f3fc", + "shortnames": [":right_fist_tone2:"], + "category": "people" + }, + ":right_facing_fist_tone3:": { + "uc_base": "1f91c-1f3fd", + "uc_output": "1f91c-1f3fd", + "uc_match": "1f91c-1f3fd", + "uc_greedy": "1f91c-1f3fd", + "shortnames": [":right_fist_tone3:"], + "category": "people" + }, + ":right_facing_fist_tone4:": { + "uc_base": "1f91c-1f3fe", + "uc_output": "1f91c-1f3fe", + "uc_match": "1f91c-1f3fe", + "uc_greedy": "1f91c-1f3fe", + "shortnames": [":right_fist_tone4:"], + "category": "people" + }, + ":right_facing_fist_tone5:": { + "uc_base": "1f91c-1f3ff", + "uc_output": "1f91c-1f3ff", + "uc_match": "1f91c-1f3ff", + "uc_greedy": "1f91c-1f3ff", + "shortnames": [":right_fist_tone5:"], + "category": "people" + }, + ":santa_tone1:": { + "uc_base": "1f385-1f3fb", + "uc_output": "1f385-1f3fb", + "uc_match": "1f385-1f3fb", + "uc_greedy": "1f385-1f3fb", + "shortnames": [], + "category": "people" + }, + ":santa_tone2:": { + "uc_base": "1f385-1f3fc", + "uc_output": "1f385-1f3fc", + "uc_match": "1f385-1f3fc", + "uc_greedy": "1f385-1f3fc", + "shortnames": [], + "category": "people" + }, + ":santa_tone3:": { + "uc_base": "1f385-1f3fd", + "uc_output": "1f385-1f3fd", + "uc_match": "1f385-1f3fd", + "uc_greedy": "1f385-1f3fd", + "shortnames": [], + "category": "people" + }, + ":santa_tone4:": { + "uc_base": "1f385-1f3fe", + "uc_output": "1f385-1f3fe", + "uc_match": "1f385-1f3fe", + "uc_greedy": "1f385-1f3fe", + "shortnames": [], + "category": "people" + }, + ":santa_tone5:": { + "uc_base": "1f385-1f3ff", + "uc_output": "1f385-1f3ff", + "uc_match": "1f385-1f3ff", + "uc_greedy": "1f385-1f3ff", + "shortnames": [], + "category": "people" + }, + ":selfie_tone1:": { + "uc_base": "1f933-1f3fb", + "uc_output": "1f933-1f3fb", + "uc_match": "1f933-1f3fb", + "uc_greedy": "1f933-1f3fb", + "shortnames": [], + "category": "people" + }, + ":selfie_tone2:": { + "uc_base": "1f933-1f3fc", + "uc_output": "1f933-1f3fc", + "uc_match": "1f933-1f3fc", + "uc_greedy": "1f933-1f3fc", + "shortnames": [], + "category": "people" + }, + ":selfie_tone3:": { + "uc_base": "1f933-1f3fd", + "uc_output": "1f933-1f3fd", + "uc_match": "1f933-1f3fd", + "uc_greedy": "1f933-1f3fd", + "shortnames": [], + "category": "people" + }, + ":selfie_tone4:": { + "uc_base": "1f933-1f3fe", + "uc_output": "1f933-1f3fe", + "uc_match": "1f933-1f3fe", + "uc_greedy": "1f933-1f3fe", + "shortnames": [], + "category": "people" + }, + ":selfie_tone5:": { + "uc_base": "1f933-1f3ff", + "uc_output": "1f933-1f3ff", + "uc_match": "1f933-1f3ff", + "uc_greedy": "1f933-1f3ff", + "shortnames": [], + "category": "people" + }, + ":snowboarder_tone1:": { + "uc_base": "1f3c2-1f3fb", + "uc_output": "1f3c2-1f3fb", + "uc_match": "1f3c2-1f3fb", + "uc_greedy": "1f3c2-1f3fb", + "shortnames": [":snowboarder_light_skin_tone:"], + "category": "activity" + }, + ":snowboarder_tone2:": { + "uc_base": "1f3c2-1f3fc", + "uc_output": "1f3c2-1f3fc", + "uc_match": "1f3c2-1f3fc", + "uc_greedy": "1f3c2-1f3fc", + "shortnames": [":snowboarder_medium_light_skin_tone:"], + "category": "activity" + }, + ":snowboarder_tone3:": { + "uc_base": "1f3c2-1f3fd", + "uc_output": "1f3c2-1f3fd", + "uc_match": "1f3c2-1f3fd", + "uc_greedy": "1f3c2-1f3fd", + "shortnames": [":snowboarder_medium_skin_tone:"], + "category": "activity" + }, + ":snowboarder_tone4:": { + "uc_base": "1f3c2-1f3fe", + "uc_output": "1f3c2-1f3fe", + "uc_match": "1f3c2-1f3fe", + "uc_greedy": "1f3c2-1f3fe", + "shortnames": [":snowboarder_medium_dark_skin_tone:"], + "category": "activity" + }, + ":snowboarder_tone5:": { + "uc_base": "1f3c2-1f3ff", + "uc_output": "1f3c2-1f3ff", + "uc_match": "1f3c2-1f3ff", + "uc_greedy": "1f3c2-1f3ff", + "shortnames": [":snowboarder_dark_skin_tone:"], + "category": "activity" + }, + ":thumbsdown_tone1:": { + "uc_base": "1f44e-1f3fb", + "uc_output": "1f44e-1f3fb", + "uc_match": "1f44e-1f3fb", + "uc_greedy": "1f44e-1f3fb", + "shortnames": [":-1_tone1:", ":thumbdown_tone1:"], + "category": "people" + }, + ":thumbsdown_tone2:": { + "uc_base": "1f44e-1f3fc", + "uc_output": "1f44e-1f3fc", + "uc_match": "1f44e-1f3fc", + "uc_greedy": "1f44e-1f3fc", + "shortnames": [":-1_tone2:", ":thumbdown_tone2:"], + "category": "people" + }, + ":thumbsdown_tone3:": { + "uc_base": "1f44e-1f3fd", + "uc_output": "1f44e-1f3fd", + "uc_match": "1f44e-1f3fd", + "uc_greedy": "1f44e-1f3fd", + "shortnames": [":-1_tone3:", ":thumbdown_tone3:"], + "category": "people" + }, + ":thumbsdown_tone4:": { + "uc_base": "1f44e-1f3fe", + "uc_output": "1f44e-1f3fe", + "uc_match": "1f44e-1f3fe", + "uc_greedy": "1f44e-1f3fe", + "shortnames": [":-1_tone4:", ":thumbdown_tone4:"], + "category": "people" + }, + ":thumbsdown_tone5:": { + "uc_base": "1f44e-1f3ff", + "uc_output": "1f44e-1f3ff", + "uc_match": "1f44e-1f3ff", + "uc_greedy": "1f44e-1f3ff", + "shortnames": [":-1_tone5:", ":thumbdown_tone5:"], + "category": "people" + }, + ":thumbsup_tone1:": { + "uc_base": "1f44d-1f3fb", + "uc_output": "1f44d-1f3fb", + "uc_match": "1f44d-1f3fb", + "uc_greedy": "1f44d-1f3fb", + "shortnames": [":+1_tone1:", ":thumbup_tone1:"], + "category": "people" + }, + ":thumbsup_tone2:": { + "uc_base": "1f44d-1f3fc", + "uc_output": "1f44d-1f3fc", + "uc_match": "1f44d-1f3fc", + "uc_greedy": "1f44d-1f3fc", + "shortnames": [":+1_tone2:", ":thumbup_tone2:"], + "category": "people" + }, + ":thumbsup_tone3:": { + "uc_base": "1f44d-1f3fd", + "uc_output": "1f44d-1f3fd", + "uc_match": "1f44d-1f3fd", + "uc_greedy": "1f44d-1f3fd", + "shortnames": [":+1_tone3:", ":thumbup_tone3:"], + "category": "people" + }, + ":thumbsup_tone4:": { + "uc_base": "1f44d-1f3fe", + "uc_output": "1f44d-1f3fe", + "uc_match": "1f44d-1f3fe", + "uc_greedy": "1f44d-1f3fe", + "shortnames": [":+1_tone4:", ":thumbup_tone4:"], + "category": "people" + }, + ":thumbsup_tone5:": { + "uc_base": "1f44d-1f3ff", + "uc_output": "1f44d-1f3ff", + "uc_match": "1f44d-1f3ff", + "uc_greedy": "1f44d-1f3ff", + "shortnames": [":+1_tone5:", ":thumbup_tone5:"], + "category": "people" + }, + ":united_nations:": { + "uc_base": "1f1fa-1f1f3", + "uc_output": "1f1fa-1f1f3", + "uc_match": "1f1fa-1f1f3", + "uc_greedy": "1f1fa-1f1f3", + "shortnames": [], + "category": "flags" + }, + ":vampire_tone1:": { + "uc_base": "1f9db-1f3fb", + "uc_output": "1f9db-1f3fb", + "uc_match": "1f9db-1f3fb", + "uc_greedy": "1f9db-1f3fb", + "shortnames": [":vampire_light_skin_tone:"], + "category": "people" + }, + ":vampire_tone2:": { + "uc_base": "1f9db-1f3fc", + "uc_output": "1f9db-1f3fc", + "uc_match": "1f9db-1f3fc", + "uc_greedy": "1f9db-1f3fc", + "shortnames": [":vampire_medium_light_skin_tone:"], + "category": "people" + }, + ":vampire_tone3:": { + "uc_base": "1f9db-1f3fd", + "uc_output": "1f9db-1f3fd", + "uc_match": "1f9db-1f3fd", + "uc_greedy": "1f9db-1f3fd", + "shortnames": [":vampire_medium_skin_tone:"], + "category": "people" + }, + ":vampire_tone4:": { + "uc_base": "1f9db-1f3fe", + "uc_output": "1f9db-1f3fe", + "uc_match": "1f9db-1f3fe", + "uc_greedy": "1f9db-1f3fe", + "shortnames": [":vampire_medium_dark_skin_tone:"], + "category": "people" + }, + ":vampire_tone5:": { + "uc_base": "1f9db-1f3ff", + "uc_output": "1f9db-1f3ff", + "uc_match": "1f9db-1f3ff", + "uc_greedy": "1f9db-1f3ff", + "shortnames": [":vampire_dark_skin_tone:"], + "category": "people" + }, + ":vulcan_tone1:": { + "uc_base": "1f596-1f3fb", + "uc_output": "1f596-1f3fb", + "uc_match": "1f596-1f3fb", + "uc_greedy": "1f596-1f3fb", + "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone1:"], + "category": "people" + }, + ":vulcan_tone2:": { + "uc_base": "1f596-1f3fc", + "uc_output": "1f596-1f3fc", + "uc_match": "1f596-1f3fc", + "uc_greedy": "1f596-1f3fc", + "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone2:"], + "category": "people" + }, + ":vulcan_tone3:": { + "uc_base": "1f596-1f3fd", + "uc_output": "1f596-1f3fd", + "uc_match": "1f596-1f3fd", + "uc_greedy": "1f596-1f3fd", + "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone3:"], + "category": "people" + }, + ":vulcan_tone4:": { + "uc_base": "1f596-1f3fe", + "uc_output": "1f596-1f3fe", + "uc_match": "1f596-1f3fe", + "uc_greedy": "1f596-1f3fe", + "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone4:"], + "category": "people" + }, + ":vulcan_tone5:": { + "uc_base": "1f596-1f3ff", + "uc_output": "1f596-1f3ff", + "uc_match": "1f596-1f3ff", + "uc_greedy": "1f596-1f3ff", + "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers_tone5:"], + "category": "people" + }, + ":wave_tone1:": { + "uc_base": "1f44b-1f3fb", + "uc_output": "1f44b-1f3fb", + "uc_match": "1f44b-1f3fb", + "uc_greedy": "1f44b-1f3fb", + "shortnames": [], + "category": "people" + }, + ":wave_tone2:": { + "uc_base": "1f44b-1f3fc", + "uc_output": "1f44b-1f3fc", + "uc_match": "1f44b-1f3fc", + "uc_greedy": "1f44b-1f3fc", + "shortnames": [], + "category": "people" + }, + ":wave_tone3:": { + "uc_base": "1f44b-1f3fd", + "uc_output": "1f44b-1f3fd", + "uc_match": "1f44b-1f3fd", + "uc_greedy": "1f44b-1f3fd", + "shortnames": [], + "category": "people" + }, + ":wave_tone4:": { + "uc_base": "1f44b-1f3fe", + "uc_output": "1f44b-1f3fe", + "uc_match": "1f44b-1f3fe", + "uc_greedy": "1f44b-1f3fe", + "shortnames": [], + "category": "people" + }, + ":wave_tone5:": { + "uc_base": "1f44b-1f3ff", + "uc_output": "1f44b-1f3ff", + "uc_match": "1f44b-1f3ff", + "uc_greedy": "1f44b-1f3ff", + "shortnames": [], + "category": "people" + }, + ":woman_tone1:": { + "uc_base": "1f469-1f3fb", + "uc_output": "1f469-1f3fb", + "uc_match": "1f469-1f3fb", + "uc_greedy": "1f469-1f3fb", + "shortnames": [], + "category": "people" + }, + ":woman_tone2:": { + "uc_base": "1f469-1f3fc", + "uc_output": "1f469-1f3fc", + "uc_match": "1f469-1f3fc", + "uc_greedy": "1f469-1f3fc", + "shortnames": [], + "category": "people" + }, + ":woman_tone3:": { + "uc_base": "1f469-1f3fd", + "uc_output": "1f469-1f3fd", + "uc_match": "1f469-1f3fd", + "uc_greedy": "1f469-1f3fd", + "shortnames": [], + "category": "people" + }, + ":woman_tone4:": { + "uc_base": "1f469-1f3fe", + "uc_output": "1f469-1f3fe", + "uc_match": "1f469-1f3fe", + "uc_greedy": "1f469-1f3fe", + "shortnames": [], + "category": "people" + }, + ":woman_tone5:": { + "uc_base": "1f469-1f3ff", + "uc_output": "1f469-1f3ff", + "uc_match": "1f469-1f3ff", + "uc_greedy": "1f469-1f3ff", + "shortnames": [], + "category": "people" + }, + ":woman_with_headscarf_tone1:": { + "uc_base": "1f9d5-1f3fb", + "uc_output": "1f9d5-1f3fb", + "uc_match": "1f9d5-1f3fb", + "uc_greedy": "1f9d5-1f3fb", + "shortnames": [":woman_with_headscarf_light_skin_tone:"], + "category": "people" + }, + ":woman_with_headscarf_tone2:": { + "uc_base": "1f9d5-1f3fc", + "uc_output": "1f9d5-1f3fc", + "uc_match": "1f9d5-1f3fc", + "uc_greedy": "1f9d5-1f3fc", + "shortnames": [":woman_with_headscarf_medium_light_skin_tone:"], + "category": "people" + }, + ":woman_with_headscarf_tone3:": { + "uc_base": "1f9d5-1f3fd", + "uc_output": "1f9d5-1f3fd", + "uc_match": "1f9d5-1f3fd", + "uc_greedy": "1f9d5-1f3fd", + "shortnames": [":woman_with_headscarf_medium_skin_tone:"], + "category": "people" + }, + ":woman_with_headscarf_tone4:": { + "uc_base": "1f9d5-1f3fe", + "uc_output": "1f9d5-1f3fe", + "uc_match": "1f9d5-1f3fe", + "uc_greedy": "1f9d5-1f3fe", + "shortnames": [":woman_with_headscarf_medium_dark_skin_tone:"], + "category": "people" + }, + ":woman_with_headscarf_tone5:": { + "uc_base": "1f9d5-1f3ff", + "uc_output": "1f9d5-1f3ff", + "uc_match": "1f9d5-1f3ff", + "uc_greedy": "1f9d5-1f3ff", + "shortnames": [":woman_with_headscarf_dark_skin_tone:"], + "category": "people" + }, + ":fist_tone1:": { + "uc_base": "270a-1f3fb", + "uc_output": "270a-1f3fb", + "uc_match": "270a-1f3fb", + "uc_greedy": "270a-1f3fb", + "shortnames": [], + "category": "people" + }, + ":fist_tone2:": { + "uc_base": "270a-1f3fc", + "uc_output": "270a-1f3fc", + "uc_match": "270a-1f3fc", + "uc_greedy": "270a-1f3fc", + "shortnames": [], + "category": "people" + }, + ":fist_tone3:": { + "uc_base": "270a-1f3fd", + "uc_output": "270a-1f3fd", + "uc_match": "270a-1f3fd", + "uc_greedy": "270a-1f3fd", + "shortnames": [], + "category": "people" + }, + ":fist_tone4:": { + "uc_base": "270a-1f3fe", + "uc_output": "270a-1f3fe", + "uc_match": "270a-1f3fe", + "uc_greedy": "270a-1f3fe", + "shortnames": [], + "category": "people" + }, + ":fist_tone5:": { + "uc_base": "270a-1f3ff", + "uc_output": "270a-1f3ff", + "uc_match": "270a-1f3ff", + "uc_greedy": "270a-1f3ff", + "shortnames": [], + "category": "people" + }, + ":person_bouncing_ball_tone1:": { + "uc_base": "26f9-1f3fb", + "uc_output": "26f9-1f3fb", + "uc_match": "26f9-fe0f-1f3fb", + "uc_greedy": "26f9-fe0f-1f3fb", + "shortnames": [":basketball_player_tone1:", ":person_with_ball_tone1:"], + "category": "activity" + }, + ":person_bouncing_ball_tone2:": { + "uc_base": "26f9-1f3fc", + "uc_output": "26f9-1f3fc", + "uc_match": "26f9-fe0f-1f3fc", + "uc_greedy": "26f9-fe0f-1f3fc", + "shortnames": [":basketball_player_tone2:", ":person_with_ball_tone2:"], + "category": "activity" + }, + ":person_bouncing_ball_tone3:": { + "uc_base": "26f9-1f3fd", + "uc_output": "26f9-1f3fd", + "uc_match": "26f9-fe0f-1f3fd", + "uc_greedy": "26f9-fe0f-1f3fd", + "shortnames": [":basketball_player_tone3:", ":person_with_ball_tone3:"], + "category": "activity" + }, + ":person_bouncing_ball_tone4:": { + "uc_base": "26f9-1f3fe", + "uc_output": "26f9-1f3fe", + "uc_match": "26f9-fe0f-1f3fe", + "uc_greedy": "26f9-fe0f-1f3fe", + "shortnames": [":basketball_player_tone4:", ":person_with_ball_tone4:"], + "category": "activity" + }, + ":person_bouncing_ball_tone5:": { + "uc_base": "26f9-1f3ff", + "uc_output": "26f9-1f3ff", + "uc_match": "26f9-fe0f-1f3ff", + "uc_greedy": "26f9-fe0f-1f3ff", + "shortnames": [":basketball_player_tone5:", ":person_with_ball_tone5:"], + "category": "activity" + }, + ":point_up_tone1:": { + "uc_base": "261d-1f3fb", + "uc_output": "261d-1f3fb", + "uc_match": "261d-fe0f-1f3fb", + "uc_greedy": "261d-fe0f-1f3fb", + "shortnames": [], + "category": "people" + }, + ":point_up_tone2:": { + "uc_base": "261d-1f3fc", + "uc_output": "261d-1f3fc", + "uc_match": "261d-fe0f-1f3fc", + "uc_greedy": "261d-fe0f-1f3fc", + "shortnames": [], + "category": "people" + }, + ":point_up_tone3:": { + "uc_base": "261d-1f3fd", + "uc_output": "261d-1f3fd", + "uc_match": "261d-fe0f-1f3fd", + "uc_greedy": "261d-fe0f-1f3fd", + "shortnames": [], + "category": "people" + }, + ":point_up_tone4:": { + "uc_base": "261d-1f3fe", + "uc_output": "261d-1f3fe", + "uc_match": "261d-fe0f-1f3fe", + "uc_greedy": "261d-fe0f-1f3fe", + "shortnames": [], + "category": "people" + }, + ":point_up_tone5:": { + "uc_base": "261d-1f3ff", + "uc_output": "261d-1f3ff", + "uc_match": "261d-fe0f-1f3ff", + "uc_greedy": "261d-fe0f-1f3ff", + "shortnames": [], + "category": "people" + }, + ":raised_hand_tone1:": { + "uc_base": "270b-1f3fb", + "uc_output": "270b-1f3fb", + "uc_match": "270b-1f3fb", + "uc_greedy": "270b-1f3fb", + "shortnames": [], + "category": "people" + }, + ":raised_hand_tone2:": { + "uc_base": "270b-1f3fc", + "uc_output": "270b-1f3fc", + "uc_match": "270b-1f3fc", + "uc_greedy": "270b-1f3fc", + "shortnames": [], + "category": "people" + }, + ":raised_hand_tone3:": { + "uc_base": "270b-1f3fd", + "uc_output": "270b-1f3fd", + "uc_match": "270b-1f3fd", + "uc_greedy": "270b-1f3fd", + "shortnames": [], + "category": "people" + }, + ":raised_hand_tone4:": { + "uc_base": "270b-1f3fe", + "uc_output": "270b-1f3fe", + "uc_match": "270b-1f3fe", + "uc_greedy": "270b-1f3fe", + "shortnames": [], + "category": "people" + }, + ":raised_hand_tone5:": { + "uc_base": "270b-1f3ff", + "uc_output": "270b-1f3ff", + "uc_match": "270b-1f3ff", + "uc_greedy": "270b-1f3ff", + "shortnames": [], + "category": "people" + }, + ":v_tone1:": { + "uc_base": "270c-1f3fb", + "uc_output": "270c-1f3fb", + "uc_match": "270c-fe0f-1f3fb", + "uc_greedy": "270c-fe0f-1f3fb", + "shortnames": [], + "category": "people" + }, + ":v_tone2:": { + "uc_base": "270c-1f3fc", + "uc_output": "270c-1f3fc", + "uc_match": "270c-fe0f-1f3fc", + "uc_greedy": "270c-fe0f-1f3fc", + "shortnames": [], + "category": "people" + }, + ":v_tone3:": { + "uc_base": "270c-1f3fd", + "uc_output": "270c-1f3fd", + "uc_match": "270c-fe0f-1f3fd", + "uc_greedy": "270c-fe0f-1f3fd", + "shortnames": [], + "category": "people" + }, + ":v_tone4:": { + "uc_base": "270c-1f3fe", + "uc_output": "270c-1f3fe", + "uc_match": "270c-fe0f-1f3fe", + "uc_greedy": "270c-fe0f-1f3fe", + "shortnames": [], + "category": "people" + }, + ":v_tone5:": { + "uc_base": "270c-1f3ff", + "uc_output": "270c-1f3ff", + "uc_match": "270c-fe0f-1f3ff", + "uc_greedy": "270c-fe0f-1f3ff", + "shortnames": [], + "category": "people" + }, + ":writing_hand_tone1:": { + "uc_base": "270d-1f3fb", + "uc_output": "270d-1f3fb", + "uc_match": "270d-fe0f-1f3fb", + "uc_greedy": "270d-fe0f-1f3fb", + "shortnames": [], + "category": "people" + }, + ":writing_hand_tone2:": { + "uc_base": "270d-1f3fc", + "uc_output": "270d-1f3fc", + "uc_match": "270d-fe0f-1f3fc", + "uc_greedy": "270d-fe0f-1f3fc", + "shortnames": [], + "category": "people" + }, + ":writing_hand_tone3:": { + "uc_base": "270d-1f3fd", + "uc_output": "270d-1f3fd", + "uc_match": "270d-fe0f-1f3fd", + "uc_greedy": "270d-fe0f-1f3fd", + "shortnames": [], + "category": "people" + }, + ":writing_hand_tone4:": { + "uc_base": "270d-1f3fe", + "uc_output": "270d-1f3fe", + "uc_match": "270d-fe0f-1f3fe", + "uc_greedy": "270d-fe0f-1f3fe", + "shortnames": [], + "category": "people" + }, + ":writing_hand_tone5:": { + "uc_base": "270d-1f3ff", + "uc_output": "270d-1f3ff", + "uc_match": "270d-fe0f-1f3ff", + "uc_greedy": "270d-fe0f-1f3ff", + "shortnames": [], + "category": "people" + }, + ":100:": { + "uc_base": "1f4af", + "uc_output": "1f4af", + "uc_match": "1f4af", + "uc_greedy": "1f4af", + "shortnames": [], + "category": "symbols" + }, + ":1234:": { + "uc_base": "1f522", + "uc_output": "1f522", + "uc_match": "1f522", + "uc_greedy": "1f522", + "shortnames": [], + "category": "symbols" + }, + ":8ball:": { + "uc_base": "1f3b1", + "uc_output": "1f3b1", + "uc_match": "1f3b1", + "uc_greedy": "1f3b1", + "shortnames": [], + "category": "activity" + }, + ":a:": { + "uc_base": "1f170", + "uc_output": "1f170", + "uc_match": "1f170-fe0f", + "uc_greedy": "1f170-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":ab:": { + "uc_base": "1f18e", + "uc_output": "1f18e", + "uc_match": "1f18e", + "uc_greedy": "1f18e", + "shortnames": [], + "category": "symbols" + }, + ":abc:": { + "uc_base": "1f524", + "uc_output": "1f524", + "uc_match": "1f524", + "uc_greedy": "1f524", + "shortnames": [], + "category": "symbols" + }, + ":abcd:": { + "uc_base": "1f521", + "uc_output": "1f521", + "uc_match": "1f521", + "uc_greedy": "1f521", + "shortnames": [], + "category": "symbols" + }, + ":accept:": { + "uc_base": "1f251", + "uc_output": "1f251", + "uc_match": "1f251", + "uc_greedy": "1f251", + "shortnames": [], + "category": "symbols" + }, + ":adult:": { + "uc_base": "1f9d1", + "uc_output": "1f9d1", + "uc_match": "1f9d1", + "uc_greedy": "1f9d1", + "shortnames": [], + "category": "people" + }, + ":aerial_tramway:": { + "uc_base": "1f6a1", + "uc_output": "1f6a1", + "uc_match": "1f6a1", + "uc_greedy": "1f6a1", + "shortnames": [], + "category": "travel" + }, + ":airplane_arriving:": { + "uc_base": "1f6ec", + "uc_output": "1f6ec", + "uc_match": "1f6ec", + "uc_greedy": "1f6ec", + "shortnames": [], + "category": "travel" + }, + ":airplane_departure:": { + "uc_base": "1f6eb", + "uc_output": "1f6eb", + "uc_match": "1f6eb", + "uc_greedy": "1f6eb", + "shortnames": [], + "category": "travel" + }, + ":airplane_small:": { + "uc_base": "1f6e9", + "uc_output": "1f6e9", + "uc_match": "1f6e9-fe0f", + "uc_greedy": "1f6e9-fe0f", + "shortnames": [":small_airplane:"], + "category": "travel" + }, + ":alien:": { + "uc_base": "1f47d", + "uc_output": "1f47d", + "uc_match": "1f47d-fe0f", + "uc_greedy": "1f47d-fe0f", + "shortnames": [], + "category": "people" + }, + ":ambulance:": { + "uc_base": "1f691", + "uc_output": "1f691", + "uc_match": "1f691-fe0f", + "uc_greedy": "1f691-fe0f", + "shortnames": [], + "category": "travel" + }, + ":amphora:": { + "uc_base": "1f3fa", + "uc_output": "1f3fa", + "uc_match": "1f3fa", + "uc_greedy": "1f3fa", + "shortnames": [], + "category": "objects" + }, + ":angel:": { + "uc_base": "1f47c", + "uc_output": "1f47c", + "uc_match": "1f47c", + "uc_greedy": "1f47c", + "shortnames": [], + "category": "people" + }, + ":anger:": { + "uc_base": "1f4a2", + "uc_output": "1f4a2", + "uc_match": "1f4a2", + "uc_greedy": "1f4a2", + "shortnames": [], + "category": "symbols" + }, + ":anger_right:": { + "uc_base": "1f5ef", + "uc_output": "1f5ef", + "uc_match": "1f5ef-fe0f", + "uc_greedy": "1f5ef-fe0f", + "shortnames": [":right_anger_bubble:"], + "category": "symbols" + }, + ":angry:": { + "uc_base": "1f620", + "uc_output": "1f620", + "uc_match": "1f620", + "uc_greedy": "1f620", + "shortnames": [], + "category": "people" + }, + ":anguished:": { + "uc_base": "1f627", + "uc_output": "1f627", + "uc_match": "1f627", + "uc_greedy": "1f627", + "shortnames": [], + "category": "people" + }, + ":ant:": { + "uc_base": "1f41c", + "uc_output": "1f41c", + "uc_match": "1f41c", + "uc_greedy": "1f41c", + "shortnames": [], + "category": "nature" + }, + ":apple:": { + "uc_base": "1f34e", + "uc_output": "1f34e", + "uc_match": "1f34e", + "uc_greedy": "1f34e", + "shortnames": [], + "category": "food" + }, + ":arrow_down_small:": { + "uc_base": "1f53d", + "uc_output": "1f53d", + "uc_match": "1f53d", + "uc_greedy": "1f53d", + "shortnames": [], + "category": "symbols" + }, + ":arrow_up_small:": { + "uc_base": "1f53c", + "uc_output": "1f53c", + "uc_match": "1f53c", + "uc_greedy": "1f53c", + "shortnames": [], + "category": "symbols" + }, + ":arrows_clockwise:": { + "uc_base": "1f503", + "uc_output": "1f503", + "uc_match": "1f503", + "uc_greedy": "1f503", + "shortnames": [], + "category": "symbols" + }, + ":arrows_counterclockwise:": { + "uc_base": "1f504", + "uc_output": "1f504", + "uc_match": "1f504", + "uc_greedy": "1f504", + "shortnames": [], + "category": "symbols" + }, + ":art:": { + "uc_base": "1f3a8", + "uc_output": "1f3a8", + "uc_match": "1f3a8", + "uc_greedy": "1f3a8", + "shortnames": [], + "category": "activity" + }, + ":articulated_lorry:": { + "uc_base": "1f69b", + "uc_output": "1f69b", + "uc_match": "1f69b", + "uc_greedy": "1f69b", + "shortnames": [], + "category": "travel" + }, + ":astonished:": { + "uc_base": "1f632", + "uc_output": "1f632", + "uc_match": "1f632", + "uc_greedy": "1f632", + "shortnames": [], + "category": "people" + }, + ":athletic_shoe:": { + "uc_base": "1f45f", + "uc_output": "1f45f", + "uc_match": "1f45f", + "uc_greedy": "1f45f", + "shortnames": [], + "category": "people" + }, + ":atm:": { + "uc_base": "1f3e7", + "uc_output": "1f3e7", + "uc_match": "1f3e7", + "uc_greedy": "1f3e7", + "shortnames": [], + "category": "symbols" + }, + ":avocado:": { + "uc_base": "1f951", + "uc_output": "1f951", + "uc_match": "1f951", + "uc_greedy": "1f951", + "shortnames": [], + "category": "food" + }, + ":b:": { + "uc_base": "1f171", + "uc_output": "1f171", + "uc_match": "1f171-fe0f", + "uc_greedy": "1f171-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":baby:": { + "uc_base": "1f476", + "uc_output": "1f476", + "uc_match": "1f476", + "uc_greedy": "1f476", + "shortnames": [], + "category": "people" + }, + ":baby_bottle:": { + "uc_base": "1f37c", + "uc_output": "1f37c", + "uc_match": "1f37c", + "uc_greedy": "1f37c", + "shortnames": [], + "category": "food" + }, + ":baby_chick:": { + "uc_base": "1f424", + "uc_output": "1f424", + "uc_match": "1f424", + "uc_greedy": "1f424", + "shortnames": [], + "category": "nature" + }, + ":baby_symbol:": { + "uc_base": "1f6bc", + "uc_output": "1f6bc", + "uc_match": "1f6bc-fe0f", + "uc_greedy": "1f6bc-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":back:": { + "uc_base": "1f519", + "uc_output": "1f519", + "uc_match": "1f519", + "uc_greedy": "1f519", + "shortnames": [], + "category": "symbols" + }, + ":bacon:": { + "uc_base": "1f953", + "uc_output": "1f953", + "uc_match": "1f953", + "uc_greedy": "1f953", + "shortnames": [], + "category": "food" + }, + ":badminton:": { + "uc_base": "1f3f8", + "uc_output": "1f3f8", + "uc_match": "1f3f8", + "uc_greedy": "1f3f8", + "shortnames": [], + "category": "activity" + }, + ":baggage_claim:": { + "uc_base": "1f6c4", + "uc_output": "1f6c4", + "uc_match": "1f6c4", + "uc_greedy": "1f6c4", + "shortnames": [], + "category": "symbols" + }, + ":balloon:": { + "uc_base": "1f388", + "uc_output": "1f388", + "uc_match": "1f388", + "uc_greedy": "1f388", + "shortnames": [], + "category": "objects" + }, + ":ballot_box:": { + "uc_base": "1f5f3", + "uc_output": "1f5f3", + "uc_match": "1f5f3-fe0f", + "uc_greedy": "1f5f3-fe0f", + "shortnames": [":ballot_box_with_ballot:"], + "category": "objects" + }, + ":bamboo:": { + "uc_base": "1f38d", + "uc_output": "1f38d", + "uc_match": "1f38d", + "uc_greedy": "1f38d", + "shortnames": [], + "category": "nature" + }, + ":banana:": { + "uc_base": "1f34c", + "uc_output": "1f34c", + "uc_match": "1f34c", + "uc_greedy": "1f34c", + "shortnames": [], + "category": "food" + }, + ":bank:": { + "uc_base": "1f3e6", + "uc_output": "1f3e6", + "uc_match": "1f3e6", + "uc_greedy": "1f3e6", + "shortnames": [], + "category": "travel" + }, + ":bar_chart:": { + "uc_base": "1f4ca", + "uc_output": "1f4ca", + "uc_match": "1f4ca", + "uc_greedy": "1f4ca", + "shortnames": [], + "category": "objects" + }, + ":barber:": { + "uc_base": "1f488", + "uc_output": "1f488", + "uc_match": "1f488", + "uc_greedy": "1f488", + "shortnames": [], + "category": "objects" + }, + ":basketball:": { + "uc_base": "1f3c0", + "uc_output": "1f3c0", + "uc_match": "1f3c0", + "uc_greedy": "1f3c0", + "shortnames": [], + "category": "activity" + }, + ":bat:": { + "uc_base": "1f987", + "uc_output": "1f987", + "uc_match": "1f987", + "uc_greedy": "1f987", + "shortnames": [], + "category": "nature" + }, + ":bath:": { + "uc_base": "1f6c0", + "uc_output": "1f6c0", + "uc_match": "1f6c0", + "uc_greedy": "1f6c0", + "shortnames": [], + "category": "objects" + }, + ":bathtub:": { + "uc_base": "1f6c1", + "uc_output": "1f6c1", + "uc_match": "1f6c1", + "uc_greedy": "1f6c1", + "shortnames": [], + "category": "objects" + }, + ":battery:": { + "uc_base": "1f50b", + "uc_output": "1f50b", + "uc_match": "1f50b", + "uc_greedy": "1f50b", + "shortnames": [], + "category": "objects" + }, + ":beach:": { + "uc_base": "1f3d6", + "uc_output": "1f3d6", + "uc_match": "1f3d6-fe0f", + "uc_greedy": "1f3d6-fe0f", + "shortnames": [":beach_with_umbrella:"], + "category": "travel" + }, + ":bear:": { + "uc_base": "1f43b", + "uc_output": "1f43b", + "uc_match": "1f43b", + "uc_greedy": "1f43b", + "shortnames": [], + "category": "nature" + }, + ":bearded_person:": { + "uc_base": "1f9d4", + "uc_output": "1f9d4", + "uc_match": "1f9d4", + "uc_greedy": "1f9d4", + "shortnames": [], + "category": "people" + }, + ":bed:": { + "uc_base": "1f6cf", + "uc_output": "1f6cf", + "uc_match": "1f6cf-fe0f", + "uc_greedy": "1f6cf-fe0f", + "shortnames": [], + "category": "objects" + }, + ":bee:": { + "uc_base": "1f41d", + "uc_output": "1f41d", + "uc_match": "1f41d", + "uc_greedy": "1f41d", + "shortnames": [], + "category": "nature" + }, + ":beer:": { + "uc_base": "1f37a", + "uc_output": "1f37a", + "uc_match": "1f37a", + "uc_greedy": "1f37a", + "shortnames": [], + "category": "food" + }, + ":beers:": { + "uc_base": "1f37b", + "uc_output": "1f37b", + "uc_match": "1f37b", + "uc_greedy": "1f37b", + "shortnames": [], + "category": "food" + }, + ":beetle:": { + "uc_base": "1f41e", + "uc_output": "1f41e", + "uc_match": "1f41e", + "uc_greedy": "1f41e", + "shortnames": [], + "category": "nature" + }, + ":beginner:": { + "uc_base": "1f530", + "uc_output": "1f530", + "uc_match": "1f530", + "uc_greedy": "1f530", + "shortnames": [], + "category": "symbols" + }, + ":bell:": { + "uc_base": "1f514", + "uc_output": "1f514", + "uc_match": "1f514", + "uc_greedy": "1f514", + "shortnames": [], + "category": "symbols" + }, + ":bellhop:": { + "uc_base": "1f6ce", + "uc_output": "1f6ce", + "uc_match": "1f6ce-fe0f", + "uc_greedy": "1f6ce-fe0f", + "shortnames": [":bellhop_bell:"], + "category": "objects" + }, + ":bento:": { + "uc_base": "1f371", + "uc_output": "1f371", + "uc_match": "1f371", + "uc_greedy": "1f371", + "shortnames": [], + "category": "food" + }, + ":bike:": { + "uc_base": "1f6b2", + "uc_output": "1f6b2", + "uc_match": "1f6b2-fe0f", + "uc_greedy": "1f6b2-fe0f", + "shortnames": [], + "category": "travel" + }, + ":bikini:": { + "uc_base": "1f459", + "uc_output": "1f459", + "uc_match": "1f459", + "uc_greedy": "1f459", + "shortnames": [], + "category": "people" + }, + ":billed_cap:": { + "uc_base": "1f9e2", + "uc_output": "1f9e2", + "uc_match": "1f9e2", + "uc_greedy": "1f9e2", + "shortnames": [], + "category": "people" + }, + ":bird:": { + "uc_base": "1f426", + "uc_output": "1f426", + "uc_match": "1f426-fe0f", + "uc_greedy": "1f426-fe0f", + "shortnames": [], + "category": "nature" + }, + ":birthday:": { + "uc_base": "1f382", + "uc_output": "1f382", + "uc_match": "1f382", + "uc_greedy": "1f382", + "shortnames": [], + "category": "food" + }, + ":black_heart:": { + "uc_base": "1f5a4", + "uc_output": "1f5a4", + "uc_match": "1f5a4", + "uc_greedy": "1f5a4", + "shortnames": [], + "category": "symbols" + }, + ":black_joker:": { + "uc_base": "1f0cf", + "uc_output": "1f0cf", + "uc_match": "1f0cf", + "uc_greedy": "1f0cf", + "shortnames": [], + "category": "symbols" + }, + ":black_square_button:": { + "uc_base": "1f532", + "uc_output": "1f532", + "uc_match": "1f532", + "uc_greedy": "1f532", + "shortnames": [], + "category": "symbols" + }, + ":blond_haired_person:": { + "uc_base": "1f471", + "uc_output": "1f471", + "uc_match": "1f471", + "uc_greedy": "1f471", + "shortnames": [":person_with_blond_hair:"], + "category": "people" + }, + ":blossom:": { + "uc_base": "1f33c", + "uc_output": "1f33c", + "uc_match": "1f33c", + "uc_greedy": "1f33c", + "shortnames": [], + "category": "nature" + }, + ":blowfish:": { + "uc_base": "1f421", + "uc_output": "1f421", + "uc_match": "1f421", + "uc_greedy": "1f421", + "shortnames": [], + "category": "nature" + }, + ":blue_book:": { + "uc_base": "1f4d8", + "uc_output": "1f4d8", + "uc_match": "1f4d8", + "uc_greedy": "1f4d8", + "shortnames": [], + "category": "objects" + }, + ":blue_car:": { + "uc_base": "1f699", + "uc_output": "1f699", + "uc_match": "1f699", + "uc_greedy": "1f699", + "shortnames": [], + "category": "travel" + }, + ":blue_circle:": { + "uc_base": "1f535", + "uc_output": "1f535", + "uc_match": "1f535", + "uc_greedy": "1f535", + "shortnames": [], + "category": "symbols" + }, + ":blue_heart:": { + "uc_base": "1f499", + "uc_output": "1f499", + "uc_match": "1f499", + "uc_greedy": "1f499", + "shortnames": [], + "category": "symbols" + }, + ":blush:": { + "uc_base": "1f60a", + "uc_output": "1f60a", + "uc_match": "1f60a", + "uc_greedy": "1f60a", + "shortnames": [], + "category": "people" + }, + ":boar:": { + "uc_base": "1f417", + "uc_output": "1f417", + "uc_match": "1f417", + "uc_greedy": "1f417", + "shortnames": [], + "category": "nature" + }, + ":bomb:": { + "uc_base": "1f4a3", + "uc_output": "1f4a3", + "uc_match": "1f4a3-fe0f", + "uc_greedy": "1f4a3-fe0f", + "shortnames": [], + "category": "objects" + }, + ":book:": { + "uc_base": "1f4d6", + "uc_output": "1f4d6", + "uc_match": "1f4d6", + "uc_greedy": "1f4d6", + "shortnames": [], + "category": "objects" + }, + ":bookmark:": { + "uc_base": "1f516", + "uc_output": "1f516", + "uc_match": "1f516", + "uc_greedy": "1f516", + "shortnames": [], + "category": "objects" + }, + ":bookmark_tabs:": { + "uc_base": "1f4d1", + "uc_output": "1f4d1", + "uc_match": "1f4d1", + "uc_greedy": "1f4d1", + "shortnames": [], + "category": "objects" + }, + ":books:": { + "uc_base": "1f4da", + "uc_output": "1f4da", + "uc_match": "1f4da-fe0f", + "uc_greedy": "1f4da-fe0f", + "shortnames": [], + "category": "objects" + }, + ":boom:": { + "uc_base": "1f4a5", + "uc_output": "1f4a5", + "uc_match": "1f4a5", + "uc_greedy": "1f4a5", + "shortnames": [], + "category": "nature" + }, + ":boot:": { + "uc_base": "1f462", + "uc_output": "1f462", + "uc_match": "1f462", + "uc_greedy": "1f462", + "shortnames": [], + "category": "people" + }, + ":bouquet:": { + "uc_base": "1f490", + "uc_output": "1f490", + "uc_match": "1f490", + "uc_greedy": "1f490", + "shortnames": [], + "category": "nature" + }, + ":bow_and_arrow:": { + "uc_base": "1f3f9", + "uc_output": "1f3f9", + "uc_match": "1f3f9", + "uc_greedy": "1f3f9", + "shortnames": [":archery:"], + "category": "activity" + }, + ":bowl_with_spoon:": { + "uc_base": "1f963", + "uc_output": "1f963", + "uc_match": "1f963", + "uc_greedy": "1f963", + "shortnames": [], + "category": "food" + }, + ":bowling:": { + "uc_base": "1f3b3", + "uc_output": "1f3b3", + "uc_match": "1f3b3", + "uc_greedy": "1f3b3", + "shortnames": [], + "category": "activity" + }, + ":boxing_glove:": { + "uc_base": "1f94a", + "uc_output": "1f94a", + "uc_match": "1f94a", + "uc_greedy": "1f94a", + "shortnames": [":boxing_gloves:"], + "category": "activity" + }, + ":boy:": { + "uc_base": "1f466", + "uc_output": "1f466", + "uc_match": "1f466", + "uc_greedy": "1f466", + "shortnames": [], + "category": "people" + }, + ":brain:": { + "uc_base": "1f9e0", + "uc_output": "1f9e0", + "uc_match": "1f9e0", + "uc_greedy": "1f9e0", + "shortnames": [], + "category": "people" + }, + ":bread:": { + "uc_base": "1f35e", + "uc_output": "1f35e", + "uc_match": "1f35e", + "uc_greedy": "1f35e", + "shortnames": [], + "category": "food" + }, + ":breast_feeding:": { + "uc_base": "1f931", + "uc_output": "1f931", + "uc_match": "1f931", + "uc_greedy": "1f931", + "shortnames": [], + "category": "people" + }, + ":bride_with_veil:": { + "uc_base": "1f470", + "uc_output": "1f470", + "uc_match": "1f470", + "uc_greedy": "1f470", + "shortnames": [], + "category": "people" + }, + ":bridge_at_night:": { + "uc_base": "1f309", + "uc_output": "1f309", + "uc_match": "1f309", + "uc_greedy": "1f309", + "shortnames": [], + "category": "travel" + }, + ":briefcase:": { + "uc_base": "1f4bc", + "uc_output": "1f4bc", + "uc_match": "1f4bc", + "uc_greedy": "1f4bc", + "shortnames": [], + "category": "people" + }, + ":broccoli:": { + "uc_base": "1f966", + "uc_output": "1f966", + "uc_match": "1f966", + "uc_greedy": "1f966", + "shortnames": [], + "category": "food" + }, + ":broken_heart:": { + "uc_base": "1f494", + "uc_output": "1f494", + "uc_match": "1f494", + "uc_greedy": "1f494", + "shortnames": [], + "category": "symbols" + }, + ":bug:": { + "uc_base": "1f41b", + "uc_output": "1f41b", + "uc_match": "1f41b", + "uc_greedy": "1f41b", + "shortnames": [], + "category": "nature" + }, + ":bulb:": { + "uc_base": "1f4a1", + "uc_output": "1f4a1", + "uc_match": "1f4a1", + "uc_greedy": "1f4a1", + "shortnames": [], + "category": "objects" + }, + ":bullettrain_front:": { + "uc_base": "1f685", + "uc_output": "1f685", + "uc_match": "1f685", + "uc_greedy": "1f685", + "shortnames": [], + "category": "travel" + }, + ":bullettrain_side:": { + "uc_base": "1f684", + "uc_output": "1f684", + "uc_match": "1f684", + "uc_greedy": "1f684", + "shortnames": [], + "category": "travel" + }, + ":burrito:": { + "uc_base": "1f32f", + "uc_output": "1f32f", + "uc_match": "1f32f", + "uc_greedy": "1f32f", + "shortnames": [], + "category": "food" + }, + ":bus:": { + "uc_base": "1f68c", + "uc_output": "1f68c", + "uc_match": "1f68c", + "uc_greedy": "1f68c", + "shortnames": [], + "category": "travel" + }, + ":busstop:": { + "uc_base": "1f68f", + "uc_output": "1f68f", + "uc_match": "1f68f", + "uc_greedy": "1f68f", + "shortnames": [], + "category": "travel" + }, + ":bust_in_silhouette:": { + "uc_base": "1f464", + "uc_output": "1f464", + "uc_match": "1f464", + "uc_greedy": "1f464", + "shortnames": [], + "category": "people" + }, + ":busts_in_silhouette:": { + "uc_base": "1f465", + "uc_output": "1f465", + "uc_match": "1f465", + "uc_greedy": "1f465", + "shortnames": [], + "category": "people" + }, + ":butterfly:": { + "uc_base": "1f98b", + "uc_output": "1f98b", + "uc_match": "1f98b", + "uc_greedy": "1f98b", + "shortnames": [], + "category": "nature" + }, + ":cactus:": { + "uc_base": "1f335", + "uc_output": "1f335", + "uc_match": "1f335", + "uc_greedy": "1f335", + "shortnames": [], + "category": "nature" + }, + ":cake:": { + "uc_base": "1f370", + "uc_output": "1f370", + "uc_match": "1f370", + "uc_greedy": "1f370", + "shortnames": [], + "category": "food" + }, + ":calendar:": { + "uc_base": "1f4c6", + "uc_output": "1f4c6", + "uc_match": "1f4c6", + "uc_greedy": "1f4c6", + "shortnames": [], + "category": "objects" + }, + ":calendar_spiral:": { + "uc_base": "1f5d3", + "uc_output": "1f5d3", + "uc_match": "1f5d3-fe0f", + "uc_greedy": "1f5d3-fe0f", + "shortnames": [":spiral_calendar_pad:"], + "category": "objects" + }, + ":call_me:": { + "uc_base": "1f919", + "uc_output": "1f919", + "uc_match": "1f919", + "uc_greedy": "1f919", + "shortnames": [":call_me_hand:"], + "category": "people" + }, + ":calling:": { + "uc_base": "1f4f2", + "uc_output": "1f4f2", + "uc_match": "1f4f2", + "uc_greedy": "1f4f2", + "shortnames": [], + "category": "objects" + }, + ":camel:": { + "uc_base": "1f42b", + "uc_output": "1f42b", + "uc_match": "1f42b", + "uc_greedy": "1f42b", + "shortnames": [], + "category": "nature" + }, + ":camera:": { + "uc_base": "1f4f7", + "uc_output": "1f4f7", + "uc_match": "1f4f7-fe0f", + "uc_greedy": "1f4f7-fe0f", + "shortnames": [], + "category": "objects" + }, + ":camera_with_flash:": { + "uc_base": "1f4f8", + "uc_output": "1f4f8", + "uc_match": "1f4f8", + "uc_greedy": "1f4f8", + "shortnames": [], + "category": "objects" + }, + ":camping:": { + "uc_base": "1f3d5", + "uc_output": "1f3d5", + "uc_match": "1f3d5-fe0f", + "uc_greedy": "1f3d5-fe0f", + "shortnames": [], + "category": "travel" + }, + ":candle:": { + "uc_base": "1f56f", + "uc_output": "1f56f", + "uc_match": "1f56f-fe0f", + "uc_greedy": "1f56f-fe0f", + "shortnames": [], + "category": "objects" + }, + ":candy:": { + "uc_base": "1f36c", + "uc_output": "1f36c", + "uc_match": "1f36c", + "uc_greedy": "1f36c", + "shortnames": [], + "category": "food" + }, + ":canned_food:": { + "uc_base": "1f96b", + "uc_output": "1f96b", + "uc_match": "1f96b", + "uc_greedy": "1f96b", + "shortnames": [], + "category": "food" + }, + ":canoe:": { + "uc_base": "1f6f6", + "uc_output": "1f6f6", + "uc_match": "1f6f6", + "uc_greedy": "1f6f6", + "shortnames": [":kayak:"], + "category": "travel" + }, + ":capital_abcd:": { + "uc_base": "1f520", + "uc_output": "1f520", + "uc_match": "1f520", + "uc_greedy": "1f520", + "shortnames": [], + "category": "symbols" + }, + ":card_box:": { + "uc_base": "1f5c3", + "uc_output": "1f5c3", + "uc_match": "1f5c3-fe0f", + "uc_greedy": "1f5c3-fe0f", + "shortnames": [":card_file_box:"], + "category": "objects" + }, + ":card_index:": { + "uc_base": "1f4c7", + "uc_output": "1f4c7", + "uc_match": "1f4c7", + "uc_greedy": "1f4c7", + "shortnames": [], + "category": "objects" + }, + ":carousel_horse:": { + "uc_base": "1f3a0", + "uc_output": "1f3a0", + "uc_match": "1f3a0", + "uc_greedy": "1f3a0", + "shortnames": [], + "category": "travel" + }, + ":carrot:": { + "uc_base": "1f955", + "uc_output": "1f955", + "uc_match": "1f955", + "uc_greedy": "1f955", + "shortnames": [], + "category": "food" + }, + ":cat2:": { + "uc_base": "1f408", + "uc_output": "1f408", + "uc_match": "1f408-fe0f", + "uc_greedy": "1f408-fe0f", + "shortnames": [], + "category": "nature" + }, + ":cat:": { + "uc_base": "1f431", + "uc_output": "1f431", + "uc_match": "1f431", + "uc_greedy": "1f431", + "shortnames": [], + "category": "nature" + }, + ":cd:": { + "uc_base": "1f4bf", + "uc_output": "1f4bf", + "uc_match": "1f4bf-fe0f", + "uc_greedy": "1f4bf-fe0f", + "shortnames": [], + "category": "objects" + }, + ":champagne:": { + "uc_base": "1f37e", + "uc_output": "1f37e", + "uc_match": "1f37e", + "uc_greedy": "1f37e", + "shortnames": [":bottle_with_popping_cork:"], + "category": "food" + }, + ":champagne_glass:": { + "uc_base": "1f942", + "uc_output": "1f942", + "uc_match": "1f942", + "uc_greedy": "1f942", + "shortnames": [":clinking_glass:"], + "category": "food" + }, + ":chart:": { + "uc_base": "1f4b9", + "uc_output": "1f4b9", + "uc_match": "1f4b9", + "uc_greedy": "1f4b9", + "shortnames": [], + "category": "symbols" + }, + ":chart_with_downwards_trend:": { + "uc_base": "1f4c9", + "uc_output": "1f4c9", + "uc_match": "1f4c9", + "uc_greedy": "1f4c9", + "shortnames": [], + "category": "objects" + }, + ":chart_with_upwards_trend:": { + "uc_base": "1f4c8", + "uc_output": "1f4c8", + "uc_match": "1f4c8", + "uc_greedy": "1f4c8", + "shortnames": [], + "category": "objects" + }, + ":checkered_flag:": { + "uc_base": "1f3c1", + "uc_output": "1f3c1", + "uc_match": "1f3c1", + "uc_greedy": "1f3c1", + "shortnames": [], + "category": "flags" + }, + ":cheese:": { + "uc_base": "1f9c0", + "uc_output": "1f9c0", + "uc_match": "1f9c0", + "uc_greedy": "1f9c0", + "shortnames": [":cheese_wedge:"], + "category": "food" + }, + ":cherries:": { + "uc_base": "1f352", + "uc_output": "1f352", + "uc_match": "1f352", + "uc_greedy": "1f352", + "shortnames": [], + "category": "food" + }, + ":cherry_blossom:": { + "uc_base": "1f338", + "uc_output": "1f338", + "uc_match": "1f338", + "uc_greedy": "1f338", + "shortnames": [], + "category": "nature" + }, + ":chestnut:": { + "uc_base": "1f330", + "uc_output": "1f330", + "uc_match": "1f330", + "uc_greedy": "1f330", + "shortnames": [], + "category": "food" + }, + ":chicken:": { + "uc_base": "1f414", + "uc_output": "1f414", + "uc_match": "1f414", + "uc_greedy": "1f414", + "shortnames": [], + "category": "nature" + }, + ":child:": { + "uc_base": "1f9d2", + "uc_output": "1f9d2", + "uc_match": "1f9d2", + "uc_greedy": "1f9d2", + "shortnames": [], + "category": "people" + }, + ":children_crossing:": { + "uc_base": "1f6b8", + "uc_output": "1f6b8", + "uc_match": "1f6b8", + "uc_greedy": "1f6b8", + "shortnames": [], + "category": "symbols" + }, + ":chipmunk:": { + "uc_base": "1f43f", + "uc_output": "1f43f", + "uc_match": "1f43f-fe0f", + "uc_greedy": "1f43f-fe0f", + "shortnames": [], + "category": "nature" + }, + ":chocolate_bar:": { + "uc_base": "1f36b", + "uc_output": "1f36b", + "uc_match": "1f36b", + "uc_greedy": "1f36b", + "shortnames": [], + "category": "food" + }, + ":chopsticks:": { + "uc_base": "1f962", + "uc_output": "1f962", + "uc_match": "1f962", + "uc_greedy": "1f962", + "shortnames": [], + "category": "food" + }, + ":christmas_tree:": { + "uc_base": "1f384", + "uc_output": "1f384", + "uc_match": "1f384", + "uc_greedy": "1f384", + "shortnames": [], + "category": "nature" + }, + ":cinema:": { + "uc_base": "1f3a6", + "uc_output": "1f3a6", + "uc_match": "1f3a6", + "uc_greedy": "1f3a6", + "shortnames": [], + "category": "symbols" + }, + ":circus_tent:": { + "uc_base": "1f3aa", + "uc_output": "1f3aa", + "uc_match": "1f3aa", + "uc_greedy": "1f3aa", + "shortnames": [], + "category": "activity" + }, + ":city_dusk:": { + "uc_base": "1f306", + "uc_output": "1f306", + "uc_match": "1f306", + "uc_greedy": "1f306", + "shortnames": [], + "category": "travel" + }, + ":city_sunset:": { + "uc_base": "1f307", + "uc_output": "1f307", + "uc_match": "1f307", + "uc_greedy": "1f307", + "shortnames": [":city_sunrise:"], + "category": "travel" + }, + ":cityscape:": { + "uc_base": "1f3d9", + "uc_output": "1f3d9", + "uc_match": "1f3d9-fe0f", + "uc_greedy": "1f3d9-fe0f", + "shortnames": [], + "category": "travel" + }, + ":cl:": { + "uc_base": "1f191", + "uc_output": "1f191", + "uc_match": "1f191", + "uc_greedy": "1f191", + "shortnames": [], + "category": "symbols" + }, + ":clap:": { + "uc_base": "1f44f", + "uc_output": "1f44f", + "uc_match": "1f44f", + "uc_greedy": "1f44f", + "shortnames": [], + "category": "people" + }, + ":clapper:": { + "uc_base": "1f3ac", + "uc_output": "1f3ac", + "uc_match": "1f3ac-fe0f", + "uc_greedy": "1f3ac-fe0f", + "shortnames": [], + "category": "activity" + }, + ":classical_building:": { + "uc_base": "1f3db", + "uc_output": "1f3db", + "uc_match": "1f3db-fe0f", + "uc_greedy": "1f3db-fe0f", + "shortnames": [], + "category": "travel" + }, + ":clipboard:": { + "uc_base": "1f4cb", + "uc_output": "1f4cb", + "uc_match": "1f4cb-fe0f", + "uc_greedy": "1f4cb-fe0f", + "shortnames": [], + "category": "objects" + }, + ":clock1030:": { + "uc_base": "1f565", + "uc_output": "1f565", + "uc_match": "1f565-fe0f", + "uc_greedy": "1f565-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock10:": { + "uc_base": "1f559", + "uc_output": "1f559", + "uc_match": "1f559-fe0f", + "uc_greedy": "1f559-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock1130:": { + "uc_base": "1f566", + "uc_output": "1f566", + "uc_match": "1f566-fe0f", + "uc_greedy": "1f566-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock11:": { + "uc_base": "1f55a", + "uc_output": "1f55a", + "uc_match": "1f55a-fe0f", + "uc_greedy": "1f55a-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock1230:": { + "uc_base": "1f567", + "uc_output": "1f567", + "uc_match": "1f567-fe0f", + "uc_greedy": "1f567-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock12:": { + "uc_base": "1f55b", + "uc_output": "1f55b", + "uc_match": "1f55b-fe0f", + "uc_greedy": "1f55b-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock130:": { + "uc_base": "1f55c", + "uc_output": "1f55c", + "uc_match": "1f55c-fe0f", + "uc_greedy": "1f55c-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock1:": { + "uc_base": "1f550", + "uc_output": "1f550", + "uc_match": "1f550-fe0f", + "uc_greedy": "1f550-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock230:": { + "uc_base": "1f55d", + "uc_output": "1f55d", + "uc_match": "1f55d-fe0f", + "uc_greedy": "1f55d-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock2:": { + "uc_base": "1f551", + "uc_output": "1f551", + "uc_match": "1f551-fe0f", + "uc_greedy": "1f551-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock330:": { + "uc_base": "1f55e", + "uc_output": "1f55e", + "uc_match": "1f55e-fe0f", + "uc_greedy": "1f55e-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock3:": { + "uc_base": "1f552", + "uc_output": "1f552", + "uc_match": "1f552-fe0f", + "uc_greedy": "1f552-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock430:": { + "uc_base": "1f55f", + "uc_output": "1f55f", + "uc_match": "1f55f-fe0f", + "uc_greedy": "1f55f-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock4:": { + "uc_base": "1f553", + "uc_output": "1f553", + "uc_match": "1f553-fe0f", + "uc_greedy": "1f553-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock530:": { + "uc_base": "1f560", + "uc_output": "1f560", + "uc_match": "1f560-fe0f", + "uc_greedy": "1f560-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock5:": { + "uc_base": "1f554", + "uc_output": "1f554", + "uc_match": "1f554-fe0f", + "uc_greedy": "1f554-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock630:": { + "uc_base": "1f561", + "uc_output": "1f561", + "uc_match": "1f561-fe0f", + "uc_greedy": "1f561-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock6:": { + "uc_base": "1f555", + "uc_output": "1f555", + "uc_match": "1f555-fe0f", + "uc_greedy": "1f555-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock730:": { + "uc_base": "1f562", + "uc_output": "1f562", + "uc_match": "1f562-fe0f", + "uc_greedy": "1f562-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock7:": { + "uc_base": "1f556", + "uc_output": "1f556", + "uc_match": "1f556-fe0f", + "uc_greedy": "1f556-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock830:": { + "uc_base": "1f563", + "uc_output": "1f563", + "uc_match": "1f563-fe0f", + "uc_greedy": "1f563-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock8:": { + "uc_base": "1f557", + "uc_output": "1f557", + "uc_match": "1f557-fe0f", + "uc_greedy": "1f557-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock930:": { + "uc_base": "1f564", + "uc_output": "1f564", + "uc_match": "1f564-fe0f", + "uc_greedy": "1f564-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock9:": { + "uc_base": "1f558", + "uc_output": "1f558", + "uc_match": "1f558-fe0f", + "uc_greedy": "1f558-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":clock:": { + "uc_base": "1f570", + "uc_output": "1f570", + "uc_match": "1f570-fe0f", + "uc_greedy": "1f570-fe0f", + "shortnames": [":mantlepiece_clock:"], + "category": "objects" + }, + ":closed_book:": { + "uc_base": "1f4d5", + "uc_output": "1f4d5", + "uc_match": "1f4d5", + "uc_greedy": "1f4d5", + "shortnames": [], + "category": "objects" + }, + ":closed_lock_with_key:": { + "uc_base": "1f510", + "uc_output": "1f510", + "uc_match": "1f510", + "uc_greedy": "1f510", + "shortnames": [], + "category": "objects" + }, + ":closed_umbrella:": { + "uc_base": "1f302", + "uc_output": "1f302", + "uc_match": "1f302", + "uc_greedy": "1f302", + "shortnames": [], + "category": "people" + }, + ":cloud_lightning:": { + "uc_base": "1f329", + "uc_output": "1f329", + "uc_match": "1f329-fe0f", + "uc_greedy": "1f329-fe0f", + "shortnames": [":cloud_with_lightning:"], + "category": "nature" + }, + ":cloud_rain:": { + "uc_base": "1f327", + "uc_output": "1f327", + "uc_match": "1f327-fe0f", + "uc_greedy": "1f327-fe0f", + "shortnames": [":cloud_with_rain:"], + "category": "nature" + }, + ":cloud_snow:": { + "uc_base": "1f328", + "uc_output": "1f328", + "uc_match": "1f328-fe0f", + "uc_greedy": "1f328-fe0f", + "shortnames": [":cloud_with_snow:"], + "category": "nature" + }, + ":cloud_tornado:": { + "uc_base": "1f32a", + "uc_output": "1f32a", + "uc_match": "1f32a-fe0f", + "uc_greedy": "1f32a-fe0f", + "shortnames": [":cloud_with_tornado:"], + "category": "nature" + }, + ":clown:": { + "uc_base": "1f921", + "uc_output": "1f921", + "uc_match": "1f921", + "uc_greedy": "1f921", + "shortnames": [":clown_face:"], + "category": "people" + }, + ":coat:": { + "uc_base": "1f9e5", + "uc_output": "1f9e5", + "uc_match": "1f9e5", + "uc_greedy": "1f9e5", + "shortnames": [], + "category": "people" + }, + ":cocktail:": { + "uc_base": "1f378", + "uc_output": "1f378", + "uc_match": "1f378-fe0f", + "uc_greedy": "1f378-fe0f", + "shortnames": [], + "category": "food" + }, + ":coconut:": { + "uc_base": "1f965", + "uc_output": "1f965", + "uc_match": "1f965", + "uc_greedy": "1f965", + "shortnames": [], + "category": "food" + }, + ":cold_sweat:": { + "uc_base": "1f630", + "uc_output": "1f630", + "uc_match": "1f630", + "uc_greedy": "1f630", + "shortnames": [], + "category": "people" + }, + ":compression:": { + "uc_base": "1f5dc", + "uc_output": "1f5dc", + "uc_match": "1f5dc-fe0f", + "uc_greedy": "1f5dc-fe0f", + "shortnames": [], + "category": "objects" + }, + ":computer:": { + "uc_base": "1f4bb", + "uc_output": "1f4bb", + "uc_match": "1f4bb-fe0f", + "uc_greedy": "1f4bb-fe0f", + "shortnames": [], + "category": "objects" + }, + ":confetti_ball:": { + "uc_base": "1f38a", + "uc_output": "1f38a", + "uc_match": "1f38a", + "uc_greedy": "1f38a", + "shortnames": [], + "category": "objects" + }, + ":confounded:": { + "uc_base": "1f616", + "uc_output": "1f616", + "uc_match": "1f616", + "uc_greedy": "1f616", + "shortnames": [], + "category": "people" + }, + ":confused:": { + "uc_base": "1f615", + "uc_output": "1f615", + "uc_match": "1f615", + "uc_greedy": "1f615", + "shortnames": [], + "category": "people" + }, + ":construction:": { + "uc_base": "1f6a7", + "uc_output": "1f6a7", + "uc_match": "1f6a7", + "uc_greedy": "1f6a7", + "shortnames": [], + "category": "travel" + }, + ":construction_site:": { + "uc_base": "1f3d7", + "uc_output": "1f3d7", + "uc_match": "1f3d7-fe0f", + "uc_greedy": "1f3d7-fe0f", + "shortnames": [":building_construction:"], + "category": "travel" + }, + ":construction_worker:": { + "uc_base": "1f477", + "uc_output": "1f477", + "uc_match": "1f477", + "uc_greedy": "1f477", + "shortnames": [], + "category": "people" + }, + ":control_knobs:": { + "uc_base": "1f39b", + "uc_output": "1f39b", + "uc_match": "1f39b-fe0f", + "uc_greedy": "1f39b-fe0f", + "shortnames": [], + "category": "objects" + }, + ":convenience_store:": { + "uc_base": "1f3ea", + "uc_output": "1f3ea", + "uc_match": "1f3ea", + "uc_greedy": "1f3ea", + "shortnames": [], + "category": "travel" + }, + ":cookie:": { + "uc_base": "1f36a", + "uc_output": "1f36a", + "uc_match": "1f36a", + "uc_greedy": "1f36a", + "shortnames": [], + "category": "food" + }, + ":cooking:": { + "uc_base": "1f373", + "uc_output": "1f373", + "uc_match": "1f373", + "uc_greedy": "1f373", + "shortnames": [], + "category": "food" + }, + ":cool:": { + "uc_base": "1f192", + "uc_output": "1f192", + "uc_match": "1f192", + "uc_greedy": "1f192", + "shortnames": [], + "category": "symbols" + }, + ":corn:": { + "uc_base": "1f33d", + "uc_output": "1f33d", + "uc_match": "1f33d", + "uc_greedy": "1f33d", + "shortnames": [], + "category": "food" + }, + ":couch:": { + "uc_base": "1f6cb", + "uc_output": "1f6cb", + "uc_match": "1f6cb-fe0f", + "uc_greedy": "1f6cb-fe0f", + "shortnames": [":couch_and_lamp:"], + "category": "objects" + }, + ":couple:": { + "uc_base": "1f46b", + "uc_output": "1f46b", + "uc_match": "1f46b", + "uc_greedy": "1f46b", + "shortnames": [], + "category": "people" + }, + ":couple_with_heart:": { + "uc_base": "1f491", + "uc_output": "1f491", + "uc_match": "1f491", + "uc_greedy": "1f491", + "shortnames": [], + "category": "people" + }, + ":couplekiss:": { + "uc_base": "1f48f", + "uc_output": "1f48f", + "uc_match": "1f48f", + "uc_greedy": "1f48f", + "shortnames": [], + "category": "people" + }, + ":cow2:": { + "uc_base": "1f404", + "uc_output": "1f404", + "uc_match": "1f404", + "uc_greedy": "1f404", + "shortnames": [], + "category": "nature" + }, + ":cow:": { + "uc_base": "1f42e", + "uc_output": "1f42e", + "uc_match": "1f42e", + "uc_greedy": "1f42e", + "shortnames": [], + "category": "nature" + }, + ":cowboy:": { + "uc_base": "1f920", + "uc_output": "1f920", + "uc_match": "1f920", + "uc_greedy": "1f920", + "shortnames": [":face_with_cowboy_hat:"], + "category": "people" + }, + ":crab:": { + "uc_base": "1f980", + "uc_output": "1f980", + "uc_match": "1f980", + "uc_greedy": "1f980", + "shortnames": [], + "category": "nature" + }, + ":crayon:": { + "uc_base": "1f58d", + "uc_output": "1f58d", + "uc_match": "1f58d-fe0f", + "uc_greedy": "1f58d-fe0f", + "shortnames": [":lower_left_crayon:"], + "category": "objects" + }, + ":crazy_face:": { + "uc_base": "1f92a", + "uc_output": "1f92a", + "uc_match": "1f92a", + "uc_greedy": "1f92a", + "shortnames": [], + "category": "people" + }, + ":credit_card:": { + "uc_base": "1f4b3", + "uc_output": "1f4b3", + "uc_match": "1f4b3-fe0f", + "uc_greedy": "1f4b3-fe0f", + "shortnames": [], + "category": "objects" + }, + ":crescent_moon:": { + "uc_base": "1f319", + "uc_output": "1f319", + "uc_match": "1f319", + "uc_greedy": "1f319", + "shortnames": [], + "category": "nature" + }, + ":cricket:": { + "uc_base": "1f997", + "uc_output": "1f997", + "uc_match": "1f997", + "uc_greedy": "1f997", + "shortnames": [], + "category": "nature" + }, + ":cricket_game:": { + "uc_base": "1f3cf", + "uc_output": "1f3cf", + "uc_match": "1f3cf", + "uc_greedy": "1f3cf", + "shortnames": [":cricket_bat_ball:"], + "category": "activity" + }, + ":crocodile:": { + "uc_base": "1f40a", + "uc_output": "1f40a", + "uc_match": "1f40a", + "uc_greedy": "1f40a", + "shortnames": [], + "category": "nature" + }, + ":croissant:": { + "uc_base": "1f950", + "uc_output": "1f950", + "uc_match": "1f950", + "uc_greedy": "1f950", + "shortnames": [], + "category": "food" + }, + ":crossed_flags:": { + "uc_base": "1f38c", + "uc_output": "1f38c", + "uc_match": "1f38c", + "uc_greedy": "1f38c", + "shortnames": [], + "category": "flags" + }, + ":crown:": { + "uc_base": "1f451", + "uc_output": "1f451", + "uc_match": "1f451", + "uc_greedy": "1f451", + "shortnames": [], + "category": "people" + }, + ":cruise_ship:": { + "uc_base": "1f6f3", + "uc_output": "1f6f3", + "uc_match": "1f6f3-fe0f", + "uc_greedy": "1f6f3-fe0f", + "shortnames": [":passenger_ship:"], + "category": "travel" + }, + ":cry:": { + "uc_base": "1f622", + "uc_output": "1f622", + "uc_match": "1f622", + "uc_greedy": "1f622", + "shortnames": [], + "category": "people" + }, + ":crying_cat_face:": { + "uc_base": "1f63f", + "uc_output": "1f63f", + "uc_match": "1f63f", + "uc_greedy": "1f63f", + "shortnames": [], + "category": "people" + }, + ":crystal_ball:": { + "uc_base": "1f52e", + "uc_output": "1f52e", + "uc_match": "1f52e", + "uc_greedy": "1f52e", + "shortnames": [], + "category": "objects" + }, + ":cucumber:": { + "uc_base": "1f952", + "uc_output": "1f952", + "uc_match": "1f952", + "uc_greedy": "1f952", + "shortnames": [], + "category": "food" + }, + ":cup_with_straw:": { + "uc_base": "1f964", + "uc_output": "1f964", + "uc_match": "1f964", + "uc_greedy": "1f964", + "shortnames": [], + "category": "food" + }, + ":cupid:": { + "uc_base": "1f498", + "uc_output": "1f498", + "uc_match": "1f498", + "uc_greedy": "1f498", + "shortnames": [], + "category": "symbols" + }, + ":curling_stone:": { + "uc_base": "1f94c", + "uc_output": "1f94c", + "uc_match": "1f94c", + "uc_greedy": "1f94c", + "shortnames": [], + "category": "activity" + }, + ":currency_exchange:": { + "uc_base": "1f4b1", + "uc_output": "1f4b1", + "uc_match": "1f4b1", + "uc_greedy": "1f4b1", + "shortnames": [], + "category": "symbols" + }, + ":curry:": { + "uc_base": "1f35b", + "uc_output": "1f35b", + "uc_match": "1f35b", + "uc_greedy": "1f35b", + "shortnames": [], + "category": "food" + }, + ":custard:": { + "uc_base": "1f36e", + "uc_output": "1f36e", + "uc_match": "1f36e", + "uc_greedy": "1f36e", + "shortnames": [":pudding:", ":flan:"], + "category": "food" + }, + ":customs:": { + "uc_base": "1f6c3", + "uc_output": "1f6c3", + "uc_match": "1f6c3", + "uc_greedy": "1f6c3", + "shortnames": [], + "category": "symbols" + }, + ":cut_of_meat:": { + "uc_base": "1f969", + "uc_output": "1f969", + "uc_match": "1f969", + "uc_greedy": "1f969", + "shortnames": [], + "category": "food" + }, + ":cyclone:": { + "uc_base": "1f300", + "uc_output": "1f300", + "uc_match": "1f300", + "uc_greedy": "1f300", + "shortnames": [], + "category": "symbols" + }, + ":dagger:": { + "uc_base": "1f5e1", + "uc_output": "1f5e1", + "uc_match": "1f5e1-fe0f", + "uc_greedy": "1f5e1-fe0f", + "shortnames": [":dagger_knife:"], + "category": "objects" + }, + ":dancer:": { + "uc_base": "1f483", + "uc_output": "1f483", + "uc_match": "1f483", + "uc_greedy": "1f483", + "shortnames": [], + "category": "people" + }, + ":dango:": { + "uc_base": "1f361", + "uc_output": "1f361", + "uc_match": "1f361", + "uc_greedy": "1f361", + "shortnames": [], + "category": "food" + }, + ":dark_sunglasses:": { + "uc_base": "1f576", + "uc_output": "1f576", + "uc_match": "1f576-fe0f", + "uc_greedy": "1f576-fe0f", + "shortnames": [], + "category": "people" + }, + ":dart:": { + "uc_base": "1f3af", + "uc_output": "1f3af", + "uc_match": "1f3af", + "uc_greedy": "1f3af", + "shortnames": [], + "category": "activity" + }, + ":dash:": { + "uc_base": "1f4a8", + "uc_output": "1f4a8", + "uc_match": "1f4a8", + "uc_greedy": "1f4a8", + "shortnames": [], + "category": "nature" + }, + ":date:": { + "uc_base": "1f4c5", + "uc_output": "1f4c5", + "uc_match": "1f4c5", + "uc_greedy": "1f4c5", + "shortnames": [], + "category": "objects" + }, + ":deciduous_tree:": { + "uc_base": "1f333", + "uc_output": "1f333", + "uc_match": "1f333", + "uc_greedy": "1f333", + "shortnames": [], + "category": "nature" + }, + ":deer:": { + "uc_base": "1f98c", + "uc_output": "1f98c", + "uc_match": "1f98c", + "uc_greedy": "1f98c", + "shortnames": [], + "category": "nature" + }, + ":department_store:": { + "uc_base": "1f3ec", + "uc_output": "1f3ec", + "uc_match": "1f3ec", + "uc_greedy": "1f3ec", + "shortnames": [], + "category": "travel" + }, + ":desert:": { + "uc_base": "1f3dc", + "uc_output": "1f3dc", + "uc_match": "1f3dc-fe0f", + "uc_greedy": "1f3dc-fe0f", + "shortnames": [], + "category": "travel" + }, + ":desktop:": { + "uc_base": "1f5a5", + "uc_output": "1f5a5", + "uc_match": "1f5a5-fe0f", + "uc_greedy": "1f5a5-fe0f", + "shortnames": [":desktop_computer:"], + "category": "objects" + }, + ":detective:": { + "uc_base": "1f575", + "uc_output": "1f575", + "uc_match": "1f575-fe0f", + "uc_greedy": "1f575-fe0f", + "shortnames": [":spy:", ":sleuth_or_spy:"], + "category": "people" + }, + ":diamond_shape_with_a_dot_inside:": { + "uc_base": "1f4a0", + "uc_output": "1f4a0", + "uc_match": "1f4a0", + "uc_greedy": "1f4a0", + "shortnames": [], + "category": "symbols" + }, + ":disappointed:": { + "uc_base": "1f61e", + "uc_output": "1f61e", + "uc_match": "1f61e", + "uc_greedy": "1f61e", + "shortnames": [], + "category": "people" + }, + ":disappointed_relieved:": { + "uc_base": "1f625", + "uc_output": "1f625", + "uc_match": "1f625", + "uc_greedy": "1f625", + "shortnames": [], + "category": "people" + }, + ":dividers:": { + "uc_base": "1f5c2", + "uc_output": "1f5c2", + "uc_match": "1f5c2-fe0f", + "uc_greedy": "1f5c2-fe0f", + "shortnames": [":card_index_dividers:"], + "category": "objects" + }, + ":dizzy:": { + "uc_base": "1f4ab", + "uc_output": "1f4ab", + "uc_match": "1f4ab", + "uc_greedy": "1f4ab", + "shortnames": [], + "category": "nature" + }, + ":dizzy_face:": { + "uc_base": "1f635", + "uc_output": "1f635", + "uc_match": "1f635", + "uc_greedy": "1f635", + "shortnames": [], + "category": "people" + }, + ":do_not_litter:": { + "uc_base": "1f6af", + "uc_output": "1f6af", + "uc_match": "1f6af", + "uc_greedy": "1f6af", + "shortnames": [], + "category": "symbols" + }, + ":dog2:": { + "uc_base": "1f415", + "uc_output": "1f415", + "uc_match": "1f415-fe0f", + "uc_greedy": "1f415-fe0f", + "shortnames": [], + "category": "nature" + }, + ":dog:": { + "uc_base": "1f436", + "uc_output": "1f436", + "uc_match": "1f436", + "uc_greedy": "1f436", + "shortnames": [], + "category": "nature" + }, + ":dollar:": { + "uc_base": "1f4b5", + "uc_output": "1f4b5", + "uc_match": "1f4b5", + "uc_greedy": "1f4b5", + "shortnames": [], + "category": "objects" + }, + ":dolls:": { + "uc_base": "1f38e", + "uc_output": "1f38e", + "uc_match": "1f38e", + "uc_greedy": "1f38e", + "shortnames": [], + "category": "objects" + }, + ":dolphin:": { + "uc_base": "1f42c", + "uc_output": "1f42c", + "uc_match": "1f42c", + "uc_greedy": "1f42c", + "shortnames": [], + "category": "nature" + }, + ":door:": { + "uc_base": "1f6aa", + "uc_output": "1f6aa", + "uc_match": "1f6aa", + "uc_greedy": "1f6aa", + "shortnames": [], + "category": "objects" + }, + ":doughnut:": { + "uc_base": "1f369", + "uc_output": "1f369", + "uc_match": "1f369", + "uc_greedy": "1f369", + "shortnames": [], + "category": "food" + }, + ":dove:": { + "uc_base": "1f54a", + "uc_output": "1f54a", + "uc_match": "1f54a-fe0f", + "uc_greedy": "1f54a-fe0f", + "shortnames": [":dove_of_peace:"], + "category": "nature" + }, + ":dragon:": { + "uc_base": "1f409", + "uc_output": "1f409", + "uc_match": "1f409", + "uc_greedy": "1f409", + "shortnames": [], + "category": "nature" + }, + ":dragon_face:": { + "uc_base": "1f432", + "uc_output": "1f432", + "uc_match": "1f432", + "uc_greedy": "1f432", + "shortnames": [], + "category": "nature" + }, + ":dress:": { + "uc_base": "1f457", + "uc_output": "1f457", + "uc_match": "1f457", + "uc_greedy": "1f457", + "shortnames": [], + "category": "people" + }, + ":dromedary_camel:": { + "uc_base": "1f42a", + "uc_output": "1f42a", + "uc_match": "1f42a", + "uc_greedy": "1f42a", + "shortnames": [], + "category": "nature" + }, + ":drooling_face:": { + "uc_base": "1f924", + "uc_output": "1f924", + "uc_match": "1f924", + "uc_greedy": "1f924", + "shortnames": [":drool:"], + "category": "people" + }, + ":droplet:": { + "uc_base": "1f4a7", + "uc_output": "1f4a7", + "uc_match": "1f4a7", + "uc_greedy": "1f4a7", + "shortnames": [], + "category": "nature" + }, + ":drum:": { + "uc_base": "1f941", + "uc_output": "1f941", + "uc_match": "1f941", + "uc_greedy": "1f941", + "shortnames": [":drum_with_drumsticks:"], + "category": "activity" + }, + ":duck:": { + "uc_base": "1f986", + "uc_output": "1f986", + "uc_match": "1f986", + "uc_greedy": "1f986", + "shortnames": [], + "category": "nature" + }, + ":dumpling:": { + "uc_base": "1f95f", + "uc_output": "1f95f", + "uc_match": "1f95f", + "uc_greedy": "1f95f", + "shortnames": [], + "category": "food" + }, + ":dvd:": { + "uc_base": "1f4c0", + "uc_output": "1f4c0", + "uc_match": "1f4c0", + "uc_greedy": "1f4c0", + "shortnames": [], + "category": "objects" + }, + ":e-mail:": { + "uc_base": "1f4e7", + "uc_output": "1f4e7", + "uc_match": "1f4e7", + "uc_greedy": "1f4e7", + "shortnames": [":email:"], + "category": "objects" + }, + ":eagle:": { + "uc_base": "1f985", + "uc_output": "1f985", + "uc_match": "1f985", + "uc_greedy": "1f985", + "shortnames": [], + "category": "nature" + }, + ":ear:": { + "uc_base": "1f442", + "uc_output": "1f442", + "uc_match": "1f442-fe0f", + "uc_greedy": "1f442-fe0f", + "shortnames": [], + "category": "people" + }, + ":ear_of_rice:": { + "uc_base": "1f33e", + "uc_output": "1f33e", + "uc_match": "1f33e", + "uc_greedy": "1f33e", + "shortnames": [], + "category": "nature" + }, + ":earth_africa:": { + "uc_base": "1f30d", + "uc_output": "1f30d", + "uc_match": "1f30d-fe0f", + "uc_greedy": "1f30d-fe0f", + "shortnames": [], + "category": "nature" + }, + ":earth_americas:": { + "uc_base": "1f30e", + "uc_output": "1f30e", + "uc_match": "1f30e-fe0f", + "uc_greedy": "1f30e-fe0f", + "shortnames": [], + "category": "nature" + }, + ":earth_asia:": { + "uc_base": "1f30f", + "uc_output": "1f30f", + "uc_match": "1f30f-fe0f", + "uc_greedy": "1f30f-fe0f", + "shortnames": [], + "category": "nature" + }, + ":egg:": { + "uc_base": "1f95a", + "uc_output": "1f95a", + "uc_match": "1f95a", + "uc_greedy": "1f95a", + "shortnames": [], + "category": "food" + }, + ":eggplant:": { + "uc_base": "1f346", + "uc_output": "1f346", + "uc_match": "1f346", + "uc_greedy": "1f346", + "shortnames": [], + "category": "food" + }, + ":electric_plug:": { + "uc_base": "1f50c", + "uc_output": "1f50c", + "uc_match": "1f50c", + "uc_greedy": "1f50c", + "shortnames": [], + "category": "objects" + }, + ":elephant:": { + "uc_base": "1f418", + "uc_output": "1f418", + "uc_match": "1f418", + "uc_greedy": "1f418", + "shortnames": [], + "category": "nature" + }, + ":elf:": { + "uc_base": "1f9dd", + "uc_output": "1f9dd", + "uc_match": "1f9dd", + "uc_greedy": "1f9dd", + "shortnames": [], + "category": "people" + }, + ":end:": { + "uc_base": "1f51a", + "uc_output": "1f51a", + "uc_match": "1f51a", + "uc_greedy": "1f51a", + "shortnames": [], + "category": "symbols" + }, + ":envelope_with_arrow:": { + "uc_base": "1f4e9", + "uc_output": "1f4e9", + "uc_match": "1f4e9", + "uc_greedy": "1f4e9", + "shortnames": [], + "category": "objects" + }, + ":euro:": { + "uc_base": "1f4b6", + "uc_output": "1f4b6", + "uc_match": "1f4b6", + "uc_greedy": "1f4b6", + "shortnames": [], + "category": "objects" + }, + ":european_castle:": { + "uc_base": "1f3f0", + "uc_output": "1f3f0", + "uc_match": "1f3f0", + "uc_greedy": "1f3f0", + "shortnames": [], + "category": "travel" + }, + ":european_post_office:": { + "uc_base": "1f3e4", + "uc_output": "1f3e4", + "uc_match": "1f3e4", + "uc_greedy": "1f3e4", + "shortnames": [], + "category": "travel" + }, + ":evergreen_tree:": { + "uc_base": "1f332", + "uc_output": "1f332", + "uc_match": "1f332", + "uc_greedy": "1f332", + "shortnames": [], + "category": "nature" + }, + ":exploding_head:": { + "uc_base": "1f92f", + "uc_output": "1f92f", + "uc_match": "1f92f", + "uc_greedy": "1f92f", + "shortnames": [], + "category": "people" + }, + ":expressionless:": { + "uc_base": "1f611", + "uc_output": "1f611", + "uc_match": "1f611", + "uc_greedy": "1f611", + "shortnames": [], + "category": "people" + }, + ":eye:": { + "uc_base": "1f441", + "uc_output": "1f441", + "uc_match": "1f441-fe0f", + "uc_greedy": "1f441-fe0f", + "shortnames": [], + "category": "people" + }, + ":eyeglasses:": { + "uc_base": "1f453", + "uc_output": "1f453", + "uc_match": "1f453-fe0f", + "uc_greedy": "1f453-fe0f", + "shortnames": [], + "category": "people" + }, + ":eyes:": { + "uc_base": "1f440", + "uc_output": "1f440", + "uc_match": "1f440", + "uc_greedy": "1f440", + "shortnames": [], + "category": "people" + }, + ":face_vomiting:": { + "uc_base": "1f92e", + "uc_output": "1f92e", + "uc_match": "1f92e", + "uc_greedy": "1f92e", + "shortnames": [], + "category": "people" + }, + ":face_with_hand_over_mouth:": { + "uc_base": "1f92d", + "uc_output": "1f92d", + "uc_match": "1f92d", + "uc_greedy": "1f92d", + "shortnames": [], + "category": "people" + }, + ":face_with_monocle:": { + "uc_base": "1f9d0", + "uc_output": "1f9d0", + "uc_match": "1f9d0", + "uc_greedy": "1f9d0", + "shortnames": [], + "category": "people" + }, + ":face_with_raised_eyebrow:": { + "uc_base": "1f928", + "uc_output": "1f928", + "uc_match": "1f928", + "uc_greedy": "1f928", + "shortnames": [], + "category": "people" + }, + ":face_with_symbols_over_mouth:": { + "uc_base": "1f92c", + "uc_output": "1f92c", + "uc_match": "1f92c", + "uc_greedy": "1f92c", + "shortnames": [], + "category": "people" + }, + ":factory:": { + "uc_base": "1f3ed", + "uc_output": "1f3ed", + "uc_match": "1f3ed-fe0f", + "uc_greedy": "1f3ed-fe0f", + "shortnames": [], + "category": "travel" + }, + ":fairy:": { + "uc_base": "1f9da", + "uc_output": "1f9da", + "uc_match": "1f9da", + "uc_greedy": "1f9da", + "shortnames": [], + "category": "people" + }, + ":fallen_leaf:": { + "uc_base": "1f342", + "uc_output": "1f342", + "uc_match": "1f342", + "uc_greedy": "1f342", + "shortnames": [], + "category": "nature" + }, + ":family:": { + "uc_base": "1f46a", + "uc_output": "1f46a", + "uc_match": "1f46a-fe0f", + "uc_greedy": "1f46a-fe0f", + "shortnames": [], + "category": "people" + }, + ":fax:": { + "uc_base": "1f4e0", + "uc_output": "1f4e0", + "uc_match": "1f4e0", + "uc_greedy": "1f4e0", + "shortnames": [], + "category": "objects" + }, + ":fearful:": { + "uc_base": "1f628", + "uc_output": "1f628", + "uc_match": "1f628", + "uc_greedy": "1f628", + "shortnames": [], + "category": "people" + }, + ":feet:": { + "uc_base": "1f43e", + "uc_output": "1f43e", + "uc_match": "1f43e", + "uc_greedy": "1f43e", + "shortnames": [":paw_prints:"], + "category": "nature" + }, + ":ferris_wheel:": { + "uc_base": "1f3a1", + "uc_output": "1f3a1", + "uc_match": "1f3a1", + "uc_greedy": "1f3a1", + "shortnames": [], + "category": "travel" + }, + ":field_hockey:": { + "uc_base": "1f3d1", + "uc_output": "1f3d1", + "uc_match": "1f3d1", + "uc_greedy": "1f3d1", + "shortnames": [], + "category": "activity" + }, + ":file_cabinet:": { + "uc_base": "1f5c4", + "uc_output": "1f5c4", + "uc_match": "1f5c4-fe0f", + "uc_greedy": "1f5c4-fe0f", + "shortnames": [], + "category": "objects" + }, + ":file_folder:": { + "uc_base": "1f4c1", + "uc_output": "1f4c1", + "uc_match": "1f4c1", + "uc_greedy": "1f4c1", + "shortnames": [], + "category": "objects" + }, + ":film_frames:": { + "uc_base": "1f39e", + "uc_output": "1f39e", + "uc_match": "1f39e-fe0f", + "uc_greedy": "1f39e-fe0f", + "shortnames": [], + "category": "objects" + }, + ":fingers_crossed:": { + "uc_base": "1f91e", + "uc_output": "1f91e", + "uc_match": "1f91e", + "uc_greedy": "1f91e", + "shortnames": [":hand_with_index_and_middle_finger_crossed:"], + "category": "people" + }, + ":fire:": { + "uc_base": "1f525", + "uc_output": "1f525", + "uc_match": "1f525", + "uc_greedy": "1f525", + "shortnames": [":flame:"], + "category": "nature" + }, + ":fire_engine:": { + "uc_base": "1f692", + "uc_output": "1f692", + "uc_match": "1f692", + "uc_greedy": "1f692", + "shortnames": [], + "category": "travel" + }, + ":fireworks:": { + "uc_base": "1f386", + "uc_output": "1f386", + "uc_match": "1f386", + "uc_greedy": "1f386", + "shortnames": [], + "category": "travel" + }, + ":first_place:": { + "uc_base": "1f947", + "uc_output": "1f947", + "uc_match": "1f947", + "uc_greedy": "1f947", + "shortnames": [":first_place_medal:"], + "category": "activity" + }, + ":first_quarter_moon:": { + "uc_base": "1f313", + "uc_output": "1f313", + "uc_match": "1f313", + "uc_greedy": "1f313", + "shortnames": [], + "category": "nature" + }, + ":first_quarter_moon_with_face:": { + "uc_base": "1f31b", + "uc_output": "1f31b", + "uc_match": "1f31b", + "uc_greedy": "1f31b", + "shortnames": [], + "category": "nature" + }, + ":fish:": { + "uc_base": "1f41f", + "uc_output": "1f41f", + "uc_match": "1f41f-fe0f", + "uc_greedy": "1f41f-fe0f", + "shortnames": [], + "category": "nature" + }, + ":fish_cake:": { + "uc_base": "1f365", + "uc_output": "1f365", + "uc_match": "1f365", + "uc_greedy": "1f365", + "shortnames": [], + "category": "food" + }, + ":fishing_pole_and_fish:": { + "uc_base": "1f3a3", + "uc_output": "1f3a3", + "uc_match": "1f3a3", + "uc_greedy": "1f3a3", + "shortnames": [], + "category": "activity" + }, + ":flag_black:": { + "uc_base": "1f3f4", + "uc_output": "1f3f4", + "uc_match": "1f3f4", + "uc_greedy": "1f3f4", + "shortnames": [":waving_black_flag:"], + "category": "flags" + }, + ":flag_white:": { + "uc_base": "1f3f3", + "uc_output": "1f3f3", + "uc_match": "1f3f3-fe0f", + "uc_greedy": "1f3f3-fe0f", + "shortnames": [":waving_white_flag:"], + "category": "flags" + }, + ":flags:": { + "uc_base": "1f38f", + "uc_output": "1f38f", + "uc_match": "1f38f", + "uc_greedy": "1f38f", + "shortnames": [], + "category": "objects" + }, + ":flashlight:": { + "uc_base": "1f526", + "uc_output": "1f526", + "uc_match": "1f526", + "uc_greedy": "1f526", + "shortnames": [], + "category": "objects" + }, + ":floppy_disk:": { + "uc_base": "1f4be", + "uc_output": "1f4be", + "uc_match": "1f4be", + "uc_greedy": "1f4be", + "shortnames": [], + "category": "objects" + }, + ":flower_playing_cards:": { + "uc_base": "1f3b4", + "uc_output": "1f3b4", + "uc_match": "1f3b4", + "uc_greedy": "1f3b4", + "shortnames": [], + "category": "symbols" + }, + ":flushed:": { + "uc_base": "1f633", + "uc_output": "1f633", + "uc_match": "1f633", + "uc_greedy": "1f633", + "shortnames": [], + "category": "people" + }, + ":flying_saucer:": { + "uc_base": "1f6f8", + "uc_output": "1f6f8", + "uc_match": "1f6f8", + "uc_greedy": "1f6f8", + "shortnames": [], + "category": "travel" + }, + ":fog:": { + "uc_base": "1f32b", + "uc_output": "1f32b", + "uc_match": "1f32b-fe0f", + "uc_greedy": "1f32b-fe0f", + "shortnames": [], + "category": "nature" + }, + ":foggy:": { + "uc_base": "1f301", + "uc_output": "1f301", + "uc_match": "1f301", + "uc_greedy": "1f301", + "shortnames": [], + "category": "travel" + }, + ":football:": { + "uc_base": "1f3c8", + "uc_output": "1f3c8", + "uc_match": "1f3c8", + "uc_greedy": "1f3c8", + "shortnames": [], + "category": "activity" + }, + ":footprints:": { + "uc_base": "1f463", + "uc_output": "1f463", + "uc_match": "1f463", + "uc_greedy": "1f463", + "shortnames": [], + "category": "people" + }, + ":fork_and_knife:": { + "uc_base": "1f374", + "uc_output": "1f374", + "uc_match": "1f374", + "uc_greedy": "1f374", + "shortnames": [], + "category": "food" + }, + ":fork_knife_plate:": { + "uc_base": "1f37d", + "uc_output": "1f37d", + "uc_match": "1f37d-fe0f", + "uc_greedy": "1f37d-fe0f", + "shortnames": [":fork_and_knife_with_plate:"], + "category": "food" + }, + ":fortune_cookie:": { + "uc_base": "1f960", + "uc_output": "1f960", + "uc_match": "1f960", + "uc_greedy": "1f960", + "shortnames": [], + "category": "food" + }, + ":four_leaf_clover:": { + "uc_base": "1f340", + "uc_output": "1f340", + "uc_match": "1f340", + "uc_greedy": "1f340", + "shortnames": [], + "category": "nature" + }, + ":fox:": { + "uc_base": "1f98a", + "uc_output": "1f98a", + "uc_match": "1f98a", + "uc_greedy": "1f98a", + "shortnames": [":fox_face:"], + "category": "nature" + }, + ":frame_photo:": { + "uc_base": "1f5bc", + "uc_output": "1f5bc", + "uc_match": "1f5bc-fe0f", + "uc_greedy": "1f5bc-fe0f", + "shortnames": [":frame_with_picture:"], + "category": "objects" + }, + ":free:": { + "uc_base": "1f193", + "uc_output": "1f193", + "uc_match": "1f193", + "uc_greedy": "1f193", + "shortnames": [], + "category": "symbols" + }, + ":french_bread:": { + "uc_base": "1f956", + "uc_output": "1f956", + "uc_match": "1f956", + "uc_greedy": "1f956", + "shortnames": [":baguette_bread:"], + "category": "food" + }, + ":fried_shrimp:": { + "uc_base": "1f364", + "uc_output": "1f364", + "uc_match": "1f364", + "uc_greedy": "1f364", + "shortnames": [], + "category": "food" + }, + ":fries:": { + "uc_base": "1f35f", + "uc_output": "1f35f", + "uc_match": "1f35f", + "uc_greedy": "1f35f", + "shortnames": [], + "category": "food" + }, + ":frog:": { + "uc_base": "1f438", + "uc_output": "1f438", + "uc_match": "1f438", + "uc_greedy": "1f438", + "shortnames": [], + "category": "nature" + }, + ":frowning:": { + "uc_base": "1f626", + "uc_output": "1f626", + "uc_match": "1f626", + "uc_greedy": "1f626", + "shortnames": [], + "category": "people" + }, + ":full_moon:": { + "uc_base": "1f315", + "uc_output": "1f315", + "uc_match": "1f315-fe0f", + "uc_greedy": "1f315-fe0f", + "shortnames": [], + "category": "nature" + }, + ":full_moon_with_face:": { + "uc_base": "1f31d", + "uc_output": "1f31d", + "uc_match": "1f31d", + "uc_greedy": "1f31d", + "shortnames": [], + "category": "nature" + }, + ":game_die:": { + "uc_base": "1f3b2", + "uc_output": "1f3b2", + "uc_match": "1f3b2", + "uc_greedy": "1f3b2", + "shortnames": [], + "category": "activity" + }, + ":gem:": { + "uc_base": "1f48e", + "uc_output": "1f48e", + "uc_match": "1f48e", + "uc_greedy": "1f48e", + "shortnames": [], + "category": "objects" + }, + ":genie:": { + "uc_base": "1f9de", + "uc_output": "1f9de", + "uc_match": "1f9de", + "uc_greedy": "1f9de", + "shortnames": [], + "category": "people" + }, + ":ghost:": { + "uc_base": "1f47b", + "uc_output": "1f47b", + "uc_match": "1f47b", + "uc_greedy": "1f47b", + "shortnames": [], + "category": "people" + }, + ":gift:": { + "uc_base": "1f381", + "uc_output": "1f381", + "uc_match": "1f381", + "uc_greedy": "1f381", + "shortnames": [], + "category": "objects" + }, + ":gift_heart:": { + "uc_base": "1f49d", + "uc_output": "1f49d", + "uc_match": "1f49d", + "uc_greedy": "1f49d", + "shortnames": [], + "category": "symbols" + }, + ":giraffe:": { + "uc_base": "1f992", + "uc_output": "1f992", + "uc_match": "1f992", + "uc_greedy": "1f992", + "shortnames": [], + "category": "nature" + }, + ":girl:": { + "uc_base": "1f467", + "uc_output": "1f467", + "uc_match": "1f467", + "uc_greedy": "1f467", + "shortnames": [], + "category": "people" + }, + ":globe_with_meridians:": { + "uc_base": "1f310", + "uc_output": "1f310", + "uc_match": "1f310", + "uc_greedy": "1f310", + "shortnames": [], + "category": "symbols" + }, + ":gloves:": { + "uc_base": "1f9e4", + "uc_output": "1f9e4", + "uc_match": "1f9e4", + "uc_greedy": "1f9e4", + "shortnames": [], + "category": "people" + }, + ":goal:": { + "uc_base": "1f945", + "uc_output": "1f945", + "uc_match": "1f945", + "uc_greedy": "1f945", + "shortnames": [":goal_net:"], + "category": "activity" + }, + ":goat:": { + "uc_base": "1f410", + "uc_output": "1f410", + "uc_match": "1f410", + "uc_greedy": "1f410", + "shortnames": [], + "category": "nature" + }, + ":gorilla:": { + "uc_base": "1f98d", + "uc_output": "1f98d", + "uc_match": "1f98d", + "uc_greedy": "1f98d", + "shortnames": [], + "category": "nature" + }, + ":grapes:": { + "uc_base": "1f347", + "uc_output": "1f347", + "uc_match": "1f347", + "uc_greedy": "1f347", + "shortnames": [], + "category": "food" + }, + ":green_apple:": { + "uc_base": "1f34f", + "uc_output": "1f34f", + "uc_match": "1f34f", + "uc_greedy": "1f34f", + "shortnames": [], + "category": "food" + }, + ":green_book:": { + "uc_base": "1f4d7", + "uc_output": "1f4d7", + "uc_match": "1f4d7", + "uc_greedy": "1f4d7", + "shortnames": [], + "category": "objects" + }, + ":green_heart:": { + "uc_base": "1f49a", + "uc_output": "1f49a", + "uc_match": "1f49a", + "uc_greedy": "1f49a", + "shortnames": [], + "category": "symbols" + }, + ":grimacing:": { + "uc_base": "1f62c", + "uc_output": "1f62c", + "uc_match": "1f62c", + "uc_greedy": "1f62c", + "shortnames": [], + "category": "people" + }, + ":grin:": { + "uc_base": "1f601", + "uc_output": "1f601", + "uc_match": "1f601", + "uc_greedy": "1f601", + "shortnames": [], + "category": "people" + }, + ":grinning:": { + "uc_base": "1f600", + "uc_output": "1f600", + "uc_match": "1f600", + "uc_greedy": "1f600", + "shortnames": [], + "category": "people" + }, + ":guard:": { + "uc_base": "1f482", + "uc_output": "1f482", + "uc_match": "1f482", + "uc_greedy": "1f482", + "shortnames": [":guardsman:"], + "category": "people" + }, + ":guitar:": { + "uc_base": "1f3b8", + "uc_output": "1f3b8", + "uc_match": "1f3b8", + "uc_greedy": "1f3b8", + "shortnames": [], + "category": "activity" + }, + ":gun:": { + "uc_base": "1f52b", + "uc_output": "1f52b", + "uc_match": "1f52b", + "uc_greedy": "1f52b", + "shortnames": [], + "category": "objects" + }, + ":hamburger:": { + "uc_base": "1f354", + "uc_output": "1f354", + "uc_match": "1f354", + "uc_greedy": "1f354", + "shortnames": [], + "category": "food" + }, + ":hammer:": { + "uc_base": "1f528", + "uc_output": "1f528", + "uc_match": "1f528", + "uc_greedy": "1f528", + "shortnames": [], + "category": "objects" + }, + ":hamster:": { + "uc_base": "1f439", + "uc_output": "1f439", + "uc_match": "1f439", + "uc_greedy": "1f439", + "shortnames": [], + "category": "nature" + }, + ":hand_splayed:": { + "uc_base": "1f590", + "uc_output": "1f590", + "uc_match": "1f590-fe0f", + "uc_greedy": "1f590-fe0f", + "shortnames": [":raised_hand_with_fingers_splayed:"], + "category": "people" + }, + ":handbag:": { + "uc_base": "1f45c", + "uc_output": "1f45c", + "uc_match": "1f45c", + "uc_greedy": "1f45c", + "shortnames": [], + "category": "people" + }, + ":handshake:": { + "uc_base": "1f91d", + "uc_output": "1f91d", + "uc_match": "1f91d", + "uc_greedy": "1f91d", + "shortnames": [":shaking_hands:"], + "category": "people" + }, + ":hatched_chick:": { + "uc_base": "1f425", + "uc_output": "1f425", + "uc_match": "1f425", + "uc_greedy": "1f425", + "shortnames": [], + "category": "nature" + }, + ":hatching_chick:": { + "uc_base": "1f423", + "uc_output": "1f423", + "uc_match": "1f423", + "uc_greedy": "1f423", + "shortnames": [], + "category": "nature" + }, + ":head_bandage:": { + "uc_base": "1f915", + "uc_output": "1f915", + "uc_match": "1f915", + "uc_greedy": "1f915", + "shortnames": [":face_with_head_bandage:"], + "category": "people" + }, + ":headphones:": { + "uc_base": "1f3a7", + "uc_output": "1f3a7", + "uc_match": "1f3a7-fe0f", + "uc_greedy": "1f3a7-fe0f", + "shortnames": [], + "category": "activity" + }, + ":hear_no_evil:": { + "uc_base": "1f649", + "uc_output": "1f649", + "uc_match": "1f649", + "uc_greedy": "1f649", + "shortnames": [], + "category": "nature" + }, + ":heart_decoration:": { + "uc_base": "1f49f", + "uc_output": "1f49f", + "uc_match": "1f49f", + "uc_greedy": "1f49f", + "shortnames": [], + "category": "symbols" + }, + ":heart_eyes:": { + "uc_base": "1f60d", + "uc_output": "1f60d", + "uc_match": "1f60d", + "uc_greedy": "1f60d", + "shortnames": [], + "category": "people" + }, + ":heart_eyes_cat:": { + "uc_base": "1f63b", + "uc_output": "1f63b", + "uc_match": "1f63b", + "uc_greedy": "1f63b", + "shortnames": [], + "category": "people" + }, + ":heartbeat:": { + "uc_base": "1f493", + "uc_output": "1f493", + "uc_match": "1f493", + "uc_greedy": "1f493", + "shortnames": [], + "category": "symbols" + }, + ":heartpulse:": { + "uc_base": "1f497", + "uc_output": "1f497", + "uc_match": "1f497", + "uc_greedy": "1f497", + "shortnames": [], + "category": "symbols" + }, + ":heavy_dollar_sign:": { + "uc_base": "1f4b2", + "uc_output": "1f4b2", + "uc_match": "1f4b2", + "uc_greedy": "1f4b2", + "shortnames": [], + "category": "symbols" + }, + ":hedgehog:": { + "uc_base": "1f994", + "uc_output": "1f994", + "uc_match": "1f994", + "uc_greedy": "1f994", + "shortnames": [], + "category": "nature" + }, + ":helicopter:": { + "uc_base": "1f681", + "uc_output": "1f681", + "uc_match": "1f681", + "uc_greedy": "1f681", + "shortnames": [], + "category": "travel" + }, + ":herb:": { + "uc_base": "1f33f", + "uc_output": "1f33f", + "uc_match": "1f33f", + "uc_greedy": "1f33f", + "shortnames": [], + "category": "nature" + }, + ":hibiscus:": { + "uc_base": "1f33a", + "uc_output": "1f33a", + "uc_match": "1f33a", + "uc_greedy": "1f33a", + "shortnames": [], + "category": "nature" + }, + ":high_brightness:": { + "uc_base": "1f506", + "uc_output": "1f506", + "uc_match": "1f506", + "uc_greedy": "1f506", + "shortnames": [], + "category": "symbols" + }, + ":high_heel:": { + "uc_base": "1f460", + "uc_output": "1f460", + "uc_match": "1f460", + "uc_greedy": "1f460", + "shortnames": [], + "category": "people" + }, + ":hockey:": { + "uc_base": "1f3d2", + "uc_output": "1f3d2", + "uc_match": "1f3d2", + "uc_greedy": "1f3d2", + "shortnames": [], + "category": "activity" + }, + ":hole:": { + "uc_base": "1f573", + "uc_output": "1f573", + "uc_match": "1f573-fe0f", + "uc_greedy": "1f573-fe0f", + "shortnames": [], + "category": "objects" + }, + ":homes:": { + "uc_base": "1f3d8", + "uc_output": "1f3d8", + "uc_match": "1f3d8-fe0f", + "uc_greedy": "1f3d8-fe0f", + "shortnames": [":house_buildings:"], + "category": "travel" + }, + ":honey_pot:": { + "uc_base": "1f36f", + "uc_output": "1f36f", + "uc_match": "1f36f", + "uc_greedy": "1f36f", + "shortnames": [], + "category": "food" + }, + ":horse:": { + "uc_base": "1f434", + "uc_output": "1f434", + "uc_match": "1f434", + "uc_greedy": "1f434", + "shortnames": [], + "category": "nature" + }, + ":horse_racing:": { + "uc_base": "1f3c7", + "uc_output": "1f3c7", + "uc_match": "1f3c7", + "uc_greedy": "1f3c7", + "shortnames": [], + "category": "activity" + }, + ":hospital:": { + "uc_base": "1f3e5", + "uc_output": "1f3e5", + "uc_match": "1f3e5", + "uc_greedy": "1f3e5", + "shortnames": [], + "category": "travel" + }, + ":hot_pepper:": { + "uc_base": "1f336", + "uc_output": "1f336", + "uc_match": "1f336-fe0f", + "uc_greedy": "1f336-fe0f", + "shortnames": [], + "category": "food" + }, + ":hotdog:": { + "uc_base": "1f32d", + "uc_output": "1f32d", + "uc_match": "1f32d", + "uc_greedy": "1f32d", + "shortnames": [":hot_dog:"], + "category": "food" + }, + ":hotel:": { + "uc_base": "1f3e8", + "uc_output": "1f3e8", + "uc_match": "1f3e8", + "uc_greedy": "1f3e8", + "shortnames": [], + "category": "travel" + }, + ":house:": { + "uc_base": "1f3e0", + "uc_output": "1f3e0", + "uc_match": "1f3e0-fe0f", + "uc_greedy": "1f3e0-fe0f", + "shortnames": [], + "category": "travel" + }, + ":house_abandoned:": { + "uc_base": "1f3da", + "uc_output": "1f3da", + "uc_match": "1f3da-fe0f", + "uc_greedy": "1f3da-fe0f", + "shortnames": [":derelict_house_building:"], + "category": "travel" + }, + ":house_with_garden:": { + "uc_base": "1f3e1", + "uc_output": "1f3e1", + "uc_match": "1f3e1", + "uc_greedy": "1f3e1", + "shortnames": [], + "category": "travel" + }, + ":hugging:": { + "uc_base": "1f917", + "uc_output": "1f917", + "uc_match": "1f917", + "uc_greedy": "1f917", + "shortnames": [":hugging_face:"], + "category": "people" + }, + ":hushed:": { + "uc_base": "1f62f", + "uc_output": "1f62f", + "uc_match": "1f62f", + "uc_greedy": "1f62f", + "shortnames": [], + "category": "people" + }, + ":ice_cream:": { + "uc_base": "1f368", + "uc_output": "1f368", + "uc_match": "1f368", + "uc_greedy": "1f368", + "shortnames": [], + "category": "food" + }, + ":icecream:": { + "uc_base": "1f366", + "uc_output": "1f366", + "uc_match": "1f366", + "uc_greedy": "1f366", + "shortnames": [], + "category": "food" + }, + ":id:": { + "uc_base": "1f194", + "uc_output": "1f194", + "uc_match": "1f194", + "uc_greedy": "1f194", + "shortnames": [], + "category": "symbols" + }, + ":ideograph_advantage:": { + "uc_base": "1f250", + "uc_output": "1f250", + "uc_match": "1f250", + "uc_greedy": "1f250", + "shortnames": [], + "category": "symbols" + }, + ":imp:": { + "uc_base": "1f47f", + "uc_output": "1f47f", + "uc_match": "1f47f", + "uc_greedy": "1f47f", + "shortnames": [], + "category": "people" + }, + ":inbox_tray:": { + "uc_base": "1f4e5", + "uc_output": "1f4e5", + "uc_match": "1f4e5-fe0f", + "uc_greedy": "1f4e5-fe0f", + "shortnames": [], + "category": "objects" + }, + ":incoming_envelope:": { + "uc_base": "1f4e8", + "uc_output": "1f4e8", + "uc_match": "1f4e8", + "uc_greedy": "1f4e8", + "shortnames": [], + "category": "objects" + }, + ":innocent:": { + "uc_base": "1f607", + "uc_output": "1f607", + "uc_match": "1f607", + "uc_greedy": "1f607", + "shortnames": [], + "category": "people" + }, + ":iphone:": { + "uc_base": "1f4f1", + "uc_output": "1f4f1", + "uc_match": "1f4f1", + "uc_greedy": "1f4f1", + "shortnames": [], + "category": "objects" + }, + ":island:": { + "uc_base": "1f3dd", + "uc_output": "1f3dd", + "uc_match": "1f3dd-fe0f", + "uc_greedy": "1f3dd-fe0f", + "shortnames": [":desert_island:"], + "category": "travel" + }, + ":izakaya_lantern:": { + "uc_base": "1f3ee", + "uc_output": "1f3ee", + "uc_match": "1f3ee", + "uc_greedy": "1f3ee", + "shortnames": [], + "category": "objects" + }, + ":jack_o_lantern:": { + "uc_base": "1f383", + "uc_output": "1f383", + "uc_match": "1f383", + "uc_greedy": "1f383", + "shortnames": [], + "category": "people" + }, + ":japan:": { + "uc_base": "1f5fe", + "uc_output": "1f5fe", + "uc_match": "1f5fe", + "uc_greedy": "1f5fe", + "shortnames": [], + "category": "travel" + }, + ":japanese_castle:": { + "uc_base": "1f3ef", + "uc_output": "1f3ef", + "uc_match": "1f3ef", + "uc_greedy": "1f3ef", + "shortnames": [], + "category": "travel" + }, + ":japanese_goblin:": { + "uc_base": "1f47a", + "uc_output": "1f47a", + "uc_match": "1f47a", + "uc_greedy": "1f47a", + "shortnames": [], + "category": "people" + }, + ":japanese_ogre:": { + "uc_base": "1f479", + "uc_output": "1f479", + "uc_match": "1f479", + "uc_greedy": "1f479", + "shortnames": [], + "category": "people" + }, + ":jeans:": { + "uc_base": "1f456", + "uc_output": "1f456", + "uc_match": "1f456", + "uc_greedy": "1f456", + "shortnames": [], + "category": "people" + }, + ":joy:": { + "uc_base": "1f602", + "uc_output": "1f602", + "uc_match": "1f602", + "uc_greedy": "1f602", + "shortnames": [], + "category": "people" + }, + ":joy_cat:": { + "uc_base": "1f639", + "uc_output": "1f639", + "uc_match": "1f639", + "uc_greedy": "1f639", + "shortnames": [], + "category": "people" + }, + ":joystick:": { + "uc_base": "1f579", + "uc_output": "1f579", + "uc_match": "1f579-fe0f", + "uc_greedy": "1f579-fe0f", + "shortnames": [], + "category": "objects" + }, + ":kaaba:": { + "uc_base": "1f54b", + "uc_output": "1f54b", + "uc_match": "1f54b", + "uc_greedy": "1f54b", + "shortnames": [], + "category": "travel" + }, + ":key2:": { + "uc_base": "1f5dd", + "uc_output": "1f5dd", + "uc_match": "1f5dd-fe0f", + "uc_greedy": "1f5dd", + "shortnames": [":old_key:"], + "category": "objects" + }, + ":key:": { + "uc_base": "1f511", + "uc_output": "1f511", + "uc_match": "1f511", + "uc_greedy": "1f511", + "shortnames": [], + "category": "objects" + }, + ":keycap_ten:": { + "uc_base": "1f51f", + "uc_output": "1f51f", + "uc_match": "1f51f", + "uc_greedy": "1f51f", + "shortnames": [], + "category": "symbols" + }, + ":kimono:": { + "uc_base": "1f458", + "uc_output": "1f458", + "uc_match": "1f458", + "uc_greedy": "1f458", + "shortnames": [], + "category": "people" + }, + ":kiss:": { + "uc_base": "1f48b", + "uc_output": "1f48b", + "uc_match": "1f48b", + "uc_greedy": "1f48b", + "shortnames": [], + "category": "people" + }, + ":kissing:": { + "uc_base": "1f617", + "uc_output": "1f617", + "uc_match": "1f617", + "uc_greedy": "1f617", + "shortnames": [], + "category": "people" + }, + ":kissing_cat:": { + "uc_base": "1f63d", + "uc_output": "1f63d", + "uc_match": "1f63d", + "uc_greedy": "1f63d", + "shortnames": [], + "category": "people" + }, + ":kissing_closed_eyes:": { + "uc_base": "1f61a", + "uc_output": "1f61a", + "uc_match": "1f61a", + "uc_greedy": "1f61a", + "shortnames": [], + "category": "people" + }, + ":kissing_heart:": { + "uc_base": "1f618", + "uc_output": "1f618", + "uc_match": "1f618", + "uc_greedy": "1f618", + "shortnames": [], + "category": "people" + }, + ":kissing_smiling_eyes:": { + "uc_base": "1f619", + "uc_output": "1f619", + "uc_match": "1f619", + "uc_greedy": "1f619", + "shortnames": [], + "category": "people" + }, + ":kiwi:": { + "uc_base": "1f95d", + "uc_output": "1f95d", + "uc_match": "1f95d", + "uc_greedy": "1f95d", + "shortnames": [":kiwifruit:"], + "category": "food" + }, + ":knife:": { + "uc_base": "1f52a", + "uc_output": "1f52a", + "uc_match": "1f52a", + "uc_greedy": "1f52a", + "shortnames": [], + "category": "objects" + }, + ":koala:": { + "uc_base": "1f428", + "uc_output": "1f428", + "uc_match": "1f428", + "uc_greedy": "1f428", + "shortnames": [], + "category": "nature" + }, + ":koko:": { + "uc_base": "1f201", + "uc_output": "1f201", + "uc_match": "1f201", + "uc_greedy": "1f201", + "shortnames": [], + "category": "symbols" + }, + ":label:": { + "uc_base": "1f3f7", + "uc_output": "1f3f7", + "uc_match": "1f3f7-fe0f", + "uc_greedy": "1f3f7-fe0f", + "shortnames": [], + "category": "objects" + }, + ":large_blue_diamond:": { + "uc_base": "1f537", + "uc_output": "1f537", + "uc_match": "1f537", + "uc_greedy": "1f537", + "shortnames": [], + "category": "symbols" + }, + ":large_orange_diamond:": { + "uc_base": "1f536", + "uc_output": "1f536", + "uc_match": "1f536", + "uc_greedy": "1f536", + "shortnames": [], + "category": "symbols" + }, + ":last_quarter_moon:": { + "uc_base": "1f317", + "uc_output": "1f317", + "uc_match": "1f317", + "uc_greedy": "1f317", + "shortnames": [], + "category": "nature" + }, + ":last_quarter_moon_with_face:": { + "uc_base": "1f31c", + "uc_output": "1f31c", + "uc_match": "1f31c-fe0f", + "uc_greedy": "1f31c-fe0f", + "shortnames": [], + "category": "nature" + }, + ":laughing:": { + "uc_base": "1f606", + "uc_output": "1f606", + "uc_match": "1f606", + "uc_greedy": "1f606", + "shortnames": [":satisfied:"], + "category": "people" + }, + ":leaves:": { + "uc_base": "1f343", + "uc_output": "1f343", + "uc_match": "1f343", + "uc_greedy": "1f343", + "shortnames": [], + "category": "nature" + }, + ":ledger:": { + "uc_base": "1f4d2", + "uc_output": "1f4d2", + "uc_match": "1f4d2", + "uc_greedy": "1f4d2", + "shortnames": [], + "category": "objects" + }, + ":left_facing_fist:": { + "uc_base": "1f91b", + "uc_output": "1f91b", + "uc_match": "1f91b", + "uc_greedy": "1f91b", + "shortnames": [":left_fist:"], + "category": "people" + }, + ":left_luggage:": { + "uc_base": "1f6c5", + "uc_output": "1f6c5", + "uc_match": "1f6c5", + "uc_greedy": "1f6c5", + "shortnames": [], + "category": "symbols" + }, + ":lemon:": { + "uc_base": "1f34b", + "uc_output": "1f34b", + "uc_match": "1f34b", + "uc_greedy": "1f34b", + "shortnames": [], + "category": "food" + }, + ":leopard:": { + "uc_base": "1f406", + "uc_output": "1f406", + "uc_match": "1f406", + "uc_greedy": "1f406", + "shortnames": [], + "category": "nature" + }, + ":level_slider:": { + "uc_base": "1f39a", + "uc_output": "1f39a", + "uc_match": "1f39a-fe0f", + "uc_greedy": "1f39a-fe0f", + "shortnames": [], + "category": "objects" + }, + ":levitate:": { + "uc_base": "1f574", + "uc_output": "1f574", + "uc_match": "1f574-fe0f", + "uc_greedy": "1f574-fe0f", + "shortnames": [":man_in_business_suit_levitating:"], + "category": "people" + }, + ":light_rail:": { + "uc_base": "1f688", + "uc_output": "1f688", + "uc_match": "1f688", + "uc_greedy": "1f688", + "shortnames": [], + "category": "travel" + }, + ":link:": { + "uc_base": "1f517", + "uc_output": "1f517", + "uc_match": "1f517", + "uc_greedy": "1f517", + "shortnames": [], + "category": "objects" + }, + ":lion_face:": { + "uc_base": "1f981", + "uc_output": "1f981", + "uc_match": "1f981", + "uc_greedy": "1f981", + "shortnames": [":lion:"], + "category": "nature" + }, + ":lips:": { + "uc_base": "1f444", + "uc_output": "1f444", + "uc_match": "1f444", + "uc_greedy": "1f444", + "shortnames": [], + "category": "people" + }, + ":lipstick:": { + "uc_base": "1f484", + "uc_output": "1f484", + "uc_match": "1f484", + "uc_greedy": "1f484", + "shortnames": [], + "category": "people" + }, + ":lizard:": { + "uc_base": "1f98e", + "uc_output": "1f98e", + "uc_match": "1f98e", + "uc_greedy": "1f98e", + "shortnames": [], + "category": "nature" + }, + ":lock:": { + "uc_base": "1f512", + "uc_output": "1f512", + "uc_match": "1f512-fe0f", + "uc_greedy": "1f512-fe0f", + "shortnames": [], + "category": "objects" + }, + ":lock_with_ink_pen:": { + "uc_base": "1f50f", + "uc_output": "1f50f", + "uc_match": "1f50f", + "uc_greedy": "1f50f", + "shortnames": [], + "category": "objects" + }, + ":lollipop:": { + "uc_base": "1f36d", + "uc_output": "1f36d", + "uc_match": "1f36d", + "uc_greedy": "1f36d", + "shortnames": [], + "category": "food" + }, + ":loud_sound:": { + "uc_base": "1f50a", + "uc_output": "1f50a", + "uc_match": "1f50a", + "uc_greedy": "1f50a", + "shortnames": [], + "category": "symbols" + }, + ":loudspeaker:": { + "uc_base": "1f4e2", + "uc_output": "1f4e2", + "uc_match": "1f4e2", + "uc_greedy": "1f4e2", + "shortnames": [], + "category": "symbols" + }, + ":love_hotel:": { + "uc_base": "1f3e9", + "uc_output": "1f3e9", + "uc_match": "1f3e9", + "uc_greedy": "1f3e9", + "shortnames": [], + "category": "travel" + }, + ":love_letter:": { + "uc_base": "1f48c", + "uc_output": "1f48c", + "uc_match": "1f48c", + "uc_greedy": "1f48c", + "shortnames": [], + "category": "objects" + }, + ":love_you_gesture:": { + "uc_base": "1f91f", + "uc_output": "1f91f", + "uc_match": "1f91f", + "uc_greedy": "1f91f", + "shortnames": [], + "category": "people" + }, + ":low_brightness:": { + "uc_base": "1f505", + "uc_output": "1f505", + "uc_match": "1f505", + "uc_greedy": "1f505", + "shortnames": [], + "category": "symbols" + }, + ":lying_face:": { + "uc_base": "1f925", + "uc_output": "1f925", + "uc_match": "1f925", + "uc_greedy": "1f925", + "shortnames": [":liar:"], + "category": "people" + }, + ":mag:": { + "uc_base": "1f50d", + "uc_output": "1f50d", + "uc_match": "1f50d-fe0f", + "uc_greedy": "1f50d-fe0f", + "shortnames": [], + "category": "objects" + }, + ":mag_right:": { + "uc_base": "1f50e", + "uc_output": "1f50e", + "uc_match": "1f50e", + "uc_greedy": "1f50e", + "shortnames": [], + "category": "objects" + }, + ":mage:": { + "uc_base": "1f9d9", + "uc_output": "1f9d9", + "uc_match": "1f9d9", + "uc_greedy": "1f9d9", + "shortnames": [], + "category": "people" + }, + ":mahjong:": { + "uc_base": "1f004", + "uc_output": "1f004", + "uc_match": "1f004-fe0f", + "uc_greedy": "1f004-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":mailbox:": { + "uc_base": "1f4eb", + "uc_output": "1f4eb", + "uc_match": "1f4eb-fe0f", + "uc_greedy": "1f4eb-fe0f", + "shortnames": [], + "category": "objects" + }, + ":mailbox_closed:": { + "uc_base": "1f4ea", + "uc_output": "1f4ea", + "uc_match": "1f4ea-fe0f", + "uc_greedy": "1f4ea-fe0f", + "shortnames": [], + "category": "objects" + }, + ":mailbox_with_mail:": { + "uc_base": "1f4ec", + "uc_output": "1f4ec", + "uc_match": "1f4ec-fe0f", + "uc_greedy": "1f4ec-fe0f", + "shortnames": [], + "category": "objects" + }, + ":mailbox_with_no_mail:": { + "uc_base": "1f4ed", + "uc_output": "1f4ed", + "uc_match": "1f4ed-fe0f", + "uc_greedy": "1f4ed-fe0f", + "shortnames": [], + "category": "objects" + }, + ":man:": { + "uc_base": "1f468", + "uc_output": "1f468", + "uc_match": "1f468", + "uc_greedy": "1f468", + "shortnames": [], + "category": "people" + }, + ":man_dancing:": { + "uc_base": "1f57a", + "uc_output": "1f57a", + "uc_match": "1f57a", + "uc_greedy": "1f57a", + "shortnames": [":male_dancer:"], + "category": "people" + }, + ":man_in_tuxedo:": { + "uc_base": "1f935", + "uc_output": "1f935", + "uc_match": "1f935", + "uc_greedy": "1f935", + "shortnames": [], + "category": "people" + }, + ":man_with_chinese_cap:": { + "uc_base": "1f472", + "uc_output": "1f472", + "uc_match": "1f472", + "uc_greedy": "1f472", + "shortnames": [":man_with_gua_pi_mao:"], + "category": "people" + }, + ":mans_shoe:": { + "uc_base": "1f45e", + "uc_output": "1f45e", + "uc_match": "1f45e", + "uc_greedy": "1f45e", + "shortnames": [], + "category": "people" + }, + ":map:": { + "uc_base": "1f5fa", + "uc_output": "1f5fa", + "uc_match": "1f5fa-fe0f", + "uc_greedy": "1f5fa-fe0f", + "shortnames": [":world_map:"], + "category": "travel" + }, + ":maple_leaf:": { + "uc_base": "1f341", + "uc_output": "1f341", + "uc_match": "1f341", + "uc_greedy": "1f341", + "shortnames": [], + "category": "nature" + }, + ":martial_arts_uniform:": { + "uc_base": "1f94b", + "uc_output": "1f94b", + "uc_match": "1f94b", + "uc_greedy": "1f94b", + "shortnames": [":karate_uniform:"], + "category": "activity" + }, + ":mask:": { + "uc_base": "1f637", + "uc_output": "1f637", + "uc_match": "1f637", + "uc_greedy": "1f637", + "shortnames": [], + "category": "people" + }, + ":meat_on_bone:": { + "uc_base": "1f356", + "uc_output": "1f356", + "uc_match": "1f356", + "uc_greedy": "1f356", + "shortnames": [], + "category": "food" + }, + ":medal:": { + "uc_base": "1f3c5", + "uc_output": "1f3c5", + "uc_match": "1f3c5", + "uc_greedy": "1f3c5", + "shortnames": [":sports_medal:"], + "category": "activity" + }, + ":mega:": { + "uc_base": "1f4e3", + "uc_output": "1f4e3", + "uc_match": "1f4e3", + "uc_greedy": "1f4e3", + "shortnames": [], + "category": "symbols" + }, + ":melon:": { + "uc_base": "1f348", + "uc_output": "1f348", + "uc_match": "1f348", + "uc_greedy": "1f348", + "shortnames": [], + "category": "food" + }, + ":menorah:": { + "uc_base": "1f54e", + "uc_output": "1f54e", + "uc_match": "1f54e", + "uc_greedy": "1f54e", + "shortnames": [], + "category": "symbols" + }, + ":mens:": { + "uc_base": "1f6b9", + "uc_output": "1f6b9", + "uc_match": "1f6b9-fe0f", + "uc_greedy": "1f6b9-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":merperson:": { + "uc_base": "1f9dc", + "uc_output": "1f9dc", + "uc_match": "1f9dc", + "uc_greedy": "1f9dc", + "shortnames": [], + "category": "people" + }, + ":metal:": { + "uc_base": "1f918", + "uc_output": "1f918", + "uc_match": "1f918", + "uc_greedy": "1f918", + "shortnames": [":sign_of_the_horns:"], + "category": "people" + }, + ":metro:": { + "uc_base": "1f687", + "uc_output": "1f687", + "uc_match": "1f687-fe0f", + "uc_greedy": "1f687-fe0f", + "shortnames": [], + "category": "travel" + }, + ":microphone2:": { + "uc_base": "1f399", + "uc_output": "1f399", + "uc_match": "1f399-fe0f", + "uc_greedy": "1f399-fe0f", + "shortnames": [":studio_microphone:"], + "category": "objects" + }, + ":microphone:": { + "uc_base": "1f3a4", + "uc_output": "1f3a4", + "uc_match": "1f3a4", + "uc_greedy": "1f3a4", + "shortnames": [], + "category": "activity" + }, + ":microscope:": { + "uc_base": "1f52c", + "uc_output": "1f52c", + "uc_match": "1f52c", + "uc_greedy": "1f52c", + "shortnames": [], + "category": "objects" + }, + ":middle_finger:": { + "uc_base": "1f595", + "uc_output": "1f595", + "uc_match": "1f595", + "uc_greedy": "1f595", + "shortnames": [":reversed_hand_with_middle_finger_extended:"], + "category": "people" + }, + ":military_medal:": { + "uc_base": "1f396", + "uc_output": "1f396", + "uc_match": "1f396-fe0f", + "uc_greedy": "1f396-fe0f", + "shortnames": [], + "category": "activity" + }, + ":milk:": { + "uc_base": "1f95b", + "uc_output": "1f95b", + "uc_match": "1f95b", + "uc_greedy": "1f95b", + "shortnames": [":glass_of_milk:"], + "category": "food" + }, + ":milky_way:": { + "uc_base": "1f30c", + "uc_output": "1f30c", + "uc_match": "1f30c", + "uc_greedy": "1f30c", + "shortnames": [], + "category": "travel" + }, + ":minibus:": { + "uc_base": "1f690", + "uc_output": "1f690", + "uc_match": "1f690", + "uc_greedy": "1f690", + "shortnames": [], + "category": "travel" + }, + ":minidisc:": { + "uc_base": "1f4bd", + "uc_output": "1f4bd", + "uc_match": "1f4bd", + "uc_greedy": "1f4bd", + "shortnames": [], + "category": "objects" + }, + ":mobile_phone_off:": { + "uc_base": "1f4f4", + "uc_output": "1f4f4", + "uc_match": "1f4f4", + "uc_greedy": "1f4f4", + "shortnames": [], + "category": "symbols" + }, + ":money_mouth:": { + "uc_base": "1f911", + "uc_output": "1f911", + "uc_match": "1f911", + "uc_greedy": "1f911", + "shortnames": [":money_mouth_face:"], + "category": "people" + }, + ":money_with_wings:": { + "uc_base": "1f4b8", + "uc_output": "1f4b8", + "uc_match": "1f4b8", + "uc_greedy": "1f4b8", + "shortnames": [], + "category": "objects" + }, + ":moneybag:": { + "uc_base": "1f4b0", + "uc_output": "1f4b0", + "uc_match": "1f4b0-fe0f", + "uc_greedy": "1f4b0-fe0f", + "shortnames": [], + "category": "objects" + }, + ":monkey:": { + "uc_base": "1f412", + "uc_output": "1f412", + "uc_match": "1f412", + "uc_greedy": "1f412", + "shortnames": [], + "category": "nature" + }, + ":monkey_face:": { + "uc_base": "1f435", + "uc_output": "1f435", + "uc_match": "1f435", + "uc_greedy": "1f435", + "shortnames": [], + "category": "nature" + }, + ":monorail:": { + "uc_base": "1f69d", + "uc_output": "1f69d", + "uc_match": "1f69d", + "uc_greedy": "1f69d", + "shortnames": [], + "category": "travel" + }, + ":mortar_board:": { + "uc_base": "1f393", + "uc_output": "1f393", + "uc_match": "1f393-fe0f", + "uc_greedy": "1f393-fe0f", + "shortnames": [], + "category": "people" + }, + ":mosque:": { + "uc_base": "1f54c", + "uc_output": "1f54c", + "uc_match": "1f54c", + "uc_greedy": "1f54c", + "shortnames": [], + "category": "travel" + }, + ":motor_scooter:": { + "uc_base": "1f6f5", + "uc_output": "1f6f5", + "uc_match": "1f6f5", + "uc_greedy": "1f6f5", + "shortnames": [":motorbike:"], + "category": "travel" + }, + ":motorboat:": { + "uc_base": "1f6e5", + "uc_output": "1f6e5", + "uc_match": "1f6e5-fe0f", + "uc_greedy": "1f6e5-fe0f", + "shortnames": [], + "category": "travel" + }, + ":motorcycle:": { + "uc_base": "1f3cd", + "uc_output": "1f3cd", + "uc_match": "1f3cd-fe0f", + "uc_greedy": "1f3cd-fe0f", + "shortnames": [":racing_motorcycle:"], + "category": "travel" + }, + ":motorway:": { + "uc_base": "1f6e3", + "uc_output": "1f6e3", + "uc_match": "1f6e3-fe0f", + "uc_greedy": "1f6e3-fe0f", + "shortnames": [], + "category": "travel" + }, + ":mount_fuji:": { + "uc_base": "1f5fb", + "uc_output": "1f5fb", + "uc_match": "1f5fb", + "uc_greedy": "1f5fb", + "shortnames": [], + "category": "travel" + }, + ":mountain_cableway:": { + "uc_base": "1f6a0", + "uc_output": "1f6a0", + "uc_match": "1f6a0", + "uc_greedy": "1f6a0", + "shortnames": [], + "category": "travel" + }, + ":mountain_railway:": { + "uc_base": "1f69e", + "uc_output": "1f69e", + "uc_match": "1f69e", + "uc_greedy": "1f69e", + "shortnames": [], + "category": "travel" + }, + ":mountain_snow:": { + "uc_base": "1f3d4", + "uc_output": "1f3d4", + "uc_match": "1f3d4-fe0f", + "uc_greedy": "1f3d4-fe0f", + "shortnames": [":snow_capped_mountain:"], + "category": "travel" + }, + ":mouse2:": { + "uc_base": "1f401", + "uc_output": "1f401", + "uc_match": "1f401", + "uc_greedy": "1f401", + "shortnames": [], + "category": "nature" + }, + ":mouse:": { + "uc_base": "1f42d", + "uc_output": "1f42d", + "uc_match": "1f42d", + "uc_greedy": "1f42d", + "shortnames": [], + "category": "nature" + }, + ":mouse_three_button:": { + "uc_base": "1f5b1", + "uc_output": "1f5b1", + "uc_match": "1f5b1-fe0f", + "uc_greedy": "1f5b1-fe0f", + "shortnames": [":three_button_mouse:"], + "category": "objects" + }, + ":movie_camera:": { + "uc_base": "1f3a5", + "uc_output": "1f3a5", + "uc_match": "1f3a5", + "uc_greedy": "1f3a5", + "shortnames": [], + "category": "objects" + }, + ":moyai:": { + "uc_base": "1f5ff", + "uc_output": "1f5ff", + "uc_match": "1f5ff", + "uc_greedy": "1f5ff", + "shortnames": [], + "category": "travel" + }, + ":mrs_claus:": { + "uc_base": "1f936", + "uc_output": "1f936", + "uc_match": "1f936", + "uc_greedy": "1f936", + "shortnames": [":mother_christmas:"], + "category": "people" + }, + ":muscle:": { + "uc_base": "1f4aa", + "uc_output": "1f4aa", + "uc_match": "1f4aa", + "uc_greedy": "1f4aa", + "shortnames": [], + "category": "people" + }, + ":mushroom:": { + "uc_base": "1f344", + "uc_output": "1f344", + "uc_match": "1f344", + "uc_greedy": "1f344", + "shortnames": [], + "category": "nature" + }, + ":musical_keyboard:": { + "uc_base": "1f3b9", + "uc_output": "1f3b9", + "uc_match": "1f3b9", + "uc_greedy": "1f3b9", + "shortnames": [], + "category": "activity" + }, + ":musical_note:": { + "uc_base": "1f3b5", + "uc_output": "1f3b5", + "uc_match": "1f3b5", + "uc_greedy": "1f3b5", + "shortnames": [], + "category": "symbols" + }, + ":musical_score:": { + "uc_base": "1f3bc", + "uc_output": "1f3bc", + "uc_match": "1f3bc", + "uc_greedy": "1f3bc", + "shortnames": [], + "category": "activity" + }, + ":mute:": { + "uc_base": "1f507", + "uc_output": "1f507", + "uc_match": "1f507", + "uc_greedy": "1f507", + "shortnames": [], + "category": "symbols" + }, + ":nail_care:": { + "uc_base": "1f485", + "uc_output": "1f485", + "uc_match": "1f485", + "uc_greedy": "1f485", + "shortnames": [], + "category": "people" + }, + ":name_badge:": { + "uc_base": "1f4db", + "uc_output": "1f4db", + "uc_match": "1f4db", + "uc_greedy": "1f4db", + "shortnames": [], + "category": "symbols" + }, + ":nauseated_face:": { + "uc_base": "1f922", + "uc_output": "1f922", + "uc_match": "1f922", + "uc_greedy": "1f922", + "shortnames": [":sick:"], + "category": "people" + }, + ":necktie:": { + "uc_base": "1f454", + "uc_output": "1f454", + "uc_match": "1f454", + "uc_greedy": "1f454", + "shortnames": [], + "category": "people" + }, + ":nerd:": { + "uc_base": "1f913", + "uc_output": "1f913", + "uc_match": "1f913", + "uc_greedy": "1f913", + "shortnames": [":nerd_face:"], + "category": "people" + }, + ":neutral_face:": { + "uc_base": "1f610", + "uc_output": "1f610", + "uc_match": "1f610-fe0f", + "uc_greedy": "1f610-fe0f", + "shortnames": [], + "category": "people" + }, + ":new:": { + "uc_base": "1f195", + "uc_output": "1f195", + "uc_match": "1f195", + "uc_greedy": "1f195", + "shortnames": [], + "category": "symbols" + }, + ":new_moon:": { + "uc_base": "1f311", + "uc_output": "1f311", + "uc_match": "1f311", + "uc_greedy": "1f311", + "shortnames": [], + "category": "nature" + }, + ":new_moon_with_face:": { + "uc_base": "1f31a", + "uc_output": "1f31a", + "uc_match": "1f31a", + "uc_greedy": "1f31a", + "shortnames": [], + "category": "nature" + }, + ":newspaper2:": { + "uc_base": "1f5de", + "uc_output": "1f5de", + "uc_match": "1f5de-fe0f", + "uc_greedy": "1f5de-fe0f", + "shortnames": [":rolled_up_newspaper:"], + "category": "objects" + }, + ":newspaper:": { + "uc_base": "1f4f0", + "uc_output": "1f4f0", + "uc_match": "1f4f0", + "uc_greedy": "1f4f0", + "shortnames": [], + "category": "objects" + }, + ":ng:": { + "uc_base": "1f196", + "uc_output": "1f196", + "uc_match": "1f196", + "uc_greedy": "1f196", + "shortnames": [], + "category": "symbols" + }, + ":night_with_stars:": { + "uc_base": "1f303", + "uc_output": "1f303", + "uc_match": "1f303", + "uc_greedy": "1f303", + "shortnames": [], + "category": "travel" + }, + ":no_bell:": { + "uc_base": "1f515", + "uc_output": "1f515", + "uc_match": "1f515", + "uc_greedy": "1f515", + "shortnames": [], + "category": "symbols" + }, + ":no_bicycles:": { + "uc_base": "1f6b3", + "uc_output": "1f6b3", + "uc_match": "1f6b3", + "uc_greedy": "1f6b3", + "shortnames": [], + "category": "symbols" + }, + ":no_entry_sign:": { + "uc_base": "1f6ab", + "uc_output": "1f6ab", + "uc_match": "1f6ab", + "uc_greedy": "1f6ab", + "shortnames": [], + "category": "symbols" + }, + ":no_mobile_phones:": { + "uc_base": "1f4f5", + "uc_output": "1f4f5", + "uc_match": "1f4f5", + "uc_greedy": "1f4f5", + "shortnames": [], + "category": "symbols" + }, + ":no_mouth:": { + "uc_base": "1f636", + "uc_output": "1f636", + "uc_match": "1f636", + "uc_greedy": "1f636", + "shortnames": [], + "category": "people" + }, + ":no_pedestrians:": { + "uc_base": "1f6b7", + "uc_output": "1f6b7", + "uc_match": "1f6b7", + "uc_greedy": "1f6b7", + "shortnames": [], + "category": "symbols" + }, + ":no_smoking:": { + "uc_base": "1f6ad", + "uc_output": "1f6ad", + "uc_match": "1f6ad-fe0f", + "uc_greedy": "1f6ad-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":non-potable_water:": { + "uc_base": "1f6b1", + "uc_output": "1f6b1", + "uc_match": "1f6b1", + "uc_greedy": "1f6b1", + "shortnames": [], + "category": "symbols" + }, + ":nose:": { + "uc_base": "1f443", + "uc_output": "1f443", + "uc_match": "1f443", + "uc_greedy": "1f443", + "shortnames": [], + "category": "people" + }, + ":notebook:": { + "uc_base": "1f4d3", + "uc_output": "1f4d3", + "uc_match": "1f4d3", + "uc_greedy": "1f4d3", + "shortnames": [], + "category": "objects" + }, + ":notebook_with_decorative_cover:": { + "uc_base": "1f4d4", + "uc_output": "1f4d4", + "uc_match": "1f4d4", + "uc_greedy": "1f4d4", + "shortnames": [], + "category": "objects" + }, + ":notepad_spiral:": { + "uc_base": "1f5d2", + "uc_output": "1f5d2", + "uc_match": "1f5d2-fe0f", + "uc_greedy": "1f5d2-fe0f", + "shortnames": [":spiral_note_pad:"], + "category": "objects" + }, + ":notes:": { + "uc_base": "1f3b6", + "uc_output": "1f3b6", + "uc_match": "1f3b6", + "uc_greedy": "1f3b6", + "shortnames": [], + "category": "symbols" + }, + ":nut_and_bolt:": { + "uc_base": "1f529", + "uc_output": "1f529", + "uc_match": "1f529", + "uc_greedy": "1f529", + "shortnames": [], + "category": "objects" + }, + ":o2:": { + "uc_base": "1f17e", + "uc_output": "1f17e", + "uc_match": "1f17e-fe0f", + "uc_greedy": "1f17e-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":ocean:": { + "uc_base": "1f30a", + "uc_output": "1f30a", + "uc_match": "1f30a", + "uc_greedy": "1f30a", + "shortnames": [], + "category": "nature" + }, + ":octagonal_sign:": { + "uc_base": "1f6d1", + "uc_output": "1f6d1", + "uc_match": "1f6d1", + "uc_greedy": "1f6d1", + "shortnames": [":stop_sign:"], + "category": "symbols" + }, + ":octopus:": { + "uc_base": "1f419", + "uc_output": "1f419", + "uc_match": "1f419", + "uc_greedy": "1f419", + "shortnames": [], + "category": "nature" + }, + ":oden:": { + "uc_base": "1f362", + "uc_output": "1f362", + "uc_match": "1f362", + "uc_greedy": "1f362", + "shortnames": [], + "category": "food" + }, + ":office:": { + "uc_base": "1f3e2", + "uc_output": "1f3e2", + "uc_match": "1f3e2", + "uc_greedy": "1f3e2", + "shortnames": [], + "category": "travel" + }, + ":oil:": { + "uc_base": "1f6e2", + "uc_output": "1f6e2", + "uc_match": "1f6e2-fe0f", + "uc_greedy": "1f6e2-fe0f", + "shortnames": [":oil_drum:"], + "category": "objects" + }, + ":ok:": { + "uc_base": "1f197", + "uc_output": "1f197", + "uc_match": "1f197", + "uc_greedy": "1f197", + "shortnames": [], + "category": "symbols" + }, + ":ok_hand:": { + "uc_base": "1f44c", + "uc_output": "1f44c", + "uc_match": "1f44c", + "uc_greedy": "1f44c", + "shortnames": [], + "category": "people" + }, + ":older_adult:": { + "uc_base": "1f9d3", + "uc_output": "1f9d3", + "uc_match": "1f9d3", + "uc_greedy": "1f9d3", + "shortnames": [], + "category": "people" + }, + ":older_man:": { + "uc_base": "1f474", + "uc_output": "1f474", + "uc_match": "1f474", + "uc_greedy": "1f474", + "shortnames": [], + "category": "people" + }, + ":older_woman:": { + "uc_base": "1f475", + "uc_output": "1f475", + "uc_match": "1f475", + "uc_greedy": "1f475", + "shortnames": [":grandma:"], + "category": "people" + }, + ":om_symbol:": { + "uc_base": "1f549", + "uc_output": "1f549", + "uc_match": "1f549-fe0f", + "uc_greedy": "1f549", + "shortnames": [], + "category": "symbols" + }, + ":on:": { + "uc_base": "1f51b", + "uc_output": "1f51b", + "uc_match": "1f51b", + "uc_greedy": "1f51b", + "shortnames": [], + "category": "symbols" + }, + ":oncoming_automobile:": { + "uc_base": "1f698", + "uc_output": "1f698", + "uc_match": "1f698-fe0f", + "uc_greedy": "1f698-fe0f", + "shortnames": [], + "category": "travel" + }, + ":oncoming_bus:": { + "uc_base": "1f68d", + "uc_output": "1f68d", + "uc_match": "1f68d-fe0f", + "uc_greedy": "1f68d-fe0f", + "shortnames": [], + "category": "travel" + }, + ":oncoming_police_car:": { + "uc_base": "1f694", + "uc_output": "1f694", + "uc_match": "1f694-fe0f", + "uc_greedy": "1f694-fe0f", + "shortnames": [], + "category": "travel" + }, + ":oncoming_taxi:": { + "uc_base": "1f696", + "uc_output": "1f696", + "uc_match": "1f696", + "uc_greedy": "1f696", + "shortnames": [], + "category": "travel" + }, + ":open_file_folder:": { + "uc_base": "1f4c2", + "uc_output": "1f4c2", + "uc_match": "1f4c2", + "uc_greedy": "1f4c2", + "shortnames": [], + "category": "objects" + }, + ":open_hands:": { + "uc_base": "1f450", + "uc_output": "1f450", + "uc_match": "1f450", + "uc_greedy": "1f450", + "shortnames": [], + "category": "people" + }, + ":open_mouth:": { + "uc_base": "1f62e", + "uc_output": "1f62e", + "uc_match": "1f62e", + "uc_greedy": "1f62e", + "shortnames": [], + "category": "people" + }, + ":orange_book:": { + "uc_base": "1f4d9", + "uc_output": "1f4d9", + "uc_match": "1f4d9", + "uc_greedy": "1f4d9", + "shortnames": [], + "category": "objects" + }, + ":orange_heart:": { + "uc_base": "1f9e1", + "uc_output": "1f9e1", + "uc_match": "1f9e1", + "uc_greedy": "1f9e1", + "shortnames": [], + "category": "symbols" + }, + ":outbox_tray:": { + "uc_base": "1f4e4", + "uc_output": "1f4e4", + "uc_match": "1f4e4-fe0f", + "uc_greedy": "1f4e4-fe0f", + "shortnames": [], + "category": "objects" + }, + ":owl:": { + "uc_base": "1f989", + "uc_output": "1f989", + "uc_match": "1f989", + "uc_greedy": "1f989", + "shortnames": [], + "category": "nature" + }, + ":ox:": { + "uc_base": "1f402", + "uc_output": "1f402", + "uc_match": "1f402", + "uc_greedy": "1f402", + "shortnames": [], + "category": "nature" + }, + ":package:": { + "uc_base": "1f4e6", + "uc_output": "1f4e6", + "uc_match": "1f4e6-fe0f", + "uc_greedy": "1f4e6-fe0f", + "shortnames": [], + "category": "objects" + }, + ":page_facing_up:": { + "uc_base": "1f4c4", + "uc_output": "1f4c4", + "uc_match": "1f4c4", + "uc_greedy": "1f4c4", + "shortnames": [], + "category": "objects" + }, + ":page_with_curl:": { + "uc_base": "1f4c3", + "uc_output": "1f4c3", + "uc_match": "1f4c3", + "uc_greedy": "1f4c3", + "shortnames": [], + "category": "objects" + }, + ":pager:": { + "uc_base": "1f4df", + "uc_output": "1f4df", + "uc_match": "1f4df-fe0f", + "uc_greedy": "1f4df-fe0f", + "shortnames": [], + "category": "objects" + }, + ":paintbrush:": { + "uc_base": "1f58c", + "uc_output": "1f58c", + "uc_match": "1f58c-fe0f", + "uc_greedy": "1f58c-fe0f", + "shortnames": [":lower_left_paintbrush:"], + "category": "objects" + }, + ":palm_tree:": { + "uc_base": "1f334", + "uc_output": "1f334", + "uc_match": "1f334", + "uc_greedy": "1f334", + "shortnames": [], + "category": "nature" + }, + ":palms_up_together:": { + "uc_base": "1f932", + "uc_output": "1f932", + "uc_match": "1f932", + "uc_greedy": "1f932", + "shortnames": [], + "category": "people" + }, + ":pancakes:": { + "uc_base": "1f95e", + "uc_output": "1f95e", + "uc_match": "1f95e", + "uc_greedy": "1f95e", + "shortnames": [], + "category": "food" + }, + ":panda_face:": { + "uc_base": "1f43c", + "uc_output": "1f43c", + "uc_match": "1f43c", + "uc_greedy": "1f43c", + "shortnames": [], + "category": "nature" + }, + ":paperclip:": { + "uc_base": "1f4ce", + "uc_output": "1f4ce", + "uc_match": "1f4ce", + "uc_greedy": "1f4ce", + "shortnames": [], + "category": "objects" + }, + ":paperclips:": { + "uc_base": "1f587", + "uc_output": "1f587", + "uc_match": "1f587-fe0f", + "uc_greedy": "1f587-fe0f", + "shortnames": [":linked_paperclips:"], + "category": "objects" + }, + ":park:": { + "uc_base": "1f3de", + "uc_output": "1f3de", + "uc_match": "1f3de-fe0f", + "uc_greedy": "1f3de-fe0f", + "shortnames": [":national_park:"], + "category": "travel" + }, + ":parking:": { + "uc_base": "1f17f", + "uc_output": "1f17f", + "uc_match": "1f17f-fe0f", + "uc_greedy": "1f17f-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":passport_control:": { + "uc_base": "1f6c2", + "uc_output": "1f6c2", + "uc_match": "1f6c2", + "uc_greedy": "1f6c2", + "shortnames": [], + "category": "symbols" + }, + ":peach:": { + "uc_base": "1f351", + "uc_output": "1f351", + "uc_match": "1f351", + "uc_greedy": "1f351", + "shortnames": [], + "category": "food" + }, + ":peanuts:": { + "uc_base": "1f95c", + "uc_output": "1f95c", + "uc_match": "1f95c", + "uc_greedy": "1f95c", + "shortnames": [":shelled_peanut:"], + "category": "food" + }, + ":pear:": { + "uc_base": "1f350", + "uc_output": "1f350", + "uc_match": "1f350", + "uc_greedy": "1f350", + "shortnames": [], + "category": "food" + }, + ":pen_ballpoint:": { + "uc_base": "1f58a", + "uc_output": "1f58a", + "uc_match": "1f58a-fe0f", + "uc_greedy": "1f58a-fe0f", + "shortnames": [":lower_left_ballpoint_pen:"], + "category": "objects" + }, + ":pen_fountain:": { + "uc_base": "1f58b", + "uc_output": "1f58b", + "uc_match": "1f58b-fe0f", + "uc_greedy": "1f58b-fe0f", + "shortnames": [":lower_left_fountain_pen:"], + "category": "objects" + }, + ":pencil:": { + "uc_base": "1f4dd", + "uc_output": "1f4dd", + "uc_match": "1f4dd", + "uc_greedy": "1f4dd", + "shortnames": [":memo:"], + "category": "objects" + }, + ":penguin:": { + "uc_base": "1f427", + "uc_output": "1f427", + "uc_match": "1f427", + "uc_greedy": "1f427", + "shortnames": [], + "category": "nature" + }, + ":pensive:": { + "uc_base": "1f614", + "uc_output": "1f614", + "uc_match": "1f614", + "uc_greedy": "1f614", + "shortnames": [], + "category": "people" + }, + ":people_with_bunny_ears_partying:": { + "uc_base": "1f46f", + "uc_output": "1f46f", + "uc_match": "1f46f", + "uc_greedy": "1f46f", + "shortnames": [":dancers:"], + "category": "people" + }, + ":people_wrestling:": { + "uc_base": "1f93c", + "uc_output": "1f93c", + "uc_match": "1f93c", + "uc_greedy": "1f93c", + "shortnames": [":wrestlers:", ":wrestling:"], + "category": "activity" + }, + ":performing_arts:": { + "uc_base": "1f3ad", + "uc_output": "1f3ad", + "uc_match": "1f3ad-fe0f", + "uc_greedy": "1f3ad-fe0f", + "shortnames": [], + "category": "activity" + }, + ":persevere:": { + "uc_base": "1f623", + "uc_output": "1f623", + "uc_match": "1f623", + "uc_greedy": "1f623", + "shortnames": [], + "category": "people" + }, + ":person_biking:": { + "uc_base": "1f6b4", + "uc_output": "1f6b4", + "uc_match": "1f6b4", + "uc_greedy": "1f6b4", + "shortnames": [":bicyclist:"], + "category": "activity" + }, + ":person_bowing:": { + "uc_base": "1f647", + "uc_output": "1f647", + "uc_match": "1f647", + "uc_greedy": "1f647", + "shortnames": [":bow:"], + "category": "people" + }, + ":person_climbing:": { + "uc_base": "1f9d7", + "uc_output": "1f9d7", + "uc_match": "1f9d7", + "uc_greedy": "1f9d7", + "shortnames": [], + "category": "activity" + }, + ":person_doing_cartwheel:": { + "uc_base": "1f938", + "uc_output": "1f938", + "uc_match": "1f938", + "uc_greedy": "1f938", + "shortnames": [":cartwheel:"], + "category": "activity" + }, + ":person_facepalming:": { + "uc_base": "1f926", + "uc_output": "1f926", + "uc_match": "1f926", + "uc_greedy": "1f926", + "shortnames": [":face_palm:", ":facepalm:"], + "category": "people" + }, + ":person_fencing:": { + "uc_base": "1f93a", + "uc_output": "1f93a", + "uc_match": "1f93a", + "uc_greedy": "1f93a", + "shortnames": [":fencer:", ":fencing:"], + "category": "activity" + }, + ":person_frowning:": { + "uc_base": "1f64d", + "uc_output": "1f64d", + "uc_match": "1f64d", + "uc_greedy": "1f64d", + "shortnames": [], + "category": "people" + }, + ":person_gesturing_no:": { + "uc_base": "1f645", + "uc_output": "1f645", + "uc_match": "1f645", + "uc_greedy": "1f645", + "shortnames": [":no_good:"], + "category": "people" + }, + ":person_gesturing_ok:": { + "uc_base": "1f646", + "uc_output": "1f646", + "uc_match": "1f646", + "uc_greedy": "1f646", + "shortnames": [":ok_woman:"], + "category": "people" + }, + ":person_getting_haircut:": { + "uc_base": "1f487", + "uc_output": "1f487", + "uc_match": "1f487", + "uc_greedy": "1f487", + "shortnames": [":haircut:"], + "category": "people" + }, + ":person_getting_massage:": { + "uc_base": "1f486", + "uc_output": "1f486", + "uc_match": "1f486", + "uc_greedy": "1f486", + "shortnames": [":massage:"], + "category": "people" + }, + ":person_golfing:": { + "uc_base": "1f3cc", + "uc_output": "1f3cc", + "uc_match": "1f3cc-fe0f", + "uc_greedy": "1f3cc-fe0f", + "shortnames": [":golfer:"], + "category": "activity" + }, + ":person_in_lotus_position:": { + "uc_base": "1f9d8", + "uc_output": "1f9d8", + "uc_match": "1f9d8", + "uc_greedy": "1f9d8", + "shortnames": [], + "category": "activity" + }, + ":person_in_steamy_room:": { + "uc_base": "1f9d6", + "uc_output": "1f9d6", + "uc_match": "1f9d6", + "uc_greedy": "1f9d6", + "shortnames": [], + "category": "people" + }, + ":person_juggling:": { + "uc_base": "1f939", + "uc_output": "1f939", + "uc_match": "1f939", + "uc_greedy": "1f939", + "shortnames": [":juggling:", ":juggler:"], + "category": "activity" + }, + ":person_lifting_weights:": { + "uc_base": "1f3cb", + "uc_output": "1f3cb", + "uc_match": "1f3cb-fe0f", + "uc_greedy": "1f3cb-fe0f", + "shortnames": [":lifter:", ":weight_lifter:"], + "category": "activity" + }, + ":person_mountain_biking:": { + "uc_base": "1f6b5", + "uc_output": "1f6b5", + "uc_match": "1f6b5", + "uc_greedy": "1f6b5", + "shortnames": [":mountain_bicyclist:"], + "category": "activity" + }, + ":person_playing_handball:": { + "uc_base": "1f93e", + "uc_output": "1f93e", + "uc_match": "1f93e", + "uc_greedy": "1f93e", + "shortnames": [":handball:"], + "category": "activity" + }, + ":person_playing_water_polo:": { + "uc_base": "1f93d", + "uc_output": "1f93d", + "uc_match": "1f93d", + "uc_greedy": "1f93d", + "shortnames": [":water_polo:"], + "category": "activity" + }, + ":person_pouting:": { + "uc_base": "1f64e", + "uc_output": "1f64e", + "uc_match": "1f64e", + "uc_greedy": "1f64e", + "shortnames": [":person_with_pouting_face:"], + "category": "people" + }, + ":person_raising_hand:": { + "uc_base": "1f64b", + "uc_output": "1f64b", + "uc_match": "1f64b", + "uc_greedy": "1f64b", + "shortnames": [":raising_hand:"], + "category": "people" + }, + ":person_rowing_boat:": { + "uc_base": "1f6a3", + "uc_output": "1f6a3", + "uc_match": "1f6a3", + "uc_greedy": "1f6a3", + "shortnames": [":rowboat:"], + "category": "activity" + }, + ":person_running:": { + "uc_base": "1f3c3", + "uc_output": "1f3c3", + "uc_match": "1f3c3", + "uc_greedy": "1f3c3", + "shortnames": [":runner:"], + "category": "people" + }, + ":person_shrugging:": { + "uc_base": "1f937", + "uc_output": "1f937", + "uc_match": "1f937", + "uc_greedy": "1f937", + "shortnames": [":shrug:"], + "category": "people" + }, + ":person_surfing:": { + "uc_base": "1f3c4", + "uc_output": "1f3c4", + "uc_match": "1f3c4-fe0f", + "uc_greedy": "1f3c4-fe0f", + "shortnames": [":surfer:"], + "category": "activity" + }, + ":person_swimming:": { + "uc_base": "1f3ca", + "uc_output": "1f3ca", + "uc_match": "1f3ca-fe0f", + "uc_greedy": "1f3ca-fe0f", + "shortnames": [":swimmer:"], + "category": "activity" + }, + ":person_tipping_hand:": { + "uc_base": "1f481", + "uc_output": "1f481", + "uc_match": "1f481", + "uc_greedy": "1f481", + "shortnames": [":information_desk_person:"], + "category": "people" + }, + ":person_walking:": { + "uc_base": "1f6b6", + "uc_output": "1f6b6", + "uc_match": "1f6b6", + "uc_greedy": "1f6b6", + "shortnames": [":walking:"], + "category": "people" + }, + ":person_wearing_turban:": { + "uc_base": "1f473", + "uc_output": "1f473", + "uc_match": "1f473", + "uc_greedy": "1f473", + "shortnames": [":man_with_turban:"], + "category": "people" + }, + ":pie:": { + "uc_base": "1f967", + "uc_output": "1f967", + "uc_match": "1f967", + "uc_greedy": "1f967", + "shortnames": [], + "category": "food" + }, + ":pig2:": { + "uc_base": "1f416", + "uc_output": "1f416", + "uc_match": "1f416", + "uc_greedy": "1f416", + "shortnames": [], + "category": "nature" + }, + ":pig:": { + "uc_base": "1f437", + "uc_output": "1f437", + "uc_match": "1f437", + "uc_greedy": "1f437", + "shortnames": [], + "category": "nature" + }, + ":pig_nose:": { + "uc_base": "1f43d", + "uc_output": "1f43d", + "uc_match": "1f43d", + "uc_greedy": "1f43d", + "shortnames": [], + "category": "nature" + }, + ":pill:": { + "uc_base": "1f48a", + "uc_output": "1f48a", + "uc_match": "1f48a", + "uc_greedy": "1f48a", + "shortnames": [], + "category": "objects" + }, + ":pineapple:": { + "uc_base": "1f34d", + "uc_output": "1f34d", + "uc_match": "1f34d", + "uc_greedy": "1f34d", + "shortnames": [], + "category": "food" + }, + ":ping_pong:": { + "uc_base": "1f3d3", + "uc_output": "1f3d3", + "uc_match": "1f3d3", + "uc_greedy": "1f3d3", + "shortnames": [":table_tennis:"], + "category": "activity" + }, + ":pizza:": { + "uc_base": "1f355", + "uc_output": "1f355", + "uc_match": "1f355", + "uc_greedy": "1f355", + "shortnames": [], + "category": "food" + }, + ":place_of_worship:": { + "uc_base": "1f6d0", + "uc_output": "1f6d0", + "uc_match": "1f6d0", + "uc_greedy": "1f6d0", + "shortnames": [":worship_symbol:"], + "category": "symbols" + }, + ":point_down:": { + "uc_base": "1f447", + "uc_output": "1f447", + "uc_match": "1f447-fe0f", + "uc_greedy": "1f447-fe0f", + "shortnames": [], + "category": "people" + }, + ":point_left:": { + "uc_base": "1f448", + "uc_output": "1f448", + "uc_match": "1f448-fe0f", + "uc_greedy": "1f448-fe0f", + "shortnames": [], + "category": "people" + }, + ":point_right:": { + "uc_base": "1f449", + "uc_output": "1f449", + "uc_match": "1f449-fe0f", + "uc_greedy": "1f449-fe0f", + "shortnames": [], + "category": "people" + }, + ":point_up_2:": { + "uc_base": "1f446", + "uc_output": "1f446", + "uc_match": "1f446-fe0f", + "uc_greedy": "1f446-fe0f", + "shortnames": [], + "category": "people" + }, + ":police_car:": { + "uc_base": "1f693", + "uc_output": "1f693", + "uc_match": "1f693", + "uc_greedy": "1f693", + "shortnames": [], + "category": "travel" + }, + ":police_officer:": { + "uc_base": "1f46e", + "uc_output": "1f46e", + "uc_match": "1f46e", + "uc_greedy": "1f46e", + "shortnames": [":cop:"], + "category": "people" + }, + ":poodle:": { + "uc_base": "1f429", + "uc_output": "1f429", + "uc_match": "1f429", + "uc_greedy": "1f429", + "shortnames": [], + "category": "nature" + }, + ":poop:": { + "uc_base": "1f4a9", + "uc_output": "1f4a9", + "uc_match": "1f4a9", + "uc_greedy": "1f4a9", + "shortnames": [":shit:", ":hankey:", ":poo:"], + "category": "people" + }, + ":popcorn:": { + "uc_base": "1f37f", + "uc_output": "1f37f", + "uc_match": "1f37f", + "uc_greedy": "1f37f", + "shortnames": [], + "category": "food" + }, + ":post_office:": { + "uc_base": "1f3e3", + "uc_output": "1f3e3", + "uc_match": "1f3e3", + "uc_greedy": "1f3e3", + "shortnames": [], + "category": "travel" + }, + ":postal_horn:": { + "uc_base": "1f4ef", + "uc_output": "1f4ef", + "uc_match": "1f4ef", + "uc_greedy": "1f4ef", + "shortnames": [], + "category": "objects" + }, + ":postbox:": { + "uc_base": "1f4ee", + "uc_output": "1f4ee", + "uc_match": "1f4ee", + "uc_greedy": "1f4ee", + "shortnames": [], + "category": "objects" + }, + ":potable_water:": { + "uc_base": "1f6b0", + "uc_output": "1f6b0", + "uc_match": "1f6b0", + "uc_greedy": "1f6b0", + "shortnames": [], + "category": "objects" + }, + ":potato:": { + "uc_base": "1f954", + "uc_output": "1f954", + "uc_match": "1f954", + "uc_greedy": "1f954", + "shortnames": [], + "category": "food" + }, + ":pouch:": { + "uc_base": "1f45d", + "uc_output": "1f45d", + "uc_match": "1f45d", + "uc_greedy": "1f45d", + "shortnames": [], + "category": "people" + }, + ":poultry_leg:": { + "uc_base": "1f357", + "uc_output": "1f357", + "uc_match": "1f357", + "uc_greedy": "1f357", + "shortnames": [], + "category": "food" + }, + ":pound:": { + "uc_base": "1f4b7", + "uc_output": "1f4b7", + "uc_match": "1f4b7", + "uc_greedy": "1f4b7", + "shortnames": [], + "category": "objects" + }, + ":pouting_cat:": { + "uc_base": "1f63e", + "uc_output": "1f63e", + "uc_match": "1f63e", + "uc_greedy": "1f63e", + "shortnames": [], + "category": "people" + }, + ":pray:": { + "uc_base": "1f64f", + "uc_output": "1f64f", + "uc_match": "1f64f", + "uc_greedy": "1f64f", + "shortnames": [], + "category": "people" + }, + ":prayer_beads:": { + "uc_base": "1f4ff", + "uc_output": "1f4ff", + "uc_match": "1f4ff", + "uc_greedy": "1f4ff", + "shortnames": [], + "category": "objects" + }, + ":pregnant_woman:": { + "uc_base": "1f930", + "uc_output": "1f930", + "uc_match": "1f930", + "uc_greedy": "1f930", + "shortnames": [":expecting_woman:"], + "category": "people" + }, + ":pretzel:": { + "uc_base": "1f968", + "uc_output": "1f968", + "uc_match": "1f968", + "uc_greedy": "1f968", + "shortnames": [], + "category": "food" + }, + ":prince:": { + "uc_base": "1f934", + "uc_output": "1f934", + "uc_match": "1f934", + "uc_greedy": "1f934", + "shortnames": [], + "category": "people" + }, + ":princess:": { + "uc_base": "1f478", + "uc_output": "1f478", + "uc_match": "1f478", + "uc_greedy": "1f478", + "shortnames": [], + "category": "people" + }, + ":printer:": { + "uc_base": "1f5a8", + "uc_output": "1f5a8", + "uc_match": "1f5a8-fe0f", + "uc_greedy": "1f5a8-fe0f", + "shortnames": [], + "category": "objects" + }, + ":projector:": { + "uc_base": "1f4fd", + "uc_output": "1f4fd", + "uc_match": "1f4fd-fe0f", + "uc_greedy": "1f4fd-fe0f", + "shortnames": [":film_projector:"], + "category": "objects" + }, + ":punch:": { + "uc_base": "1f44a", + "uc_output": "1f44a", + "uc_match": "1f44a", + "uc_greedy": "1f44a", + "shortnames": [], + "category": "people" + }, + ":purple_heart:": { + "uc_base": "1f49c", + "uc_output": "1f49c", + "uc_match": "1f49c", + "uc_greedy": "1f49c", + "shortnames": [], + "category": "symbols" + }, + ":purse:": { + "uc_base": "1f45b", + "uc_output": "1f45b", + "uc_match": "1f45b", + "uc_greedy": "1f45b", + "shortnames": [], + "category": "people" + }, + ":pushpin:": { + "uc_base": "1f4cc", + "uc_output": "1f4cc", + "uc_match": "1f4cc", + "uc_greedy": "1f4cc", + "shortnames": [], + "category": "objects" + }, + ":put_litter_in_its_place:": { + "uc_base": "1f6ae", + "uc_output": "1f6ae", + "uc_match": "1f6ae", + "uc_greedy": "1f6ae", + "shortnames": [], + "category": "symbols" + }, + ":rabbit2:": { + "uc_base": "1f407", + "uc_output": "1f407", + "uc_match": "1f407", + "uc_greedy": "1f407", + "shortnames": [], + "category": "nature" + }, + ":rabbit:": { + "uc_base": "1f430", + "uc_output": "1f430", + "uc_match": "1f430", + "uc_greedy": "1f430", + "shortnames": [], + "category": "nature" + }, + ":race_car:": { + "uc_base": "1f3ce", + "uc_output": "1f3ce", + "uc_match": "1f3ce-fe0f", + "uc_greedy": "1f3ce-fe0f", + "shortnames": [":racing_car:"], + "category": "travel" + }, + ":racehorse:": { + "uc_base": "1f40e", + "uc_output": "1f40e", + "uc_match": "1f40e", + "uc_greedy": "1f40e", + "shortnames": [], + "category": "nature" + }, + ":radio:": { + "uc_base": "1f4fb", + "uc_output": "1f4fb", + "uc_match": "1f4fb-fe0f", + "uc_greedy": "1f4fb-fe0f", + "shortnames": [], + "category": "objects" + }, + ":radio_button:": { + "uc_base": "1f518", + "uc_output": "1f518", + "uc_match": "1f518", + "uc_greedy": "1f518", + "shortnames": [], + "category": "symbols" + }, + ":rage:": { + "uc_base": "1f621", + "uc_output": "1f621", + "uc_match": "1f621", + "uc_greedy": "1f621", + "shortnames": [], + "category": "people" + }, + ":railway_car:": { + "uc_base": "1f683", + "uc_output": "1f683", + "uc_match": "1f683", + "uc_greedy": "1f683", + "shortnames": [], + "category": "travel" + }, + ":railway_track:": { + "uc_base": "1f6e4", + "uc_output": "1f6e4", + "uc_match": "1f6e4-fe0f", + "uc_greedy": "1f6e4-fe0f", + "shortnames": [":railroad_track:"], + "category": "travel" + }, + ":rainbow:": { + "uc_base": "1f308", + "uc_output": "1f308", + "uc_match": "1f308", + "uc_greedy": "1f308", + "shortnames": [], + "category": "nature" + }, + ":raised_back_of_hand:": { + "uc_base": "1f91a", + "uc_output": "1f91a", + "uc_match": "1f91a", + "uc_greedy": "1f91a", + "shortnames": [":back_of_hand:"], + "category": "people" + }, + ":raised_hands:": { + "uc_base": "1f64c", + "uc_output": "1f64c", + "uc_match": "1f64c", + "uc_greedy": "1f64c", + "shortnames": [], + "category": "people" + }, + ":ram:": { + "uc_base": "1f40f", + "uc_output": "1f40f", + "uc_match": "1f40f", + "uc_greedy": "1f40f", + "shortnames": [], + "category": "nature" + }, + ":ramen:": { + "uc_base": "1f35c", + "uc_output": "1f35c", + "uc_match": "1f35c", + "uc_greedy": "1f35c", + "shortnames": [], + "category": "food" + }, + ":rat:": { + "uc_base": "1f400", + "uc_output": "1f400", + "uc_match": "1f400", + "uc_greedy": "1f400", + "shortnames": [], + "category": "nature" + }, + ":red_car:": { + "uc_base": "1f697", + "uc_output": "1f697", + "uc_match": "1f697", + "uc_greedy": "1f697", + "shortnames": [], + "category": "travel" + }, + ":red_circle:": { + "uc_base": "1f534", + "uc_output": "1f534", + "uc_match": "1f534", + "uc_greedy": "1f534", + "shortnames": [], + "category": "symbols" + }, + ":regional_indicator_a:": { + "uc_base": "1f1e6", + "uc_output": "1f1e6", + "uc_match": "1f1e6", + "uc_greedy": "1f1e6", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_b:": { + "uc_base": "1f1e7", + "uc_output": "1f1e7", + "uc_match": "1f1e7", + "uc_greedy": "1f1e7", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_c:": { + "uc_base": "1f1e8", + "uc_output": "1f1e8", + "uc_match": "1f1e8", + "uc_greedy": "1f1e8", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_d:": { + "uc_base": "1f1e9", + "uc_output": "1f1e9", + "uc_match": "1f1e9", + "uc_greedy": "1f1e9", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_e:": { + "uc_base": "1f1ea", + "uc_output": "1f1ea", + "uc_match": "1f1ea", + "uc_greedy": "1f1ea", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_f:": { + "uc_base": "1f1eb", + "uc_output": "1f1eb", + "uc_match": "1f1eb", + "uc_greedy": "1f1eb", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_g:": { + "uc_base": "1f1ec", + "uc_output": "1f1ec", + "uc_match": "1f1ec", + "uc_greedy": "1f1ec", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_h:": { + "uc_base": "1f1ed", + "uc_output": "1f1ed", + "uc_match": "1f1ed", + "uc_greedy": "1f1ed", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_i:": { + "uc_base": "1f1ee", + "uc_output": "1f1ee", + "uc_match": "1f1ee", + "uc_greedy": "1f1ee", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_j:": { + "uc_base": "1f1ef", + "uc_output": "1f1ef", + "uc_match": "1f1ef", + "uc_greedy": "1f1ef", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_k:": { + "uc_base": "1f1f0", + "uc_output": "1f1f0", + "uc_match": "1f1f0", + "uc_greedy": "1f1f0", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_l:": { + "uc_base": "1f1f1", + "uc_output": "1f1f1", + "uc_match": "1f1f1", + "uc_greedy": "1f1f1", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_m:": { + "uc_base": "1f1f2", + "uc_output": "1f1f2", + "uc_match": "1f1f2", + "uc_greedy": "1f1f2", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_n:": { + "uc_base": "1f1f3", + "uc_output": "1f1f3", + "uc_match": "1f1f3", + "uc_greedy": "1f1f3", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_o:": { + "uc_base": "1f1f4", + "uc_output": "1f1f4", + "uc_match": "1f1f4", + "uc_greedy": "1f1f4", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_p:": { + "uc_base": "1f1f5", + "uc_output": "1f1f5", + "uc_match": "1f1f5", + "uc_greedy": "1f1f5", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_q:": { + "uc_base": "1f1f6", + "uc_output": "1f1f6", + "uc_match": "1f1f6", + "uc_greedy": "1f1f6", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_r:": { + "uc_base": "1f1f7", + "uc_output": "1f1f7", + "uc_match": "1f1f7", + "uc_greedy": "1f1f7", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_s:": { + "uc_base": "1f1f8", + "uc_output": "1f1f8", + "uc_match": "1f1f8", + "uc_greedy": "1f1f8", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_t:": { + "uc_base": "1f1f9", + "uc_output": "1f1f9", + "uc_match": "1f1f9", + "uc_greedy": "1f1f9", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_u:": { + "uc_base": "1f1fa", + "uc_output": "1f1fa", + "uc_match": "1f1fa", + "uc_greedy": "1f1fa", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_v:": { + "uc_base": "1f1fb", + "uc_output": "1f1fb", + "uc_match": "1f1fb", + "uc_greedy": "1f1fb", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_w:": { + "uc_base": "1f1fc", + "uc_output": "1f1fc", + "uc_match": "1f1fc", + "uc_greedy": "1f1fc", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_x:": { + "uc_base": "1f1fd", + "uc_output": "1f1fd", + "uc_match": "1f1fd", + "uc_greedy": "1f1fd", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_y:": { + "uc_base": "1f1fe", + "uc_output": "1f1fe", + "uc_match": "1f1fe", + "uc_greedy": "1f1fe", + "shortnames": [], + "category": "regional" + }, + ":regional_indicator_z:": { + "uc_base": "1f1ff", + "uc_output": "1f1ff", + "uc_match": "1f1ff", + "uc_greedy": "1f1ff", + "shortnames": [], + "category": "regional" + }, + ":relieved:": { + "uc_base": "1f60c", + "uc_output": "1f60c", + "uc_match": "1f60c", + "uc_greedy": "1f60c", + "shortnames": [], + "category": "people" + }, + ":reminder_ribbon:": { + "uc_base": "1f397", + "uc_output": "1f397", + "uc_match": "1f397-fe0f", + "uc_greedy": "1f397-fe0f", + "shortnames": [], + "category": "activity" + }, + ":repeat:": { + "uc_base": "1f501", + "uc_output": "1f501", + "uc_match": "1f501", + "uc_greedy": "1f501", + "shortnames": [], + "category": "symbols" + }, + ":repeat_one:": { + "uc_base": "1f502", + "uc_output": "1f502", + "uc_match": "1f502", + "uc_greedy": "1f502", + "shortnames": [], + "category": "symbols" + }, + ":restroom:": { + "uc_base": "1f6bb", + "uc_output": "1f6bb", + "uc_match": "1f6bb", + "uc_greedy": "1f6bb", + "shortnames": [], + "category": "symbols" + }, + ":revolving_hearts:": { + "uc_base": "1f49e", + "uc_output": "1f49e", + "uc_match": "1f49e", + "uc_greedy": "1f49e", + "shortnames": [], + "category": "symbols" + }, + ":rhino:": { + "uc_base": "1f98f", + "uc_output": "1f98f", + "uc_match": "1f98f", + "uc_greedy": "1f98f", + "shortnames": [":rhinoceros:"], + "category": "nature" + }, + ":ribbon:": { + "uc_base": "1f380", + "uc_output": "1f380", + "uc_match": "1f380", + "uc_greedy": "1f380", + "shortnames": [], + "category": "objects" + }, + ":rice:": { + "uc_base": "1f35a", + "uc_output": "1f35a", + "uc_match": "1f35a", + "uc_greedy": "1f35a", + "shortnames": [], + "category": "food" + }, + ":rice_ball:": { + "uc_base": "1f359", + "uc_output": "1f359", + "uc_match": "1f359", + "uc_greedy": "1f359", + "shortnames": [], + "category": "food" + }, + ":rice_cracker:": { + "uc_base": "1f358", + "uc_output": "1f358", + "uc_match": "1f358", + "uc_greedy": "1f358", + "shortnames": [], + "category": "food" + }, + ":rice_scene:": { + "uc_base": "1f391", + "uc_output": "1f391", + "uc_match": "1f391", + "uc_greedy": "1f391", + "shortnames": [], + "category": "travel" + }, + ":right_facing_fist:": { + "uc_base": "1f91c", + "uc_output": "1f91c", + "uc_match": "1f91c", + "uc_greedy": "1f91c", + "shortnames": [":right_fist:"], + "category": "people" + }, + ":ring:": { + "uc_base": "1f48d", + "uc_output": "1f48d", + "uc_match": "1f48d", + "uc_greedy": "1f48d", + "shortnames": [], + "category": "people" + }, + ":robot:": { + "uc_base": "1f916", + "uc_output": "1f916", + "uc_match": "1f916", + "uc_greedy": "1f916", + "shortnames": [":robot_face:"], + "category": "people" + }, + ":rocket:": { + "uc_base": "1f680", + "uc_output": "1f680", + "uc_match": "1f680", + "uc_greedy": "1f680", + "shortnames": [], + "category": "travel" + }, + ":rofl:": { + "uc_base": "1f923", + "uc_output": "1f923", + "uc_match": "1f923", + "uc_greedy": "1f923", + "shortnames": [":rolling_on_the_floor_laughing:"], + "category": "people" + }, + ":roller_coaster:": { + "uc_base": "1f3a2", + "uc_output": "1f3a2", + "uc_match": "1f3a2", + "uc_greedy": "1f3a2", + "shortnames": [], + "category": "travel" + }, + ":rolling_eyes:": { + "uc_base": "1f644", + "uc_output": "1f644", + "uc_match": "1f644", + "uc_greedy": "1f644", + "shortnames": [":face_with_rolling_eyes:"], + "category": "people" + }, + ":rooster:": { + "uc_base": "1f413", + "uc_output": "1f413", + "uc_match": "1f413", + "uc_greedy": "1f413", + "shortnames": [], + "category": "nature" + }, + ":rose:": { + "uc_base": "1f339", + "uc_output": "1f339", + "uc_match": "1f339", + "uc_greedy": "1f339", + "shortnames": [], + "category": "nature" + }, + ":rosette:": { + "uc_base": "1f3f5", + "uc_output": "1f3f5", + "uc_match": "1f3f5-fe0f", + "uc_greedy": "1f3f5-fe0f", + "shortnames": [], + "category": "activity" + }, + ":rotating_light:": { + "uc_base": "1f6a8", + "uc_output": "1f6a8", + "uc_match": "1f6a8", + "uc_greedy": "1f6a8", + "shortnames": [], + "category": "travel" + }, + ":round_pushpin:": { + "uc_base": "1f4cd", + "uc_output": "1f4cd", + "uc_match": "1f4cd", + "uc_greedy": "1f4cd", + "shortnames": [], + "category": "objects" + }, + ":rugby_football:": { + "uc_base": "1f3c9", + "uc_output": "1f3c9", + "uc_match": "1f3c9", + "uc_greedy": "1f3c9", + "shortnames": [], + "category": "activity" + }, + ":running_shirt_with_sash:": { + "uc_base": "1f3bd", + "uc_output": "1f3bd", + "uc_match": "1f3bd", + "uc_greedy": "1f3bd", + "shortnames": [], + "category": "activity" + }, + ":sa:": { + "uc_base": "1f202", + "uc_output": "1f202", + "uc_match": "1f202-fe0f", + "uc_greedy": "1f202-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":sake:": { + "uc_base": "1f376", + "uc_output": "1f376", + "uc_match": "1f376", + "uc_greedy": "1f376", + "shortnames": [], + "category": "food" + }, + ":salad:": { + "uc_base": "1f957", + "uc_output": "1f957", + "uc_match": "1f957", + "uc_greedy": "1f957", + "shortnames": [":green_salad:"], + "category": "food" + }, + ":sandal:": { + "uc_base": "1f461", + "uc_output": "1f461", + "uc_match": "1f461", + "uc_greedy": "1f461", + "shortnames": [], + "category": "people" + }, + ":sandwich:": { + "uc_base": "1f96a", + "uc_output": "1f96a", + "uc_match": "1f96a", + "uc_greedy": "1f96a", + "shortnames": [], + "category": "food" + }, + ":santa:": { + "uc_base": "1f385", + "uc_output": "1f385", + "uc_match": "1f385", + "uc_greedy": "1f385", + "shortnames": [], + "category": "people" + }, + ":satellite:": { + "uc_base": "1f4e1", + "uc_output": "1f4e1", + "uc_match": "1f4e1", + "uc_greedy": "1f4e1", + "shortnames": [], + "category": "objects" + }, + ":satellite_orbital:": { + "uc_base": "1f6f0", + "uc_output": "1f6f0", + "uc_match": "1f6f0-fe0f", + "uc_greedy": "1f6f0-fe0f", + "shortnames": [], + "category": "travel" + }, + ":sauropod:": { + "uc_base": "1f995", + "uc_output": "1f995", + "uc_match": "1f995", + "uc_greedy": "1f995", + "shortnames": [], + "category": "nature" + }, + ":saxophone:": { + "uc_base": "1f3b7", + "uc_output": "1f3b7", + "uc_match": "1f3b7", + "uc_greedy": "1f3b7", + "shortnames": [], + "category": "activity" + }, + ":scarf:": { + "uc_base": "1f9e3", + "uc_output": "1f9e3", + "uc_match": "1f9e3", + "uc_greedy": "1f9e3", + "shortnames": [], + "category": "people" + }, + ":school:": { + "uc_base": "1f3eb", + "uc_output": "1f3eb", + "uc_match": "1f3eb", + "uc_greedy": "1f3eb", + "shortnames": [], + "category": "travel" + }, + ":school_satchel:": { + "uc_base": "1f392", + "uc_output": "1f392", + "uc_match": "1f392", + "uc_greedy": "1f392", + "shortnames": [], + "category": "people" + }, + ":scooter:": { + "uc_base": "1f6f4", + "uc_output": "1f6f4", + "uc_match": "1f6f4", + "uc_greedy": "1f6f4", + "shortnames": [], + "category": "travel" + }, + ":scorpion:": { + "uc_base": "1f982", + "uc_output": "1f982", + "uc_match": "1f982", + "uc_greedy": "1f982", + "shortnames": [], + "category": "nature" + }, + ":scream:": { + "uc_base": "1f631", + "uc_output": "1f631", + "uc_match": "1f631", + "uc_greedy": "1f631", + "shortnames": [], + "category": "people" + }, + ":scream_cat:": { + "uc_base": "1f640", + "uc_output": "1f640", + "uc_match": "1f640", + "uc_greedy": "1f640", + "shortnames": [], + "category": "people" + }, + ":scroll:": { + "uc_base": "1f4dc", + "uc_output": "1f4dc", + "uc_match": "1f4dc", + "uc_greedy": "1f4dc", + "shortnames": [], + "category": "objects" + }, + ":seat:": { + "uc_base": "1f4ba", + "uc_output": "1f4ba", + "uc_match": "1f4ba", + "uc_greedy": "1f4ba", + "shortnames": [], + "category": "travel" + }, + ":second_place:": { + "uc_base": "1f948", + "uc_output": "1f948", + "uc_match": "1f948", + "uc_greedy": "1f948", + "shortnames": [":second_place_medal:"], + "category": "activity" + }, + ":see_no_evil:": { + "uc_base": "1f648", + "uc_output": "1f648", + "uc_match": "1f648", + "uc_greedy": "1f648", + "shortnames": [], + "category": "nature" + }, + ":seedling:": { + "uc_base": "1f331", + "uc_output": "1f331", + "uc_match": "1f331", + "uc_greedy": "1f331", + "shortnames": [], + "category": "nature" + }, + ":selfie:": { + "uc_base": "1f933", + "uc_output": "1f933", + "uc_match": "1f933", + "uc_greedy": "1f933", + "shortnames": [], + "category": "people" + }, + ":shallow_pan_of_food:": { + "uc_base": "1f958", + "uc_output": "1f958", + "uc_match": "1f958", + "uc_greedy": "1f958", + "shortnames": [":paella:"], + "category": "food" + }, + ":shark:": { + "uc_base": "1f988", + "uc_output": "1f988", + "uc_match": "1f988", + "uc_greedy": "1f988", + "shortnames": [], + "category": "nature" + }, + ":shaved_ice:": { + "uc_base": "1f367", + "uc_output": "1f367", + "uc_match": "1f367", + "uc_greedy": "1f367", + "shortnames": [], + "category": "food" + }, + ":sheep:": { + "uc_base": "1f411", + "uc_output": "1f411", + "uc_match": "1f411", + "uc_greedy": "1f411", + "shortnames": [], + "category": "nature" + }, + ":shell:": { + "uc_base": "1f41a", + "uc_output": "1f41a", + "uc_match": "1f41a", + "uc_greedy": "1f41a", + "shortnames": [], + "category": "nature" + }, + ":shield:": { + "uc_base": "1f6e1", + "uc_output": "1f6e1", + "uc_match": "1f6e1-fe0f", + "uc_greedy": "1f6e1-fe0f", + "shortnames": [], + "category": "objects" + }, + ":ship:": { + "uc_base": "1f6a2", + "uc_output": "1f6a2", + "uc_match": "1f6a2", + "uc_greedy": "1f6a2", + "shortnames": [], + "category": "travel" + }, + ":shirt:": { + "uc_base": "1f455", + "uc_output": "1f455", + "uc_match": "1f455", + "uc_greedy": "1f455", + "shortnames": [], + "category": "people" + }, + ":shopping_bags:": { + "uc_base": "1f6cd", + "uc_output": "1f6cd", + "uc_match": "1f6cd-fe0f", + "uc_greedy": "1f6cd-fe0f", + "shortnames": [], + "category": "objects" + }, + ":shopping_cart:": { + "uc_base": "1f6d2", + "uc_output": "1f6d2", + "uc_match": "1f6d2", + "uc_greedy": "1f6d2", + "shortnames": [":shopping_trolley:"], + "category": "objects" + }, + ":shower:": { + "uc_base": "1f6bf", + "uc_output": "1f6bf", + "uc_match": "1f6bf", + "uc_greedy": "1f6bf", + "shortnames": [], + "category": "objects" + }, + ":shrimp:": { + "uc_base": "1f990", + "uc_output": "1f990", + "uc_match": "1f990", + "uc_greedy": "1f990", + "shortnames": [], + "category": "nature" + }, + ":shushing_face:": { + "uc_base": "1f92b", + "uc_output": "1f92b", + "uc_match": "1f92b", + "uc_greedy": "1f92b", + "shortnames": [], + "category": "people" + }, + ":signal_strength:": { + "uc_base": "1f4f6", + "uc_output": "1f4f6", + "uc_match": "1f4f6", + "uc_greedy": "1f4f6", + "shortnames": [], + "category": "symbols" + }, + ":six_pointed_star:": { + "uc_base": "1f52f", + "uc_output": "1f52f", + "uc_match": "1f52f", + "uc_greedy": "1f52f", + "shortnames": [], + "category": "symbols" + }, + ":ski:": { + "uc_base": "1f3bf", + "uc_output": "1f3bf", + "uc_match": "1f3bf", + "uc_greedy": "1f3bf", + "shortnames": [], + "category": "activity" + }, + ":skull:": { + "uc_base": "1f480", + "uc_output": "1f480", + "uc_match": "1f480", + "uc_greedy": "1f480", + "shortnames": [":skeleton:"], + "category": "people" + }, + ":sled:": { + "uc_base": "1f6f7", + "uc_output": "1f6f7", + "uc_match": "1f6f7", + "uc_greedy": "1f6f7", + "shortnames": [], + "category": "activity" + }, + ":sleeping:": { + "uc_base": "1f634", + "uc_output": "1f634", + "uc_match": "1f634", + "uc_greedy": "1f634", + "shortnames": [], + "category": "people" + }, + ":sleeping_accommodation:": { + "uc_base": "1f6cc", + "uc_output": "1f6cc", + "uc_match": "1f6cc", + "uc_greedy": "1f6cc", + "shortnames": [], + "category": "objects" + }, + ":sleepy:": { + "uc_base": "1f62a", + "uc_output": "1f62a", + "uc_match": "1f62a", + "uc_greedy": "1f62a", + "shortnames": [], + "category": "people" + }, + ":slight_frown:": { + "uc_base": "1f641", + "uc_output": "1f641", + "uc_match": "1f641", + "uc_greedy": "1f641", + "shortnames": [":slightly_frowning_face:"], + "category": "people" + }, + ":slight_smile:": { + "uc_base": "1f642", + "uc_output": "1f642", + "uc_match": "1f642", + "uc_greedy": "1f642", + "shortnames": [":slightly_smiling_face:"], + "category": "people" + }, + ":slot_machine:": { + "uc_base": "1f3b0", + "uc_output": "1f3b0", + "uc_match": "1f3b0", + "uc_greedy": "1f3b0", + "shortnames": [], + "category": "activity" + }, + ":small_blue_diamond:": { + "uc_base": "1f539", + "uc_output": "1f539", + "uc_match": "1f539", + "uc_greedy": "1f539", + "shortnames": [], + "category": "symbols" + }, + ":small_orange_diamond:": { + "uc_base": "1f538", + "uc_output": "1f538", + "uc_match": "1f538", + "uc_greedy": "1f538", + "shortnames": [], + "category": "symbols" + }, + ":small_red_triangle:": { + "uc_base": "1f53a", + "uc_output": "1f53a", + "uc_match": "1f53a", + "uc_greedy": "1f53a", + "shortnames": [], + "category": "symbols" + }, + ":small_red_triangle_down:": { + "uc_base": "1f53b", + "uc_output": "1f53b", + "uc_match": "1f53b", + "uc_greedy": "1f53b", + "shortnames": [], + "category": "symbols" + }, + ":smile:": { + "uc_base": "1f604", + "uc_output": "1f604", + "uc_match": "1f604", + "uc_greedy": "1f604", + "shortnames": [], + "category": "people" + }, + ":smile_cat:": { + "uc_base": "1f638", + "uc_output": "1f638", + "uc_match": "1f638", + "uc_greedy": "1f638", + "shortnames": [], + "category": "people" + }, + ":smiley:": { + "uc_base": "1f603", + "uc_output": "1f603", + "uc_match": "1f603", + "uc_greedy": "1f603", + "shortnames": [], + "category": "people" + }, + ":smiley_cat:": { + "uc_base": "1f63a", + "uc_output": "1f63a", + "uc_match": "1f63a", + "uc_greedy": "1f63a", + "shortnames": [], + "category": "people" + }, + ":smiling_imp:": { + "uc_base": "1f608", + "uc_output": "1f608", + "uc_match": "1f608", + "uc_greedy": "1f608", + "shortnames": [], + "category": "people" + }, + ":smirk:": { + "uc_base": "1f60f", + "uc_output": "1f60f", + "uc_match": "1f60f", + "uc_greedy": "1f60f", + "shortnames": [], + "category": "people" + }, + ":smirk_cat:": { + "uc_base": "1f63c", + "uc_output": "1f63c", + "uc_match": "1f63c", + "uc_greedy": "1f63c", + "shortnames": [], + "category": "people" + }, + ":smoking:": { + "uc_base": "1f6ac", + "uc_output": "1f6ac", + "uc_match": "1f6ac", + "uc_greedy": "1f6ac", + "shortnames": [], + "category": "objects" + }, + ":snail:": { + "uc_base": "1f40c", + "uc_output": "1f40c", + "uc_match": "1f40c", + "uc_greedy": "1f40c", + "shortnames": [], + "category": "nature" + }, + ":snake:": { + "uc_base": "1f40d", + "uc_output": "1f40d", + "uc_match": "1f40d", + "uc_greedy": "1f40d", + "shortnames": [], + "category": "nature" + }, + ":sneezing_face:": { + "uc_base": "1f927", + "uc_output": "1f927", + "uc_match": "1f927", + "uc_greedy": "1f927", + "shortnames": [":sneeze:"], + "category": "people" + }, + ":snowboarder:": { + "uc_base": "1f3c2", + "uc_output": "1f3c2", + "uc_match": "1f3c2-fe0f", + "uc_greedy": "1f3c2-fe0f", + "shortnames": [], + "category": "activity" + }, + ":sob:": { + "uc_base": "1f62d", + "uc_output": "1f62d", + "uc_match": "1f62d", + "uc_greedy": "1f62d", + "shortnames": [], + "category": "people" + }, + ":socks:": { + "uc_base": "1f9e6", + "uc_output": "1f9e6", + "uc_match": "1f9e6", + "uc_greedy": "1f9e6", + "shortnames": [], + "category": "people" + }, + ":soon:": { + "uc_base": "1f51c", + "uc_output": "1f51c", + "uc_match": "1f51c", + "uc_greedy": "1f51c", + "shortnames": [], + "category": "symbols" + }, + ":sos:": { + "uc_base": "1f198", + "uc_output": "1f198", + "uc_match": "1f198", + "uc_greedy": "1f198", + "shortnames": [], + "category": "symbols" + }, + ":sound:": { + "uc_base": "1f509", + "uc_output": "1f509", + "uc_match": "1f509", + "uc_greedy": "1f509", + "shortnames": [], + "category": "symbols" + }, + ":space_invader:": { + "uc_base": "1f47e", + "uc_output": "1f47e", + "uc_match": "1f47e", + "uc_greedy": "1f47e", + "shortnames": [], + "category": "people" + }, + ":spaghetti:": { + "uc_base": "1f35d", + "uc_output": "1f35d", + "uc_match": "1f35d", + "uc_greedy": "1f35d", + "shortnames": [], + "category": "food" + }, + ":sparkler:": { + "uc_base": "1f387", + "uc_output": "1f387", + "uc_match": "1f387", + "uc_greedy": "1f387", + "shortnames": [], + "category": "travel" + }, + ":sparkling_heart:": { + "uc_base": "1f496", + "uc_output": "1f496", + "uc_match": "1f496", + "uc_greedy": "1f496", + "shortnames": [], + "category": "symbols" + }, + ":speak_no_evil:": { + "uc_base": "1f64a", + "uc_output": "1f64a", + "uc_match": "1f64a", + "uc_greedy": "1f64a", + "shortnames": [], + "category": "nature" + }, + ":speaker:": { + "uc_base": "1f508", + "uc_output": "1f508", + "uc_match": "1f508-fe0f", + "uc_greedy": "1f508-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":speaking_head:": { + "uc_base": "1f5e3", + "uc_output": "1f5e3", + "uc_match": "1f5e3-fe0f", + "uc_greedy": "1f5e3-fe0f", + "shortnames": [":speaking_head_in_silhouette:"], + "category": "people" + }, + ":speech_balloon:": { + "uc_base": "1f4ac", + "uc_output": "1f4ac", + "uc_match": "1f4ac", + "uc_greedy": "1f4ac", + "shortnames": [], + "category": "symbols" + }, + ":speech_left:": { + "uc_base": "1f5e8", + "uc_output": "1f5e8", + "uc_match": "1f5e8-fe0f", + "uc_greedy": "1f5e8-fe0f", + "shortnames": [":left_speech_bubble:"], + "category": "symbols" + }, + ":speedboat:": { + "uc_base": "1f6a4", + "uc_output": "1f6a4", + "uc_match": "1f6a4", + "uc_greedy": "1f6a4", + "shortnames": [], + "category": "travel" + }, + ":spider:": { + "uc_base": "1f577", + "uc_output": "1f577", + "uc_match": "1f577-fe0f", + "uc_greedy": "1f577-fe0f", + "shortnames": [], + "category": "nature" + }, + ":spider_web:": { + "uc_base": "1f578", + "uc_output": "1f578", + "uc_match": "1f578-fe0f", + "uc_greedy": "1f578-fe0f", + "shortnames": [], + "category": "nature" + }, + ":spoon:": { + "uc_base": "1f944", + "uc_output": "1f944", + "uc_match": "1f944", + "uc_greedy": "1f944", + "shortnames": [], + "category": "food" + }, + ":squid:": { + "uc_base": "1f991", + "uc_output": "1f991", + "uc_match": "1f991", + "uc_greedy": "1f991", + "shortnames": [], + "category": "nature" + }, + ":stadium:": { + "uc_base": "1f3df", + "uc_output": "1f3df", + "uc_match": "1f3df-fe0f", + "uc_greedy": "1f3df-fe0f", + "shortnames": [], + "category": "travel" + }, + ":star2:": { + "uc_base": "1f31f", + "uc_output": "1f31f", + "uc_match": "1f31f", + "uc_greedy": "1f31f", + "shortnames": [], + "category": "nature" + }, + ":star_struck:": { + "uc_base": "1f929", + "uc_output": "1f929", + "uc_match": "1f929", + "uc_greedy": "1f929", + "shortnames": [], + "category": "people" + }, + ":stars:": { + "uc_base": "1f320", + "uc_output": "1f320", + "uc_match": "1f320", + "uc_greedy": "1f320", + "shortnames": [], + "category": "travel" + }, + ":station:": { + "uc_base": "1f689", + "uc_output": "1f689", + "uc_match": "1f689", + "uc_greedy": "1f689", + "shortnames": [], + "category": "travel" + }, + ":statue_of_liberty:": { + "uc_base": "1f5fd", + "uc_output": "1f5fd", + "uc_match": "1f5fd", + "uc_greedy": "1f5fd", + "shortnames": [], + "category": "travel" + }, + ":steam_locomotive:": { + "uc_base": "1f682", + "uc_output": "1f682", + "uc_match": "1f682", + "uc_greedy": "1f682", + "shortnames": [], + "category": "travel" + }, + ":stew:": { + "uc_base": "1f372", + "uc_output": "1f372", + "uc_match": "1f372", + "uc_greedy": "1f372", + "shortnames": [], + "category": "food" + }, + ":straight_ruler:": { + "uc_base": "1f4cf", + "uc_output": "1f4cf", + "uc_match": "1f4cf", + "uc_greedy": "1f4cf", + "shortnames": [], + "category": "objects" + }, + ":strawberry:": { + "uc_base": "1f353", + "uc_output": "1f353", + "uc_match": "1f353", + "uc_greedy": "1f353", + "shortnames": [], + "category": "food" + }, + ":stuck_out_tongue:": { + "uc_base": "1f61b", + "uc_output": "1f61b", + "uc_match": "1f61b", + "uc_greedy": "1f61b", + "shortnames": [], + "category": "people" + }, + ":stuck_out_tongue_closed_eyes:": { + "uc_base": "1f61d", + "uc_output": "1f61d", + "uc_match": "1f61d", + "uc_greedy": "1f61d", + "shortnames": [], + "category": "people" + }, + ":stuck_out_tongue_winking_eye:": { + "uc_base": "1f61c", + "uc_output": "1f61c", + "uc_match": "1f61c", + "uc_greedy": "1f61c", + "shortnames": [], + "category": "people" + }, + ":stuffed_flatbread:": { + "uc_base": "1f959", + "uc_output": "1f959", + "uc_match": "1f959", + "uc_greedy": "1f959", + "shortnames": [":stuffed_pita:"], + "category": "food" + }, + ":sun_with_face:": { + "uc_base": "1f31e", + "uc_output": "1f31e", + "uc_match": "1f31e", + "uc_greedy": "1f31e", + "shortnames": [], + "category": "nature" + }, + ":sunflower:": { + "uc_base": "1f33b", + "uc_output": "1f33b", + "uc_match": "1f33b", + "uc_greedy": "1f33b", + "shortnames": [], + "category": "nature" + }, + ":sunglasses:": { + "uc_base": "1f60e", + "uc_output": "1f60e", + "uc_match": "1f60e", + "uc_greedy": "1f60e", + "shortnames": [], + "category": "people" + }, + ":sunrise:": { + "uc_base": "1f305", + "uc_output": "1f305", + "uc_match": "1f305", + "uc_greedy": "1f305", + "shortnames": [], + "category": "travel" + }, + ":sunrise_over_mountains:": { + "uc_base": "1f304", + "uc_output": "1f304", + "uc_match": "1f304", + "uc_greedy": "1f304", + "shortnames": [], + "category": "travel" + }, + ":sushi:": { + "uc_base": "1f363", + "uc_output": "1f363", + "uc_match": "1f363", + "uc_greedy": "1f363", + "shortnames": [], + "category": "food" + }, + ":suspension_railway:": { + "uc_base": "1f69f", + "uc_output": "1f69f", + "uc_match": "1f69f", + "uc_greedy": "1f69f", + "shortnames": [], + "category": "travel" + }, + ":sweat:": { + "uc_base": "1f613", + "uc_output": "1f613", + "uc_match": "1f613", + "uc_greedy": "1f613", + "shortnames": [], + "category": "people" + }, + ":sweat_drops:": { + "uc_base": "1f4a6", + "uc_output": "1f4a6", + "uc_match": "1f4a6", + "uc_greedy": "1f4a6", + "shortnames": [], + "category": "nature" + }, + ":sweat_smile:": { + "uc_base": "1f605", + "uc_output": "1f605", + "uc_match": "1f605", + "uc_greedy": "1f605", + "shortnames": [], + "category": "people" + }, + ":sweet_potato:": { + "uc_base": "1f360", + "uc_output": "1f360", + "uc_match": "1f360", + "uc_greedy": "1f360", + "shortnames": [], + "category": "food" + }, + ":symbols:": { + "uc_base": "1f523", + "uc_output": "1f523", + "uc_match": "1f523", + "uc_greedy": "1f523", + "shortnames": [], + "category": "symbols" + }, + ":synagogue:": { + "uc_base": "1f54d", + "uc_output": "1f54d", + "uc_match": "1f54d", + "uc_greedy": "1f54d", + "shortnames": [], + "category": "travel" + }, + ":syringe:": { + "uc_base": "1f489", + "uc_output": "1f489", + "uc_match": "1f489", + "uc_greedy": "1f489", + "shortnames": [], + "category": "objects" + }, + ":t_rex:": { + "uc_base": "1f996", + "uc_output": "1f996", + "uc_match": "1f996", + "uc_greedy": "1f996", + "shortnames": [], + "category": "nature" + }, + ":taco:": { + "uc_base": "1f32e", + "uc_output": "1f32e", + "uc_match": "1f32e", + "uc_greedy": "1f32e", + "shortnames": [], + "category": "food" + }, + ":tada:": { + "uc_base": "1f389", + "uc_output": "1f389", + "uc_match": "1f389", + "uc_greedy": "1f389", + "shortnames": [], + "category": "objects" + }, + ":takeout_box:": { + "uc_base": "1f961", + "uc_output": "1f961", + "uc_match": "1f961", + "uc_greedy": "1f961", + "shortnames": [], + "category": "food" + }, + ":tanabata_tree:": { + "uc_base": "1f38b", + "uc_output": "1f38b", + "uc_match": "1f38b", + "uc_greedy": "1f38b", + "shortnames": [], + "category": "nature" + }, + ":tangerine:": { + "uc_base": "1f34a", + "uc_output": "1f34a", + "uc_match": "1f34a", + "uc_greedy": "1f34a", + "shortnames": [], + "category": "food" + }, + ":taxi:": { + "uc_base": "1f695", + "uc_output": "1f695", + "uc_match": "1f695", + "uc_greedy": "1f695", + "shortnames": [], + "category": "travel" + }, + ":tea:": { + "uc_base": "1f375", + "uc_output": "1f375", + "uc_match": "1f375", + "uc_greedy": "1f375", + "shortnames": [], + "category": "food" + }, + ":telephone_receiver:": { + "uc_base": "1f4de", + "uc_output": "1f4de", + "uc_match": "1f4de", + "uc_greedy": "1f4de", + "shortnames": [], + "category": "objects" + }, + ":telescope:": { + "uc_base": "1f52d", + "uc_output": "1f52d", + "uc_match": "1f52d", + "uc_greedy": "1f52d", + "shortnames": [], + "category": "objects" + }, + ":tennis:": { + "uc_base": "1f3be", + "uc_output": "1f3be", + "uc_match": "1f3be", + "uc_greedy": "1f3be", + "shortnames": [], + "category": "activity" + }, + ":thermometer:": { + "uc_base": "1f321", + "uc_output": "1f321", + "uc_match": "1f321-fe0f", + "uc_greedy": "1f321-fe0f", + "shortnames": [], + "category": "objects" + }, + ":thermometer_face:": { + "uc_base": "1f912", + "uc_output": "1f912", + "uc_match": "1f912", + "uc_greedy": "1f912", + "shortnames": [":face_with_thermometer:"], + "category": "people" + }, + ":thinking:": { + "uc_base": "1f914", + "uc_output": "1f914", + "uc_match": "1f914", + "uc_greedy": "1f914", + "shortnames": [":thinking_face:"], + "category": "people" + }, + ":third_place:": { + "uc_base": "1f949", + "uc_output": "1f949", + "uc_match": "1f949", + "uc_greedy": "1f949", + "shortnames": [":third_place_medal:"], + "category": "activity" + }, + ":thought_balloon:": { + "uc_base": "1f4ad", + "uc_output": "1f4ad", + "uc_match": "1f4ad", + "uc_greedy": "1f4ad", + "shortnames": [], + "category": "symbols" + }, + ":thumbsdown:": { + "uc_base": "1f44e", + "uc_output": "1f44e", + "uc_match": "1f44e-fe0f", + "uc_greedy": "1f44e-fe0f", + "shortnames": [":-1:", ":thumbdown:"], + "category": "people" + }, + ":thumbsup:": { + "uc_base": "1f44d", + "uc_output": "1f44d", + "uc_match": "1f44d-fe0f", + "uc_greedy": "1f44d-fe0f", + "shortnames": [":+1:", ":thumbup:"], + "category": "people" + }, + ":ticket:": { + "uc_base": "1f3ab", + "uc_output": "1f3ab", + "uc_match": "1f3ab", + "uc_greedy": "1f3ab", + "shortnames": [], + "category": "activity" + }, + ":tickets:": { + "uc_base": "1f39f", + "uc_output": "1f39f", + "uc_match": "1f39f-fe0f", + "uc_greedy": "1f39f-fe0f", + "shortnames": [":admission_tickets:"], + "category": "activity" + }, + ":tiger2:": { + "uc_base": "1f405", + "uc_output": "1f405", + "uc_match": "1f405", + "uc_greedy": "1f405", + "shortnames": [], + "category": "nature" + }, + ":tiger:": { + "uc_base": "1f42f", + "uc_output": "1f42f", + "uc_match": "1f42f", + "uc_greedy": "1f42f", + "shortnames": [], + "category": "nature" + }, + ":tired_face:": { + "uc_base": "1f62b", + "uc_output": "1f62b", + "uc_match": "1f62b", + "uc_greedy": "1f62b", + "shortnames": [], + "category": "people" + }, + ":toilet:": { + "uc_base": "1f6bd", + "uc_output": "1f6bd", + "uc_match": "1f6bd", + "uc_greedy": "1f6bd", + "shortnames": [], + "category": "objects" + }, + ":tokyo_tower:": { + "uc_base": "1f5fc", + "uc_output": "1f5fc", + "uc_match": "1f5fc", + "uc_greedy": "1f5fc", + "shortnames": [], + "category": "travel" + }, + ":tomato:": { + "uc_base": "1f345", + "uc_output": "1f345", + "uc_match": "1f345", + "uc_greedy": "1f345", + "shortnames": [], + "category": "food" + }, + ":tone1:": { + "uc_base": "1f3fb", + "uc_output": "1f3fb", + "uc_match": "1f3fb", + "uc_greedy": "1f3fb", + "shortnames": [], + "category": "modifier" + }, + ":tone2:": { + "uc_base": "1f3fc", + "uc_output": "1f3fc", + "uc_match": "1f3fc", + "uc_greedy": "1f3fc", + "shortnames": [], + "category": "modifier" + }, + ":tone3:": { + "uc_base": "1f3fd", + "uc_output": "1f3fd", + "uc_match": "1f3fd", + "uc_greedy": "1f3fd", + "shortnames": [], + "category": "modifier" + }, + ":tone4:": { + "uc_base": "1f3fe", + "uc_output": "1f3fe", + "uc_match": "1f3fe", + "uc_greedy": "1f3fe", + "shortnames": [], + "category": "modifier" + }, + ":tone5:": { + "uc_base": "1f3ff", + "uc_output": "1f3ff", + "uc_match": "1f3ff", + "uc_greedy": "1f3ff", + "shortnames": [], + "category": "modifier" + }, + ":tongue:": { + "uc_base": "1f445", + "uc_output": "1f445", + "uc_match": "1f445", + "uc_greedy": "1f445", + "shortnames": [], + "category": "people" + }, + ":tools:": { + "uc_base": "1f6e0", + "uc_output": "1f6e0", + "uc_match": "1f6e0-fe0f", + "uc_greedy": "1f6e0-fe0f", + "shortnames": [":hammer_and_wrench:"], + "category": "objects" + }, + ":top:": { + "uc_base": "1f51d", + "uc_output": "1f51d", + "uc_match": "1f51d", + "uc_greedy": "1f51d", + "shortnames": [], + "category": "symbols" + }, + ":tophat:": { + "uc_base": "1f3a9", + "uc_output": "1f3a9", + "uc_match": "1f3a9", + "uc_greedy": "1f3a9", + "shortnames": [], + "category": "people" + }, + ":trackball:": { + "uc_base": "1f5b2", + "uc_output": "1f5b2", + "uc_match": "1f5b2-fe0f", + "uc_greedy": "1f5b2-fe0f", + "shortnames": [], + "category": "objects" + }, + ":tractor:": { + "uc_base": "1f69c", + "uc_output": "1f69c", + "uc_match": "1f69c", + "uc_greedy": "1f69c", + "shortnames": [], + "category": "travel" + }, + ":traffic_light:": { + "uc_base": "1f6a5", + "uc_output": "1f6a5", + "uc_match": "1f6a5", + "uc_greedy": "1f6a5", + "shortnames": [], + "category": "travel" + }, + ":train2:": { + "uc_base": "1f686", + "uc_output": "1f686", + "uc_match": "1f686", + "uc_greedy": "1f686", + "shortnames": [], + "category": "travel" + }, + ":train:": { + "uc_base": "1f68b", + "uc_output": "1f68b", + "uc_match": "1f68b", + "uc_greedy": "1f68b", + "shortnames": [], + "category": "travel" + }, + ":tram:": { + "uc_base": "1f68a", + "uc_output": "1f68a", + "uc_match": "1f68a", + "uc_greedy": "1f68a", + "shortnames": [], + "category": "travel" + }, + ":triangular_flag_on_post:": { + "uc_base": "1f6a9", + "uc_output": "1f6a9", + "uc_match": "1f6a9", + "uc_greedy": "1f6a9", + "shortnames": [], + "category": "flags" + }, + ":triangular_ruler:": { + "uc_base": "1f4d0", + "uc_output": "1f4d0", + "uc_match": "1f4d0", + "uc_greedy": "1f4d0", + "shortnames": [], + "category": "objects" + }, + ":trident:": { + "uc_base": "1f531", + "uc_output": "1f531", + "uc_match": "1f531", + "uc_greedy": "1f531", + "shortnames": [], + "category": "symbols" + }, + ":triumph:": { + "uc_base": "1f624", + "uc_output": "1f624", + "uc_match": "1f624", + "uc_greedy": "1f624", + "shortnames": [], + "category": "people" + }, + ":trolleybus:": { + "uc_base": "1f68e", + "uc_output": "1f68e", + "uc_match": "1f68e", + "uc_greedy": "1f68e", + "shortnames": [], + "category": "travel" + }, + ":trophy:": { + "uc_base": "1f3c6", + "uc_output": "1f3c6", + "uc_match": "1f3c6-fe0f", + "uc_greedy": "1f3c6-fe0f", + "shortnames": [], + "category": "activity" + }, + ":tropical_drink:": { + "uc_base": "1f379", + "uc_output": "1f379", + "uc_match": "1f379", + "uc_greedy": "1f379", + "shortnames": [], + "category": "food" + }, + ":tropical_fish:": { + "uc_base": "1f420", + "uc_output": "1f420", + "uc_match": "1f420", + "uc_greedy": "1f420", + "shortnames": [], + "category": "nature" + }, + ":truck:": { + "uc_base": "1f69a", + "uc_output": "1f69a", + "uc_match": "1f69a", + "uc_greedy": "1f69a", + "shortnames": [], + "category": "travel" + }, + ":trumpet:": { + "uc_base": "1f3ba", + "uc_output": "1f3ba", + "uc_match": "1f3ba", + "uc_greedy": "1f3ba", + "shortnames": [], + "category": "activity" + }, + ":tulip:": { + "uc_base": "1f337", + "uc_output": "1f337", + "uc_match": "1f337", + "uc_greedy": "1f337", + "shortnames": [], + "category": "nature" + }, + ":tumbler_glass:": { + "uc_base": "1f943", + "uc_output": "1f943", + "uc_match": "1f943", + "uc_greedy": "1f943", + "shortnames": [":whisky:"], + "category": "food" + }, + ":turkey:": { + "uc_base": "1f983", + "uc_output": "1f983", + "uc_match": "1f983", + "uc_greedy": "1f983", + "shortnames": [], + "category": "nature" + }, + ":turtle:": { + "uc_base": "1f422", + "uc_output": "1f422", + "uc_match": "1f422", + "uc_greedy": "1f422", + "shortnames": [], + "category": "nature" + }, + ":tv:": { + "uc_base": "1f4fa", + "uc_output": "1f4fa", + "uc_match": "1f4fa-fe0f", + "uc_greedy": "1f4fa-fe0f", + "shortnames": [], + "category": "objects" + }, + ":twisted_rightwards_arrows:": { + "uc_base": "1f500", + "uc_output": "1f500", + "uc_match": "1f500", + "uc_greedy": "1f500", + "shortnames": [], + "category": "symbols" + }, + ":two_hearts:": { + "uc_base": "1f495", + "uc_output": "1f495", + "uc_match": "1f495", + "uc_greedy": "1f495", + "shortnames": [], + "category": "symbols" + }, + ":two_men_holding_hands:": { + "uc_base": "1f46c", + "uc_output": "1f46c", + "uc_match": "1f46c", + "uc_greedy": "1f46c", + "shortnames": [], + "category": "people" + }, + ":two_women_holding_hands:": { + "uc_base": "1f46d", + "uc_output": "1f46d", + "uc_match": "1f46d", + "uc_greedy": "1f46d", + "shortnames": [], + "category": "people" + }, + ":u5272:": { + "uc_base": "1f239", + "uc_output": "1f239", + "uc_match": "1f239", + "uc_greedy": "1f239", + "shortnames": [], + "category": "symbols" + }, + ":u5408:": { + "uc_base": "1f234", + "uc_output": "1f234", + "uc_match": "1f234", + "uc_greedy": "1f234", + "shortnames": [], + "category": "symbols" + }, + ":u55b6:": { + "uc_base": "1f23a", + "uc_output": "1f23a", + "uc_match": "1f23a", + "uc_greedy": "1f23a", + "shortnames": [], + "category": "symbols" + }, + ":u6307:": { + "uc_base": "1f22f", + "uc_output": "1f22f", + "uc_match": "1f22f-fe0f", + "uc_greedy": "1f22f-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":u6708:": { + "uc_base": "1f237", + "uc_output": "1f237", + "uc_match": "1f237-fe0f", + "uc_greedy": "1f237-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":u6709:": { + "uc_base": "1f236", + "uc_output": "1f236", + "uc_match": "1f236", + "uc_greedy": "1f236", + "shortnames": [], + "category": "symbols" + }, + ":u6e80:": { + "uc_base": "1f235", + "uc_output": "1f235", + "uc_match": "1f235", + "uc_greedy": "1f235", + "shortnames": [], + "category": "symbols" + }, + ":u7121:": { + "uc_base": "1f21a", + "uc_output": "1f21a", + "uc_match": "1f21a-fe0f", + "uc_greedy": "1f21a-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":u7533:": { + "uc_base": "1f238", + "uc_output": "1f238", + "uc_match": "1f238", + "uc_greedy": "1f238", + "shortnames": [], + "category": "symbols" + }, + ":u7981:": { + "uc_base": "1f232", + "uc_output": "1f232", + "uc_match": "1f232", + "uc_greedy": "1f232", + "shortnames": [], + "category": "symbols" + }, + ":u7a7a:": { + "uc_base": "1f233", + "uc_output": "1f233", + "uc_match": "1f233", + "uc_greedy": "1f233", + "shortnames": [], + "category": "symbols" + }, + ":unamused:": { + "uc_base": "1f612", + "uc_output": "1f612", + "uc_match": "1f612", + "uc_greedy": "1f612", + "shortnames": [], + "category": "people" + }, + ":underage:": { + "uc_base": "1f51e", + "uc_output": "1f51e", + "uc_match": "1f51e", + "uc_greedy": "1f51e", + "shortnames": [], + "category": "symbols" + }, + ":unicorn:": { + "uc_base": "1f984", + "uc_output": "1f984", + "uc_match": "1f984", + "uc_greedy": "1f984", + "shortnames": [":unicorn_face:"], + "category": "nature" + }, + ":unlock:": { + "uc_base": "1f513", + "uc_output": "1f513", + "uc_match": "1f513-fe0f", + "uc_greedy": "1f513-fe0f", + "shortnames": [], + "category": "objects" + }, + ":up:": { + "uc_base": "1f199", + "uc_output": "1f199", + "uc_match": "1f199", + "uc_greedy": "1f199", + "shortnames": [], + "category": "symbols" + }, + ":upside_down:": { + "uc_base": "1f643", + "uc_output": "1f643", + "uc_match": "1f643", + "uc_greedy": "1f643", + "shortnames": [":upside_down_face:"], + "category": "people" + }, + ":vampire:": { + "uc_base": "1f9db", + "uc_output": "1f9db", + "uc_match": "1f9db", + "uc_greedy": "1f9db", + "shortnames": [], + "category": "people" + }, + ":vertical_traffic_light:": { + "uc_base": "1f6a6", + "uc_output": "1f6a6", + "uc_match": "1f6a6", + "uc_greedy": "1f6a6", + "shortnames": [], + "category": "travel" + }, + ":vhs:": { + "uc_base": "1f4fc", + "uc_output": "1f4fc", + "uc_match": "1f4fc", + "uc_greedy": "1f4fc", + "shortnames": [], + "category": "objects" + }, + ":vibration_mode:": { + "uc_base": "1f4f3", + "uc_output": "1f4f3", + "uc_match": "1f4f3", + "uc_greedy": "1f4f3", + "shortnames": [], + "category": "symbols" + }, + ":video_camera:": { + "uc_base": "1f4f9", + "uc_output": "1f4f9", + "uc_match": "1f4f9-fe0f", + "uc_greedy": "1f4f9-fe0f", + "shortnames": [], + "category": "objects" + }, + ":video_game:": { + "uc_base": "1f3ae", + "uc_output": "1f3ae", + "uc_match": "1f3ae-fe0f", + "uc_greedy": "1f3ae-fe0f", + "shortnames": [], + "category": "activity" + }, + ":violin:": { + "uc_base": "1f3bb", + "uc_output": "1f3bb", + "uc_match": "1f3bb", + "uc_greedy": "1f3bb", + "shortnames": [], + "category": "activity" + }, + ":volcano:": { + "uc_base": "1f30b", + "uc_output": "1f30b", + "uc_match": "1f30b", + "uc_greedy": "1f30b", + "shortnames": [], + "category": "travel" + }, + ":volleyball:": { + "uc_base": "1f3d0", + "uc_output": "1f3d0", + "uc_match": "1f3d0", + "uc_greedy": "1f3d0", + "shortnames": [], + "category": "activity" + }, + ":vs:": { + "uc_base": "1f19a", + "uc_output": "1f19a", + "uc_match": "1f19a", + "uc_greedy": "1f19a", + "shortnames": [], + "category": "symbols" + }, + ":vulcan:": { + "uc_base": "1f596", + "uc_output": "1f596", + "uc_match": "1f596", + "uc_greedy": "1f596", + "shortnames": [":raised_hand_with_part_between_middle_and_ring_fingers:"], + "category": "people" + }, + ":waning_crescent_moon:": { + "uc_base": "1f318", + "uc_output": "1f318", + "uc_match": "1f318", + "uc_greedy": "1f318", + "shortnames": [], + "category": "nature" + }, + ":waning_gibbous_moon:": { + "uc_base": "1f316", + "uc_output": "1f316", + "uc_match": "1f316", + "uc_greedy": "1f316", + "shortnames": [], + "category": "nature" + }, + ":wastebasket:": { + "uc_base": "1f5d1", + "uc_output": "1f5d1", + "uc_match": "1f5d1-fe0f", + "uc_greedy": "1f5d1-fe0f", + "shortnames": [], + "category": "objects" + }, + ":water_buffalo:": { + "uc_base": "1f403", + "uc_output": "1f403", + "uc_match": "1f403", + "uc_greedy": "1f403", + "shortnames": [], + "category": "nature" + }, + ":watermelon:": { + "uc_base": "1f349", + "uc_output": "1f349", + "uc_match": "1f349", + "uc_greedy": "1f349", + "shortnames": [], + "category": "food" + }, + ":wave:": { + "uc_base": "1f44b", + "uc_output": "1f44b", + "uc_match": "1f44b", + "uc_greedy": "1f44b", + "shortnames": [], + "category": "people" + }, + ":waxing_crescent_moon:": { + "uc_base": "1f312", + "uc_output": "1f312", + "uc_match": "1f312", + "uc_greedy": "1f312", + "shortnames": [], + "category": "nature" + }, + ":waxing_gibbous_moon:": { + "uc_base": "1f314", + "uc_output": "1f314", + "uc_match": "1f314", + "uc_greedy": "1f314", + "shortnames": [], + "category": "nature" + }, + ":wc:": { + "uc_base": "1f6be", + "uc_output": "1f6be", + "uc_match": "1f6be", + "uc_greedy": "1f6be", + "shortnames": [], + "category": "symbols" + }, + ":weary:": { + "uc_base": "1f629", + "uc_output": "1f629", + "uc_match": "1f629", + "uc_greedy": "1f629", + "shortnames": [], + "category": "people" + }, + ":wedding:": { + "uc_base": "1f492", + "uc_output": "1f492", + "uc_match": "1f492", + "uc_greedy": "1f492", + "shortnames": [], + "category": "travel" + }, + ":whale2:": { + "uc_base": "1f40b", + "uc_output": "1f40b", + "uc_match": "1f40b", + "uc_greedy": "1f40b", + "shortnames": [], + "category": "nature" + }, + ":whale:": { + "uc_base": "1f433", + "uc_output": "1f433", + "uc_match": "1f433", + "uc_greedy": "1f433", + "shortnames": [], + "category": "nature" + }, + ":white_flower:": { + "uc_base": "1f4ae", + "uc_output": "1f4ae", + "uc_match": "1f4ae", + "uc_greedy": "1f4ae", + "shortnames": [], + "category": "symbols" + }, + ":white_square_button:": { + "uc_base": "1f533", + "uc_output": "1f533", + "uc_match": "1f533", + "uc_greedy": "1f533", + "shortnames": [], + "category": "symbols" + }, + ":white_sun_cloud:": { + "uc_base": "1f325", + "uc_output": "1f325", + "uc_match": "1f325-fe0f", + "uc_greedy": "1f325-fe0f", + "shortnames": [":white_sun_behind_cloud:"], + "category": "nature" + }, + ":white_sun_rain_cloud:": { + "uc_base": "1f326", + "uc_output": "1f326", + "uc_match": "1f326-fe0f", + "uc_greedy": "1f326-fe0f", + "shortnames": [":white_sun_behind_cloud_with_rain:"], + "category": "nature" + }, + ":white_sun_small_cloud:": { + "uc_base": "1f324", + "uc_output": "1f324", + "uc_match": "1f324-fe0f", + "uc_greedy": "1f324-fe0f", + "shortnames": [":white_sun_with_small_cloud:"], + "category": "nature" + }, + ":wilted_rose:": { + "uc_base": "1f940", + "uc_output": "1f940", + "uc_match": "1f940", + "uc_greedy": "1f940", + "shortnames": [":wilted_flower:"], + "category": "nature" + }, + ":wind_blowing_face:": { + "uc_base": "1f32c", + "uc_output": "1f32c", + "uc_match": "1f32c-fe0f", + "uc_greedy": "1f32c-fe0f", + "shortnames": [], + "category": "nature" + }, + ":wind_chime:": { + "uc_base": "1f390", + "uc_output": "1f390", + "uc_match": "1f390", + "uc_greedy": "1f390", + "shortnames": [], + "category": "objects" + }, + ":wine_glass:": { + "uc_base": "1f377", + "uc_output": "1f377", + "uc_match": "1f377", + "uc_greedy": "1f377", + "shortnames": [], + "category": "food" + }, + ":wink:": { + "uc_base": "1f609", + "uc_output": "1f609", + "uc_match": "1f609", + "uc_greedy": "1f609", + "shortnames": [], + "category": "people" + }, + ":wolf:": { + "uc_base": "1f43a", + "uc_output": "1f43a", + "uc_match": "1f43a", + "uc_greedy": "1f43a", + "shortnames": [], + "category": "nature" + }, + ":woman:": { + "uc_base": "1f469", + "uc_output": "1f469", + "uc_match": "1f469", + "uc_greedy": "1f469", + "shortnames": [], + "category": "people" + }, + ":woman_with_headscarf:": { + "uc_base": "1f9d5", + "uc_output": "1f9d5", + "uc_match": "1f9d5", + "uc_greedy": "1f9d5", + "shortnames": [], + "category": "people" + }, + ":womans_clothes:": { + "uc_base": "1f45a", + "uc_output": "1f45a", + "uc_match": "1f45a", + "uc_greedy": "1f45a", + "shortnames": [], + "category": "people" + }, + ":womans_hat:": { + "uc_base": "1f452", + "uc_output": "1f452", + "uc_match": "1f452", + "uc_greedy": "1f452", + "shortnames": [], + "category": "people" + }, + ":womens:": { + "uc_base": "1f6ba", + "uc_output": "1f6ba", + "uc_match": "1f6ba-fe0f", + "uc_greedy": "1f6ba-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":worried:": { + "uc_base": "1f61f", + "uc_output": "1f61f", + "uc_match": "1f61f", + "uc_greedy": "1f61f", + "shortnames": [], + "category": "people" + }, + ":wrench:": { + "uc_base": "1f527", + "uc_output": "1f527", + "uc_match": "1f527", + "uc_greedy": "1f527", + "shortnames": [], + "category": "objects" + }, + ":yellow_heart:": { + "uc_base": "1f49b", + "uc_output": "1f49b", + "uc_match": "1f49b", + "uc_greedy": "1f49b", + "shortnames": [], + "category": "symbols" + }, + ":yen:": { + "uc_base": "1f4b4", + "uc_output": "1f4b4", + "uc_match": "1f4b4", + "uc_greedy": "1f4b4", + "shortnames": [], + "category": "objects" + }, + ":yum:": { + "uc_base": "1f60b", + "uc_output": "1f60b", + "uc_match": "1f60b", + "uc_greedy": "1f60b", + "shortnames": [], + "category": "people" + }, + ":zebra:": { + "uc_base": "1f993", + "uc_output": "1f993", + "uc_match": "1f993", + "uc_greedy": "1f993", + "shortnames": [], + "category": "nature" + }, + ":zipper_mouth:": { + "uc_base": "1f910", + "uc_output": "1f910", + "uc_match": "1f910", + "uc_greedy": "1f910", + "shortnames": [":zipper_mouth_face:"], + "category": "people" + }, + ":zombie:": { + "uc_base": "1f9df", + "uc_output": "1f9df", + "uc_match": "1f9df", + "uc_greedy": "1f9df", + "shortnames": [], + "category": "people" + }, + ":zzz:": { + "uc_base": "1f4a4", + "uc_output": "1f4a4", + "uc_match": "1f4a4", + "uc_greedy": "1f4a4", + "shortnames": [], + "category": "symbols" + }, + ":airplane:": { + "uc_base": "2708", + "uc_output": "2708", + "uc_match": "2708-fe0f", + "uc_greedy": "2708-fe0f", + "shortnames": [], + "category": "travel" + }, + ":alarm_clock:": { + "uc_base": "23f0", + "uc_output": "23f0", + "uc_match": "23f0", + "uc_greedy": "23f0", + "shortnames": [], + "category": "objects" + }, + ":alembic:": { + "uc_base": "2697", + "uc_output": "2697", + "uc_match": "2697-fe0f", + "uc_greedy": "2697-fe0f", + "shortnames": [], + "category": "objects" + }, + ":anchor:": { + "uc_base": "2693", + "uc_output": "2693", + "uc_match": "2693-fe0f", + "uc_greedy": "2693-fe0f", + "shortnames": [], + "category": "travel" + }, + ":aquarius:": { + "uc_base": "2652", + "uc_output": "2652", + "uc_match": "2652-fe0f", + "uc_greedy": "2652-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":aries:": { + "uc_base": "2648", + "uc_output": "2648", + "uc_match": "2648-fe0f", + "uc_greedy": "2648-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":arrow_backward:": { + "uc_base": "25c0", + "uc_output": "25c0", + "uc_match": "25c0-fe0f", + "uc_greedy": "25c0", + "shortnames": [], + "category": "symbols" + }, + ":arrow_double_down:": { + "uc_base": "23ec", + "uc_output": "23ec", + "uc_match": "23ec", + "uc_greedy": "23ec", + "shortnames": [], + "category": "symbols" + }, + ":arrow_double_up:": { + "uc_base": "23eb", + "uc_output": "23eb", + "uc_match": "23eb", + "uc_greedy": "23eb", + "shortnames": [], + "category": "symbols" + }, + ":arrow_down:": { + "uc_base": "2b07", + "uc_output": "2b07", + "uc_match": "2b07-fe0f", + "uc_greedy": "2b07", + "shortnames": [], + "category": "symbols" + }, + ":arrow_forward:": { + "uc_base": "25b6", + "uc_output": "25b6", + "uc_match": "25b6-fe0f", + "uc_greedy": "25b6", + "shortnames": [], + "category": "symbols" + }, + ":arrow_heading_down:": { + "uc_base": "2935", + "uc_output": "2935", + "uc_match": "2935-fe0f", + "uc_greedy": "2935", + "shortnames": [], + "category": "symbols" + }, + ":arrow_heading_up:": { + "uc_base": "2934", + "uc_output": "2934", + "uc_match": "2934-fe0f", + "uc_greedy": "2934", + "shortnames": [], + "category": "symbols" + }, + ":arrow_left:": { + "uc_base": "2b05", + "uc_output": "2b05", + "uc_match": "2b05-fe0f", + "uc_greedy": "2b05", + "shortnames": [], + "category": "symbols" + }, + ":arrow_lower_left:": { + "uc_base": "2199", + "uc_output": "2199", + "uc_match": "2199-fe0f", + "uc_greedy": "2199", + "shortnames": [], + "category": "symbols" + }, + ":arrow_lower_right:": { + "uc_base": "2198", + "uc_output": "2198", + "uc_match": "2198-fe0f", + "uc_greedy": "2198", + "shortnames": [], + "category": "symbols" + }, + ":arrow_right:": { + "uc_base": "27a1", + "uc_output": "27a1", + "uc_match": "27a1-fe0f", + "uc_greedy": "27a1", + "shortnames": [], + "category": "symbols" + }, + ":arrow_right_hook:": { + "uc_base": "21aa", + "uc_output": "21aa", + "uc_match": "21aa-fe0f", + "uc_greedy": "21aa", + "shortnames": [], + "category": "symbols" + }, + ":arrow_up:": { + "uc_base": "2b06", + "uc_output": "2b06", + "uc_match": "2b06-fe0f", + "uc_greedy": "2b06", + "shortnames": [], + "category": "symbols" + }, + ":arrow_up_down:": { + "uc_base": "2195", + "uc_output": "2195", + "uc_match": "2195-fe0f", + "uc_greedy": "2195", + "shortnames": [], + "category": "symbols" + }, + ":arrow_upper_left:": { + "uc_base": "2196", + "uc_output": "2196", + "uc_match": "2196-fe0f", + "uc_greedy": "2196", + "shortnames": [], + "category": "symbols" + }, + ":arrow_upper_right:": { + "uc_base": "2197", + "uc_output": "2197", + "uc_match": "2197-fe0f", + "uc_greedy": "2197", + "shortnames": [], + "category": "symbols" + }, + ":atom:": { + "uc_base": "269b", + "uc_output": "269b", + "uc_match": "269b-fe0f", + "uc_greedy": "269b", + "shortnames": [":atom_symbol:"], + "category": "symbols" + }, + ":ballot_box_with_check:": { + "uc_base": "2611", + "uc_output": "2611", + "uc_match": "2611-fe0f", + "uc_greedy": "2611", + "shortnames": [], + "category": "symbols" + }, + ":bangbang:": { + "uc_base": "203c", + "uc_output": "203c", + "uc_match": "203c-fe0f", + "uc_greedy": "203c", + "shortnames": [], + "category": "symbols" + }, + ":baseball:": { + "uc_base": "26be", + "uc_output": "26be", + "uc_match": "26be-fe0f", + "uc_greedy": "26be-fe0f", + "shortnames": [], + "category": "activity" + }, + ":beach_umbrella:": { + "uc_base": "26f1", + "uc_output": "26f1", + "uc_match": "26f1-fe0f", + "uc_greedy": "26f1-fe0f", + "shortnames": [":umbrella_on_ground:"], + "category": "travel" + }, + ":biohazard:": { + "uc_base": "2623", + "uc_output": "2623", + "uc_match": "2623-fe0f", + "uc_greedy": "2623", + "shortnames": [":biohazard_sign:"], + "category": "symbols" + }, + ":black_circle:": { + "uc_base": "26ab", + "uc_output": "26ab", + "uc_match": "26ab-fe0f", + "uc_greedy": "26ab-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":black_large_square:": { + "uc_base": "2b1b", + "uc_output": "2b1b", + "uc_match": "2b1b-fe0f", + "uc_greedy": "2b1b-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":black_medium_small_square:": { + "uc_base": "25fe", + "uc_output": "25fe", + "uc_match": "25fe-fe0f", + "uc_greedy": "25fe-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":black_medium_square:": { + "uc_base": "25fc", + "uc_output": "25fc", + "uc_match": "25fc-fe0f", + "uc_greedy": "25fc", + "shortnames": [], + "category": "symbols" + }, + ":black_nib:": { + "uc_base": "2712", + "uc_output": "2712", + "uc_match": "2712-fe0f", + "uc_greedy": "2712-fe0f", + "shortnames": [], + "category": "objects" + }, + ":black_small_square:": { + "uc_base": "25aa", + "uc_output": "25aa", + "uc_match": "25aa-fe0f", + "uc_greedy": "25aa", + "shortnames": [], + "category": "symbols" + }, + ":cancer:": { + "uc_base": "264b", + "uc_output": "264b", + "uc_match": "264b-fe0f", + "uc_greedy": "264b-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":capricorn:": { + "uc_base": "2651", + "uc_output": "2651", + "uc_match": "2651-fe0f", + "uc_greedy": "2651-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":chains:": { + "uc_base": "26d3", + "uc_output": "26d3", + "uc_match": "26d3-fe0f", + "uc_greedy": "26d3-fe0f", + "shortnames": [], + "category": "objects" + }, + ":church:": { + "uc_base": "26ea", + "uc_output": "26ea", + "uc_match": "26ea-fe0f", + "uc_greedy": "26ea-fe0f", + "shortnames": [], + "category": "travel" + }, + ":cloud:": { + "uc_base": "2601", + "uc_output": "2601", + "uc_match": "2601-fe0f", + "uc_greedy": "2601-fe0f", + "shortnames": [], + "category": "nature" + }, + ":clubs:": { + "uc_base": "2663", + "uc_output": "2663", + "uc_match": "2663-fe0f", + "uc_greedy": "2663-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":coffee:": { + "uc_base": "2615", + "uc_output": "2615", + "uc_match": "2615-fe0f", + "uc_greedy": "2615-fe0f", + "shortnames": [], + "category": "food" + }, + ":coffin:": { + "uc_base": "26b0", + "uc_output": "26b0", + "uc_match": "26b0-fe0f", + "uc_greedy": "26b0-fe0f", + "shortnames": [], + "category": "objects" + }, + ":comet:": { + "uc_base": "2604", + "uc_output": "2604", + "uc_match": "2604-fe0f", + "uc_greedy": "2604-fe0f", + "shortnames": [], + "category": "nature" + }, + ":congratulations:": { + "uc_base": "3297", + "uc_output": "3297", + "uc_match": "3297-fe0f", + "uc_greedy": "3297-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":copyright:": { + "uc_base": "00a9", + "uc_output": "00a9", + "uc_match": "00a9-fe0f", + "uc_greedy": "00a9", + "shortnames": [], + "category": "symbols" + }, + ":cross:": { + "uc_base": "271d", + "uc_output": "271d", + "uc_match": "271d-fe0f", + "uc_greedy": "271d", + "shortnames": [":latin_cross:"], + "category": "symbols" + }, + ":crossed_swords:": { + "uc_base": "2694", + "uc_output": "2694", + "uc_match": "2694-fe0f", + "uc_greedy": "2694-fe0f", + "shortnames": [], + "category": "objects" + }, + ":curly_loop:": { + "uc_base": "27b0", + "uc_output": "27b0", + "uc_match": "27b0", + "uc_greedy": "27b0", + "shortnames": [], + "category": "symbols" + }, + ":diamonds:": { + "uc_base": "2666", + "uc_output": "2666", + "uc_match": "2666-fe0f", + "uc_greedy": "2666-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":eight_pointed_black_star:": { + "uc_base": "2734", + "uc_output": "2734", + "uc_match": "2734-fe0f", + "uc_greedy": "2734-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":eight_spoked_asterisk:": { + "uc_base": "2733", + "uc_output": "2733", + "uc_match": "2733-fe0f", + "uc_greedy": "2733-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":eject:": { + "uc_base": "23cf", + "uc_output": "23cf", + "uc_match": "23cf-fe0f", + "uc_greedy": "23cf", + "shortnames": [":eject_symbol:"], + "category": "symbols" + }, + ":envelope:": { + "uc_base": "2709", + "uc_output": "2709", + "uc_match": "2709-fe0f", + "uc_greedy": "2709-fe0f", + "shortnames": [], + "category": "objects" + }, + ":exclamation:": { + "uc_base": "2757", + "uc_output": "2757", + "uc_match": "2757-fe0f", + "uc_greedy": "2757-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":fast_forward:": { + "uc_base": "23e9", + "uc_output": "23e9", + "uc_match": "23e9-fe0f", + "uc_greedy": "23e9-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":female_sign:": { + "uc_base": "2640", + "uc_output": "2640", + "uc_match": "2640-fe0f", + "uc_greedy": "2640", + "shortnames": [], + "category": "symbols" + }, + ":ferry:": { + "uc_base": "26f4", + "uc_output": "26f4", + "uc_match": "26f4-fe0f", + "uc_greedy": "26f4-fe0f", + "shortnames": [], + "category": "travel" + }, + ":fist:": { + "uc_base": "270a", + "uc_output": "270a", + "uc_match": "270a", + "uc_greedy": "270a", + "shortnames": [], + "category": "people" + }, + ":fleur-de-lis:": { + "uc_base": "269c", + "uc_output": "269c", + "uc_match": "269c-fe0f", + "uc_greedy": "269c-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":fountain:": { + "uc_base": "26f2", + "uc_output": "26f2", + "uc_match": "26f2-fe0f", + "uc_greedy": "26f2-fe0f", + "shortnames": [], + "category": "travel" + }, + ":frowning2:": { + "uc_base": "2639", + "uc_output": "2639", + "uc_match": "2639-fe0f", + "uc_greedy": "2639-fe0f", + "shortnames": [":white_frowning_face:"], + "category": "people" + }, + ":fuelpump:": { + "uc_base": "26fd", + "uc_output": "26fd", + "uc_match": "26fd-fe0f", + "uc_greedy": "26fd-fe0f", + "shortnames": [], + "category": "travel" + }, + ":gear:": { + "uc_base": "2699", + "uc_output": "2699", + "uc_match": "2699-fe0f", + "uc_greedy": "2699-fe0f", + "shortnames": [], + "category": "objects" + }, + ":gemini:": { + "uc_base": "264a", + "uc_output": "264a", + "uc_match": "264a-fe0f", + "uc_greedy": "264a-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":golf:": { + "uc_base": "26f3", + "uc_output": "26f3", + "uc_match": "26f3-fe0f", + "uc_greedy": "26f3-fe0f", + "shortnames": [], + "category": "activity" + }, + ":grey_exclamation:": { + "uc_base": "2755", + "uc_output": "2755", + "uc_match": "2755", + "uc_greedy": "2755", + "shortnames": [], + "category": "symbols" + }, + ":grey_question:": { + "uc_base": "2754", + "uc_output": "2754", + "uc_match": "2754", + "uc_greedy": "2754", + "shortnames": [], + "category": "symbols" + }, + ":hammer_pick:": { + "uc_base": "2692", + "uc_output": "2692", + "uc_match": "2692-fe0f", + "uc_greedy": "2692-fe0f", + "shortnames": [":hammer_and_pick:"], + "category": "objects" + }, + ":heart:": { + "uc_base": "2764", + "uc_output": "2764", + "uc_match": "2764-fe0f", + "uc_greedy": "2764-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":heart_exclamation:": { + "uc_base": "2763", + "uc_output": "2763", + "uc_match": "2763-fe0f", + "uc_greedy": "2763-fe0f", + "shortnames": [":heavy_heart_exclamation_mark_ornament:"], + "category": "symbols" + }, + ":hearts:": { + "uc_base": "2665", + "uc_output": "2665", + "uc_match": "2665-fe0f", + "uc_greedy": "2665-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":heavy_check_mark:": { + "uc_base": "2714", + "uc_output": "2714", + "uc_match": "2714-fe0f", + "uc_greedy": "2714", + "shortnames": [], + "category": "symbols" + }, + ":heavy_division_sign:": { + "uc_base": "2797", + "uc_output": "2797", + "uc_match": "2797", + "uc_greedy": "2797", + "shortnames": [], + "category": "symbols" + }, + ":heavy_minus_sign:": { + "uc_base": "2796", + "uc_output": "2796", + "uc_match": "2796", + "uc_greedy": "2796", + "shortnames": [], + "category": "symbols" + }, + ":heavy_multiplication_x:": { + "uc_base": "2716", + "uc_output": "2716", + "uc_match": "2716-fe0f", + "uc_greedy": "2716", + "shortnames": [], + "category": "symbols" + }, + ":heavy_plus_sign:": { + "uc_base": "2795", + "uc_output": "2795", + "uc_match": "2795", + "uc_greedy": "2795", + "shortnames": [], + "category": "symbols" + }, + ":helmet_with_cross:": { + "uc_base": "26d1", + "uc_output": "26d1", + "uc_match": "26d1-fe0f", + "uc_greedy": "26d1-fe0f", + "shortnames": [":helmet_with_white_cross:"], + "category": "people" + }, + ":hotsprings:": { + "uc_base": "2668", + "uc_output": "2668", + "uc_match": "2668-fe0f", + "uc_greedy": "2668-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":hourglass:": { + "uc_base": "231b", + "uc_output": "231b", + "uc_match": "231b-fe0f", + "uc_greedy": "231b-fe0f", + "shortnames": [], + "category": "objects" + }, + ":hourglass_flowing_sand:": { + "uc_base": "23f3", + "uc_output": "23f3", + "uc_match": "23f3-fe0f", + "uc_greedy": "23f3-fe0f", + "shortnames": [], + "category": "objects" + }, + ":ice_skate:": { + "uc_base": "26f8", + "uc_output": "26f8", + "uc_match": "26f8-fe0f", + "uc_greedy": "26f8-fe0f", + "shortnames": [], + "category": "activity" + }, + ":information_source:": { + "uc_base": "2139", + "uc_output": "2139", + "uc_match": "2139-fe0f", + "uc_greedy": "2139", + "shortnames": [], + "category": "symbols" + }, + ":interrobang:": { + "uc_base": "2049", + "uc_output": "2049", + "uc_match": "2049-fe0f", + "uc_greedy": "2049", + "shortnames": [], + "category": "symbols" + }, + ":keyboard:": { + "uc_base": "2328", + "uc_output": "2328", + "uc_match": "2328-fe0f", + "uc_greedy": "2328-fe0f", + "shortnames": [], + "category": "objects" + }, + ":left_right_arrow:": { + "uc_base": "2194", + "uc_output": "2194", + "uc_match": "2194-fe0f", + "uc_greedy": "2194", + "shortnames": [], + "category": "symbols" + }, + ":leftwards_arrow_with_hook:": { + "uc_base": "21a9", + "uc_output": "21a9", + "uc_match": "21a9-fe0f", + "uc_greedy": "21a9", + "shortnames": [], + "category": "symbols" + }, + ":leo:": { + "uc_base": "264c", + "uc_output": "264c", + "uc_match": "264c-fe0f", + "uc_greedy": "264c-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":libra:": { + "uc_base": "264e", + "uc_output": "264e", + "uc_match": "264e-fe0f", + "uc_greedy": "264e-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":loop:": { + "uc_base": "27bf", + "uc_output": "27bf", + "uc_match": "27bf", + "uc_greedy": "27bf", + "shortnames": [], + "category": "symbols" + }, + ":m:": { + "uc_base": "24c2", + "uc_output": "24c2", + "uc_match": "24c2-fe0f", + "uc_greedy": "24c2-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":male_sign:": { + "uc_base": "2642", + "uc_output": "2642", + "uc_match": "2642-fe0f", + "uc_greedy": "2642", + "shortnames": [], + "category": "symbols" + }, + ":medical_symbol:": { + "uc_base": "2695", + "uc_output": "2695", + "uc_match": "2695-fe0f", + "uc_greedy": "2695", + "shortnames": [], + "category": "symbols" + }, + ":mountain:": { + "uc_base": "26f0", + "uc_output": "26f0", + "uc_match": "26f0-fe0f", + "uc_greedy": "26f0-fe0f", + "shortnames": [], + "category": "travel" + }, + ":negative_squared_cross_mark:": { + "uc_base": "274e", + "uc_output": "274e", + "uc_match": "274e", + "uc_greedy": "274e", + "shortnames": [], + "category": "symbols" + }, + ":no_entry:": { + "uc_base": "26d4", + "uc_output": "26d4", + "uc_match": "26d4-fe0f", + "uc_greedy": "26d4-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":o:": { + "uc_base": "2b55", + "uc_output": "2b55", + "uc_match": "2b55-fe0f", + "uc_greedy": "2b55-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":ophiuchus:": { + "uc_base": "26ce", + "uc_output": "26ce", + "uc_match": "26ce", + "uc_greedy": "26ce", + "shortnames": [], + "category": "symbols" + }, + ":orthodox_cross:": { + "uc_base": "2626", + "uc_output": "2626", + "uc_match": "2626-fe0f", + "uc_greedy": "2626", + "shortnames": [], + "category": "symbols" + }, + ":part_alternation_mark:": { + "uc_base": "303d", + "uc_output": "303d", + "uc_match": "303d-fe0f", + "uc_greedy": "303d-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":partly_sunny:": { + "uc_base": "26c5", + "uc_output": "26c5", + "uc_match": "26c5-fe0f", + "uc_greedy": "26c5-fe0f", + "shortnames": [], + "category": "nature" + }, + ":pause_button:": { + "uc_base": "23f8", + "uc_output": "23f8", + "uc_match": "23f8-fe0f", + "uc_greedy": "23f8", + "shortnames": [":double_vertical_bar:"], + "category": "symbols" + }, + ":peace:": { + "uc_base": "262e", + "uc_output": "262e", + "uc_match": "262e-fe0f", + "uc_greedy": "262e", + "shortnames": [":peace_symbol:"], + "category": "symbols" + }, + ":pencil2:": { + "uc_base": "270f", + "uc_output": "270f", + "uc_match": "270f-fe0f", + "uc_greedy": "270f-fe0f", + "shortnames": [], + "category": "objects" + }, + ":person_bouncing_ball:": { + "uc_base": "26f9", + "uc_output": "26f9", + "uc_match": "26f9-fe0f", + "uc_greedy": "26f9-fe0f", + "shortnames": [":basketball_player:", ":person_with_ball:"], + "category": "activity" + }, + ":pick:": { + "uc_base": "26cf", + "uc_output": "26cf", + "uc_match": "26cf-fe0f", + "uc_greedy": "26cf-fe0f", + "shortnames": [], + "category": "objects" + }, + ":pisces:": { + "uc_base": "2653", + "uc_output": "2653", + "uc_match": "2653-fe0f", + "uc_greedy": "2653-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":play_pause:": { + "uc_base": "23ef", + "uc_output": "23ef", + "uc_match": "23ef-fe0f", + "uc_greedy": "23ef", + "shortnames": [], + "category": "symbols" + }, + ":point_up:": { + "uc_base": "261d", + "uc_output": "261d", + "uc_match": "261d-fe0f", + "uc_greedy": "261d-fe0f", + "shortnames": [], + "category": "people" + }, + ":question:": { + "uc_base": "2753", + "uc_output": "2753", + "uc_match": "2753-fe0f", + "uc_greedy": "2753-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":radioactive:": { + "uc_base": "2622", + "uc_output": "2622", + "uc_match": "2622-fe0f", + "uc_greedy": "2622", + "shortnames": [":radioactive_sign:"], + "category": "symbols" + }, + ":raised_hand:": { + "uc_base": "270b", + "uc_output": "270b", + "uc_match": "270b", + "uc_greedy": "270b", + "shortnames": [], + "category": "people" + }, + ":record_button:": { + "uc_base": "23fa", + "uc_output": "23fa", + "uc_match": "23fa-fe0f", + "uc_greedy": "23fa", + "shortnames": [], + "category": "symbols" + }, + ":recycle:": { + "uc_base": "267b", + "uc_output": "267b", + "uc_match": "267b-fe0f", + "uc_greedy": "267b-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":registered:": { + "uc_base": "00ae", + "uc_output": "00ae", + "uc_match": "00ae-fe0f", + "uc_greedy": "00ae", + "shortnames": [], + "category": "symbols" + }, + ":relaxed:": { + "uc_base": "263a", + "uc_output": "263a", + "uc_match": "263a-fe0f", + "uc_greedy": "263a-fe0f", + "shortnames": [], + "category": "people" + }, + ":rewind:": { + "uc_base": "23ea", + "uc_output": "23ea", + "uc_match": "23ea-fe0f", + "uc_greedy": "23ea-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":sagittarius:": { + "uc_base": "2650", + "uc_output": "2650", + "uc_match": "2650-fe0f", + "uc_greedy": "2650-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":sailboat:": { + "uc_base": "26f5", + "uc_output": "26f5", + "uc_match": "26f5-fe0f", + "uc_greedy": "26f5-fe0f", + "shortnames": [], + "category": "travel" + }, + ":scales:": { + "uc_base": "2696", + "uc_output": "2696", + "uc_match": "2696-fe0f", + "uc_greedy": "2696-fe0f", + "shortnames": [], + "category": "objects" + }, + ":scissors:": { + "uc_base": "2702", + "uc_output": "2702", + "uc_match": "2702-fe0f", + "uc_greedy": "2702-fe0f", + "shortnames": [], + "category": "objects" + }, + ":scorpius:": { + "uc_base": "264f", + "uc_output": "264f", + "uc_match": "264f-fe0f", + "uc_greedy": "264f-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":secret:": { + "uc_base": "3299", + "uc_output": "3299", + "uc_match": "3299-fe0f", + "uc_greedy": "3299-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":shamrock:": { + "uc_base": "2618", + "uc_output": "2618", + "uc_match": "2618-fe0f", + "uc_greedy": "2618-fe0f", + "shortnames": [], + "category": "nature" + }, + ":shinto_shrine:": { + "uc_base": "26e9", + "uc_output": "26e9", + "uc_match": "26e9-fe0f", + "uc_greedy": "26e9-fe0f", + "shortnames": [], + "category": "travel" + }, + ":skier:": { + "uc_base": "26f7", + "uc_output": "26f7", + "uc_match": "26f7-fe0f", + "uc_greedy": "26f7-fe0f", + "shortnames": [], + "category": "activity" + }, + ":skull_crossbones:": { + "uc_base": "2620", + "uc_output": "2620", + "uc_match": "2620-fe0f", + "uc_greedy": "2620-fe0f", + "shortnames": [":skull_and_crossbones:"], + "category": "people" + }, + ":snowflake:": { + "uc_base": "2744", + "uc_output": "2744", + "uc_match": "2744-fe0f", + "uc_greedy": "2744-fe0f", + "shortnames": [], + "category": "nature" + }, + ":snowman2:": { + "uc_base": "2603", + "uc_output": "2603", + "uc_match": "2603-fe0f", + "uc_greedy": "2603-fe0f", + "shortnames": [], + "category": "nature" + }, + ":snowman:": { + "uc_base": "26c4", + "uc_output": "26c4", + "uc_match": "26c4-fe0f", + "uc_greedy": "26c4-fe0f", + "shortnames": [], + "category": "nature" + }, + ":soccer:": { + "uc_base": "26bd", + "uc_output": "26bd", + "uc_match": "26bd-fe0f", + "uc_greedy": "26bd-fe0f", + "shortnames": [], + "category": "activity" + }, + ":spades:": { + "uc_base": "2660", + "uc_output": "2660", + "uc_match": "2660-fe0f", + "uc_greedy": "2660-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":sparkle:": { + "uc_base": "2747", + "uc_output": "2747", + "uc_match": "2747-fe0f", + "uc_greedy": "2747-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":sparkles:": { + "uc_base": "2728", + "uc_output": "2728", + "uc_match": "2728", + "uc_greedy": "2728", + "shortnames": [], + "category": "nature" + }, + ":star:": { + "uc_base": "2b50", + "uc_output": "2b50", + "uc_match": "2b50-fe0f", + "uc_greedy": "2b50-fe0f", + "shortnames": [], + "category": "nature" + }, + ":star_and_crescent:": { + "uc_base": "262a", + "uc_output": "262a", + "uc_match": "262a-fe0f", + "uc_greedy": "262a", + "shortnames": [], + "category": "symbols" + }, + ":star_of_david:": { + "uc_base": "2721", + "uc_output": "2721", + "uc_match": "2721-fe0f", + "uc_greedy": "2721", + "shortnames": [], + "category": "symbols" + }, + ":stop_button:": { + "uc_base": "23f9", + "uc_output": "23f9", + "uc_match": "23f9-fe0f", + "uc_greedy": "23f9", + "shortnames": [], + "category": "symbols" + }, + ":stopwatch:": { + "uc_base": "23f1", + "uc_output": "23f1", + "uc_match": "23f1-fe0f", + "uc_greedy": "23f1-fe0f", + "shortnames": [], + "category": "objects" + }, + ":sunny:": { + "uc_base": "2600", + "uc_output": "2600", + "uc_match": "2600-fe0f", + "uc_greedy": "2600-fe0f", + "shortnames": [], + "category": "nature" + }, + ":taurus:": { + "uc_base": "2649", + "uc_output": "2649", + "uc_match": "2649-fe0f", + "uc_greedy": "2649-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":telephone:": { + "uc_base": "260e", + "uc_output": "260e", + "uc_match": "260e-fe0f", + "uc_greedy": "260e-fe0f", + "shortnames": [], + "category": "objects" + }, + ":tent:": { + "uc_base": "26fa", + "uc_output": "26fa", + "uc_match": "26fa-fe0f", + "uc_greedy": "26fa-fe0f", + "shortnames": [], + "category": "travel" + }, + ":thunder_cloud_rain:": { + "uc_base": "26c8", + "uc_output": "26c8", + "uc_match": "26c8-fe0f", + "uc_greedy": "26c8-fe0f", + "shortnames": [":thunder_cloud_and_rain:"], + "category": "nature" + }, + ":timer:": { + "uc_base": "23f2", + "uc_output": "23f2", + "uc_match": "23f2-fe0f", + "uc_greedy": "23f2-fe0f", + "shortnames": [":timer_clock:"], + "category": "objects" + }, + ":tm:": { + "uc_base": "2122", + "uc_output": "2122", + "uc_match": "2122-fe0f", + "uc_greedy": "2122", + "shortnames": [], + "category": "symbols" + }, + ":track_next:": { + "uc_base": "23ed", + "uc_output": "23ed", + "uc_match": "23ed-fe0f", + "uc_greedy": "23ed", + "shortnames": [":next_track:"], + "category": "symbols" + }, + ":track_previous:": { + "uc_base": "23ee", + "uc_output": "23ee", + "uc_match": "23ee-fe0f", + "uc_greedy": "23ee", + "shortnames": [":previous_track:"], + "category": "symbols" + }, + ":umbrella2:": { + "uc_base": "2602", + "uc_output": "2602", + "uc_match": "2602-fe0f", + "uc_greedy": "2602-fe0f", + "shortnames": [], + "category": "nature" + }, + ":umbrella:": { + "uc_base": "2614", + "uc_output": "2614", + "uc_match": "2614-fe0f", + "uc_greedy": "2614-fe0f", + "shortnames": [], + "category": "nature" + }, + ":urn:": { + "uc_base": "26b1", + "uc_output": "26b1", + "uc_match": "26b1-fe0f", + "uc_greedy": "26b1-fe0f", + "shortnames": [":funeral_urn:"], + "category": "objects" + }, + ":v:": { + "uc_base": "270c", + "uc_output": "270c", + "uc_match": "270c-fe0f", + "uc_greedy": "270c-fe0f", + "shortnames": [], + "category": "people" + }, + ":virgo:": { + "uc_base": "264d", + "uc_output": "264d", + "uc_match": "264d-fe0f", + "uc_greedy": "264d-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":warning:": { + "uc_base": "26a0", + "uc_output": "26a0", + "uc_match": "26a0-fe0f", + "uc_greedy": "26a0", + "shortnames": [], + "category": "symbols" + }, + ":watch:": { + "uc_base": "231a", + "uc_output": "231a", + "uc_match": "231a-fe0f", + "uc_greedy": "231a-fe0f", + "shortnames": [], + "category": "objects" + }, + ":wavy_dash:": { + "uc_base": "3030", + "uc_output": "3030", + "uc_match": "3030-fe0f", + "uc_greedy": "3030", + "shortnames": [], + "category": "symbols" + }, + ":wheel_of_dharma:": { + "uc_base": "2638", + "uc_output": "2638", + "uc_match": "2638-fe0f", + "uc_greedy": "2638", + "shortnames": [], + "category": "symbols" + }, + ":wheelchair:": { + "uc_base": "267f", + "uc_output": "267f", + "uc_match": "267f-fe0f", + "uc_greedy": "267f-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":white_check_mark:": { + "uc_base": "2705", + "uc_output": "2705", + "uc_match": "2705", + "uc_greedy": "2705", + "shortnames": [], + "category": "symbols" + }, + ":white_circle:": { + "uc_base": "26aa", + "uc_output": "26aa", + "uc_match": "26aa-fe0f", + "uc_greedy": "26aa-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":white_large_square:": { + "uc_base": "2b1c", + "uc_output": "2b1c", + "uc_match": "2b1c-fe0f", + "uc_greedy": "2b1c-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":white_medium_small_square:": { + "uc_base": "25fd", + "uc_output": "25fd", + "uc_match": "25fd-fe0f", + "uc_greedy": "25fd-fe0f", + "shortnames": [], + "category": "symbols" + }, + ":white_medium_square:": { + "uc_base": "25fb", + "uc_output": "25fb", + "uc_match": "25fb-fe0f", + "uc_greedy": "25fb", + "shortnames": [], + "category": "symbols" + }, + ":white_small_square:": { + "uc_base": "25ab", + "uc_output": "25ab", + "uc_match": "25ab-fe0f", + "uc_greedy": "25ab", + "shortnames": [], + "category": "symbols" + }, + ":writing_hand:": { + "uc_base": "270d", + "uc_output": "270d", + "uc_match": "270d-fe0f", + "uc_greedy": "270d-fe0f", + "shortnames": [], + "category": "people" + }, + ":x:": { + "uc_base": "274c", + "uc_output": "274c", + "uc_match": "274c", + "uc_greedy": "274c", + "shortnames": [], + "category": "symbols" + }, + ":yin_yang:": { + "uc_base": "262f", + "uc_output": "262f", + "uc_match": "262f-fe0f", + "uc_greedy": "262f", + "shortnames": [], + "category": "symbols" + }, + ":zap:": { + "uc_base": "26a1", + "uc_output": "26a1", + "uc_match": "26a1-fe0f", + "uc_greedy": "26a1-fe0f", + "shortnames": [], + "category": "nature" + } +}; +const ascii_list = { + '*\\0/*': '1f646', + '*\\O/*': '1f646', + '-___-': '1f611', + ':\'-)': '1f602', + '\':-)': '1f605', + '\':-D': '1f605', + '>:-)': '1f606', + '\':-(': '1f613', + '>:-(': '1f620', + ':\'-(': '1f622', + 'O:-)': '1f607', + '0:-3': '1f607', + '0:-)': '1f607', + '0;^)': '1f607', + 'O;-)': '1f607', + '0;-)': '1f607', + 'O:-3': '1f607', + '-__-': '1f611', + ':-Þ': '1f61b', + ':)': '1f606', + '>;)': '1f606', + '>=)': '1f606', + ';-)': '1f609', + '*-)': '1f609', + ';-]': '1f609', + ';^)': '1f609', + '\':(': '1f613', + '\'=(': '1f613', + ':-*': '1f618', + ':^*': '1f618', + '>:P': '1f61c', + 'X-P': '1f61c', + '>:[': '1f61e', + ':-(': '1f61e', + ':-[': '1f61e', + '>:(': '1f620', + ':\'(': '1f622', + ';-(': '1f622', + '>.<': '1f623', + '#-)': '1f635', + '%-)': '1f635', + 'X-)': '1f635', + '\\0/': '1f646', + '\\O/': '1f646', + '0:3': '1f607', + '0:)': '1f607', + 'O:)': '1f607', + 'O=)': '1f607', + 'O:3': '1f607', + 'B-)': '1f60e', + '8-)': '1f60e', + 'B-D': '1f60e', + '8-D': '1f60e', + '-_-': '1f611', + '>:\\': '1f615', + '>:/': '1f615', + ':-/': '1f615', + ':-.': '1f615', + ':-P': '1f61b', + ':Þ': '1f61b', + ':-b': '1f61b', + ':-O': '1f62e', + 'O_O': '1f62e', + '>:O': '1f62e', + ':-X': '1f636', + ':-#': '1f636', + ':-)': '1f642', + '(y)': '1f44d', + '<3': '2764', + ':D': '1f603', + '=D': '1f603', + ';)': '1f609', + '*)': '1f609', + ';]': '1f609', + ';D': '1f609', + ':*': '1f618', + '=*': '1f618', + ':(': '1f61e', + ':[': '1f61e', + '=(': '1f61e', + ':@': '1f620', + ';(': '1f622', + 'D:': '1f628', + ':$': '1f633', + '=$': '1f633', + '#)': '1f635', + '%)': '1f635', + 'X)': '1f635', + 'B)': '1f60e', + '8)': '1f60e', + ':/': '1f615', + ':\\': '1f615', + '=/': '1f615', + '=\\': '1f615', + ':L': '1f615', + '=L': '1f615', + ':P': '1f61b', + '=P': '1f61b', + ':b': '1f61b', + ':O': '1f62e', + ':X': '1f636', + ':#': '1f636', + '=X': '1f636', + '=#': '1f636', + ':)': '1f642', + '=]': '1f642', + '=)': '1f642', + ':]': '1f642' +}; +let shortnames = []; + +for (var emoji in emoji_list) { + if (!Object.prototype.hasOwnProperty.call(emoji_list, emoji) || emoji === '') continue; + shortnames.push(emoji.replace(/[+]/g, "\\$&")); + + for (var i = 0; i < emoji_list[emoji].shortnames.length; i++) { + shortnames.push(emoji_list[emoji].shortnames[i].replace(/[+]/g, "\\$&")); + } +} + +shortnames = shortnames.join('|'); +const SHORTNAMES_REGEX = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(" + shortnames + ")", "gi"); +const ASCII_REGEX = '(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:\'\\-\\)|\'\\:\\-\\)|\'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|\'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:\'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:\'\\)|\\:\\-D|\'\\:\\)|\'\\=\\)|\'\\:D|\'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|\'\\:\\(|\'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:\'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\:D|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\])'; +const ASCII_REPLACE_REGEX = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|((\\s|^)" + ASCII_REGEX + "(?=\\s|$|[!,.?]))", "gi"); + +function convert(unicode) { + /* For converting unicode code points and code pairs + * to their respective characters + */ + if (unicode.indexOf("-") > -1) { + const parts = [], + s = unicode.split('-'); + + for (let i = 0; i < s.length; i++) { + let part = parseInt(s[i], 16); + + if (part >= 0x10000 && part <= 0x10FFFF) { + const hi = Math.floor((part - 0x10000) / 0x400) + 0xD800; + const lo = (part - 0x10000) % 0x400 + 0xDC00; + part = String.fromCharCode(hi) + String.fromCharCode(lo); + } else { + part = String.fromCharCode(part); + } + + parts.push(part); + } + + return parts.join(''); + } + + return twemoji__WEBPACK_IMPORTED_MODULE_0__["default"].convert.fromCodePoint(unicode); +} + +_core__WEBPACK_IMPORTED_MODULE_2__["default"].shortnameToUnicode = function (str) { + /* will output unicode from shortname + * useful for sending emojis back to mobile devices + */ + // Replace regular shortnames first + str = str.replace(SHORTNAMES_REGEX, shortname => { + if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emoji_list)) { + // if the shortname doesnt exist just return the entire matchhju + return shortname; + } + + const unicode = emoji_list[shortname].uc_output.toUpperCase(); + return convert(unicode); + }); // Also replace ASCII smileys + + str = str.replace(ASCII_REPLACE_REGEX, (entire, m1, m2, m3) => { + if (typeof m3 === 'undefined' || m3 === '' || !(_core__WEBPACK_IMPORTED_MODULE_2__["default"].unescapeHTML(m3) in ascii_list)) { + // if the ascii doesnt exist just return the entire match + return entire; + } + + m3 = _core__WEBPACK_IMPORTED_MODULE_2__["default"].unescapeHTML(m3); + const unicode = ascii_list[m3].toUpperCase(); + return m2 + convert(unicode); + }); + return str; +}; + +_core__WEBPACK_IMPORTED_MODULE_2__["default"].addEmoji = function (_converse, text) { + if (_converse.use_system_emojis) { + return _core__WEBPACK_IMPORTED_MODULE_2__["default"].shortnameToUnicode(text); + } else { + return twemoji__WEBPACK_IMPORTED_MODULE_0__["default"].parse(text); + } +}; + +_core__WEBPACK_IMPORTED_MODULE_2__["default"].getEmojisByCategory = function (_converse) { + /* Return a dict of emojis with the categories as keys and + * lists of emojis in that category as values. + */ + if (_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isUndefined(_converse.emojis_by_category)) { + const emojis = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.values(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.mapValues(emoji_list, function (value, key, o) { + value._shortname = key; + return value; + })); + + const tones = [':tone1:', ':tone2:', ':tone3:', ':tone4:', ':tone5:']; + const excluded = [':kiss_ww:', ':kiss_mm:', ':kiss_woman_man:']; + const excluded_substrings = [':woman', ':man', ':women_', ':men_', '_man_', '_woman_', '_woman:', '_man:']; + const excluded_categories = ['modifier', 'regional']; + + const categories = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.difference(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.uniq(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.map(emojis, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a, 'category'))), excluded_categories); + + const emojis_by_category = {}; + + _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.forEach(categories, cat => { + let list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.sortBy(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.filter(emojis, ['category', cat]), ['uc_base']); + + list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.filter(list, item => !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.concat(tones, excluded), item._shortname) && !_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.some(excluded_substrings, _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes, item._shortname))); + + if (cat === 'people') { + const idx = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.findIndex(list, ['uc_base', '1f600']); + + list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.union(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, idx), _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 0, idx + 1)); + } else if (cat === 'activity') { + list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.union(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 27 - 1), _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 0, 27)); + } else if (cat === 'objects') { + list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.union(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 24 - 1), _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 0, 24)); + } else if (cat === 'travel') { + list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.union(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 17 - 1), _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 0, 17)); + } else if (cat === 'symbols') { + list = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.union(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 60 - 1), _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.slice(list, 0, 60)); + } + + emojis_by_category[cat] = list; + }); + + _converse.emojis_by_category = emojis_by_category; + } + + return _converse.emojis_by_category; +}; + +_core__WEBPACK_IMPORTED_MODULE_2__["default"].getTonedEmojis = function (_converse) { + _converse.toned_emojis = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.uniq(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.map(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.filter(_core__WEBPACK_IMPORTED_MODULE_2__["default"].getEmojisByCategory(_converse).people, person => _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(person._shortname, '_tone')), person => person._shortname.replace(/_tone[1-5]/, ''))); + return _converse.toned_emojis; +}; + +_core__WEBPACK_IMPORTED_MODULE_2__["default"].getEmojiRenderer = function (_converse) { + return _converse.use_system_emojis ? _core__WEBPACK_IMPORTED_MODULE_2__["default"].shortnameToUnicode : _lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.flow(_core__WEBPACK_IMPORTED_MODULE_2__["default"].shortnameToUnicode, twemoji__WEBPACK_IMPORTED_MODULE_0__["default"].parse); +}; + +/* harmony default export */ __webpack_exports__["default"] = (_core__WEBPACK_IMPORTED_MODULE_2__["default"]); + +/***/ }), + +/***/ "./src/headless/utils/form.js": +/*!************************************!*\ + !*** ./src/headless/utils/form.js ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../lodash.noconflict */ "./src/headless/lodash.noconflict.js"); +/* harmony import */ var _lodash_noconflict__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_lodash_noconflict__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _templates_field_html__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../templates/field.html */ "./src/headless/templates/field.html"); +/* harmony import */ var _templates_field_html__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_templates_field_html__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./core */ "./src/headless/utils/core.js"); +// Converse.js (A browser based XMPP chat client) +// http://conversejs.org +// +// This is the utilities module. +// +// Copyright (c) 2013-2018, Jan-Carel Brand +// Licensed under the Mozilla Public License (MPLv2) + + + + +_core__WEBPACK_IMPORTED_MODULE_2__["default"].webForm2xForm = function (field) { + /* Takes an HTML DOM and turns it into an XForm field. + * + * Parameters: + * (DOMElement) field - the field to convert + */ + let value; + + if (field.getAttribute('type') === 'checkbox') { + value = field.checked && 1 || 0; + } else if (field.tagName == "TEXTAREA") { + value = _lodash_noconflict__WEBPACK_IMPORTED_MODULE_0___default.a.filter(field.value.split('\n'), _lodash_noconflict__WEBPACK_IMPORTED_MODULE_0___default.a.trim); + } else if (field.tagName == "SELECT") { + value = _core__WEBPACK_IMPORTED_MODULE_2__["default"].getSelectValues(field); + } else { + value = field.value; + } + + return _core__WEBPACK_IMPORTED_MODULE_2__["default"].stringToNode(_templates_field_html__WEBPACK_IMPORTED_MODULE_1___default()({ + 'name': field.getAttribute('name'), + 'value': value + })); +}; + +/* harmony default export */ __webpack_exports__["default"] = (_core__WEBPACK_IMPORTED_MODULE_2__["default"]); /***/ }), @@ -100061,123 +100333,120 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /*!***********************************!*\ !*** ./src/headless/utils/muc.js ***! \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @converse/headless/converse-core */ "./src/headless/converse-core.js"); +/* harmony import */ var _core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./core */ "./src/headless/utils/core.js"); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // This is the utilities module. // -// Copyright (c) 2012-2017, Jan-Carel Brand +// Copyright (c) 2013-2018, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) // -/*global define, escape, Jed */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! ../converse-core */ "./src/headless/converse-core.js"), __webpack_require__(/*! ./core */ "./src/headless/utils/core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (converse, u) { - "use strict"; - - const _converse$env = converse.env, - Strophe = _converse$env.Strophe, - sizzle = _converse$env.sizzle, - _ = _converse$env._; - - u.computeAffiliationsDelta = function computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list) { - /* Given two lists of objects with 'jid', 'affiliation' and - * 'reason' properties, return a new list containing - * those objects that are new, changed or removed - * (depending on the 'remove_absentees' boolean). - * - * The affiliations for new and changed members stay the - * same, for removed members, the affiliation is set to 'none'. - * - * The 'reason' property is not taken into account when - * comparing whether affiliations have been changed. - * - * Parameters: - * (Boolean) exclude_existing: Indicates whether JIDs from - * the new list which are also in the old list - * (regardless of affiliation) should be excluded - * from the delta. One reason to do this - * would be when you want to add a JID only if it - * doesn't have *any* existing affiliation at all. - * (Boolean) remove_absentees: Indicates whether JIDs - * from the old list which are not in the new list - * should be considered removed and therefore be - * included in the delta with affiliation set - * to 'none'. - * (Array) new_list: Array containing the new affiliations - * (Array) old_list: Array containing the old affiliations - */ - const new_jids = _.map(new_list, 'jid'); - - const old_jids = _.map(old_list, 'jid'); // Get the new affiliations +/*global escape, Jed */ - let delta = _.map(_.difference(new_jids, old_jids), jid => new_list[_.indexOf(new_jids, jid)]); +const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].env, + Strophe = _converse$env.Strophe, + sizzle = _converse$env.sizzle, + _ = _converse$env._; - if (!exclude_existing) { - // Get the changed affiliations - delta = delta.concat(_.filter(new_list, function (item) { - const idx = _.indexOf(old_jids, item.jid); +_core__WEBPACK_IMPORTED_MODULE_1__["default"].computeAffiliationsDelta = function computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list) { + /* Given two lists of objects with 'jid', 'affiliation' and + * 'reason' properties, return a new list containing + * those objects that are new, changed or removed + * (depending on the 'remove_absentees' boolean). + * + * The affiliations for new and changed members stay the + * same, for removed members, the affiliation is set to 'none'. + * + * The 'reason' property is not taken into account when + * comparing whether affiliations have been changed. + * + * Parameters: + * (Boolean) exclude_existing: Indicates whether JIDs from + * the new list which are also in the old list + * (regardless of affiliation) should be excluded + * from the delta. One reason to do this + * would be when you want to add a JID only if it + * doesn't have *any* existing affiliation at all. + * (Boolean) remove_absentees: Indicates whether JIDs + * from the old list which are not in the new list + * should be considered removed and therefore be + * included in the delta with affiliation set + * to 'none'. + * (Array) new_list: Array containing the new affiliations + * (Array) old_list: Array containing the old affiliations + */ + const new_jids = _.map(new_list, 'jid'); - if (idx >= 0) { - return item.affiliation !== old_list[idx].affiliation; - } + const old_jids = _.map(old_list, 'jid'); // Get the new affiliations - return false; - })); + + let delta = _.map(_.difference(new_jids, old_jids), jid => new_list[_.indexOf(new_jids, jid)]); + + if (!exclude_existing) { + // Get the changed affiliations + delta = delta.concat(_.filter(new_list, function (item) { + const idx = _.indexOf(old_jids, item.jid); + + if (idx >= 0) { + return item.affiliation !== old_list[idx].affiliation; + } + + return false; + })); + } + + if (remove_absentees) { + // Get the removed affiliations + delta = delta.concat(_.map(_.difference(old_jids, new_jids), jid => ({ + 'jid': jid, + 'affiliation': 'none' + }))); + } + + return delta; +}; + +_core__WEBPACK_IMPORTED_MODULE_1__["default"].parseMemberListIQ = function parseMemberListIQ(iq) { + /* Given an IQ stanza with a member list, create an array of member objects. + */ + return _.map(sizzle(`query[xmlns="${Strophe.NS.MUC_ADMIN}"] item`, iq), item => { + const data = { + 'affiliation': item.getAttribute('affiliation') + }; + const jid = item.getAttribute('jid'); + + if (_core__WEBPACK_IMPORTED_MODULE_1__["default"].isValidJID(jid)) { + data['jid'] = jid; + } else { + // XXX: Prosody sends nick for the jid attribute value + // Perhaps for anonymous room? + data['nick'] = jid; } - if (remove_absentees) { - // Get the removed affiliations - delta = delta.concat(_.map(_.difference(old_jids, new_jids), jid => ({ - 'jid': jid, - 'affiliation': 'none' - }))); + const nick = item.getAttribute('nick'); + + if (nick) { + data['nick'] = nick; } - return delta; - }; + const role = item.getAttribute('role'); - u.parseMemberListIQ = function parseMemberListIQ(iq) { - /* Given an IQ stanza with a member list, create an array of member objects. - */ - return _.map(sizzle(`query[xmlns="${Strophe.NS.MUC_ADMIN}"] item`, iq), item => { - const data = { - 'affiliation': item.getAttribute('affiliation') - }; - const jid = item.getAttribute('jid'); + if (role) { + data['role'] = nick; + } - if (u.isValidJID(jid)) { - data['jid'] = jid; - } else { - // XXX: Prosody sends nick for the jid attribute value - // Perhaps for anonymous room? - data['nick'] = jid; - } - - const nick = item.getAttribute('nick'); - - if (nick) { - data['nick'] = nick; - } - - const role = item.getAttribute('role'); - - if (role) { - data['role'] = nick; - } - - return data; - }); - }; -}); + return data; + }); +}; /***/ }), @@ -103408,626 +103677,666 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*global define /*!***************************!*\ !*** ./src/utils/html.js ***! \***************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Converse.js (A browser based XMPP chat client) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var urijs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! urijs */ "./node_modules/urijs/src/URI.js"); +/* harmony import */ var urijs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(urijs__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../headless/lodash.noconflict */ "./src/headless/lodash.noconflict.js"); +/* harmony import */ var _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"); +/* harmony import */ var sizzle__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(sizzle__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _templates_audio_html__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../templates/audio.html */ "./src/templates/audio.html"); +/* harmony import */ var _templates_audio_html__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_templates_audio_html__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _converse_headless_templates_field_html__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @converse/headless/templates/field.html */ "./src/headless/templates/field.html"); +/* harmony import */ var _converse_headless_templates_field_html__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_converse_headless_templates_field_html__WEBPACK_IMPORTED_MODULE_4__); +/* harmony import */ var _templates_file_html__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../templates/file.html */ "./src/templates/file.html"); +/* harmony import */ var _templates_file_html__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_templates_file_html__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var _templates_form_captcha_html__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../templates/form_captcha.html */ "./src/templates/form_captcha.html"); +/* harmony import */ var _templates_form_captcha_html__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_templates_form_captcha_html__WEBPACK_IMPORTED_MODULE_6__); +/* harmony import */ var _templates_form_checkbox_html__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../templates/form_checkbox.html */ "./src/templates/form_checkbox.html"); +/* harmony import */ var _templates_form_checkbox_html__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(_templates_form_checkbox_html__WEBPACK_IMPORTED_MODULE_7__); +/* harmony import */ var _templates_form_input_html__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../templates/form_input.html */ "./src/templates/form_input.html"); +/* harmony import */ var _templates_form_input_html__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_templates_form_input_html__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var _templates_form_select_html__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../templates/form_select.html */ "./src/templates/form_select.html"); +/* harmony import */ var _templates_form_select_html__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(_templates_form_select_html__WEBPACK_IMPORTED_MODULE_9__); +/* harmony import */ var _templates_form_textarea_html__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../templates/form_textarea.html */ "./src/templates/form_textarea.html"); +/* harmony import */ var _templates_form_textarea_html__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(_templates_form_textarea_html__WEBPACK_IMPORTED_MODULE_10__); +/* harmony import */ var _templates_form_url_html__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../templates/form_url.html */ "./src/templates/form_url.html"); +/* harmony import */ var _templates_form_url_html__WEBPACK_IMPORTED_MODULE_11___default = /*#__PURE__*/__webpack_require__.n(_templates_form_url_html__WEBPACK_IMPORTED_MODULE_11__); +/* harmony import */ var _templates_form_username_html__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../templates/form_username.html */ "./src/templates/form_username.html"); +/* harmony import */ var _templates_form_username_html__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(_templates_form_username_html__WEBPACK_IMPORTED_MODULE_12__); +/* harmony import */ var _templates_image_html__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../templates/image.html */ "./src/templates/image.html"); +/* harmony import */ var _templates_image_html__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(_templates_image_html__WEBPACK_IMPORTED_MODULE_13__); +/* harmony import */ var _templates_select_option_html__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../templates/select_option.html */ "./src/templates/select_option.html"); +/* harmony import */ var _templates_select_option_html__WEBPACK_IMPORTED_MODULE_14___default = /*#__PURE__*/__webpack_require__.n(_templates_select_option_html__WEBPACK_IMPORTED_MODULE_14__); +/* harmony import */ var _templates_video_html__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../templates/video.html */ "./src/templates/video.html"); +/* harmony import */ var _templates_video_html__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(_templates_video_html__WEBPACK_IMPORTED_MODULE_15__); +/* harmony import */ var _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ../headless/utils/core */ "./src/headless/utils/core.js"); +// Converse.js (A browser based XMPP chat client) // http://conversejs.org // // This is a form utilities module. // // Copyright (c) 2013-2018, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) -// -/*global define */ -(function (root, factory) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! sizzle */ "./node_modules/sizzle/dist/sizzle.js"), __webpack_require__(/*! ../headless/lodash.noconflict */ "./src/headless/lodash.noconflict.js"), __webpack_require__(/*! ../headless/utils/core */ "./src/headless/utils/core.js"), __webpack_require__(/*! urijs */ "./node_modules/urijs/src/URI.js"), __webpack_require__(/*! ../templates/audio.html */ "./src/templates/audio.html"), __webpack_require__(/*! ../headless/templates/field.html */ "./src/headless/templates/field.html"), __webpack_require__(/*! ../templates/file.html */ "./src/templates/file.html"), __webpack_require__(/*! ../templates/form_captcha.html */ "./src/templates/form_captcha.html"), __webpack_require__(/*! ../templates/form_checkbox.html */ "./src/templates/form_checkbox.html"), __webpack_require__(/*! ../templates/form_input.html */ "./src/templates/form_input.html"), __webpack_require__(/*! ../templates/form_select.html */ "./src/templates/form_select.html"), __webpack_require__(/*! ../templates/form_textarea.html */ "./src/templates/form_textarea.html"), __webpack_require__(/*! ../templates/form_url.html */ "./src/templates/form_url.html"), __webpack_require__(/*! ../templates/form_username.html */ "./src/templates/form_username.html"), __webpack_require__(/*! ../templates/image.html */ "./src/templates/image.html"), __webpack_require__(/*! ../templates/select_option.html */ "./src/templates/select_option.html"), __webpack_require__(/*! ../templates/video.html */ "./src/templates/video.html")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); -})(this, function (sizzle, _, u, URI, tpl_audio, tpl_field, tpl_file, tpl_form_captcha, tpl_form_checkbox, tpl_form_input, tpl_form_select, tpl_form_textarea, tpl_form_url, tpl_form_username, tpl_image, tpl_select_option, tpl_video) { - "use strict"; - const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g; - const logger = _.assign({ - 'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop, - 'error': _.get(console, 'log') ? console.log.bind(console) : _.noop, - 'info': _.get(console, 'log') ? console.log.bind(console) : _.noop, - 'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop - }, console); - const XFORM_TYPE_MAP = { - 'text-private': 'password', - 'text-single': 'text', - 'fixed': 'label', - 'boolean': 'checkbox', - 'hidden': 'hidden', - 'jid-multi': 'textarea', - 'list-single': 'dropdown', - 'list-multi': 'dropdown' - }; - function slideOutWrapup(el) { - /* Wrapup function for slideOut. */ - el.removeAttribute('data-slider-marker'); - el.classList.remove('collapsed'); - el.style.overflow = ""; - el.style.height = ""; + + + + + + + + + + + + +const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g; + +const logger = _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.assign({ + 'debug': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(console, 'log') ? console.log.bind(console) : _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.noop, + 'error': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(console, 'log') ? console.log.bind(console) : _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.noop, + 'info': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(console, 'log') ? console.log.bind(console) : _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.noop, + 'warn': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(console, 'log') ? console.log.bind(console) : _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.noop +}, console); + +const XFORM_TYPE_MAP = { + 'text-private': 'password', + 'text-single': 'text', + 'fixed': 'label', + 'boolean': 'checkbox', + 'hidden': 'hidden', + 'jid-multi': 'textarea', + 'list-single': 'dropdown', + 'list-multi': 'dropdown' +}; + +function slideOutWrapup(el) { + /* Wrapup function for slideOut. */ + el.removeAttribute('data-slider-marker'); + el.classList.remove('collapsed'); + el.style.overflow = ""; + el.style.height = ""; +} + +const isImage = function isImage(url) { + return new Promise((resolve, reject) => { + var img = new Image(); + var timer = window.setTimeout(function () { + reject(new Error("Could not determine whether it's an image")); + img = null; + }, 3000); + + img.onerror = img.onabort = function () { + clearTimeout(timer); + reject(new Error("Could not determine whether it's an image")); + }; + + img.onload = function () { + clearTimeout(timer); + resolve(img); + }; + + img.src = url; + }); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isAudioURL = function (url) { + if (!(url instanceof urijs__WEBPACK_IMPORTED_MODULE_0___default.a)) { + url = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); } - const isImage = function isImage(url) { - return new Promise((resolve, reject) => { - var img = new Image(); - var timer = window.setTimeout(function () { - reject(new Error("Could not determine whether it's an image")); - img = null; - }, 3000); + const filename = url.filename().toLowerCase(); - img.onerror = img.onabort = function () { - clearTimeout(timer); - reject(new Error("Could not determine whether it's an image")); - }; + if (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(["https", "http"], url.protocol().toLowerCase())) { + return false; + } - img.onload = function () { - clearTimeout(timer); - resolve(img); - }; + return filename.endsWith('.ogg') || filename.endsWith('.mp3') || filename.endsWith('.m4a'); +}; - img.src = url; - }); - }; +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isImageURL = function (url) { + if (!(url instanceof urijs__WEBPACK_IMPORTED_MODULE_0___default.a)) { + url = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + } - u.isAudioURL = function (url) { - if (!(url instanceof URI)) { - url = new URI(url); - } + const filename = url.filename().toLowerCase(); - const filename = url.filename().toLowerCase(); + if (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(["https", "http"], url.protocol().toLowerCase())) { + return false; + } - if (!_.includes(["https", "http"], url.protocol().toLowerCase())) { - return false; - } + return filename.endsWith('.jpg') || filename.endsWith('.jpeg') || filename.endsWith('.png') || filename.endsWith('.gif') || filename.endsWith('.bmp') || filename.endsWith('.tiff') || filename.endsWith('.svg'); +}; - return filename.endsWith('.ogg') || filename.endsWith('.mp3') || filename.endsWith('.m4a'); - }; +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isVideoURL = function (url) { + if (!(url instanceof urijs__WEBPACK_IMPORTED_MODULE_0___default.a)) { + url = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + } - u.isImageURL = function (url) { - if (!(url instanceof URI)) { - url = new URI(url); - } + const filename = url.filename().toLowerCase(); - const filename = url.filename().toLowerCase(); + if (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(["https", "http"], url.protocol().toLowerCase())) { + return false; + } - if (!_.includes(["https", "http"], url.protocol().toLowerCase())) { - return false; - } + return filename.endsWith('.mp4') || filename.endsWith('.webm'); +}; - return filename.endsWith('.jpg') || filename.endsWith('.jpeg') || filename.endsWith('.png') || filename.endsWith('.gif') || filename.endsWith('.bmp') || filename.endsWith('.tiff') || filename.endsWith('.svg'); - }; - - u.isVideoURL = function (url) { - if (!(url instanceof URI)) { - url = new URI(url); - } - - const filename = url.filename().toLowerCase(); - - if (!_.includes(["https", "http"], url.protocol().toLowerCase())) { - return false; - } - - return filename.endsWith('.mp4') || filename.endsWith('.webm'); - }; - - u.renderAudioURL = function (_converse, url) { - const uri = new URI(url); - - if (u.isAudioURL(uri)) { - const __ = _converse.__; - return tpl_audio({ - 'url': url, - 'label_download': __('Download audio file "%1$s"', decodeURI(uri.filename())) - }); - } - - return url; - }; - - u.renderFileURL = function (_converse, url) { - const uri = new URI(url); - - if (u.isImageURL(uri) || u.isVideoURL(uri) || u.isAudioURL(uri)) { - return url; - } - - const __ = _converse.__, - filename = uri.filename(); - return tpl_file({ - 'url': url, - 'label_download': __('Download file "%1$s"', decodeURI(filename)) - }); - }; - - u.renderImageURL = function (_converse, url) { - if (!_converse.show_images_inline) { - return u.addHyperlinks(url); - } - - const uri = new URI(url); - - if (u.isImageURL(uri)) { - const __ = _converse.__; - return tpl_image({ - 'url': url, - 'label_download': __('Download image "%1$s"', decodeURI(uri.filename())) - }); - } - - return url; - }; - - u.renderImageURLs = function (_converse, el) { - /* Returns a Promise which resolves once all images have been loaded. - */ - if (!_converse.show_images_inline) { - return Promise.resolve(); - } +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].renderAudioURL = function (_converse, url) { + const uri = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + if (_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isAudioURL(uri)) { const __ = _converse.__; - const list = el.textContent.match(URL_REGEX) || []; - return Promise.all(_.map(list, url => new Promise((resolve, reject) => { - if (u.isImageURL(url)) { - return isImage(url).then(img => { - const i = new Image(); - i.src = img.src; - i.addEventListener('load', resolve); // We also resolve for non-images, otherwise the - // Promise.all resolves prematurely. - - i.addEventListener('error', resolve); - const __ = _converse.__; - - _.each(sizzle(`a[href="${url}"]`, el), a => { - a.outerHTML = tpl_image({ - 'url': url, - 'label_download': __('Download') - }); - }); - }).catch(resolve); - } else { - return resolve(); - } - }))); - }; - - u.renderMovieURL = function (_converse, url) { - const uri = new URI(url); - - if (u.isVideoURL(uri)) { - const __ = _converse.__; - return tpl_video({ - 'url': url, - 'label_download': __('Download video file "%1$s"', decodeURI(uri.filename())) - }); - } - - return url; - }; - - u.renderNewLines = function (text) { - return text.replace(/\n\n+/g, '

').replace(/\n/g, '
'); - }; - - u.calculateElementHeight = function (el) { - /* Return the height of the passed in DOM element, - * based on the heights of its children. - */ - return _.reduce(el.children, (result, child) => result + child.offsetHeight, 0); - }; - - u.getNextElement = function (el, selector = '*') { - let next_el = el.nextElementSibling; - - while (!_.isNull(next_el) && !sizzle.matchesSelector(next_el, selector)) { - next_el = next_el.nextElementSibling; - } - - return next_el; - }; - - u.getPreviousElement = function (el, selector = '*') { - let prev_el = el.previousSibling; - - while (!_.isNull(prev_el) && !sizzle.matchesSelector(prev_el, selector)) { - prev_el = prev_el.previousSibling; - } - - return prev_el; - }; - - u.getFirstChildElement = function (el, selector = '*') { - let first_el = el.firstElementChild; - - while (!_.isNull(first_el) && !sizzle.matchesSelector(first_el, selector)) { - first_el = first_el.nextSibling; - } - - return first_el; - }; - - u.getLastChildElement = function (el, selector = '*') { - let last_el = el.lastElementChild; - - while (!_.isNull(last_el) && !sizzle.matchesSelector(last_el, selector)) { - last_el = last_el.previousSibling; - } - - return last_el; - }; - - u.hasClass = function (className, el) { - return _.includes(el.classList, className); - }; - - u.addClass = function (className, el) { - if (el instanceof Element) { - el.classList.add(className); - } - }; - - u.removeClass = function (className, el) { - if (el instanceof Element) { - el.classList.remove(className); - } - - return el; - }; - - u.removeElement = function (el) { - if (!_.isNil(el) && !_.isNil(el.parentNode)) { - el.parentNode.removeChild(el); - } - }; - - u.showElement = _.flow(_.partial(u.removeClass, 'collapsed'), _.partial(u.removeClass, 'hidden')); - - u.hideElement = function (el) { - if (!_.isNil(el)) { - el.classList.add('hidden'); - } - - return el; - }; - - u.ancestor = function (el, selector) { - let parent = el; - - while (!_.isNil(parent) && !sizzle.matchesSelector(parent, selector)) { - parent = parent.parentElement; - } - - return parent; - }; - - u.nextUntil = function (el, selector, include_self = false) { - /* Return the element's siblings until one matches the selector. */ - const matches = []; - let sibling_el = el.nextElementSibling; - - while (!_.isNil(sibling_el) && !sibling_el.matches(selector)) { - matches.push(sibling_el); - sibling_el = sibling_el.nextElementSibling; - } - - return matches; - }; - - u.unescapeHTML = function (string) { - /* Helper method that replace HTML-escaped symbols with equivalent characters - * (e.g. transform occurrences of '&' to '&') - * - * Parameters: - * (String) string: a String containing the HTML-escaped symbols. - */ - var div = document.createElement('div'); - div.innerHTML = string; - return div.innerText; - }; - - u.escapeHTML = function (string) { - return string.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); - }; - - u.addMentionsMarkup = function (text, references, chatbox) { - if (chatbox.get('message_type') !== 'groupchat') { - return text; - } - - const nick = chatbox.get('nick'); - references.sort((a, b) => b.begin - a.begin).forEach(ref => { - const mention = text.slice(ref.begin, ref.end); - chatbox; - - if (mention === nick) { - text = text.slice(0, ref.begin) + `${mention}` + text.slice(ref.end); - } else { - text = text.slice(0, ref.begin) + `${mention}` + text.slice(ref.end); - } + return _templates_audio_html__WEBPACK_IMPORTED_MODULE_3___default()({ + 'url': url, + 'label_download': __('Download audio file "%1$s"', decodeURI(uri.filename())) }); - return text; - }; - - u.addHyperlinks = function (text) { - return URI.withinString(text, url => { - const uri = new URI(url); - url = uri.normalize()._string; - const pretty_url = uri._parts.urn ? url : uri.readable(); - - if (!uri._parts.protocol && !url.startsWith('http://') && !url.startsWith('https://')) { - url = 'http://' + url; - } - - if (uri._parts.protocol === 'xmpp' && uri._parts.query === 'join') { - return `${u.escapeHTML(pretty_url)}`; - } - - return `${u.escapeHTML(pretty_url)}`; - }, { - 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi - }); - }; - - u.slideInAllElements = function (elements, duration = 300) { - return Promise.all(_.map(elements, _.partial(u.slideIn, _, duration))); - }; - - u.slideToggleElement = function (el, duration) { - if (_.includes(el.classList, 'collapsed') || _.includes(el.classList, 'hidden')) { - return u.slideOut(el, duration); - } else { - return u.slideIn(el, duration); - } - }; - - u.slideOut = function (el, duration = 200) { - /* Shows/expands an element by sliding it out of itself - * - * Parameters: - * (HTMLElement) el - The HTML string - * (Number) duration - The duration amount in milliseconds - */ - return new Promise((resolve, reject) => { - if (_.isNil(el)) { - const err = "Undefined or null element passed into slideOut"; - logger.warn(err); - reject(new Error(err)); - return; - } - - const marker = el.getAttribute('data-slider-marker'); - - if (marker) { - el.removeAttribute('data-slider-marker'); - window.cancelAnimationFrame(marker); - } - - const end_height = u.calculateElementHeight(el); - - if (window.converse_disable_effects) { - // Effects are disabled (for tests) - el.style.height = end_height + 'px'; - slideOutWrapup(el); - resolve(); - return; - } - - if (!u.hasClass('collapsed', el) && !u.hasClass('hidden', el)) { - resolve(); - return; - } - - const steps = duration / 17; // We assume 17ms per animation which is ~60FPS - - let height = 0; - - function draw() { - height += end_height / steps; - - if (height < end_height) { - el.style.height = height + 'px'; - el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); - } else { - // We recalculate the height to work around an apparent - // browser bug where browsers don't know the correct - // offsetHeight beforehand. - el.removeAttribute('data-slider-marker'); - el.style.height = u.calculateElementHeight(el) + 'px'; - el.style.overflow = ""; - el.style.height = ""; - resolve(); - } - } - - el.style.height = '0'; - el.style.overflow = 'hidden'; - el.classList.remove('hidden'); - el.classList.remove('collapsed'); - el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); - }); - }; - - u.slideIn = function (el, duration = 200) { - /* Hides/collapses an element by sliding it into itself. */ - return new Promise((resolve, reject) => { - if (_.isNil(el)) { - const err = "Undefined or null element passed into slideIn"; - logger.warn(err); - return reject(new Error(err)); - } else if (_.includes(el.classList, 'collapsed')) { - return resolve(el); - } else if (window.converse_disable_effects) { - // Effects are disabled (for tests) - el.classList.add('collapsed'); - el.style.height = ""; - return resolve(el); - } - - const marker = el.getAttribute('data-slider-marker'); - - if (marker) { - el.removeAttribute('data-slider-marker'); - window.cancelAnimationFrame(marker); - } - - const original_height = el.offsetHeight, - steps = duration / 17; // We assume 17ms per animation which is ~60FPS - - let height = original_height; - el.style.overflow = 'hidden'; - - function draw() { - height -= original_height / steps; - - if (height > 0) { - el.style.height = height + 'px'; - el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); - } else { - el.removeAttribute('data-slider-marker'); - el.classList.add('collapsed'); - el.style.height = ""; - resolve(el); - } - } - - el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); - }); - }; - - function afterAnimationEnds(el, callback) { - el.classList.remove('visible'); - - if (_.isFunction(callback)) { - callback(); - } } - u.fadeIn = function (el, callback) { - if (_.isNil(el)) { - logger.warn("Undefined or null element passed into fadeIn"); + return url; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].renderFileURL = function (_converse, url) { + const uri = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + + if (_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isImageURL(uri) || _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isVideoURL(uri) || _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isAudioURL(uri)) { + return url; + } + + const __ = _converse.__, + filename = uri.filename(); + return _templates_file_html__WEBPACK_IMPORTED_MODULE_5___default()({ + 'url': url, + 'label_download': __('Download file "%1$s"', decodeURI(filename)) + }); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].renderImageURL = function (_converse, url) { + if (!_converse.show_images_inline) { + return _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].addHyperlinks(url); + } + + const uri = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + + if (_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isImageURL(uri)) { + const __ = _converse.__; + return _templates_image_html__WEBPACK_IMPORTED_MODULE_13___default()({ + 'url': url, + 'label_download': __('Download image "%1$s"', decodeURI(uri.filename())) + }); + } + + return url; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].renderImageURLs = function (_converse, el) { + /* Returns a Promise which resolves once all images have been loaded. + */ + if (!_converse.show_images_inline) { + return Promise.resolve(); + } + + const __ = _converse.__; + const list = el.textContent.match(URL_REGEX) || []; + return Promise.all(_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.map(list, url => new Promise((resolve, reject) => { + if (_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isImageURL(url)) { + return isImage(url).then(img => { + const i = new Image(); + i.src = img.src; + i.addEventListener('load', resolve); // We also resolve for non-images, otherwise the + // Promise.all resolves prematurely. + + i.addEventListener('error', resolve); + const __ = _converse.__; + + _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.each(sizzle__WEBPACK_IMPORTED_MODULE_2___default()(`a[href="${url}"]`, el), a => { + a.outerHTML = _templates_image_html__WEBPACK_IMPORTED_MODULE_13___default()({ + 'url': url, + 'label_download': __('Download') + }); + }); + }).catch(resolve); + } else { + return resolve(); } + }))); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].renderMovieURL = function (_converse, url) { + const uri = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + + if (_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].isVideoURL(uri)) { + const __ = _converse.__; + return _templates_video_html__WEBPACK_IMPORTED_MODULE_15___default()({ + 'url': url, + 'label_download': __('Download video file "%1$s"', decodeURI(uri.filename())) + }); + } + + return url; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].renderNewLines = function (text) { + return text.replace(/\n\n+/g, '

').replace(/\n/g, '
'); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].calculateElementHeight = function (el) { + /* Return the height of the passed in DOM element, + * based on the heights of its children. + */ + return _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.reduce(el.children, (result, child) => result + child.offsetHeight, 0); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getNextElement = function (el, selector = '*') { + let next_el = el.nextElementSibling; + + while (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNull(next_el) && !sizzle__WEBPACK_IMPORTED_MODULE_2___default.a.matchesSelector(next_el, selector)) { + next_el = next_el.nextElementSibling; + } + + return next_el; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getPreviousElement = function (el, selector = '*') { + let prev_el = el.previousSibling; + + while (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNull(prev_el) && !sizzle__WEBPACK_IMPORTED_MODULE_2___default.a.matchesSelector(prev_el, selector)) { + prev_el = prev_el.previousSibling; + } + + return prev_el; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getFirstChildElement = function (el, selector = '*') { + let first_el = el.firstElementChild; + + while (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNull(first_el) && !sizzle__WEBPACK_IMPORTED_MODULE_2___default.a.matchesSelector(first_el, selector)) { + first_el = first_el.nextSibling; + } + + return first_el; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getLastChildElement = function (el, selector = '*') { + let last_el = el.lastElementChild; + + while (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNull(last_el) && !sizzle__WEBPACK_IMPORTED_MODULE_2___default.a.matchesSelector(last_el, selector)) { + last_el = last_el.previousSibling; + } + + return last_el; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].hasClass = function (className, el) { + return _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(el.classList, className); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].addClass = function (className, el) { + if (el instanceof Element) { + el.classList.add(className); + } +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].removeClass = function (className, el) { + if (el instanceof Element) { + el.classList.remove(className); + } + + return el; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].removeElement = function (el) { + if (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(el) && !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(el.parentNode)) { + el.parentNode.removeChild(el); + } +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].showElement = _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.flow(_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].removeClass, 'collapsed'), _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].removeClass, 'hidden')); + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].hideElement = function (el) { + if (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(el)) { + el.classList.add('hidden'); + } + + return el; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].ancestor = function (el, selector) { + let parent = el; + + while (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(parent) && !sizzle__WEBPACK_IMPORTED_MODULE_2___default.a.matchesSelector(parent, selector)) { + parent = parent.parentElement; + } + + return parent; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].nextUntil = function (el, selector, include_self = false) { + /* Return the element's siblings until one matches the selector. */ + const matches = []; + let sibling_el = el.nextElementSibling; + + while (!_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(sibling_el) && !sibling_el.matches(selector)) { + matches.push(sibling_el); + sibling_el = sibling_el.nextElementSibling; + } + + return matches; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].unescapeHTML = function (string) { + /* Helper method that replace HTML-escaped symbols with equivalent characters + * (e.g. transform occurrences of '&' to '&') + * + * Parameters: + * (String) string: a String containing the HTML-escaped symbols. + */ + var div = document.createElement('div'); + div.innerHTML = string; + return div.innerText; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].escapeHTML = function (string) { + return string.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].addMentionsMarkup = function (text, references, chatbox) { + if (chatbox.get('message_type') !== 'groupchat') { + return text; + } + + const nick = chatbox.get('nick'); + references.sort((a, b) => b.begin - a.begin).forEach(ref => { + const mention = text.slice(ref.begin, ref.end); + chatbox; + + if (mention === nick) { + text = text.slice(0, ref.begin) + `${mention}` + text.slice(ref.end); + } else { + text = text.slice(0, ref.begin) + `${mention}` + text.slice(ref.end); + } + }); + return text; +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].addHyperlinks = function (text) { + return urijs__WEBPACK_IMPORTED_MODULE_0___default.a.withinString(text, url => { + const uri = new urijs__WEBPACK_IMPORTED_MODULE_0___default.a(url); + url = uri.normalize()._string; + const pretty_url = uri._parts.urn ? url : uri.readable(); + + if (!uri._parts.protocol && !url.startsWith('http://') && !url.startsWith('https://')) { + url = 'http://' + url; + } + + if (uri._parts.protocol === 'xmpp' && uri._parts.query === 'join') { + return `${_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].escapeHTML(pretty_url)}`; + } + + return `${_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].escapeHTML(pretty_url)}`; + }, { + 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi + }); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideInAllElements = function (elements, duration = 300) { + return Promise.all(_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.map(elements, _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideIn, _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a, duration))); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideToggleElement = function (el, duration) { + if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(el.classList, 'collapsed') || _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(el.classList, 'hidden')) { + return _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideOut(el, duration); + } else { + return _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideIn(el, duration); + } +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideOut = function (el, duration = 200) { + /* Shows/expands an element by sliding it out of itself + * + * Parameters: + * (HTMLElement) el - The HTML string + * (Number) duration - The duration amount in milliseconds + */ + return new Promise((resolve, reject) => { + if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(el)) { + const err = "Undefined or null element passed into slideOut"; + logger.warn(err); + reject(new Error(err)); + return; + } + + const marker = el.getAttribute('data-slider-marker'); + + if (marker) { + el.removeAttribute('data-slider-marker'); + window.cancelAnimationFrame(marker); + } + + const end_height = _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].calculateElementHeight(el); if (window.converse_disable_effects) { - el.classList.remove('hidden'); - return afterAnimationEnds(el, callback); + // Effects are disabled (for tests) + el.style.height = end_height + 'px'; + slideOutWrapup(el); + resolve(); + return; } - if (_.includes(el.classList, 'hidden')) { - el.classList.add('visible'); - el.classList.remove('hidden'); - el.addEventListener("webkitAnimationEnd", _.partial(afterAnimationEnds, el, callback)); - el.addEventListener("animationend", _.partial(afterAnimationEnds, el, callback)); - el.addEventListener("oanimationend", _.partial(afterAnimationEnds, el, callback)); - } else { - afterAnimationEnds(el, callback); + if (!_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].hasClass('collapsed', el) && !_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].hasClass('hidden', el)) { + resolve(); + return; } - }; - u.xForm2webForm = function (field, stanza, domain) { - /* Takes a field in XMPP XForm (XEP-004: Data Forms) format - * and turns it into an HTML field. - * - * Returns either text or a DOM element (which is not ideal, but fine - * for now). - * - * Parameters: - * (XMLElement) field - the field to convert - */ - if (field.getAttribute('type')) { - if (field.getAttribute('type') === 'list-single' || field.getAttribute('type') === 'list-multi') { - const values = _.map(u.queryChildren(field, 'value'), _.partial(_.get, _, 'textContent')); + const steps = duration / 17; // We assume 17ms per animation which is ~60FPS - const options = _.map(u.queryChildren(field, 'option'), function (option) { - const value = _.get(option.querySelector('value'), 'textContent'); + let height = 0; - return tpl_select_option({ - 'value': value, - 'label': option.getAttribute('label'), - 'selected': _.includes(values, value), - 'required': !_.isNil(field.querySelector('required')) - }); - }); + function draw() { + height += end_height / steps; - return tpl_form_select({ - 'id': u.getUniqueId(), - 'name': field.getAttribute('var'), - 'label': field.getAttribute('label'), - 'options': options.join(''), - 'multiple': field.getAttribute('type') === 'list-multi', - 'required': !_.isNil(field.querySelector('required')) - }); - } else if (field.getAttribute('type') === 'fixed') { - const text = _.get(field.querySelector('value'), 'textContent'); - - return '

' + text + '

'; - } else if (field.getAttribute('type') === 'jid-multi') { - return tpl_form_textarea({ - 'name': field.getAttribute('var'), - 'label': field.getAttribute('label') || '', - 'value': _.get(field.querySelector('value'), 'textContent'), - 'required': !_.isNil(field.querySelector('required')) - }); - } else if (field.getAttribute('type') === 'boolean') { - return tpl_form_checkbox({ - 'id': u.getUniqueId(), - 'name': field.getAttribute('var'), - 'label': field.getAttribute('label') || '', - 'checked': _.get(field.querySelector('value'), 'textContent') === "1" && 'checked="1"' || '', - 'required': !_.isNil(field.querySelector('required')) - }); - } else if (field.getAttribute('var') === 'url') { - return tpl_form_url({ - 'label': field.getAttribute('label') || '', - 'value': _.get(field.querySelector('value'), 'textContent') - }); - } else if (field.getAttribute('var') === 'username') { - return tpl_form_username({ - 'domain': ' @' + domain, - 'name': field.getAttribute('var'), - 'type': XFORM_TYPE_MAP[field.getAttribute('type')], - 'label': field.getAttribute('label') || '', - 'value': _.get(field.querySelector('value'), 'textContent'), - 'required': !_.isNil(field.querySelector('required')) - }); + if (height < end_height) { + el.style.height = height + 'px'; + el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); } else { - return tpl_form_input({ - 'id': u.getUniqueId(), - 'label': field.getAttribute('label') || '', - 'name': field.getAttribute('var'), - 'placeholder': null, - 'required': !_.isNil(field.querySelector('required')), - 'type': XFORM_TYPE_MAP[field.getAttribute('type')], - 'value': _.get(field.querySelector('value'), 'textContent') - }); - } - } else { - if (field.getAttribute('var') === 'ocr') { - // Captcha - const uri = field.querySelector('uri'); - const el = sizzle('data[cid="' + uri.textContent.replace(/^cid:/, '') + '"]', stanza)[0]; - return tpl_form_captcha({ - 'label': field.getAttribute('label'), - 'name': field.getAttribute('var'), - 'data': _.get(el, 'textContent'), - 'type': uri.getAttribute('type'), - 'required': !_.isNil(field.querySelector('required')) - }); + // We recalculate the height to work around an apparent + // browser bug where browsers don't know the correct + // offsetHeight beforehand. + el.removeAttribute('data-slider-marker'); + el.style.height = _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].calculateElementHeight(el) + 'px'; + el.style.overflow = ""; + el.style.height = ""; + resolve(); } } - }; - return u; -}); + el.style.height = '0'; + el.style.overflow = 'hidden'; + el.classList.remove('hidden'); + el.classList.remove('collapsed'); + el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); + }); +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].slideIn = function (el, duration = 200) { + /* Hides/collapses an element by sliding it into itself. */ + return new Promise((resolve, reject) => { + if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(el)) { + const err = "Undefined or null element passed into slideIn"; + logger.warn(err); + return reject(new Error(err)); + } else if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(el.classList, 'collapsed')) { + return resolve(el); + } else if (window.converse_disable_effects) { + // Effects are disabled (for tests) + el.classList.add('collapsed'); + el.style.height = ""; + return resolve(el); + } + + const marker = el.getAttribute('data-slider-marker'); + + if (marker) { + el.removeAttribute('data-slider-marker'); + window.cancelAnimationFrame(marker); + } + + const original_height = el.offsetHeight, + steps = duration / 17; // We assume 17ms per animation which is ~60FPS + + let height = original_height; + el.style.overflow = 'hidden'; + + function draw() { + height -= original_height / steps; + + if (height > 0) { + el.style.height = height + 'px'; + el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); + } else { + el.removeAttribute('data-slider-marker'); + el.classList.add('collapsed'); + el.style.height = ""; + resolve(el); + } + } + + el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw)); + }); +}; + +function afterAnimationEnds(el, callback) { + el.classList.remove('visible'); + + if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isFunction(callback)) { + callback(); + } +} + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].fadeIn = function (el, callback) { + if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(el)) { + logger.warn("Undefined or null element passed into fadeIn"); + } + + if (window.converse_disable_effects) { + el.classList.remove('hidden'); + return afterAnimationEnds(el, callback); + } + + if (_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(el.classList, 'hidden')) { + el.classList.add('visible'); + el.classList.remove('hidden'); + el.addEventListener("webkitAnimationEnd", _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(afterAnimationEnds, el, callback)); + el.addEventListener("animationend", _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(afterAnimationEnds, el, callback)); + el.addEventListener("oanimationend", _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(afterAnimationEnds, el, callback)); + } else { + afterAnimationEnds(el, callback); + } +}; + +_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].xForm2webForm = function (field, stanza, domain) { + /* Takes a field in XMPP XForm (XEP-004: Data Forms) format + * and turns it into an HTML field. + * + * Returns either text or a DOM element (which is not ideal, but fine + * for now). + * + * Parameters: + * (XMLElement) field - the field to convert + */ + if (field.getAttribute('type')) { + if (field.getAttribute('type') === 'list-single' || field.getAttribute('type') === 'list-multi') { + const values = _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.map(_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].queryChildren(field, 'value'), _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.partial(_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get, _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a, 'textContent')); + + const options = _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.map(_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].queryChildren(field, 'option'), function (option) { + const value = _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(option.querySelector('value'), 'textContent'); + + return _templates_select_option_html__WEBPACK_IMPORTED_MODULE_14___default()({ + 'value': value, + 'label': option.getAttribute('label'), + 'selected': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.includes(values, value), + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')) + }); + }); + + return _templates_form_select_html__WEBPACK_IMPORTED_MODULE_9___default()({ + 'id': _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getUniqueId(), + 'name': field.getAttribute('var'), + 'label': field.getAttribute('label'), + 'options': options.join(''), + 'multiple': field.getAttribute('type') === 'list-multi', + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')) + }); + } else if (field.getAttribute('type') === 'fixed') { + const text = _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(field.querySelector('value'), 'textContent'); + + return '

' + text + '

'; + } else if (field.getAttribute('type') === 'jid-multi') { + return _templates_form_textarea_html__WEBPACK_IMPORTED_MODULE_10___default()({ + 'name': field.getAttribute('var'), + 'label': field.getAttribute('label') || '', + 'value': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(field.querySelector('value'), 'textContent'), + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')) + }); + } else if (field.getAttribute('type') === 'boolean') { + return _templates_form_checkbox_html__WEBPACK_IMPORTED_MODULE_7___default()({ + 'id': _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getUniqueId(), + 'name': field.getAttribute('var'), + 'label': field.getAttribute('label') || '', + 'checked': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(field.querySelector('value'), 'textContent') === "1" && 'checked="1"' || '', + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')) + }); + } else if (field.getAttribute('var') === 'url') { + return _templates_form_url_html__WEBPACK_IMPORTED_MODULE_11___default()({ + 'label': field.getAttribute('label') || '', + 'value': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(field.querySelector('value'), 'textContent') + }); + } else if (field.getAttribute('var') === 'username') { + return _templates_form_username_html__WEBPACK_IMPORTED_MODULE_12___default()({ + 'domain': ' @' + domain, + 'name': field.getAttribute('var'), + 'type': XFORM_TYPE_MAP[field.getAttribute('type')], + 'label': field.getAttribute('label') || '', + 'value': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(field.querySelector('value'), 'textContent'), + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')) + }); + } else { + return _templates_form_input_html__WEBPACK_IMPORTED_MODULE_8___default()({ + 'id': _headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"].getUniqueId(), + 'label': field.getAttribute('label') || '', + 'name': field.getAttribute('var'), + 'placeholder': null, + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')), + 'type': XFORM_TYPE_MAP[field.getAttribute('type')], + 'value': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(field.querySelector('value'), 'textContent') + }); + } + } else { + if (field.getAttribute('var') === 'ocr') { + // Captcha + const uri = field.querySelector('uri'); + const el = sizzle__WEBPACK_IMPORTED_MODULE_2___default()('data[cid="' + uri.textContent.replace(/^cid:/, '') + '"]', stanza)[0]; + return _templates_form_captcha_html__WEBPACK_IMPORTED_MODULE_6___default()({ + 'label': field.getAttribute('label'), + 'name': field.getAttribute('var'), + 'data': _headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.get(el, 'textContent'), + 'type': uri.getAttribute('type'), + 'required': !_headless_lodash_noconflict__WEBPACK_IMPORTED_MODULE_1___default.a.isNil(field.querySelector('required')) + }); + } + } +}; + +/* harmony default export */ __webpack_exports__["default"] = (_headless_utils_core__WEBPACK_IMPORTED_MODULE_16__["default"]); /***/ }) diff --git a/spec/chatroom.js b/spec/chatroom.js index 39082b876..0573dead0 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -1555,34 +1555,32 @@ it("properly handles notification that a room has been destroyed", mock.initConverseWithPromises( null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + async function (done, _converse) { - test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy') - .then(function () { - const presence = $pres().attrs({ - from:'problematic@muc.localhost', - id:'n13mt3l', - to:'dummy@localhost/pda', - type:'error'}) - .c('error').attrs({'type':'cancel'}) - .c('gone').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}) - .t('xmpp:other-room@chat.jabberfr.org?join').up() - .c('text').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}) - .t("We didn't like the name").nodeTree; + await test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy') + const presence = $pres().attrs({ + from:'problematic@muc.localhost', + id:'n13mt3l', + to:'dummy@localhost/pda', + type:'error'}) + .c('error').attrs({'type':'cancel'}) + .c('gone').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}) + .t('xmpp:other-room@chat.jabberfr.org?join').up() + .c('text').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}) + .t("We didn't like the name").nodeTree; - const view = _converse.chatboxviews.get('problematic@muc.localhost'); - spyOn(view, 'showErrorMessage').and.callThrough(); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(view.el.querySelector('.chatroom-body .disconnect-msg').textContent) - .toBe('This room no longer exists'); - expect(view.el.querySelector('.chatroom-body .destroyed-reason').textContent) - .toBe(`"We didn't like the name"`); - expect(view.el.querySelector('.chatroom-body .moved-label').textContent.trim()) - .toBe('The conversation has moved. Click below to enter.'); - expect(view.el.querySelector('.chatroom-body .moved-link').textContent.trim()) - .toBe(`other-room@chat.jabberfr.org`); - done(); - }).catch(_.partial(console.error, _)); + const view = _converse.chatboxviews.get('problematic@muc.localhost'); + spyOn(view, 'showErrorMessage').and.callThrough(); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect(view.el.querySelector('.chatroom-body .disconnect-msg').textContent) + .toBe('This room no longer exists'); + expect(view.el.querySelector('.chatroom-body .destroyed-reason').textContent) + .toBe(`"We didn't like the name"`); + expect(view.el.querySelector('.chatroom-body .moved-label').textContent.trim()) + .toBe('The conversation has moved. Click below to enter.'); + expect(view.el.querySelector('.chatroom-body .moved-link').textContent.trim()) + .toBe(`other-room@chat.jabberfr.org`); + done(); })); it("will use the user's reserved nickname, if it exists", diff --git a/spec/roomslist.js b/spec/roomslist.js index 794306162..19d211876 100644 --- a/spec/roomslist.js +++ b/spec/roomslist.js @@ -12,46 +12,41 @@ describe("A list of open rooms", function () { it("is shown in the \"Rooms\" panel", mock.initConverseWithPromises( - null, ['rosterGroupsFetched', 'chatBoxesFetched'], - { allow_bookmarks: false // Makes testing easier, otherwise we - // have to mock stanza traffic. - }, - function (done, _converse) { - test_utils.openControlBox(); - const controlbox = _converse.chatboxviews.get('controlbox'); - let list = controlbox.el.querySelector('div.rooms-list-container'); - expect(_.includes(list.classList, 'hidden')).toBeTruthy(); + null, ['rosterGroupsFetched', 'chatBoxesFetched'], + { allow_bookmarks: false // Makes testing easier, otherwise we + // have to mock stanza traffic. + }, async function (done, _converse) { - let room_els; + test_utils.openControlBox(); + const controlbox = _converse.chatboxviews.get('controlbox'); + let list = controlbox.el.querySelector('div.rooms-list-container'); + expect(_.includes(list.classList, 'hidden')).toBeTruthy(); - test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC') - .then(() => { - expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy(); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(1); - expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit'); - return test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); - }).then(() => { - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(2); + await test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC'); + expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy(); + let room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(1); + expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit'); + await test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(2); - var view = _converse.chatboxviews.get('room@conference.shakespeare.lit'); - view.close(); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(1); - expect(room_els[0].innerText).toBe('lounge@localhost'); - list = controlbox.el.querySelector('div.rooms-list-container'); - expect(_.includes(list.classList, 'hidden')).toBeFalsy(); + let view = _converse.chatboxviews.get('room@conference.shakespeare.lit'); + view.close(); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(1); + expect(room_els[0].innerText).toBe('lounge@localhost'); + list = controlbox.el.querySelector('div.rooms-list-container'); + expect(_.includes(list.classList, 'hidden')).toBeFalsy(); - view = _converse.chatboxviews.get('lounge@localhost'); - view.close(); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(0); + view = _converse.chatboxviews.get('lounge@localhost'); + view.close(); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(0); - list = controlbox.el.querySelector('div.rooms-list-container'); - expect(_.includes(list.classList, 'hidden')).toBeTruthy(); - done(); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + list = controlbox.el.querySelector('div.rooms-list-container'); + expect(_.includes(list.classList, 'hidden')).toBeTruthy(); + done(); } )); }); diff --git a/src/converse-autocomplete.js b/src/converse-autocomplete.js index 3db6f0655..f87c310a8 100644 --- a/src/converse-autocomplete.js +++ b/src/converse-autocomplete.js @@ -7,409 +7,408 @@ // This plugin started as a fork of Lea Verou's Awesomplete // https://leaverou.github.io/awesomplete/ -(function (root, factory) { - define(["@converse/headless/converse-core"], factory); -}(this, function (converse) { - const { _, Backbone } = converse.env, - u = converse.env.utils; +import converse from "@converse/headless/converse-core"; + +const { _, Backbone } = converse.env, + u = converse.env.utils; - converse.plugins.add("converse-autocomplete", { - initialize () { - const { _converse } = this; +converse.plugins.add("converse-autocomplete", { - _converse.FILTER_CONTAINS = function (text, input) { - return RegExp(helpers.regExpEscape(input.trim()), "i").test(text); - }; + initialize () { + const { _converse } = this; - _converse.FILTER_STARTSWITH = function (text, input) { - return RegExp("^" + helpers.regExpEscape(input.trim()), "i").test(text); - }; + _converse.FILTER_CONTAINS = function (text, input) { + return RegExp(helpers.regExpEscape(input.trim()), "i").test(text); + }; - const SORT_BYLENGTH = function (a, b) { - if (a.length !== b.length) { - return a.length - b.length; - } - return a < b? -1 : 1; - }; + _converse.FILTER_STARTSWITH = function (text, input) { + return RegExp("^" + helpers.regExpEscape(input.trim()), "i").test(text); + }; - const ITEM = (text, input) => { - input = input.trim(); - const element = document.createElement("li"); - element.setAttribute("aria-selected", "false"); - - const regex = new RegExp("("+input+")", "ig"); - const parts = input ? text.split(regex) : [text]; - parts.forEach((txt) => { - if (input && txt.match(regex)) { - const match = document.createElement("mark"); - match.textContent = txt; - element.appendChild(match); - } else { - element.appendChild(document.createTextNode(txt)); - } - }); - return element; - }; - - - class AutoComplete { - - constructor (el, config={}) { - this.is_opened = false; - - if (u.hasClass('.suggestion-box', el)) { - this.container = el; - } else { - this.container = el.querySelector('.suggestion-box'); - } - this.input = this.container.querySelector('.suggestion-box__input'); - this.input.setAttribute("autocomplete", "off"); - this.input.setAttribute("aria-autocomplete", "list"); - - this.ul = this.container.querySelector('.suggestion-box__results'); - this.status = this.container.querySelector('.suggestion-box__additions'); - - _.assignIn(this, { - 'match_current_word': false, // Match only the current word, otherwise all input is matched - 'match_on_tab': false, // Whether matching should only start when tab's pressed - 'trigger_on_at': false, // Whether @ should trigger autocomplete - 'min_chars': 2, - 'max_items': 10, - 'auto_evaluate': true, - 'auto_first': false, - 'data': _.identity, - 'filter': _converse.FILTER_CONTAINS, - 'sort': config.sort === false ? false : SORT_BYLENGTH, - 'item': ITEM - }, config); - - this.index = -1; - - this.bindEvents() - - if (this.input.hasAttribute("list")) { - this.list = "#" + this.input.getAttribute("list"); - this.input.removeAttribute("list"); - } else { - this.list = this.input.getAttribute("data-list") || config.list || []; - } - } - - bindEvents () { - // Bind events - const input = { - "blur": () => this.close({'reason': 'blur'}) - } - if (this.auto_evaluate) { - input["input"] = () => this.evaluate(); - } - - this._events = { - 'input': input, - 'form': { - "submit": () => this.close({'reason': 'submit'}) - }, - 'ul': { - "mousedown": (ev) => this.onMouseDown(ev), - "mouseover": (ev) => this.onMouseOver(ev) - } - }; - helpers.bind(this.input, this._events.input); - helpers.bind(this.input.form, this._events.form); - helpers.bind(this.ul, this._events.ul); - } - - set list (list) { - if (Array.isArray(list) || typeof list === "function") { - this._list = list; - } else if (typeof list === "string" && _.includes(list, ",")) { - this._list = list.split(/\s*,\s*/); - } else { // Element or CSS selector - list = helpers.getElement(list); - if (list && list.children) { - const items = []; - slice.apply(list.children).forEach(function (el) { - if (!el.disabled) { - const text = el.textContent.trim(), - value = el.value || text, - label = el.label || text; - if (value !== "") { - items.push({ label: label, value: value }); - } - } - }); - this._list = items; - } - } - - if (document.activeElement === this.input) { - this.evaluate(); - } - } - - get selected () { - return this.index > -1; - } - - get opened () { - return this.is_opened; - } - - close (o) { - if (!this.opened) { - return; - } - this.ul.setAttribute("hidden", ""); - this.is_opened = false; - this.index = -1; - this.trigger("suggestion-box-close", o || {}); - } - - insertValue (suggestion) { - let value; - if (this.match_current_word) { - u.replaceCurrentWord(this.input, suggestion.value); - } else { - this.input.value = suggestion.value; - } - } - - open () { - this.ul.removeAttribute("hidden"); - this.is_opened = true; - - if (this.auto_first && this.index === -1) { - this.goto(0); - } - this.trigger("suggestion-box-open"); - } - - destroy () { - //remove events from the input and its form - helpers.unbind(this.input, this._events.input); - helpers.unbind(this.input.form, this._events.form); - - //move the input out of the suggestion-box container and remove the container and its children - const parentNode = this.container.parentNode; - - parentNode.insertBefore(this.input, this.container); - parentNode.removeChild(this.container); - - //remove autocomplete and aria-autocomplete attributes - this.input.removeAttribute("autocomplete"); - this.input.removeAttribute("aria-autocomplete"); - } - - next () { - const count = this.ul.children.length; - this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) ); - } - - previous () { - const count = this.ul.children.length, - pos = this.index - 1; - this.goto(this.selected && pos !== -1 ? pos : count - 1); - } - - goto (i) { - // Should not be used directly, highlights specific item without any checks! - const list = this.ul.children; - if (this.selected) { - list[this.index].setAttribute("aria-selected", "false"); - } - this.index = i; - - if (i > -1 && list.length > 0) { - list[i].setAttribute("aria-selected", "true"); - list[i].focus(); - this.status.textContent = list[i].textContent; - // scroll to highlighted element in case parent's height is fixed - this.ul.scrollTop = list[i].offsetTop - this.ul.clientHeight + list[i].clientHeight; - this.trigger("suggestion-box-highlight", {'text': this.suggestions[this.index]}); - } - } - - select (selected, origin) { - if (selected) { - this.index = u.siblingIndex(selected); - } else { - selected = this.ul.children[this.index]; - } - if (selected) { - const suggestion = this.suggestions[this.index]; - this.insertValue(suggestion); - this.close({'reason': 'select'}); - this.auto_completing = false; - this.trigger("suggestion-box-selectcomplete", {'text': suggestion}); - } - } - - onMouseOver (ev) { - const li = u.ancestor(ev.target, 'li'); - if (li) { - this.goto(Array.prototype.slice.call(this.ul.children).indexOf(li)) - } - } - - onMouseDown (ev) { - if (ev.button !== 0) { - return; // Only select on left click - } - const li = u.ancestor(ev.target, 'li'); - if (li) { - ev.preventDefault(); - this.select(li, ev.target); - } - } - - keyPressed (ev) { - if (this.opened) { - if (_.includes([_converse.keycodes.ENTER, _converse.keycodes.TAB], ev.keyCode) && this.selected) { - ev.preventDefault(); - ev.stopPropagation(); - this.select(); - return true; - } else if (ev.keyCode === _converse.keycodes.ESCAPE) { - this.close({'reason': 'esc'}); - return true; - } else if (_.includes([_converse.keycodes.UP_ARROW, _converse.keycodes.DOWN_ARROW], ev.keyCode)) { - ev.preventDefault(); - ev.stopPropagation(); - this[ev.keyCode === _converse.keycodes.UP_ARROW ? "previous" : "next"](); - return true; - } - } - - if (_.includes([ - _converse.keycodes.SHIFT, - _converse.keycodes.META, - _converse.keycodes.META_RIGHT, - _converse.keycodes.ESCAPE, - _converse.keycodes.ALT] - , ev.keyCode)) { - return; - } - if (this.match_on_tab && ev.keyCode === _converse.keycodes.TAB) { - ev.preventDefault(); - this.auto_completing = true; - } else if (this.trigger_on_at && ev.keyCode === _converse.keycodes.AT) { - this.auto_completing = true; - } - } - - evaluate (ev) { - const arrow_pressed = ( - ev.keyCode === _converse.keycodes.UP_ARROW || - ev.keyCode === _converse.keycodes.DOWN_ARROW - ); - if (!this.auto_completing || (this.selected && arrow_pressed)) { - return; - } - - const list = typeof this._list === "function" ? this._list() : this._list; - if (list.length === 0) { - return; - } - - let value = this.match_current_word ? u.getCurrentWord(this.input) : this.input.value; - - let ignore_min_chars = false; - if (this.trigger_on_at && value.startsWith('@')) { - ignore_min_chars = true; - value = value.slice('1'); - } - - if ((value.length >= this.min_chars) || ignore_min_chars) { - this.index = -1; - // Populate list with options that match - this.ul.innerHTML = ""; - - this.suggestions = list - .map(item => new Suggestion(this.data(item, value))) - .filter(item => this.filter(item, value)); - - if (this.sort !== false) { - this.suggestions = this.suggestions.sort(this.sort); - } - this.suggestions = this.suggestions.slice(0, this.max_items); - this.suggestions.forEach((text) => this.ul.appendChild(this.item(text, value))); - - if (this.ul.children.length === 0) { - this.close({'reason': 'nomatches'}); - } else { - this.open(); - } - } else { - this.close({'reason': 'nomatches'}); - this.auto_completing = false; - } - } + const SORT_BYLENGTH = function (a, b) { + if (a.length !== b.length) { + return a.length - b.length; } + return a < b? -1 : 1; + }; - // Make it an event emitter - _.extend(AutoComplete.prototype, Backbone.Events); + const ITEM = (text, input) => { + input = input.trim(); + const element = document.createElement("li"); + element.setAttribute("aria-selected", "false"); - - // Private functions - - function Suggestion(data) { - const o = Array.isArray(data) - ? { label: data[0], value: data[1] } - : typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data }; - - this.label = o.label || o.value; - this.value = o.value; - } - - Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { - get: function() { return this.label.length; } + const regex = new RegExp("("+input+")", "ig"); + const parts = input ? text.split(regex) : [text]; + parts.forEach((txt) => { + if (input && txt.match(regex)) { + const match = document.createElement("mark"); + match.textContent = txt; + element.appendChild(match); + } else { + element.appendChild(document.createTextNode(txt)); + } }); + return element; + }; - Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { - return "" + this.label; - }; - // Helpers - var slice = Array.prototype.slice; + class AutoComplete { - const helpers = { + constructor (el, config={}) { + this.is_opened = false; - getElement (expr, el) { - return typeof expr === "string"? (el || document).querySelector(expr) : expr || null; - }, + if (u.hasClass('.suggestion-box', el)) { + this.container = el; + } else { + this.container = el.querySelector('.suggestion-box'); + } + this.input = this.container.querySelector('.suggestion-box__input'); + this.input.setAttribute("autocomplete", "off"); + this.input.setAttribute("aria-autocomplete", "list"); - bind (element, o) { - if (element) { - for (var event in o) { - if (!Object.prototype.hasOwnProperty.call(o, event)) { - continue; - } - const callback = o[event]; - event.split(/\s+/).forEach(event => element.addEventListener(event, callback)); - } - } - }, + this.ul = this.container.querySelector('.suggestion-box__results'); + this.status = this.container.querySelector('.suggestion-box__additions'); - unbind (element, o) { - if (element) { - for (var event in o) { - if (!Object.prototype.hasOwnProperty.call(o, event)) { - continue; - } - const callback = o[event]; - event.split(/\s+/).forEach(event => element.removeEventListener(event, callback)); - } - } - }, + _.assignIn(this, { + 'match_current_word': false, // Match only the current word, otherwise all input is matched + 'match_on_tab': false, // Whether matching should only start when tab's pressed + 'trigger_on_at': false, // Whether @ should trigger autocomplete + 'min_chars': 2, + 'max_items': 10, + 'auto_evaluate': true, + 'auto_first': false, + 'data': _.identity, + 'filter': _converse.FILTER_CONTAINS, + 'sort': config.sort === false ? false : SORT_BYLENGTH, + 'item': ITEM + }, config); - regExpEscape (s) { - return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); + this.index = -1; + + this.bindEvents() + + if (this.input.hasAttribute("list")) { + this.list = "#" + this.input.getAttribute("list"); + this.input.removeAttribute("list"); + } else { + this.list = this.input.getAttribute("data-list") || config.list || []; } } - _converse.AutoComplete = AutoComplete; + bindEvents () { + // Bind events + const input = { + "blur": () => this.close({'reason': 'blur'}) + } + if (this.auto_evaluate) { + input["input"] = () => this.evaluate(); + } + + this._events = { + 'input': input, + 'form': { + "submit": () => this.close({'reason': 'submit'}) + }, + 'ul': { + "mousedown": (ev) => this.onMouseDown(ev), + "mouseover": (ev) => this.onMouseOver(ev) + } + }; + helpers.bind(this.input, this._events.input); + helpers.bind(this.input.form, this._events.form); + helpers.bind(this.ul, this._events.ul); + } + + set list (list) { + if (Array.isArray(list) || typeof list === "function") { + this._list = list; + } else if (typeof list === "string" && _.includes(list, ",")) { + this._list = list.split(/\s*,\s*/); + } else { // Element or CSS selector + list = helpers.getElement(list); + if (list && list.children) { + const items = []; + slice.apply(list.children).forEach(function (el) { + if (!el.disabled) { + const text = el.textContent.trim(), + value = el.value || text, + label = el.label || text; + if (value !== "") { + items.push({ label: label, value: value }); + } + } + }); + this._list = items; + } + } + + if (document.activeElement === this.input) { + this.evaluate(); + } + } + + get selected () { + return this.index > -1; + } + + get opened () { + return this.is_opened; + } + + close (o) { + if (!this.opened) { + return; + } + this.ul.setAttribute("hidden", ""); + this.is_opened = false; + this.index = -1; + this.trigger("suggestion-box-close", o || {}); + } + + insertValue (suggestion) { + let value; + if (this.match_current_word) { + u.replaceCurrentWord(this.input, suggestion.value); + } else { + this.input.value = suggestion.value; + } + } + + open () { + this.ul.removeAttribute("hidden"); + this.is_opened = true; + + if (this.auto_first && this.index === -1) { + this.goto(0); + } + this.trigger("suggestion-box-open"); + } + + destroy () { + //remove events from the input and its form + helpers.unbind(this.input, this._events.input); + helpers.unbind(this.input.form, this._events.form); + + //move the input out of the suggestion-box container and remove the container and its children + const parentNode = this.container.parentNode; + + parentNode.insertBefore(this.input, this.container); + parentNode.removeChild(this.container); + + //remove autocomplete and aria-autocomplete attributes + this.input.removeAttribute("autocomplete"); + this.input.removeAttribute("aria-autocomplete"); + } + + next () { + const count = this.ul.children.length; + this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) ); + } + + previous () { + const count = this.ul.children.length, + pos = this.index - 1; + this.goto(this.selected && pos !== -1 ? pos : count - 1); + } + + goto (i) { + // Should not be used directly, highlights specific item without any checks! + const list = this.ul.children; + if (this.selected) { + list[this.index].setAttribute("aria-selected", "false"); + } + this.index = i; + + if (i > -1 && list.length > 0) { + list[i].setAttribute("aria-selected", "true"); + list[i].focus(); + this.status.textContent = list[i].textContent; + // scroll to highlighted element in case parent's height is fixed + this.ul.scrollTop = list[i].offsetTop - this.ul.clientHeight + list[i].clientHeight; + this.trigger("suggestion-box-highlight", {'text': this.suggestions[this.index]}); + } + } + + select (selected, origin) { + if (selected) { + this.index = u.siblingIndex(selected); + } else { + selected = this.ul.children[this.index]; + } + if (selected) { + const suggestion = this.suggestions[this.index]; + this.insertValue(suggestion); + this.close({'reason': 'select'}); + this.auto_completing = false; + this.trigger("suggestion-box-selectcomplete", {'text': suggestion}); + } + } + + onMouseOver (ev) { + const li = u.ancestor(ev.target, 'li'); + if (li) { + this.goto(Array.prototype.slice.call(this.ul.children).indexOf(li)) + } + } + + onMouseDown (ev) { + if (ev.button !== 0) { + return; // Only select on left click + } + const li = u.ancestor(ev.target, 'li'); + if (li) { + ev.preventDefault(); + this.select(li, ev.target); + } + } + + keyPressed (ev) { + if (this.opened) { + if (_.includes([_converse.keycodes.ENTER, _converse.keycodes.TAB], ev.keyCode) && this.selected) { + ev.preventDefault(); + ev.stopPropagation(); + this.select(); + return true; + } else if (ev.keyCode === _converse.keycodes.ESCAPE) { + this.close({'reason': 'esc'}); + return true; + } else if (_.includes([_converse.keycodes.UP_ARROW, _converse.keycodes.DOWN_ARROW], ev.keyCode)) { + ev.preventDefault(); + ev.stopPropagation(); + this[ev.keyCode === _converse.keycodes.UP_ARROW ? "previous" : "next"](); + return true; + } + } + + if (_.includes([ + _converse.keycodes.SHIFT, + _converse.keycodes.META, + _converse.keycodes.META_RIGHT, + _converse.keycodes.ESCAPE, + _converse.keycodes.ALT] + , ev.keyCode)) { + return; + } + if (this.match_on_tab && ev.keyCode === _converse.keycodes.TAB) { + ev.preventDefault(); + this.auto_completing = true; + } else if (this.trigger_on_at && ev.keyCode === _converse.keycodes.AT) { + this.auto_completing = true; + } + } + + evaluate (ev) { + const arrow_pressed = ( + ev.keyCode === _converse.keycodes.UP_ARROW || + ev.keyCode === _converse.keycodes.DOWN_ARROW + ); + if (!this.auto_completing || (this.selected && arrow_pressed)) { + return; + } + + const list = typeof this._list === "function" ? this._list() : this._list; + if (list.length === 0) { + return; + } + + let value = this.match_current_word ? u.getCurrentWord(this.input) : this.input.value; + + let ignore_min_chars = false; + if (this.trigger_on_at && value.startsWith('@')) { + ignore_min_chars = true; + value = value.slice('1'); + } + + if ((value.length >= this.min_chars) || ignore_min_chars) { + this.index = -1; + // Populate list with options that match + this.ul.innerHTML = ""; + + this.suggestions = list + .map(item => new Suggestion(this.data(item, value))) + .filter(item => this.filter(item, value)); + + if (this.sort !== false) { + this.suggestions = this.suggestions.sort(this.sort); + } + this.suggestions = this.suggestions.slice(0, this.max_items); + this.suggestions.forEach((text) => this.ul.appendChild(this.item(text, value))); + + if (this.ul.children.length === 0) { + this.close({'reason': 'nomatches'}); + } else { + this.open(); + } + } else { + this.close({'reason': 'nomatches'}); + this.auto_completing = false; + } + } } - }); -})); + + // Make it an event emitter + _.extend(AutoComplete.prototype, Backbone.Events); + + + // Private functions + + function Suggestion(data) { + const o = Array.isArray(data) + ? { label: data[0], value: data[1] } + : typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data }; + + this.label = o.label || o.value; + this.value = o.value; + } + + Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { + get: function() { return this.label.length; } + }); + + Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { + return "" + this.label; + }; + + // Helpers + var slice = Array.prototype.slice; + + const helpers = { + + getElement (expr, el) { + return typeof expr === "string"? (el || document).querySelector(expr) : expr || null; + }, + + bind (element, o) { + if (element) { + for (var event in o) { + if (!Object.prototype.hasOwnProperty.call(o, event)) { + continue; + } + const callback = o[event]; + event.split(/\s+/).forEach(event => element.addEventListener(event, callback)); + } + } + }, + + unbind (element, o) { + if (element) { + for (var event in o) { + if (!Object.prototype.hasOwnProperty.call(o, event)) { + continue; + } + const callback = o[event]; + event.split(/\s+/).forEach(event => element.removeEventListener(event, callback)); + } + } + }, + + regExpEscape (s) { + return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); + } + } + + _converse.AutoComplete = AutoComplete; + } +}); diff --git a/src/converse-bookmarks.js b/src/converse-bookmarks.js index d5c7ee220..2af696a6c 100644 --- a/src/converse-bookmarks.js +++ b/src/converse-bookmarks.js @@ -9,579 +9,569 @@ /* This is a Converse.js plugin which add support for bookmarks specified * in XEP-0048. */ -(function (root, factory) { - define(["@converse/headless/converse-core", - "@converse/headless/converse-muc", - "templates/chatroom_bookmark_form.html", - "templates/chatroom_bookmark_toggle.html", - "templates/bookmark.html", - "templates/bookmarks_list.html" - ], - factory); -}(this, function ( - converse, - muc, - tpl_chatroom_bookmark_form, - tpl_chatroom_bookmark_toggle, - tpl_bookmark, - tpl_bookmarks_list - ) { - const { Backbone, Promise, Strophe, $iq, b64_sha1, sizzle, _ } = converse.env; - const u = converse.env.utils; +import converse from "@converse/headless/converse-core"; +import muc from "@converse/headless/converse-muc"; +import tpl_bookmark from "templates/bookmark.html"; +import tpl_bookmarks_list from "templates/bookmarks_list.html" +import tpl_chatroom_bookmark_form from "templates/chatroom_bookmark_form.html"; +import tpl_chatroom_bookmark_toggle from "templates/chatroom_bookmark_toggle.html"; - converse.plugins.add('converse-bookmarks', { +const { Backbone, Promise, Strophe, $iq, b64_sha1, sizzle, _ } = converse.env; +const u = converse.env.utils; - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatboxes", "@converse/headless/converse-muc", "converse-muc-views"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. +converse.plugins.add('converse-bookmarks', { - ChatRoomView: { - events: { - 'click .toggle-bookmark': 'toggleBookmark' - }, + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatboxes", "converse-muc", "converse-muc-views"], - initialize () { - this.__super__.initialize.apply(this, arguments); - this.model.on('change:bookmarked', this.onBookmarked, this); - this.setBookmarkState(); - }, + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. - renderBookmarkToggle () { - if (this.el.querySelector('.chat-head .toggle-bookmark')) { - return; - } - const { _converse } = this.__super__, - { __ } = _converse; + ChatRoomView: { + events: { + 'click .toggle-bookmark': 'toggleBookmark' + }, - const bookmark_button = tpl_chatroom_bookmark_toggle( - _.assignIn(this.model.toJSON(), { - info_toggle_bookmark: __('Bookmark this groupchat'), - bookmarked: this.model.get('bookmarked') - })); - const close_button = this.el.querySelector('.close-chatbox-button'); - close_button.insertAdjacentHTML('afterend', bookmark_button); - }, + initialize () { + this.__super__.initialize.apply(this, arguments); + this.model.on('change:bookmarked', this.onBookmarked, this); + this.setBookmarkState(); + }, - renderHeading () { - this.__super__.renderHeading.apply(this, arguments); - const { _converse } = this.__super__; - if (_converse.allow_bookmarks) { - _converse.checkBookmarksSupport().then((supported) => { - if (supported) { - this.renderBookmarkToggle(); - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - }, - - checkForReservedNick () { - /* Check if the user has a bookmark with a saved nickanme - * for this groupchat, and if so use it. - * Otherwise delegate to the super method. - */ - const { _converse } = this.__super__; - if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) { - return this.__super__.checkForReservedNick.apply(this, arguments); - } - const model = _converse.bookmarks.findWhere({'jid': this.model.get('jid')}); - if (!_.isUndefined(model) && model.get('nick')) { - this.join(model.get('nick')); - } else { - return this.__super__.checkForReservedNick.apply(this, arguments); - } - }, - - onBookmarked () { - const icon = this.el.querySelector('.toggle-bookmark'); - if (_.isNull(icon)) { - return; - } - if (this.model.get('bookmarked')) { - icon.classList.add('button-on'); - } else { - icon.classList.remove('button-on'); - } - }, - - setBookmarkState () { - /* Set whether the groupchat is bookmarked or not. - */ - const { _converse } = this.__super__; - if (!_.isUndefined(_converse.bookmarks)) { - const models = _converse.bookmarks.where({'jid': this.model.get('jid')}); - if (!models.length) { - this.model.save('bookmarked', false); - } else { - this.model.save('bookmarked', true); - } - } - }, - - renderBookmarkForm () { - const { _converse } = this.__super__, - { __ } = _converse, - body = this.el.querySelector('.chatroom-body'); - - _.each(body.children, child => child.classList.add('hidden')); - _.each(body.querySelectorAll('.chatroom-form-container'), u.removeElement); - - body.insertAdjacentHTML( - 'beforeend', - tpl_chatroom_bookmark_form({ - 'default_nick': this.model.get('nick'), - 'heading': __('Bookmark this groupchat'), - 'label_autojoin': __('Would you like this groupchat to be automatically joined upon startup?'), - 'label_cancel': __('Cancel'), - 'label_name': __('The name for this bookmark:'), - 'label_nick': __('What should your nickname for this groupchat be?'), - 'label_submit': __('Save'), - 'name': this.model.get('name') - }) - ); - const form = body.querySelector('form.chatroom-form'); - form.addEventListener('submit', ev => this.onBookmarkFormSubmitted(ev)); - form.querySelector('.button-cancel').addEventListener('click', () => this.closeForm()); - }, - - onBookmarkFormSubmitted (ev) { - ev.preventDefault(); - const { _converse } = this.__super__; - _converse.bookmarks.createBookmark({ - 'jid': this.model.get('jid'), - 'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false, - 'name': _.get(ev.target.querySelector('input[name=name]'), 'value'), - 'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value') - }); - u.removeElement(this.el.querySelector('div.chatroom-form-container')); - this.renderAfterTransition(); - }, - - toggleBookmark (ev) { - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - const { _converse } = this.__super__; - const models = _converse.bookmarks.where({'jid': this.model.get('jid')}); - if (!models.length) { - this.renderBookmarkForm(); - } else { - _.forEach(models, function (model) { - model.destroy(); - }); - this.el.querySelector('.toggle-bookmark').classList.remove('button-on'); - } - } - } - }, - - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; - - // Configuration values for this plugin - // ==================================== - // Refer to docs/source/configuration.rst for explanations of these - // configuration settings. - _converse.api.settings.update({ - allow_bookmarks: true, - allow_public_bookmarks: false, - hide_open_bookmarks: true - }); - // Promises exposed by this plugin - _converse.api.promises.add('bookmarksInitialized'); - - // Pure functions on the _converse object - _.extend(_converse, { - removeBookmarkViaEvent (ev) { - /* Remove a bookmark as determined by the passed in - * event. - */ - ev.preventDefault(); - const name = ev.target.getAttribute('data-bookmark-name'); - const jid = ev.target.getAttribute('data-room-jid'); - if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) { - _.invokeMap(_converse.bookmarks.where({'jid': jid}), Backbone.Model.prototype.destroy); - } - }, - - addBookmarkViaEvent (ev) { - /* Add a bookmark as determined by the passed in - * event. - */ - ev.preventDefault(); - const jid = ev.target.getAttribute('data-room-jid'); - const chatroom = _converse.api.rooms.open(jid, {'bring_to_foreground': true}); - _converse.chatboxviews.get(jid).renderBookmarkForm(); - }, - }); - - _converse.Bookmark = Backbone.Model; - - _converse.Bookmarks = Backbone.Collection.extend({ - model: _converse.Bookmark, - comparator: (item) => item.get('name').toLowerCase(), - - initialize () { - this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked)); - this.on('remove', this.markRoomAsUnbookmarked, this); - this.on('remove', this.sendBookmarkStanza, this); - - const storage = _converse.config.get('storage'), - cache_key = `converse.room-bookmarks${_converse.bare_jid}`; - this.fetched_flag = b64_sha1(cache_key+'fetched'); - this.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(cache_key)); - }, - - openBookmarkedRoom (bookmark) { - if (bookmark.get('autojoin')) { - const groupchat = _converse.api.rooms.create(bookmark.get('jid'), bookmark.get('nick')); - if (!groupchat.get('hidden')) { - groupchat.trigger('show'); - } - } - return bookmark; - }, - - fetchBookmarks () { - const deferred = u.getResolveablePromise(); - if (this.browserStorage.records.length > 0) { - this.fetch({ - 'success': _.bind(this.onCachedBookmarksFetched, this, deferred), - 'error': _.bind(this.onCachedBookmarksFetched, this, deferred) - }); - } else if (! window.sessionStorage.getItem(this.fetched_flag)) { - // There aren't any cached bookmarks and the - // `fetched_flag` is off, so we query the XMPP server. - // If nothing is returned from the XMPP server, we set - // the `fetched_flag` to avoid calling the server again. - this.fetchBookmarksFromServer(deferred); - } else { - deferred.resolve(); - } - return deferred; - }, - - onCachedBookmarksFetched (deferred) { - return deferred.resolve(); - }, - - createBookmark (options) { - this.create(options); - this.sendBookmarkStanza().catch(iq => this.onBookmarkError(iq, options)); - }, - - sendBookmarkStanza () { - const stanza = $iq({ - 'type': 'set', - 'from': _converse.connection.jid, - }) - .c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) - .c('publish', {'node': 'storage:bookmarks'}) - .c('item', {'id': 'current'}) - .c('storage', {'xmlns':'storage:bookmarks'}); - this.each(model => { - stanza.c('conference', { - 'name': model.get('name'), - 'autojoin': model.get('autojoin'), - 'jid': model.get('jid'), - }).c('nick').t(model.get('nick')).up().up(); - }); - stanza.up().up().up(); - stanza.c('publish-options') - .c('x', {'xmlns': Strophe.NS.XFORM, 'type':'submit'}) - .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) - .c('value').t('http://jabber.org/protocol/pubsub#publish-options').up().up() - .c('field', {'var':'pubsub#persist_items'}) - .c('value').t('true').up().up() - .c('field', {'var':'pubsub#access_model'}) - .c('value').t('whitelist'); - return _converse.api.sendIQ(stanza); - }, - - onBookmarkError (iq, options) { - _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR); - _converse.log(iq); - _converse.api.alert.show( - Strophe.LogLevel.ERROR, - __('Error'), [__("Sorry, something went wrong while trying to save your bookmark.")] - ) - this.findWhere({'jid': options.jid}).destroy(); - }, - - fetchBookmarksFromServer (deferred) { - const stanza = $iq({ - 'from': _converse.connection.jid, - 'type': 'get', - }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) - .c('items', {'node': 'storage:bookmarks'}); - _converse.api.sendIQ(stanza) - .then((iq) => this.onBookmarksReceived(deferred, iq)) - .catch((iq) => this.onBookmarksReceivedError(deferred, iq) - ); - }, - - markRoomAsBookmarked (bookmark) { - const groupchat = _converse.chatboxes.get(bookmark.get('jid')); - if (!_.isUndefined(groupchat)) { - groupchat.save('bookmarked', true); - } - }, - - markRoomAsUnbookmarked (bookmark) { - const groupchat = _converse.chatboxes.get(bookmark.get('jid')); - if (!_.isUndefined(groupchat)) { - groupchat.save('bookmarked', false); - } - }, - - createBookmarksFromStanza (stanza) { - const bookmarks = sizzle( - 'items[node="storage:bookmarks"] '+ - 'item#current '+ - 'storage[xmlns="storage:bookmarks"] '+ - 'conference', - stanza - ) - _.forEach(bookmarks, (bookmark) => { - const jid = bookmark.getAttribute('jid'); - this.create({ - 'jid': jid, - 'name': bookmark.getAttribute('name') || jid, - 'autojoin': bookmark.getAttribute('autojoin') === 'true', - 'nick': _.get(bookmark.querySelector('nick'), 'textContent') - }); - }); - }, - - onBookmarksReceived (deferred, iq) { - this.createBookmarksFromStanza(iq); - if (!_.isUndefined(deferred)) { - return deferred.resolve(); - } - }, - - onBookmarksReceivedError (deferred, iq) { - window.sessionStorage.setItem(this.fetched_flag, true); - _converse.log('Error while fetching bookmarks', Strophe.LogLevel.WARN); - _converse.log(iq.outerHTML, Strophe.LogLevel.DEBUG); - if (!_.isNil(deferred)) { - if (iq.querySelector('error[type="cancel"] item-not-found')) { - // Not an exception, the user simply doesn't have - // any bookmarks. - return deferred.resolve(); - } else { - return deferred.reject(new Error("Could not fetch bookmarks")); - } - } - } - }); - - _converse.BookmarksList = Backbone.Model.extend({ - defaults: { - "toggle-state": _converse.OPENED - } - }); - - _converse.BookmarkView = Backbone.VDOMView.extend({ - toHTML () { - return tpl_bookmark({ - 'hidden': _converse.hide_open_bookmarks && - _converse.chatboxes.where({'jid': this.model.get('jid')}).length, - 'bookmarked': true, - 'info_leave_room': __('Leave this groupchat'), - 'info_remove': __('Remove this bookmark'), - 'info_remove_bookmark': __('Unbookmark this groupchat'), - 'info_title': __('Show more information on this groupchat'), - 'jid': this.model.get('jid'), - 'name': Strophe.xmlunescape(this.model.get('name')), - 'open_title': __('Click to open this groupchat') - }); - } - }); - - _converse.BookmarksView = Backbone.OrderedListView.extend({ - tagName: 'div', - className: 'bookmarks-list list-container rooms-list-container', - events: { - 'click .add-bookmark': 'addBookmark', - 'click .bookmarks-toggle': 'toggleBookmarksList', - 'click .remove-bookmark': 'removeBookmark', - 'click .open-room': 'openRoom', - }, - listSelector: '.rooms-list', - ItemView: _converse.BookmarkView, - subviewIndex: 'jid', - - initialize () { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - - this.model.on('add', this.showOrHide, this); - this.model.on('remove', this.showOrHide, this); - - _converse.chatboxes.on('add', this.renderBookmarkListElement, this); - _converse.chatboxes.on('remove', this.renderBookmarkListElement, this); - - const storage = _converse.config.get('storage'), - id = b64_sha1(`converse.room-bookmarks${_converse.bare_jid}-list-model`); - this.list_model = new _converse.BookmarksList({'id': id}); - this.list_model.browserStorage = new Backbone.BrowserStorage[storage](id); - this.list_model.fetch(); - this.render(); - this.sortAndPositionAllItems(); - }, - - render () { - this.el.innerHTML = tpl_bookmarks_list({ - 'toggle_state': this.list_model.get('toggle-state'), - 'desc_bookmarks': __('Click to toggle the bookmarks list'), - 'label_bookmarks': __('Bookmarks'), - '_converse': _converse - }); - this.showOrHide(); - this.insertIntoControlBox(); - return this; - }, - - insertIntoControlBox () { - const controlboxview = _converse.chatboxviews.get('controlbox'); - if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) { - const el = controlboxview.el.querySelector('.bookmarks-list'); - if (!_.isNull(el)) { - el.parentNode.replaceChild(this.el, el); - } - } - }, - - openRoom (ev) { - ev.preventDefault(); - const name = ev.target.textContent; - const jid = ev.target.getAttribute('data-room-jid'); - const data = { - 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid - } - _converse.api.rooms.open(jid, data); - }, - - removeBookmark: _converse.removeBookmarkViaEvent, - addBookmark: _converse.addBookmarkViaEvent, - - renderBookmarkListElement (chatbox) { - const bookmarkview = this.get(chatbox.get('jid')); - if (_.isNil(bookmarkview)) { - // A chat box has been closed, but we don't have a - // bookmark for it, so nothing further to do here. - return; - } - bookmarkview.render(); - this.showOrHide(); - }, - - showOrHide (item) { - if (_converse.hide_open_bookmarks) { - const bookmarks = this.model.filter((bookmark) => - !_converse.chatboxes.get(bookmark.get('jid'))); - if (!bookmarks.length) { - u.hideElement(this.el); - return; - } - } - if (this.model.models.length) { - u.showElement(this.el); - } - }, - - toggleBookmarksList (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - const icon_el = ev.target.querySelector('.fa'); - if (u.hasClass('fa-caret-down', icon_el)) { - u.slideIn(this.el.querySelector('.bookmarks')); - this.list_model.save({'toggle-state': _converse.CLOSED}); - icon_el.classList.remove("fa-caret-down"); - icon_el.classList.add("fa-caret-right"); - } else { - icon_el.classList.remove("fa-caret-right"); - icon_el.classList.add("fa-caret-down"); - u.slideOut(this.el.querySelector('.bookmarks')); - this.list_model.save({'toggle-state': _converse.OPENED}); - } - } - }); - - _converse.checkBookmarksSupport = function () { - return new Promise((resolve, reject) => { - Promise.all([ - _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid), - _converse.api.disco.supports(Strophe.NS.PUBSUB+'#publish-options', _converse.bare_jid) - ]).then((args) => { - resolve(args[0] && (args[1].length || _converse.allow_public_bookmarks)); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - - const initBookmarks = function () { - if (!_converse.allow_bookmarks) { + renderBookmarkToggle () { + if (this.el.querySelector('.chat-head .toggle-bookmark')) { return; } - _converse.checkBookmarksSupport().then((supported) => { - if (supported) { - _converse.bookmarks = new _converse.Bookmarks(); - _converse.bookmarksview = new _converse.BookmarksView({'model': _converse.bookmarks}); - _converse.bookmarks.fetchBookmarks() - .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) - .then(() => _converse.emit('bookmarksInitialized')); + const { _converse } = this.__super__, + { __ } = _converse; + + const bookmark_button = tpl_chatroom_bookmark_toggle( + _.assignIn(this.model.toJSON(), { + info_toggle_bookmark: __('Bookmark this groupchat'), + bookmarked: this.model.get('bookmarked') + })); + const close_button = this.el.querySelector('.close-chatbox-button'); + close_button.insertAdjacentHTML('afterend', bookmark_button); + }, + + renderHeading () { + this.__super__.renderHeading.apply(this, arguments); + const { _converse } = this.__super__; + if (_converse.allow_bookmarks) { + _converse.checkBookmarksSupport().then((supported) => { + if (supported) { + this.renderBookmarkToggle(); + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + }, + + checkForReservedNick () { + /* Check if the user has a bookmark with a saved nickanme + * for this groupchat, and if so use it. + * Otherwise delegate to the super method. + */ + const { _converse } = this.__super__; + if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) { + return this.__super__.checkForReservedNick.apply(this, arguments); + } + const model = _converse.bookmarks.findWhere({'jid': this.model.get('jid')}); + if (!_.isUndefined(model) && model.get('nick')) { + this.join(model.get('nick')); + } else { + return this.__super__.checkForReservedNick.apply(this, arguments); + } + }, + + onBookmarked () { + const icon = this.el.querySelector('.toggle-bookmark'); + if (_.isNull(icon)) { + return; + } + if (this.model.get('bookmarked')) { + icon.classList.add('button-on'); + } else { + icon.classList.remove('button-on'); + } + }, + + setBookmarkState () { + /* Set whether the groupchat is bookmarked or not. + */ + const { _converse } = this.__super__; + if (!_.isUndefined(_converse.bookmarks)) { + const models = _converse.bookmarks.where({'jid': this.model.get('jid')}); + if (!models.length) { + this.model.save('bookmarked', false); } else { - _converse.emit('bookmarksInitialized'); + this.model.save('bookmarked', true); } + } + }, + + renderBookmarkForm () { + const { _converse } = this.__super__, + { __ } = _converse, + body = this.el.querySelector('.chatroom-body'); + + _.each(body.children, child => child.classList.add('hidden')); + _.each(body.querySelectorAll('.chatroom-form-container'), u.removeElement); + + body.insertAdjacentHTML( + 'beforeend', + tpl_chatroom_bookmark_form({ + 'default_nick': this.model.get('nick'), + 'heading': __('Bookmark this groupchat'), + 'label_autojoin': __('Would you like this groupchat to be automatically joined upon startup?'), + 'label_cancel': __('Cancel'), + 'label_name': __('The name for this bookmark:'), + 'label_nick': __('What should your nickname for this groupchat be?'), + 'label_submit': __('Save'), + 'name': this.model.get('name') + }) + ); + const form = body.querySelector('form.chatroom-form'); + form.addEventListener('submit', ev => this.onBookmarkFormSubmitted(ev)); + form.querySelector('.button-cancel').addEventListener('click', () => this.closeForm()); + }, + + onBookmarkFormSubmitted (ev) { + ev.preventDefault(); + const { _converse } = this.__super__; + _converse.bookmarks.createBookmark({ + 'jid': this.model.get('jid'), + 'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false, + 'name': _.get(ev.target.querySelector('input[name=name]'), 'value'), + 'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value') + }); + u.removeElement(this.el.querySelector('div.chatroom-form-container')); + this.renderAfterTransition(); + }, + + toggleBookmark (ev) { + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + const { _converse } = this.__super__; + const models = _converse.bookmarks.where({'jid': this.model.get('jid')}); + if (!models.length) { + this.renderBookmarkForm(); + } else { + _.forEach(models, function (model) { + model.destroy(); + }); + this.el.querySelector('.toggle-bookmark').classList.remove('button-on'); + } + } + } + }, + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; + + // Configuration values for this plugin + // ==================================== + // Refer to docs/source/configuration.rst for explanations of these + // configuration settings. + _converse.api.settings.update({ + allow_bookmarks: true, + allow_public_bookmarks: false, + hide_open_bookmarks: true + }); + // Promises exposed by this plugin + _converse.api.promises.add('bookmarksInitialized'); + + // Pure functions on the _converse object + _.extend(_converse, { + removeBookmarkViaEvent (ev) { + /* Remove a bookmark as determined by the passed in + * event. + */ + ev.preventDefault(); + const name = ev.target.getAttribute('data-bookmark-name'); + const jid = ev.target.getAttribute('data-room-jid'); + if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) { + _.invokeMap(_converse.bookmarks.where({'jid': jid}), Backbone.Model.prototype.destroy); + } + }, + + addBookmarkViaEvent (ev) { + /* Add a bookmark as determined by the passed in + * event. + */ + ev.preventDefault(); + const jid = ev.target.getAttribute('data-room-jid'); + const chatroom = _converse.api.rooms.open(jid, {'bring_to_foreground': true}); + _converse.chatboxviews.get(jid).renderBookmarkForm(); + }, + }); + + _converse.Bookmark = Backbone.Model; + + _converse.Bookmarks = Backbone.Collection.extend({ + model: _converse.Bookmark, + comparator: (item) => item.get('name').toLowerCase(), + + initialize () { + this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked)); + this.on('remove', this.markRoomAsUnbookmarked, this); + this.on('remove', this.sendBookmarkStanza, this); + + const storage = _converse.config.get('storage'), + cache_key = `converse.room-bookmarks${_converse.bare_jid}`; + this.fetched_flag = b64_sha1(cache_key+'fetched'); + this.browserStorage = new Backbone.BrowserStorage[storage](b64_sha1(cache_key)); + }, + + openBookmarkedRoom (bookmark) { + if (bookmark.get('autojoin')) { + const groupchat = _converse.api.rooms.create(bookmark.get('jid'), bookmark.get('nick')); + if (!groupchat.get('hidden')) { + groupchat.trigger('show'); + } + } + return bookmark; + }, + + fetchBookmarks () { + const deferred = u.getResolveablePromise(); + if (this.browserStorage.records.length > 0) { + this.fetch({ + 'success': _.bind(this.onCachedBookmarksFetched, this, deferred), + 'error': _.bind(this.onCachedBookmarksFetched, this, deferred) + }); + } else if (! window.sessionStorage.getItem(this.fetched_flag)) { + // There aren't any cached bookmarks and the + // `fetched_flag` is off, so we query the XMPP server. + // If nothing is returned from the XMPP server, we set + // the `fetched_flag` to avoid calling the server again. + this.fetchBookmarksFromServer(deferred); + } else { + deferred.resolve(); + } + return deferred; + }, + + onCachedBookmarksFetched (deferred) { + return deferred.resolve(); + }, + + createBookmark (options) { + this.create(options); + this.sendBookmarkStanza().catch(iq => this.onBookmarkError(iq, options)); + }, + + sendBookmarkStanza () { + const stanza = $iq({ + 'type': 'set', + 'from': _converse.connection.jid, + }) + .c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) + .c('publish', {'node': 'storage:bookmarks'}) + .c('item', {'id': 'current'}) + .c('storage', {'xmlns':'storage:bookmarks'}); + this.each(model => { + stanza.c('conference', { + 'name': model.get('name'), + 'autojoin': model.get('autojoin'), + 'jid': model.get('jid'), + }).c('nick').t(model.get('nick')).up().up(); + }); + stanza.up().up().up(); + stanza.c('publish-options') + .c('x', {'xmlns': Strophe.NS.XFORM, 'type':'submit'}) + .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) + .c('value').t('http://jabber.org/protocol/pubsub#publish-options').up().up() + .c('field', {'var':'pubsub#persist_items'}) + .c('value').t('true').up().up() + .c('field', {'var':'pubsub#access_model'}) + .c('value').t('whitelist'); + return _converse.api.sendIQ(stanza); + }, + + onBookmarkError (iq, options) { + _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR); + _converse.log(iq); + _converse.api.alert.show( + Strophe.LogLevel.ERROR, + __('Error'), [__("Sorry, something went wrong while trying to save your bookmark.")] + ) + this.findWhere({'jid': options.jid}).destroy(); + }, + + fetchBookmarksFromServer (deferred) { + const stanza = $iq({ + 'from': _converse.connection.jid, + 'type': 'get', + }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) + .c('items', {'node': 'storage:bookmarks'}); + _converse.api.sendIQ(stanza) + .then((iq) => this.onBookmarksReceived(deferred, iq)) + .catch((iq) => this.onBookmarksReceivedError(deferred, iq) + ); + }, + + markRoomAsBookmarked (bookmark) { + const groupchat = _converse.chatboxes.get(bookmark.get('jid')); + if (!_.isUndefined(groupchat)) { + groupchat.save('bookmarked', true); + } + }, + + markRoomAsUnbookmarked (bookmark) { + const groupchat = _converse.chatboxes.get(bookmark.get('jid')); + if (!_.isUndefined(groupchat)) { + groupchat.save('bookmarked', false); + } + }, + + createBookmarksFromStanza (stanza) { + const bookmarks = sizzle( + 'items[node="storage:bookmarks"] '+ + 'item#current '+ + 'storage[xmlns="storage:bookmarks"] '+ + 'conference', + stanza + ) + _.forEach(bookmarks, (bookmark) => { + const jid = bookmark.getAttribute('jid'); + this.create({ + 'jid': jid, + 'name': bookmark.getAttribute('name') || jid, + 'autojoin': bookmark.getAttribute('autojoin') === 'true', + 'nick': _.get(bookmark.querySelector('nick'), 'textContent') + }); + }); + }, + + onBookmarksReceived (deferred, iq) { + this.createBookmarksFromStanza(iq); + if (!_.isUndefined(deferred)) { + return deferred.resolve(); + } + }, + + onBookmarksReceivedError (deferred, iq) { + window.sessionStorage.setItem(this.fetched_flag, true); + _converse.log('Error while fetching bookmarks', Strophe.LogLevel.WARN); + _converse.log(iq.outerHTML, Strophe.LogLevel.DEBUG); + if (!_.isNil(deferred)) { + if (iq.querySelector('error[type="cancel"] item-not-found')) { + // Not an exception, the user simply doesn't have + // any bookmarks. + return deferred.resolve(); + } else { + return deferred.reject(new Error("Could not fetch bookmarks")); + } + } + } + }); + + _converse.BookmarksList = Backbone.Model.extend({ + defaults: { + "toggle-state": _converse.OPENED + } + }); + + _converse.BookmarkView = Backbone.VDOMView.extend({ + toHTML () { + return tpl_bookmark({ + 'hidden': _converse.hide_open_bookmarks && + _converse.chatboxes.where({'jid': this.model.get('jid')}).length, + 'bookmarked': true, + 'info_leave_room': __('Leave this groupchat'), + 'info_remove': __('Remove this bookmark'), + 'info_remove_bookmark': __('Unbookmark this groupchat'), + 'info_title': __('Show more information on this groupchat'), + 'jid': this.model.get('jid'), + 'name': Strophe.xmlunescape(this.model.get('name')), + 'open_title': __('Click to open this groupchat') }); } + }); - u.onMultipleEvents([ - {'object': _converse, 'event': 'chatBoxesFetched'}, - {'object': _converse, 'event': 'roomsPanelRendered'} - ], initBookmarks); + _converse.BookmarksView = Backbone.OrderedListView.extend({ + tagName: 'div', + className: 'bookmarks-list list-container rooms-list-container', + events: { + 'click .add-bookmark': 'addBookmark', + 'click .bookmarks-toggle': 'toggleBookmarksList', + 'click .remove-bookmark': 'removeBookmark', + 'click .open-room': 'openRoom', + }, + listSelector: '.rooms-list', + ItemView: _converse.BookmarkView, + subviewIndex: 'jid', + initialize () { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); - _converse.on('clearSession', () => { - if (!_.isUndefined(_converse.bookmarks)) { - _converse.bookmarks.browserStorage._clear(); - window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag); + this.model.on('add', this.showOrHide, this); + this.model.on('remove', this.showOrHide, this); + + _converse.chatboxes.on('add', this.renderBookmarkListElement, this); + _converse.chatboxes.on('remove', this.renderBookmarkListElement, this); + + const storage = _converse.config.get('storage'), + id = b64_sha1(`converse.room-bookmarks${_converse.bare_jid}-list-model`); + this.list_model = new _converse.BookmarksList({'id': id}); + this.list_model.browserStorage = new Backbone.BrowserStorage[storage](id); + this.list_model.fetch(); + this.render(); + this.sortAndPositionAllItems(); + }, + + render () { + this.el.innerHTML = tpl_bookmarks_list({ + 'toggle_state': this.list_model.get('toggle-state'), + 'desc_bookmarks': __('Click to toggle the bookmarks list'), + 'label_bookmarks': __('Bookmarks'), + '_converse': _converse + }); + this.showOrHide(); + this.insertIntoControlBox(); + return this; + }, + + insertIntoControlBox () { + const controlboxview = _converse.chatboxviews.get('controlbox'); + if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) { + const el = controlboxview.el.querySelector('.bookmarks-list'); + if (!_.isNull(el)) { + el.parentNode.replaceChild(this.el, el); + } + } + }, + + openRoom (ev) { + ev.preventDefault(); + const name = ev.target.textContent; + const jid = ev.target.getAttribute('data-room-jid'); + const data = { + 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid + } + _converse.api.rooms.open(jid, data); + }, + + removeBookmark: _converse.removeBookmarkViaEvent, + addBookmark: _converse.addBookmarkViaEvent, + + renderBookmarkListElement (chatbox) { + const bookmarkview = this.get(chatbox.get('jid')); + if (_.isNil(bookmarkview)) { + // A chat box has been closed, but we don't have a + // bookmark for it, so nothing further to do here. + return; + } + bookmarkview.render(); + this.showOrHide(); + }, + + showOrHide (item) { + if (_converse.hide_open_bookmarks) { + const bookmarks = this.model.filter((bookmark) => + !_converse.chatboxes.get(bookmark.get('jid'))); + if (!bookmarks.length) { + u.hideElement(this.el); + return; + } + } + if (this.model.models.length) { + u.showElement(this.el); + } + }, + + toggleBookmarksList (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + const icon_el = ev.target.querySelector('.fa'); + if (u.hasClass('fa-caret-down', icon_el)) { + u.slideIn(this.el.querySelector('.bookmarks')); + this.list_model.save({'toggle-state': _converse.CLOSED}); + icon_el.classList.remove("fa-caret-down"); + icon_el.classList.add("fa-caret-right"); + } else { + icon_el.classList.remove("fa-caret-right"); + icon_el.classList.add("fa-caret-down"); + u.slideOut(this.el.querySelector('.bookmarks')); + this.list_model.save({'toggle-state': _converse.OPENED}); + } + } + }); + + _converse.checkBookmarksSupport = function () { + return new Promise((resolve, reject) => { + Promise.all([ + _converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid), + _converse.api.disco.supports(Strophe.NS.PUBSUB+'#publish-options', _converse.bare_jid) + ]).then((args) => { + resolve(args[0] && (args[1].length || _converse.allow_public_bookmarks)); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + + const initBookmarks = function () { + if (!_converse.allow_bookmarks) { + return; + } + _converse.checkBookmarksSupport().then((supported) => { + if (supported) { + _converse.bookmarks = new _converse.Bookmarks(); + _converse.bookmarksview = new _converse.BookmarksView({'model': _converse.bookmarks}); + _converse.bookmarks.fetchBookmarks() + .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) + .then(() => _converse.emit('bookmarksInitialized')); + } else { + _converse.emit('bookmarksInitialized'); } }); - - _converse.on('reconnected', initBookmarks); - - _converse.on('connected', () => { - // Add a handler for bookmarks pushed from other connected clients - // (from the same user obviously) - _converse.connection.addHandler((message) => { - if (sizzle('event[xmlns="'+Strophe.NS.PUBSUB+'#event"] items[node="storage:bookmarks"]', message).length) { - _converse.api.waitUntil('bookmarksInitialized') - .then(() => _converse.bookmarks.createBookmarksFromStanza(message)) - .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - } - }, null, 'message', 'headline', null, _converse.bare_jid); - }); - } - }); -})); + + u.onMultipleEvents([ + {'object': _converse, 'event': 'chatBoxesFetched'}, + {'object': _converse, 'event': 'roomsPanelRendered'} + ], initBookmarks); + + + _converse.on('clearSession', () => { + if (!_.isUndefined(_converse.bookmarks)) { + _converse.bookmarks.browserStorage._clear(); + window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag); + } + }); + + _converse.on('reconnected', initBookmarks); + + _converse.on('connected', () => { + // Add a handler for bookmarks pushed from other connected clients + // (from the same user obviously) + _converse.connection.addHandler((message) => { + if (sizzle('event[xmlns="'+Strophe.NS.PUBSUB+'#event"] items[node="storage:bookmarks"]', message).length) { + _converse.api.waitUntil('bookmarksInitialized') + .then(() => _converse.bookmarks.createBookmarksFromStanza(message)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + } + }, null, 'message', 'headline', null, _converse.bare_jid); + }); + + } +}); diff --git a/src/converse-caps.js b/src/converse-caps.js index 81fc8686f..cf428b96c 100644 --- a/src/converse-caps.js +++ b/src/converse-caps.js @@ -4,60 +4,58 @@ // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - define(["@converse/headless/converse-core"], factory); -}(this, function (converse) { - const { Strophe, $build, _, b64_sha1 } = converse.env; +import converse from "@converse/headless/converse-core"; - Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps"); +const { Strophe, $build, _, b64_sha1 } = converse.env; - function propertySort (array, property) { - return array.sort((a, b) => { return a[property] > b[property] ? -1 : 1 }); +Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps"); + +function propertySort (array, property) { + return array.sort((a, b) => { return a[property] > b[property] ? -1 : 1 }); +} + +function generateVerificationString (_converse) { + const identities = _converse.api.disco.own.identities.get(), + features = _converse.api.disco.own.features.get(); + + if (identities.length > 1) { + propertySort(identities, "category"); + propertySort(identities, "type"); + propertySort(identities, "lang"); } - function generateVerificationString (_converse) { - const identities = _converse.api.disco.own.identities.get(), - features = _converse.api.disco.own.features.get(); + let S = _.reduce( + identities, + (result, id) => `${result}${id.category}/${id.type}/${_.get(id, 'lang', '')}/${id.name}<`, + ""); - if (identities.length > 1) { - propertySort(identities, "category"); - propertySort(identities, "type"); - propertySort(identities, "lang"); - } + features.sort(); + S = _.reduce(features, (result, feature) => `${result}${feature}<`, S); + return b64_sha1(S); +} - let S = _.reduce( - identities, - (result, id) => `${result}${id.category}/${id.type}/${_.get(id, 'lang', '')}/${id.name}<`, - ""); +function createCapsNode (_converse) { + return $build("c", { + 'xmlns': Strophe.NS.CAPS, + 'hash': "sha-1", + 'node': "https://conversejs.org", + 'ver': generateVerificationString(_converse) + }).nodeTree; +} - features.sort(); - S = _.reduce(features, (result, feature) => `${result}${feature}<`, S); - return b64_sha1(S); - } +converse.plugins.add('converse-caps', { - function createCapsNode (_converse) { - return $build("c", { - 'xmlns': Strophe.NS.CAPS, - 'hash': "sha-1", - 'node': "https://conversejs.org", - 'ver': generateVerificationString(_converse) - }).nodeTree; - } - - converse.plugins.add('converse-caps', { - - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - XMPPStatus: { - constructPresence () { - const presence = this.__super__.constructPresence.apply(this, arguments); - presence.root().cnode(createCapsNode(this.__super__._converse)); - return presence; - } + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + XMPPStatus: { + constructPresence () { + const presence = this.__super__.constructPresence.apply(this, arguments); + presence.root().cnode(createCapsNode(this.__super__._converse)); + return presence; } } - }); -})); + } +}); diff --git a/src/converse-chatboxviews.js b/src/converse-chatboxviews.js index 1c614356d..267069be2 100644 --- a/src/converse-chatboxviews.js +++ b/src/converse-chatboxviews.js @@ -4,176 +4,169 @@ // Copyright (c) 2012-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - define([ - "@converse/headless/converse-core", - "templates/chatboxes.html", - "@converse/headless/converse-chatboxes", - "backbone.nativeview", - "backbone.overview" - ], factory); -}(this, function (converse, tpl_chatboxes) { - "use strict"; +import "@converse/headless/converse-chatboxes"; +import "backbone.nativeview"; +import "backbone.overview"; +import converse from "@converse/headless/converse-core"; +import tpl_chatboxes from "templates/chatboxes.html"; - const { Backbone, _ } = converse.env; +const { Backbone, _ } = converse.env; - const AvatarMixin = { +const AvatarMixin = { - renderAvatar (el) { - el = el || this.el; - const canvas_el = el.querySelector('canvas'); - if (_.isNull(canvas_el)) { - return; - } - const image_type = this.model.vcard.get('image_type'), - image = this.model.vcard.get('image'), - img_src = "data:" + image_type + ";base64," + image, - img = new Image(); - - return new Promise((resolve, reject) => { - img.onload = () => { - const ctx = canvas_el.getContext('2d'), - ratio = img.width / img.height; - ctx.clearRect(0, 0, canvas_el.width, canvas_el.height); - if (ratio < 1) { - const scaled_img_with = canvas_el.width*ratio, - x = Math.floor((canvas_el.width-scaled_img_with)/2); - ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height); - } else { - ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio); - } - resolve(); - }; - img.src = img_src; - }); - }, - }; - - - converse.plugins.add('converse-chatboxviews', { - - dependencies: ["converse-chatboxes"], - - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - - initStatus: function (reconnecting) { - const { _converse } = this.__super__; - if (!reconnecting) { - _converse.chatboxviews.closeAllChatBoxes(); - } - return this.__super__.initStatus.apply(this, arguments); - } - }, - - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; - - _converse.api.promises.add([ - 'chatBoxViewsInitialized' - ]); - - _converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin); - _converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin); - - - _converse.ChatBoxViews = Backbone.Overview.extend({ - - _ensureElement () { - /* Override method from backbone.js - * If the #conversejs element doesn't exist, create it. - */ - if (!this.el) { - let el = _converse.root.querySelector('#conversejs'); - if (_.isNull(el)) { - el = document.createElement('div'); - el.setAttribute('id', 'conversejs'); - const body = _converse.root.querySelector('body'); - if (body) { - body.appendChild(el); - } else { - // Perhaps inside a web component? - _converse.root.appendChild(el); - } - } - el.innerHTML = ''; - this.setElement(el, false); - } else { - this.setElement(_.result(this, 'el'), false); - } - }, - - initialize () { - this.model.on("destroy", this.removeChat, this); - this.el.classList.add(`converse-${_converse.view_mode}`); - this.render(); - }, - - render () { - try { - this.el.innerHTML = tpl_chatboxes(); - } catch (e) { - this._ensureElement(); - this.el.innerHTML = tpl_chatboxes(); - } - this.row_el = this.el.querySelector('.row'); - }, - - insertRowColumn (el) { - /* Add a new DOM element (likely a chat box) into the - * the row managed by this overview. - */ - this.row_el.insertAdjacentElement('afterBegin', el); - }, - - removeChat (item) { - this.remove(item.get('id')); - }, - - closeAllChatBoxes () { - /* This method gets overridden in src/converse-controlbox.js if - * the controlbox plugin is active. - */ - this.each(function (view) { view.close(); }); - return this; - }, - - chatBoxMayBeShown (chatbox) { - return this.model.chatBoxMayBeShown(chatbox); - } - }); - - - /************************ BEGIN Event Handlers ************************/ - _converse.api.waitUntil('rosterContactsFetched').then(() => { - _converse.roster.on('add', (contact) => { - /* When a new contact is added, check if we already have a - * chatbox open for it, and if so attach it to the chatbox. - */ - const chatbox = _converse.chatboxes.findWhere({'jid': contact.get('jid')}); - if (chatbox) { - chatbox.addRelatedContact(contact); - } - }); - }); - - - _converse.api.listen.on('chatBoxesInitialized', () => { - _converse.chatboxviews = new _converse.ChatBoxViews({ - 'model': _converse.chatboxes - }); - _converse.emit('chatBoxViewsInitialized'); - }); - - _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes()); - /************************ END Event Handlers ************************/ + renderAvatar (el) { + el = el || this.el; + const canvas_el = el.querySelector('canvas'); + if (_.isNull(canvas_el)) { + return; } - }); - return converse; -})); + const image_type = this.model.vcard.get('image_type'), + image = this.model.vcard.get('image'), + img_src = "data:" + image_type + ";base64," + image, + img = new Image(); + + return new Promise((resolve, reject) => { + img.onload = () => { + const ctx = canvas_el.getContext('2d'), + ratio = img.width / img.height; + ctx.clearRect(0, 0, canvas_el.width, canvas_el.height); + if (ratio < 1) { + const scaled_img_with = canvas_el.width*ratio, + x = Math.floor((canvas_el.width-scaled_img_with)/2); + ctx.drawImage(img, x, 0, scaled_img_with, canvas_el.height); + } else { + ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio); + } + resolve(); + }; + img.src = img_src; + }); + }, +}; + + +converse.plugins.add('converse-chatboxviews', { + + dependencies: ["converse-chatboxes"], + + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + + initStatus: function (reconnecting) { + const { _converse } = this.__super__; + if (!reconnecting) { + _converse.chatboxviews.closeAllChatBoxes(); + } + return this.__super__.initStatus.apply(this, arguments); + } + }, + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; + + _converse.api.promises.add([ + 'chatBoxViewsInitialized' + ]); + + _converse.ViewWithAvatar = Backbone.NativeView.extend(AvatarMixin); + _converse.VDOMViewWithAvatar = Backbone.VDOMView.extend(AvatarMixin); + + + _converse.ChatBoxViews = Backbone.Overview.extend({ + + _ensureElement () { + /* Override method from backbone.js + * If the #conversejs element doesn't exist, create it. + */ + if (!this.el) { + let el = _converse.root.querySelector('#conversejs'); + if (_.isNull(el)) { + el = document.createElement('div'); + el.setAttribute('id', 'conversejs'); + const body = _converse.root.querySelector('body'); + if (body) { + body.appendChild(el); + } else { + // Perhaps inside a web component? + _converse.root.appendChild(el); + } + } + el.innerHTML = ''; + this.setElement(el, false); + } else { + this.setElement(_.result(this, 'el'), false); + } + }, + + initialize () { + this.model.on("destroy", this.removeChat, this); + this.el.classList.add(`converse-${_converse.view_mode}`); + this.render(); + }, + + render () { + try { + this.el.innerHTML = tpl_chatboxes(); + } catch (e) { + this._ensureElement(); + this.el.innerHTML = tpl_chatboxes(); + } + this.row_el = this.el.querySelector('.row'); + }, + + insertRowColumn (el) { + /* Add a new DOM element (likely a chat box) into the + * the row managed by this overview. + */ + this.row_el.insertAdjacentElement('afterBegin', el); + }, + + removeChat (item) { + this.remove(item.get('id')); + }, + + closeAllChatBoxes () { + /* This method gets overridden in src/converse-controlbox.js if + * the controlbox plugin is active. + */ + this.each(function (view) { view.close(); }); + return this; + }, + + chatBoxMayBeShown (chatbox) { + return this.model.chatBoxMayBeShown(chatbox); + } + }); + + + /************************ BEGIN Event Handlers ************************/ + _converse.api.waitUntil('rosterContactsFetched').then(() => { + _converse.roster.on('add', (contact) => { + /* When a new contact is added, check if we already have a + * chatbox open for it, and if so attach it to the chatbox. + */ + const chatbox = _converse.chatboxes.findWhere({'jid': contact.get('jid')}); + if (chatbox) { + chatbox.addRelatedContact(contact); + } + }); + }); + + + _converse.api.listen.on('chatBoxesInitialized', () => { + _converse.chatboxviews = new _converse.ChatBoxViews({ + 'model': _converse.chatboxes + }); + _converse.emit('chatBoxViewsInitialized'); + }); + + _converse.api.listen.on('clearSession', () => _converse.chatboxviews.closeAllChatBoxes()); + /************************ END Event Handlers ************************/ + } +}); diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 317b06339..69435078e 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -1,1346 +1,1320 @@ // Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - define([ - "utils/emoji", - "@converse/headless/converse-core", - "bootstrap", - "twemoji", - "xss", - "templates/chatbox.html", - "templates/chatbox_head.html", - "templates/chatbox_message_form.html", - "templates/emojis.html", - "templates/error_message.html", - "templates/help_message.html", - "templates/info.html", - "templates/new_day.html", - "templates/user_details_modal.html", - "templates/toolbar_fileupload.html", - "templates/spinner.html", - "templates/spoiler_button.html", - "templates/status_message.html", - "templates/toolbar.html", - "converse-modal", - "converse-chatboxviews", - "converse-message-view" - ], factory); -}(this, function ( - u, - converse, - bootstrap, - twemoji, - xss, - tpl_chatbox, - tpl_chatbox_head, - tpl_chatbox_message_form, - tpl_emojis, - tpl_error_message, - tpl_help_message, - tpl_info, - tpl_new_day, - tpl_user_details_modal, - tpl_toolbar_fileupload, - tpl_spinner, - tpl_spoiler_button, - tpl_status_message, - tpl_toolbar - ) { - "use strict"; - const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env; +import "converse-chatboxviews"; +import "converse-message-view"; +import "converse-modal"; +import * as twemoji from "twemoji"; +import bootstrap from "bootstrap"; +import converse from "@converse/headless/converse-core"; +import tpl_chatbox from "templates/chatbox.html"; +import tpl_chatbox_head from "templates/chatbox_head.html"; +import tpl_chatbox_message_form from "templates/chatbox_message_form.html"; +import tpl_emojis from "templates/emojis.html"; +import tpl_error_message from "templates/error_message.html"; +import tpl_help_message from "templates/help_message.html"; +import tpl_info from "templates/info.html"; +import tpl_new_day from "templates/new_day.html"; +import tpl_spinner from "templates/spinner.html"; +import tpl_spoiler_button from "templates/spoiler_button.html"; +import tpl_status_message from "templates/status_message.html"; +import tpl_toolbar from "templates/toolbar.html"; +import tpl_toolbar_fileupload from "templates/toolbar_fileupload.html"; +import tpl_user_details_modal from "templates/user_details_modal.html"; +import u from "utils/emoji"; +import xss from "xss"; - converse.plugins.add('converse-chatview', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. +const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env; + + +converse.plugins.add('converse-chatview', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"], + + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. */ - dependencies: ["converse-chatboxviews", "converse-disco", "converse-message-view", "converse-modal"], - - - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; - - _converse.api.settings.update({ - 'emoji_image_path': twemoji.default.base, - 'show_send_button': false, - 'show_toolbar': true, - 'time_format': 'HH:mm', - 'use_system_emojis': true, - 'visible_toolbar_buttons': { - 'call': false, - 'clear': true, - 'emoji': true, - 'spoiler': true - }, - }); - twemoji.default.base = _converse.emoji_image_path; - - function onWindowStateChanged (data) { - if (_converse.chatboxviews) { - _converse.chatboxviews.each(view => { - if (view.model.get('id') !== 'controlbox') { - view.onWindowStateChanged(data.state); - } - }); - } - } - _converse.api.listen.on('windowStateChanged', onWindowStateChanged); - - - _converse.EmojiPicker = Backbone.Model.extend({ - defaults: { - 'current_category': 'people', - 'current_skintone': '', - 'scroll_position': 0 - } - }); - - - _converse.EmojiPickerView = Backbone.VDOMView.extend({ - className: 'emoji-picker-container', - events: { - 'click .emoji-category-picker li.emoji-category': 'chooseCategory', - 'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone' - }, - - initialize () { - this.model.on('change:current_skintone', this.render, this); - this.model.on('change:current_category', this.render, this); - }, - - toHTML () { - return tpl_emojis( - _.extend( - this.model.toJSON(), { - '_': _, - 'transform': u.getEmojiRenderer(_converse), - 'emojis_by_category': u.getEmojisByCategory(_converse), - 'toned_emojis': u.getTonedEmojis(_converse), - 'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'], - 'shouldBeHidden': this.shouldBeHidden - } - )); - }, - - shouldBeHidden (shortname, current_skintone, toned_emojis) { - /* Helper method for the template which decides whether an - * emoji should be hidden, based on which skin tone is - * currently being applied. - */ - if (_.includes(shortname, '_tone')) { - if (!current_skintone || !_.includes(shortname, current_skintone)) { - return true; - } - } else { - if (current_skintone && _.includes(toned_emojis, shortname)) { - return true; - } - } - return false; - }, - - chooseSkinTone (ev) { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target.nodeName === 'IMG' ? - ev.target.parentElement : ev.target; - const skintone = target.getAttribute("data-skintone").trim(); - if (this.model.get('current_skintone') === skintone) { - this.model.save({'current_skintone': ''}); - } else { - this.model.save({'current_skintone': skintone}); - } - }, - - chooseCategory (ev) { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target.nodeName === 'IMG' ? - ev.target.parentElement : ev.target; - const category = target.getAttribute("data-category").trim(); - this.model.save({ - 'current_category': category, - 'scroll_position': 0 - }); - } - }); - - - _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({ - initialize () { - this.model.on('change:status', this.onStatusMessageChanged, this); - this.model.vcard.on('change', this.render, this); - }, - - render () { - this.el.innerHTML = tpl_chatbox_head( - _.extend( - this.model.vcard.toJSON(), - this.model.toJSON(), - { '_converse': _converse, - 'info_close': __('Close this chat box') - } - ) - ); - this.renderAvatar(); - return this; - }, - - onStatusMessageChanged (item) { - this.render(); - _converse.emit('contactStatusMessageChanged', { - 'contact': item.attributes, - 'message': item.get('status') - }); - } - }); - - - _converse.UserDetailsModal = _converse.BootstrapModal.extend({ - - events: { - 'click button.remove-contact': 'removeContact', - 'click button.refresh-contact': 'refreshContact', - 'click .fingerprint-trust .btn input': 'toggleDeviceTrust' - }, - - initialize () { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - this.model.on('contactAdded', this.registerContactEventHandlers, this); - this.model.on('change', this.render, this); - this.registerContactEventHandlers(); - _converse.emit('userDetailsModalInitialized', this.model); - }, - - toHTML () { - return tpl_user_details_modal(_.extend( - this.model.toJSON(), - this.model.vcard.toJSON(), { - '_': _, - '__': __, - 'view': this, - '_converse': _converse, - 'allow_contact_removal': _converse.allow_contact_removal, - 'display_name': this.model.getDisplayName(), - 'is_roster_contact': !_.isUndefined(this.model.contact), - 'utils': u - })); - }, - - registerContactEventHandlers () { - if (!_.isUndefined(this.model.contact)) { - this.model.contact.on('change', this.render, this); - this.model.contact.vcard.on('change', this.render, this); - this.model.contact.on('destroy', () => { - delete this.model.contact; - this.render(); - }); - } - }, - - refreshContact (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - const refresh_icon = this.el.querySelector('.fa-refresh'); - u.addClass('fa-spin', refresh_icon); - _converse.api.vcard.update(this.model.contact.vcard, true) - .then(() => u.removeClass('fa-spin', refresh_icon)) - .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - removeContact (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - if (!_converse.allow_contact_removal) { return; } - const result = confirm(__("Are you sure you want to remove this contact?")); - if (result === true) { - this.modal.hide(); - this.model.contact.removeFromRoster( - (iq) => { - this.model.contact.destroy(); - }, - (err) => { - _converse.log(err, Strophe.LogLevel.ERROR); - _converse.api.alert.show( - Strophe.LogLevel.ERROR, - __('Error'), - [__('Sorry, there was an error while trying to remove %1$s as a contact.', - this.model.contact.getDisplayName()) - ] - ) - } - ); - } - }, - }); - - - _converse.ChatBoxView = Backbone.NativeView.extend({ - length: 200, - className: 'chatbox hidden', - is_chatroom: false, // Leaky abstraction from MUC - - events: { - 'change input.fileupload': 'onFileSelection', - 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', - 'click .chatbox-navback': 'showControlBox', - 'click .close-chatbox-button': 'close', - 'click .new-msgs-indicator': 'viewUnreadMessages', - 'click .send-button': 'onFormSubmitted', - 'click .show-user-details-modal': 'showUserDetailsModal', - 'click .spoiler-toggle': 'toggleSpoilerMessage', - 'click .toggle-call': 'toggleCall', - 'click .toggle-clear': 'clearMessages', - 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage', - 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', - 'click .toggle-smiley': 'toggleEmojiMenu', - 'click .upload-file': 'toggleFileUpload', - 'input .chat-textarea': 'inputChanged', - 'keydown .chat-textarea': 'keyPressed' - }, - - initialize () { - this.initDebounced(); - - this.model.messages.on('add', this.onMessageAdded, this); - this.model.messages.on('rendered', this.scrollDown, this); - - this.model.on('show', this.show, this); - this.model.on('destroy', this.remove, this); - - this.model.presence.on('change:show', this.onPresenceChanged, this); - this.model.on('showHelpMessages', this.showHelpMessages, this); - this.render(); - - this.fetchMessages(); - _converse.emit('chatBoxOpened', this); - _converse.emit('chatBoxInitialized', this); - }, - - initDebounced () { - this.scrollDown = _.debounce(this._scrollDown, 250); - this.markScrolled = _.debounce(this._markScrolled, 100); - this.show = _.debounce(this._show, 250, {'leading': true}); - }, - - render () { - // XXX: Is this still needed? - this.el.setAttribute('id', this.model.get('box_id')); - this.el.innerHTML = tpl_chatbox( - _.extend(this.model.toJSON(), { - 'unread_msgs': __('You have unread messages') - } - )); - this.content = this.el.querySelector('.chat-content'); - this.renderMessageForm(); - this.insertHeading(); - return this; - }, - - renderToolbar (toolbar, options) { - if (!_converse.show_toolbar) { - return this; - } - toolbar = toolbar || tpl_toolbar; - options = _.assign( - this.model.toJSON(), - this.getToolbarOptions(options || {}) - ); - this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options); - this.addSpoilerButton(options); - this.addFileUploadButton(); - _converse.emit('renderToolbar', this); - return this; - }, - - renderMessageForm () { - let placeholder; - if (this.model.get('composing_spoiler')) { - placeholder = __('Hidden message'); - } else { - placeholder = __('Message'); - } - const form_container = this.el.querySelector('.message-form-container'); - form_container.innerHTML = tpl_chatbox_message_form( - _.extend(this.model.toJSON(), { - 'hint_value': _.get(this.el.querySelector('.spoiler-hint'), 'value'), - 'label_message': placeholder, - 'label_send': __('Send'), - 'label_spoiler_hint': __('Optional hint'), - 'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'), - 'show_send_button': _converse.show_send_button, - 'show_toolbar': _converse.show_toolbar, - 'unread_msgs': __('You have unread messages') - })); - this.renderToolbar(); - }, - - showControlBox () { - // Used in mobile view, to navigate back to the controlbox - const view = _converse.chatboxviews.get('controlbox'); - view.show(); - this.hide(); - }, - - showUserDetailsModal (ev) { - ev.preventDefault(); - if (_.isUndefined(this.user_details_modal)) { - this.user_details_modal = new _converse.UserDetailsModal({model: this.model}); - } - this.user_details_modal.show(ev); - }, - - toggleFileUpload (ev) { - this.el.querySelector('input.fileupload').click(); - }, - - onFileSelection (evt) { - this.model.sendFiles(evt.target.files); - }, - - addFileUploadButton (options) { - _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then((result) => { - if (result.length) { - this.el.querySelector('.chat-toolbar').insertAdjacentHTML( - 'beforeend', - tpl_toolbar_fileupload({'tooltip_upload_file': __('Choose a file to send')})); - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - addSpoilerButton (options) { - /* Asynchronously adds a button for writing spoiler - * messages, based on whether the contact's client supports - * it. - */ - if (!options.show_spoiler_button || this.model.get('type') === 'chatroom') { - return; - } - const contact_jid = this.model.get('jid'); - const resources = this.model.presence.get('resources'); - if (_.isEmpty(resources)) { - return; - } - Promise.all(_.map(_.keys(resources), (resource) => - _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`) - )).then((results) => { - if (_.filter(results, 'length').length) { - const html = tpl_spoiler_button(this.model.toJSON()); - if (_converse.visible_toolbar_buttons.emoji) { - this.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html); - } else { - this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html); - } - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - insertHeading () { - this.heading = new _converse.ChatBoxHeading({'model': this.model}); - this.heading.render(); - this.heading.chatview = this; - - if (!_.isUndefined(this.model.contact)) { - this.model.contact.on('destroy', this.heading.render, this); - } - const flyout = this.el.querySelector('.flyout'); - flyout.insertBefore(this.heading.el, flyout.querySelector('.chat-body')); - return this; - }, - - getToolbarOptions (options) { - let label_toggle_spoiler; - if (this.model.get('composing_spoiler')) { - label_toggle_spoiler = __('Click to write as a normal (non-spoiler) message'); - } else { - label_toggle_spoiler = __('Click to write your message as a spoiler'); - } - return _.extend(options || {}, { - 'label_clear': __('Clear all messages'), - 'tooltip_insert_smiley': __('Insert emojis'), - 'tooltip_start_call': __('Start a call'), - 'label_toggle_spoiler': label_toggle_spoiler, - 'show_call_button': _converse.visible_toolbar_buttons.call, - 'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler, - 'use_emoji': _converse.visible_toolbar_buttons.emoji, - }); - }, - - afterMessagesFetched () { - this.insertIntoDOM(); - this.scrollDown(); - this.content.addEventListener('scroll', this.markScrolled.bind(this)); - _converse.emit('afterMessagesFetched', this); - }, - - fetchMessages () { - this.model.messages.fetch({ - 'add': true, - 'success': this.afterMessagesFetched.bind(this), - 'error': this.afterMessagesFetched.bind(this), - }); - return this; - }, - - insertIntoDOM () { - /* This method gets overridden in src/converse-controlbox.js - * as well as src/converse-muc.js (if those plugins are - * enabled). - */ - _converse.chatboxviews.insertRowColumn(this.el); - return this; - }, - - showChatEvent (message) { - const isodate = moment().format(); - this.content.insertAdjacentHTML( - 'beforeend', - tpl_info({ - 'extra_classes': 'chat-event', - 'message': message, - 'isodate': isodate, - })); - this.insertDayIndicator(this.content.lastElementChild); - this.scrollDown(); - return isodate; - }, - - showErrorMessage (message) { - this.content.insertAdjacentHTML( - 'beforeend', - tpl_error_message({'message': message, 'isodate': moment().format() }) - ); - this.scrollDown(); - }, - - addSpinner (append=false) { - if (_.isNull(this.el.querySelector('.spinner'))) { - if (append) { - this.content.insertAdjacentHTML('beforeend', tpl_spinner()); - this.scrollDown(); - } else { - this.content.insertAdjacentHTML('afterbegin', tpl_spinner()); - } - } - }, - - clearSpinner () { - _.each( - this.content.querySelectorAll('span.spinner'), - (el) => el.parentNode.removeChild(el) - ); - }, - - insertDayIndicator (next_msg_el) { - /* Inserts an indicator into the chat area, showing the - * day as given by the passed in date. - * - * The indicator is only inserted if necessary. - * - * Parameters: - * (HTMLElement) next_msg_el - The message element before - * which the day indicator element must be inserted. - * This element must have a "data-isodate" attribute - * which specifies its creation date. - */ - const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"), - prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'), - next_msg_date = next_msg_el.getAttribute('data-isodate'); - - if (_.isNull(prev_msg_date) || moment(next_msg_date).isAfter(prev_msg_date, 'day')) { - const day_date = moment(next_msg_date).startOf('day'); - next_msg_el.insertAdjacentHTML('beforeBegin', - tpl_new_day({ - 'isodate': day_date.format(), - 'datestring': day_date.format("dddd MMM Do YYYY") - }) - ); - } - }, - - getLastMessageDate (cutoff) { - /* Return the ISO8601 format date of the latest message. - * - * Parameters: - * (Object) cutoff: Moment Date cutoff date. The last - * message received cutoff this date will be returned. - */ - const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'), - oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null; - if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) { - return null; - } - const last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'), - most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null; - if (_.isNull(most_recent_date) || moment(most_recent_date).isBefore(cutoff)) { - return most_recent_date; - } - /* XXX: We avoid .chat-state-notification messages, since they are - * temporary and get removed once a new element is - * inserted into the chat area, so we don't query for - * them here, otherwise we get a null reference later - * upon element insertion. - */ - const msg_dates = _.invokeMap( - sizzle('.message:not(.chat-state-notification)', this.content), - Element.prototype.getAttribute, 'data-isodate' - ) - if (_.isObject(cutoff)) { - cutoff = cutoff.format(); - } - msg_dates.push(cutoff); - msg_dates.sort(); - const idx = msg_dates.lastIndexOf(cutoff); - if (idx === 0) { - return null; - } else { - return msg_dates[idx-1]; - } - }, - - setScrollPosition (message_el) { - /* Given a newly inserted message, determine whether we - * should keep the scrollbar in place (so as to not scroll - * up when using infinite scroll). - */ - if (this.model.get('scrolled')) { - const next_msg_el = u.getNextElement(message_el, ".chat-msg"); - if (next_msg_el) { - // The currently received message is not new, there - // are newer messages after it. So let's see if we - // should maintain our current scroll position. - if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) { - const top_visible_message = this.model.get('top_visible_message') || next_msg_el; - - this.model.set('top_visible_message', top_visible_message); - this.content.scrollTop = top_visible_message.offsetTop - 30; - } - } - } else { - this.scrollDown(); - } - }, - - showHelpMessages (msgs, type, spinner) { - _.each(msgs, (msg) => { - this.content.insertAdjacentHTML( - 'beforeend', - tpl_help_message({ - 'isodate': moment().format(), - 'type': type, - 'message': xss.filterXSS(msg, {'whiteList': {'strong': []}}) - }) - ); - }); - if (spinner === true) { - this.addSpinner(); - } else if (spinner === false) { - this.clearSpinner(); - } - return this.scrollDown(); - }, - - clearChatStateNotification (message, isodate) { - if (isodate) { - _.each( - sizzle(`.chat-state-notification[data-csn="${message.get('from')}"][data-isodate="${isodate}"]`, this.content), - u.removeElement - ); - } else { - _.each(sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content), u.removeElement); - } - }, - - shouldShowOnTextMessage () { - return !u.isVisible(this.el); - }, - - insertMessage (view) { - /* Given a view representing a message, insert it into the - * content area of the chat box. - * - * Parameters: - * (Backbone.View) message: The message Backbone.View - */ - if (view.model.get('type') === 'error') { - const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); - if (previous_msg_el) { - previous_msg_el.insertAdjacentElement('afterend', view.el); - return this.trigger('messageInserted', view.el); - } - } - const current_msg_date = moment(view.model.get('time')) || moment, - previous_msg_date = this.getLastMessageDate(current_msg_date); - - if (_.isNull(previous_msg_date)) { - this.content.insertAdjacentElement('afterbegin', view.el); - } else { - const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop(); - if (view.model.get('type') === 'error' && - u.hasClass('chat-error', previous_msg_el) && - previous_msg_el.textContent === view.model.get('message')) { - // We don't show a duplicate error message - return; - } - previous_msg_el.insertAdjacentElement('afterend', view.el); - this.markFollowups(view.el); - } - return this.trigger('messageInserted', view.el); - }, - - markFollowups (el) { - /* Given a message element, determine wether it should be - * marked as a followup message to the previous element. - * - * Also determine whether the element following it is a - * followup message or not. - * - * Followup messages are subsequent ones written by the same - * author with no other conversation elements inbetween and - * posted within 10 minutes of one another. - * - * Parameters: - * (HTMLElement) el - The message element. - */ - const from = el.getAttribute('data-from'), - previous_el = el.previousElementSibling, - date = moment(el.getAttribute('data-isodate')), - next_el = el.nextElementSibling; - - if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) && - previous_el.getAttribute('data-from') === from && - date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) && - el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) { - u.addClass('chat-msg--followup', el); - } - if (!next_el) { return; } - - if (!u.hasClass('chat-msg--action', 'el') && - next_el.getAttribute('data-from') === from && - moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && - el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) { - u.addClass('chat-msg--followup', next_el); - } else { - u.removeClass('chat-msg--followup', next_el); - } - }, - - async showMessage (message) { - /* Inserts a chat message into the content area of the chat box. - * - * Will also insert a new day indicator if the message is on a - * different day. - * - * Parameters: - * (Backbone.Model) message: The message object - */ - const view = new _converse.MessageView({'model': message}); - await view.render(); - - this.clearChatStateNotification(message); - this.insertMessage(view); - this.insertDayIndicator(view.el); - this.setScrollPosition(view.el); - - if (u.isNewMessage(message)) { - if (message.get('sender') === 'me') { - // We remove the "scrolled" flag so that the chat area - // gets scrolled down. We always want to scroll down - // when the user writes a message as opposed to when a - // message is received. - this.model.set('scrolled', false); - } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) { - this.showNewMessagesIndicator(); - } - } - if (this.shouldShowOnTextMessage()) { - this.show(); - } else { - this.scrollDown(); - } - }, - - onMessageAdded (message) { - /* Handler that gets called when a new message object is created. - * - * Parameters: - * (Object) message - The message Backbone object that was added. - */ - this.showMessage(message); - if (message.get('correcting')) { - this.insertIntoTextArea(message.get('message'), true, true); - } - _converse.emit('messageAdded', { - 'message': message, - 'chatbox': this.model - }); - }, - - parseMessageForCommands (text) { - const match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/); - if (match) { - if (match[1] === "clear") { - this.clearMessages(); - return true; - } - else if (match[1] === "help") { - const msgs = [ - `/clear: ${__('Remove messages')}`, - `/me: ${__('Write in the third person')}`, - `/help: ${__('Show this menu')}` - ]; - this.showHelpMessages(msgs); - return true; - } - } - }, - - onMessageSubmitted (text, spoiler_hint) { - /* This method gets called once the user has typed a message - * and then pressed enter in a chat box. - * - * Parameters: - * (String) text - The chat message text. - * (String) spoiler_hint - A hint in case the message - * text is a hidden/spoiler message. See XEP-0382 - */ - if (!_converse.connection.authenticated) { - return this.showHelpMessages( - ['Sorry, the connection has been lost, '+ - 'and your message could not be sent'], - 'error' - ); - } - if (this.parseMessageForCommands(text)) { - return; - } - const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); - this.model.sendMessage(attrs); - }, - - setChatState (state, options) { - /* Mutator for setting the chat state of this chat session. - * Handles clearing of any chat state notification timeouts and - * setting new ones if necessary. - * Timeouts are set when the state being set is COMPOSING or PAUSED. - * After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE. - * See XEP-0085 Chat State Notifications. - * - * Parameters: - * (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE) - */ - if (!_.isUndefined(this.chat_state_timeout)) { - window.clearTimeout(this.chat_state_timeout); - delete this.chat_state_timeout; - } - if (state === _converse.COMPOSING) { - this.chat_state_timeout = window.setTimeout( - this.setChatState.bind(this), - _converse.TIMEOUTS.PAUSED, - _converse.PAUSED - ); - } else if (state === _converse.PAUSED) { - this.chat_state_timeout = window.setTimeout( - this.setChatState.bind(this), - _converse.TIMEOUTS.INACTIVE, - _converse.INACTIVE - ); - } - this.model.set('chat_state', state, options); - return this; - }, - - onFormSubmitted (ev) { - ev.preventDefault(); - const textarea = this.el.querySelector('.chat-textarea'), - message = textarea.value; - - if (!message.replace(/\s/g, '').length) { - return; - } - let spoiler_hint; - if (this.model.get('composing_spoiler')) { - const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); - spoiler_hint = hint_el.value; - hint_el.value = ''; - } - textarea.value = ''; - u.removeClass('correcting', textarea); - textarea.focus(); - // Trigger input event, so that the textarea resizes - const event = document.createEvent('Event'); - event.initEvent('input', true, true); - textarea.dispatchEvent(event); - - this.onMessageSubmitted(message, spoiler_hint); - _converse.emit('messageSend', message); - // Suppress events, otherwise superfluous CSN gets set - // immediately after the message, causing rate-limiting issues. - this.setChatState(_converse.ACTIVE, {'silent': true}); - }, - - keyPressed (ev) { - /* Event handler for when a key is pressed in a chat box textarea. - */ - if (ev.ctrlKey) { - // When ctrl is pressed, no chars are entered into the textarea. - return; - } - if (!ev.shiftKey && !ev.altKey) { - if (ev.keyCode === _converse.keycodes.FORWARD_SLASH) { - // Forward slash is used to run commands. Nothing to do here. - return; - } else if (ev.keyCode === _converse.keycodes.ESCAPE) { - return this.onEscapePressed(ev); - } else if (ev.keyCode === _converse.keycodes.ENTER) { - if (this.emoji_dropdown && u.isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) { - this.emoji_dropdown.toggle(); - } - return this.onFormSubmitted(ev); - } else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) { - return this.editEarlierMessage(); - } else if (ev.keyCode === _converse.keycodes.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) { - return this.editLaterMessage(); - } - } - if (_.includes([ - _converse.keycodes.SHIFT, - _converse.keycodes.META, - _converse.keycodes.META_RIGHT, - _converse.keycodes.ESCAPE, - _converse.keycodes.ALT] - , ev.keyCode)) { - return; - } - if (this.model.get('chat_state') !== _converse.COMPOSING) { - // Set chat state to composing if keyCode is not a forward-slash - // (which would imply an internal command and not a message). - this.setChatState(_converse.COMPOSING); - } - }, - - getOwnMessages () { - return f(this.model.messages.filter({'sender': 'me'})); - }, - - onEscapePressed (ev) { - ev.preventDefault(); - const idx = this.model.messages.findLastIndex('correcting'), - message = idx >=0 ? this.model.messages.at(idx) : null; - - if (message) { - message.save('correcting', false); - } - this.insertIntoTextArea('', true, false); - }, - - onMessageEditButtonClicked (ev) { - ev.preventDefault(); - const idx = this.model.messages.findLastIndex('correcting'), - currently_correcting = idx >=0 ? this.model.messages.at(idx) : null, - message_el = u.ancestor(ev.target, '.chat-msg'), - message = this.model.messages.findWhere({'msgid': message_el.getAttribute('data-msgid')}); - - if (currently_correcting !== message) { - if (!_.isNil(currently_correcting)) { - currently_correcting.save('correcting', false); - } - message.save('correcting', true); - this.insertIntoTextArea(u.prefixMentions(message), true, true); - } else { - message.save('correcting', false); - this.insertIntoTextArea('', true, false); - } - }, - - editLaterMessage () { - let message; - let idx = this.model.messages.findLastIndex('correcting'); - if (idx >= 0) { - this.model.messages.at(idx).save('correcting', false); - while (idx < this.model.messages.length-1) { - idx += 1; - const candidate = this.model.messages.at(idx); - if (candidate.get('sender') === 'me' && candidate.get('message')) { - message = candidate; - break; - } - } - } - if (message) { - this.insertIntoTextArea(message.get('message'), true, true); - message.save('correcting', true); - } else { - this.insertIntoTextArea('', true, false); - } - }, - - editEarlierMessage () { - let message; - let idx = this.model.messages.findLastIndex('correcting'); - if (idx >= 0) { - this.model.messages.at(idx).save('correcting', false); - while (idx > 0) { - idx -= 1; - const candidate = this.model.messages.at(idx); - if (candidate.get('sender') === 'me' && candidate.get('message')) { - message = candidate; - break; - } - } - } - message = message || this.getOwnMessages().findLast((msg) => msg.get('message')); - if (message) { - this.insertIntoTextArea(message.get('message'), true, true); - message.save('correcting', true); - } - }, - - inputChanged (ev) { - ev.target.style.height = 'auto'; // Fixes weirdness - ev.target.style.height = (ev.target.scrollHeight) + 'px'; - }, - - clearMessages (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - const result = confirm(__("Are you sure you want to clear the messages from this conversation?")); - if (result === true) { - this.content.innerHTML = ''; - this.model.messages.reset(); - this.model.messages.browserStorage._clear(); - } - return this; - }, - - insertIntoTextArea (value, replace=false, correcting=false) { - const textarea = this.el.querySelector('.chat-textarea'); - if (correcting) { - u.addClass('correcting', textarea); - } else { - u.removeClass('correcting', textarea); - } - if (replace) { - textarea.value = ''; - textarea.value = value; - } else { - let existing = textarea.value; - if (existing && (existing[existing.length-1] !== ' ')) { - existing = existing + ' '; - } - textarea.value = ''; - textarea.value = existing+value+' '; - } - u.putCurserAtEnd(textarea); - }, - - createEmojiPicker () { - if (_.isUndefined(_converse.emojipicker)) { - const storage = _converse.config.get('storage'), - id = `converse.emoji-${_converse.bare_jid}`; - _converse.emojipicker = new _converse.EmojiPicker({'id': id}); - _converse.emojipicker.browserStorage = new Backbone.BrowserStorage[storage](id); - _converse.emojipicker.fetch(); - } - this.emoji_picker_view = new _converse.EmojiPickerView({ - 'model': _converse.emojipicker - }); - }, - - insertEmoji (ev) { - ev.preventDefault(); - ev.stopPropagation(); - const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; - this.insertIntoTextArea(target.getAttribute('data-emoji')); - }, - - toggleEmojiMenu (ev) { - if (_.isUndefined(this.emoji_dropdown)) { - ev.stopPropagation(); - this.createEmojiPicker(); - this.insertEmojiPicker(); - this.renderEmojiPicker(); - - const dropdown_el = this.el.querySelector('.toggle-smiley.dropup'); - this.emoji_dropdown = new bootstrap.Dropdown(dropdown_el, true); - this.emoji_dropdown.el = dropdown_el; - this.emoji_dropdown.toggle(); - } - }, - - toggleCall (ev) { - ev.stopPropagation(); - _converse.emit('callButtonClicked', { - connection: _converse.connection, - model: this.model - }); - }, - - toggleComposeSpoilerMessage () { - this.model.set('composing_spoiler', !this.model.get('composing_spoiler')); - this.renderMessageForm(); - this.focus(); - }, - - toggleSpoilerMessage (ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - const toggle_el = ev.target, - icon_el = toggle_el.firstElementChild; - - u.slideToggleElement( - toggle_el.parentElement.parentElement.querySelector('.spoiler') - ); - if (toggle_el.getAttribute("data-toggle-state") == "closed") { - toggle_el.textContent = 'Show less'; - icon_el.classList.remove("fa-eye"); - icon_el.classList.add("fa-eye-slash"); - toggle_el.insertAdjacentElement('afterBegin', icon_el); - toggle_el.setAttribute("data-toggle-state", "open"); - } else { - toggle_el.textContent = 'Show more'; - icon_el.classList.remove("fa-eye-slash"); - icon_el.classList.add("fa-eye"); - toggle_el.insertAdjacentElement('afterBegin', icon_el); - toggle_el.setAttribute("data-toggle-state", "closed"); - } - }, - - onPresenceChanged (item) { - const show = item.get('show'), - fullname = this.model.getDisplayName(); - - let text; - if (u.isVisible(this.el)) { - if (show === 'offline') { - text = __('%1$s has gone offline', fullname); - } else if (show === 'away') { - text = __('%1$s has gone away', fullname); - } else if ((show === 'dnd')) { - text = __('%1$s is busy', fullname); - } else if (show === 'online') { - text = __('%1$s is online', fullname); - } - if (text) { - this.content.insertAdjacentHTML( - 'beforeend', - tpl_status_message({ - 'message': text, - 'isodate': moment().format(), - })); - this.scrollDown(); - } - } - }, - - close (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) { - _converse.router.navigate(''); - } - if (_converse.connection.connected) { - // Immediately sending the chat state, because the - // model is going to be destroyed afterwards. - this.setChatState(_converse.INACTIVE); - this.model.sendChatState(); - } - try { - this.model.destroy(); - } catch (e) { - _converse.log(e, Strophe.LogLevel.ERROR); - } - this.remove(); - _converse.emit('chatBoxClosed', this); - return this; - }, - - renderEmojiPicker () { - this.emoji_picker_view.render(); - }, - - insertEmojiPicker () { - var picker_el = this.el.querySelector('.emoji-picker'); - if (!_.isNull(picker_el)) { - picker_el.innerHTML = ''; - picker_el.appendChild(this.emoji_picker_view.el); - } - }, - - focus () { - const textarea_el = this.el.querySelector('.chat-textarea'); - if (!_.isNull(textarea_el)) { - textarea_el.focus(); - _converse.emit('chatBoxFocused', this); - } - return this; - }, - - hide () { - this.el.classList.add('hidden'); - return this; - }, - - afterShown () { - this.model.clearUnreadMsgCounter(); - this.setChatState(_converse.ACTIVE); - this.scrollDown(); - this.focus(); - }, - - _show (f) { - /* Inner show method that gets debounced */ - if (u.isVisible(this.el)) { - this.focus(); - return; - } - u.fadeIn(this.el, _.bind(this.afterShown, this)); - }, - - showNewMessagesIndicator () { - u.showElement(this.el.querySelector('.new-msgs-indicator')); - }, - - hideNewMessagesIndicator () { - const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator'); - if (!_.isNull(new_msgs_indicator)) { - new_msgs_indicator.classList.add('hidden'); - } - }, - - _markScrolled: function (ev) { - /* Called when the chat content is scrolled up or down. - * We want to record when the user has scrolled away from - * the bottom, so that we don't automatically scroll away - * from what the user is reading when new messages are - * received. - */ - if (ev && ev.preventDefault) { ev.preventDefault(); } - let scrolled = true; - const is_at_bottom = - (this.content.scrollTop + this.content.clientHeight) >= - this.content.scrollHeight - 62; // sigh... - - if (is_at_bottom) { - scrolled = false; - this.onScrolledDown(); - } - u.safeSave(this.model, { - 'scrolled': scrolled, - 'top_visible_message': null - }); - }, - - viewUnreadMessages () { - this.model.save({ - 'scrolled': false, - 'top_visible_message': null - }); - this.scrollDown(); - }, - - _scrollDown () { - /* Inner method that gets debounced */ - if (_.isUndefined(this.content)) { - return; - } - if (u.isVisible(this.content) && !this.model.get('scrolled')) { - this.content.scrollTop = this.content.scrollHeight; - } - }, - - onScrolledDown () { - this.hideNewMessagesIndicator(); - if (_converse.windowState !== 'hidden') { - this.model.clearUnreadMsgCounter(); - } - _converse.emit('chatBoxScrolledDown', {'chatbox': this.model}); - }, - - onWindowStateChanged (state) { - if (state === 'visible') { - if (!this.model.isHidden()) { - this.setChatState(_converse.ACTIVE); - if (this.model.get('num_unread', 0)) { - this.model.clearUnreadMsgCounter(); - } - } - } else if (state === 'hidden') { - this.setChatState(_converse.INACTIVE, {'silent': true}); - this.model.sendChatState(); - _converse.connection.flush(); - } - } - }); - - _converse.on('chatBoxViewsInitialized', () => { - const that = _converse.chatboxviews; - _converse.chatboxes.on('add', item => { - if (!that.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) { - that.add(item.get('id'), new _converse.ChatBoxView({model: item})); + const { _converse } = this, + { __ } = _converse; + + _converse.api.settings.update({ + 'emoji_image_path': twemoji.default.base, + 'show_send_button': false, + 'show_toolbar': true, + 'time_format': 'HH:mm', + 'use_system_emojis': true, + 'visible_toolbar_buttons': { + 'call': false, + 'clear': true, + 'emoji': true, + 'spoiler': true + }, + }); + twemoji.default.base = _converse.emoji_image_path; + + function onWindowStateChanged (data) { + if (_converse.chatboxviews) { + _converse.chatboxviews.each(view => { + if (view.model.get('id') !== 'controlbox') { + view.onWindowStateChanged(data.state); } }); - }); + } + } + _converse.api.listen.on('windowStateChanged', onWindowStateChanged); - _converse.on('connected', () => { - // Advertise that we support XEP-0382 Message Spoilers - _converse.api.disco.own.features.add(Strophe.NS.SPOILER); - }); - /************************ BEGIN API ************************/ - _.extend(_converse.api, { - /** - * The "chatview" namespace groups methods pertaining to views - * for one-on-one chats. - * - * @namespace _converse.api.chatviews - * @memberOf _converse.api + _converse.EmojiPicker = Backbone.Model.extend({ + defaults: { + 'current_category': 'people', + 'current_skintone': '', + 'scroll_position': 0 + } + }); + + + _converse.EmojiPickerView = Backbone.VDOMView.extend({ + className: 'emoji-picker-container', + events: { + 'click .emoji-category-picker li.emoji-category': 'chooseCategory', + 'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone' + }, + + initialize () { + this.model.on('change:current_skintone', this.render, this); + this.model.on('change:current_category', this.render, this); + }, + + toHTML () { + return tpl_emojis( + _.extend( + this.model.toJSON(), { + '_': _, + 'transform': u.getEmojiRenderer(_converse), + 'emojis_by_category': u.getEmojisByCategory(_converse), + 'toned_emojis': u.getTonedEmojis(_converse), + 'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'], + 'shouldBeHidden': this.shouldBeHidden + } + )); + }, + + shouldBeHidden (shortname, current_skintone, toned_emojis) { + /* Helper method for the template which decides whether an + * emoji should be hidden, based on which skin tone is + * currently being applied. */ - 'chatviews': { - /** - * Get the view of an already open chat. - * - * @method _converse.api.chatviews.get - * @returns {ChatBoxView} A [Backbone.View](http://backbonejs.org/#View) instance. - * The chat should already be open, otherwise `undefined` will be returned. - * - * @example - * // To return a single view, provide the JID of the contact: - * _converse.api.chatviews.get('buddy@example.com') - * - * @example - * // To return an array of views, provide an array of JIDs: - * _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com']) - */ - 'get' (jids) { - if (_.isUndefined(jids)) { - _converse.log( - "chats.create: You need to provide at least one JID", - Strophe.LogLevel.ERROR - ); - return null; - } - if (_.isString(jids)) { - return _converse.chatboxviews.get(jids); - } - return _.map(jids, (jid) => _converse.chatboxviews.get(jids)); + if (_.includes(shortname, '_tone')) { + if (!current_skintone || !_.includes(shortname, current_skintone)) { + return true; + } + } else { + if (current_skintone && _.includes(toned_emojis, shortname)) { + return true; } } - }); - /************************ END API ************************/ - } - }); + return false; + }, - return converse; -})); + chooseSkinTone (ev) { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target.nodeName === 'IMG' ? + ev.target.parentElement : ev.target; + const skintone = target.getAttribute("data-skintone").trim(); + if (this.model.get('current_skintone') === skintone) { + this.model.save({'current_skintone': ''}); + } else { + this.model.save({'current_skintone': skintone}); + } + }, + + chooseCategory (ev) { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target.nodeName === 'IMG' ? + ev.target.parentElement : ev.target; + const category = target.getAttribute("data-category").trim(); + this.model.save({ + 'current_category': category, + 'scroll_position': 0 + }); + } + }); + + + _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({ + initialize () { + this.model.on('change:status', this.onStatusMessageChanged, this); + this.model.vcard.on('change', this.render, this); + }, + + render () { + this.el.innerHTML = tpl_chatbox_head( + _.extend( + this.model.vcard.toJSON(), + this.model.toJSON(), + { '_converse': _converse, + 'info_close': __('Close this chat box') + } + ) + ); + this.renderAvatar(); + return this; + }, + + onStatusMessageChanged (item) { + this.render(); + _converse.emit('contactStatusMessageChanged', { + 'contact': item.attributes, + 'message': item.get('status') + }); + } + }); + + + _converse.UserDetailsModal = _converse.BootstrapModal.extend({ + + events: { + 'click button.remove-contact': 'removeContact', + 'click button.refresh-contact': 'refreshContact', + 'click .fingerprint-trust .btn input': 'toggleDeviceTrust' + }, + + initialize () { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + this.model.on('contactAdded', this.registerContactEventHandlers, this); + this.model.on('change', this.render, this); + this.registerContactEventHandlers(); + _converse.emit('userDetailsModalInitialized', this.model); + }, + + toHTML () { + return tpl_user_details_modal(_.extend( + this.model.toJSON(), + this.model.vcard.toJSON(), { + '_': _, + '__': __, + 'view': this, + '_converse': _converse, + 'allow_contact_removal': _converse.allow_contact_removal, + 'display_name': this.model.getDisplayName(), + 'is_roster_contact': !_.isUndefined(this.model.contact), + 'utils': u + })); + }, + + registerContactEventHandlers () { + if (!_.isUndefined(this.model.contact)) { + this.model.contact.on('change', this.render, this); + this.model.contact.vcard.on('change', this.render, this); + this.model.contact.on('destroy', () => { + delete this.model.contact; + this.render(); + }); + } + }, + + refreshContact (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + const refresh_icon = this.el.querySelector('.fa-refresh'); + u.addClass('fa-spin', refresh_icon); + _converse.api.vcard.update(this.model.contact.vcard, true) + .then(() => u.removeClass('fa-spin', refresh_icon)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + removeContact (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + if (!_converse.allow_contact_removal) { return; } + const result = confirm(__("Are you sure you want to remove this contact?")); + if (result === true) { + this.modal.hide(); + this.model.contact.removeFromRoster( + (iq) => { + this.model.contact.destroy(); + }, + (err) => { + _converse.log(err, Strophe.LogLevel.ERROR); + _converse.api.alert.show( + Strophe.LogLevel.ERROR, + __('Error'), + [__('Sorry, there was an error while trying to remove %1$s as a contact.', + this.model.contact.getDisplayName()) + ] + ) + } + ); + } + }, + }); + + + _converse.ChatBoxView = Backbone.NativeView.extend({ + length: 200, + className: 'chatbox hidden', + is_chatroom: false, // Leaky abstraction from MUC + + events: { + 'change input.fileupload': 'onFileSelection', + 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', + 'click .chatbox-navback': 'showControlBox', + 'click .close-chatbox-button': 'close', + 'click .new-msgs-indicator': 'viewUnreadMessages', + 'click .send-button': 'onFormSubmitted', + 'click .show-user-details-modal': 'showUserDetailsModal', + 'click .spoiler-toggle': 'toggleSpoilerMessage', + 'click .toggle-call': 'toggleCall', + 'click .toggle-clear': 'clearMessages', + 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage', + 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', + 'click .toggle-smiley': 'toggleEmojiMenu', + 'click .upload-file': 'toggleFileUpload', + 'input .chat-textarea': 'inputChanged', + 'keydown .chat-textarea': 'keyPressed' + }, + + initialize () { + this.initDebounced(); + + this.model.messages.on('add', this.onMessageAdded, this); + this.model.messages.on('rendered', this.scrollDown, this); + + this.model.on('show', this.show, this); + this.model.on('destroy', this.remove, this); + + this.model.presence.on('change:show', this.onPresenceChanged, this); + this.model.on('showHelpMessages', this.showHelpMessages, this); + this.render(); + + this.fetchMessages(); + _converse.emit('chatBoxOpened', this); + _converse.emit('chatBoxInitialized', this); + }, + + initDebounced () { + this.scrollDown = _.debounce(this._scrollDown, 250); + this.markScrolled = _.debounce(this._markScrolled, 100); + this.show = _.debounce(this._show, 250, {'leading': true}); + }, + + render () { + // XXX: Is this still needed? + this.el.setAttribute('id', this.model.get('box_id')); + this.el.innerHTML = tpl_chatbox( + _.extend(this.model.toJSON(), { + 'unread_msgs': __('You have unread messages') + } + )); + this.content = this.el.querySelector('.chat-content'); + this.renderMessageForm(); + this.insertHeading(); + return this; + }, + + renderToolbar (toolbar, options) { + if (!_converse.show_toolbar) { + return this; + } + toolbar = toolbar || tpl_toolbar; + options = _.assign( + this.model.toJSON(), + this.getToolbarOptions(options || {}) + ); + this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options); + this.addSpoilerButton(options); + this.addFileUploadButton(); + _converse.emit('renderToolbar', this); + return this; + }, + + renderMessageForm () { + let placeholder; + if (this.model.get('composing_spoiler')) { + placeholder = __('Hidden message'); + } else { + placeholder = __('Message'); + } + const form_container = this.el.querySelector('.message-form-container'); + form_container.innerHTML = tpl_chatbox_message_form( + _.extend(this.model.toJSON(), { + 'hint_value': _.get(this.el.querySelector('.spoiler-hint'), 'value'), + 'label_message': placeholder, + 'label_send': __('Send'), + 'label_spoiler_hint': __('Optional hint'), + 'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'), + 'show_send_button': _converse.show_send_button, + 'show_toolbar': _converse.show_toolbar, + 'unread_msgs': __('You have unread messages') + })); + this.renderToolbar(); + }, + + showControlBox () { + // Used in mobile view, to navigate back to the controlbox + const view = _converse.chatboxviews.get('controlbox'); + view.show(); + this.hide(); + }, + + showUserDetailsModal (ev) { + ev.preventDefault(); + if (_.isUndefined(this.user_details_modal)) { + this.user_details_modal = new _converse.UserDetailsModal({model: this.model}); + } + this.user_details_modal.show(ev); + }, + + toggleFileUpload (ev) { + this.el.querySelector('input.fileupload').click(); + }, + + onFileSelection (evt) { + this.model.sendFiles(evt.target.files); + }, + + addFileUploadButton (options) { + _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then((result) => { + if (result.length) { + this.el.querySelector('.chat-toolbar').insertAdjacentHTML( + 'beforeend', + tpl_toolbar_fileupload({'tooltip_upload_file': __('Choose a file to send')})); + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + addSpoilerButton (options) { + /* Asynchronously adds a button for writing spoiler + * messages, based on whether the contact's client supports + * it. + */ + if (!options.show_spoiler_button || this.model.get('type') === 'chatroom') { + return; + } + const contact_jid = this.model.get('jid'); + const resources = this.model.presence.get('resources'); + if (_.isEmpty(resources)) { + return; + } + Promise.all(_.map(_.keys(resources), (resource) => + _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`) + )).then((results) => { + if (_.filter(results, 'length').length) { + const html = tpl_spoiler_button(this.model.toJSON()); + if (_converse.visible_toolbar_buttons.emoji) { + this.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html); + } else { + this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html); + } + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + insertHeading () { + this.heading = new _converse.ChatBoxHeading({'model': this.model}); + this.heading.render(); + this.heading.chatview = this; + + if (!_.isUndefined(this.model.contact)) { + this.model.contact.on('destroy', this.heading.render, this); + } + const flyout = this.el.querySelector('.flyout'); + flyout.insertBefore(this.heading.el, flyout.querySelector('.chat-body')); + return this; + }, + + getToolbarOptions (options) { + let label_toggle_spoiler; + if (this.model.get('composing_spoiler')) { + label_toggle_spoiler = __('Click to write as a normal (non-spoiler) message'); + } else { + label_toggle_spoiler = __('Click to write your message as a spoiler'); + } + return _.extend(options || {}, { + 'label_clear': __('Clear all messages'), + 'tooltip_insert_smiley': __('Insert emojis'), + 'tooltip_start_call': __('Start a call'), + 'label_toggle_spoiler': label_toggle_spoiler, + 'show_call_button': _converse.visible_toolbar_buttons.call, + 'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler, + 'use_emoji': _converse.visible_toolbar_buttons.emoji, + }); + }, + + afterMessagesFetched () { + this.insertIntoDOM(); + this.scrollDown(); + this.content.addEventListener('scroll', this.markScrolled.bind(this)); + _converse.emit('afterMessagesFetched', this); + }, + + fetchMessages () { + this.model.messages.fetch({ + 'add': true, + 'success': this.afterMessagesFetched.bind(this), + 'error': this.afterMessagesFetched.bind(this), + }); + return this; + }, + + insertIntoDOM () { + /* This method gets overridden in src/converse-controlbox.js + * as well as src/converse-muc.js (if those plugins are + * enabled). + */ + _converse.chatboxviews.insertRowColumn(this.el); + return this; + }, + + showChatEvent (message) { + const isodate = moment().format(); + this.content.insertAdjacentHTML( + 'beforeend', + tpl_info({ + 'extra_classes': 'chat-event', + 'message': message, + 'isodate': isodate, + })); + this.insertDayIndicator(this.content.lastElementChild); + this.scrollDown(); + return isodate; + }, + + showErrorMessage (message) { + this.content.insertAdjacentHTML( + 'beforeend', + tpl_error_message({'message': message, 'isodate': moment().format() }) + ); + this.scrollDown(); + }, + + addSpinner (append=false) { + if (_.isNull(this.el.querySelector('.spinner'))) { + if (append) { + this.content.insertAdjacentHTML('beforeend', tpl_spinner()); + this.scrollDown(); + } else { + this.content.insertAdjacentHTML('afterbegin', tpl_spinner()); + } + } + }, + + clearSpinner () { + _.each( + this.content.querySelectorAll('span.spinner'), + (el) => el.parentNode.removeChild(el) + ); + }, + + insertDayIndicator (next_msg_el) { + /* Inserts an indicator into the chat area, showing the + * day as given by the passed in date. + * + * The indicator is only inserted if necessary. + * + * Parameters: + * (HTMLElement) next_msg_el - The message element before + * which the day indicator element must be inserted. + * This element must have a "data-isodate" attribute + * which specifies its creation date. + */ + const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"), + prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'), + next_msg_date = next_msg_el.getAttribute('data-isodate'); + + if (_.isNull(prev_msg_date) || moment(next_msg_date).isAfter(prev_msg_date, 'day')) { + const day_date = moment(next_msg_date).startOf('day'); + next_msg_el.insertAdjacentHTML('beforeBegin', + tpl_new_day({ + 'isodate': day_date.format(), + 'datestring': day_date.format("dddd MMM Do YYYY") + }) + ); + } + }, + + getLastMessageDate (cutoff) { + /* Return the ISO8601 format date of the latest message. + * + * Parameters: + * (Object) cutoff: Moment Date cutoff date. The last + * message received cutoff this date will be returned. + */ + const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'), + oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null; + if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) { + return null; + } + const last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'), + most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null; + if (_.isNull(most_recent_date) || moment(most_recent_date).isBefore(cutoff)) { + return most_recent_date; + } + /* XXX: We avoid .chat-state-notification messages, since they are + * temporary and get removed once a new element is + * inserted into the chat area, so we don't query for + * them here, otherwise we get a null reference later + * upon element insertion. + */ + const msg_dates = _.invokeMap( + sizzle('.message:not(.chat-state-notification)', this.content), + Element.prototype.getAttribute, 'data-isodate' + ) + if (_.isObject(cutoff)) { + cutoff = cutoff.format(); + } + msg_dates.push(cutoff); + msg_dates.sort(); + const idx = msg_dates.lastIndexOf(cutoff); + if (idx === 0) { + return null; + } else { + return msg_dates[idx-1]; + } + }, + + setScrollPosition (message_el) { + /* Given a newly inserted message, determine whether we + * should keep the scrollbar in place (so as to not scroll + * up when using infinite scroll). + */ + if (this.model.get('scrolled')) { + const next_msg_el = u.getNextElement(message_el, ".chat-msg"); + if (next_msg_el) { + // The currently received message is not new, there + // are newer messages after it. So let's see if we + // should maintain our current scroll position. + if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) { + const top_visible_message = this.model.get('top_visible_message') || next_msg_el; + + this.model.set('top_visible_message', top_visible_message); + this.content.scrollTop = top_visible_message.offsetTop - 30; + } + } + } else { + this.scrollDown(); + } + }, + + showHelpMessages (msgs, type, spinner) { + _.each(msgs, (msg) => { + this.content.insertAdjacentHTML( + 'beforeend', + tpl_help_message({ + 'isodate': moment().format(), + 'type': type, + 'message': xss.filterXSS(msg, {'whiteList': {'strong': []}}) + }) + ); + }); + if (spinner === true) { + this.addSpinner(); + } else if (spinner === false) { + this.clearSpinner(); + } + return this.scrollDown(); + }, + + clearChatStateNotification (message, isodate) { + if (isodate) { + _.each( + sizzle(`.chat-state-notification[data-csn="${message.get('from')}"][data-isodate="${isodate}"]`, this.content), + u.removeElement + ); + } else { + _.each(sizzle(`.chat-state-notification[data-csn="${message.get('from')}"]`, this.content), u.removeElement); + } + }, + + shouldShowOnTextMessage () { + return !u.isVisible(this.el); + }, + + insertMessage (view) { + /* Given a view representing a message, insert it into the + * content area of the chat box. + * + * Parameters: + * (Backbone.View) message: The message Backbone.View + */ + if (view.model.get('type') === 'error') { + const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); + if (previous_msg_el) { + previous_msg_el.insertAdjacentElement('afterend', view.el); + return this.trigger('messageInserted', view.el); + } + } + const current_msg_date = moment(view.model.get('time')) || moment, + previous_msg_date = this.getLastMessageDate(current_msg_date); + + if (_.isNull(previous_msg_date)) { + this.content.insertAdjacentElement('afterbegin', view.el); + } else { + const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop(); + if (view.model.get('type') === 'error' && + u.hasClass('chat-error', previous_msg_el) && + previous_msg_el.textContent === view.model.get('message')) { + // We don't show a duplicate error message + return; + } + previous_msg_el.insertAdjacentElement('afterend', view.el); + this.markFollowups(view.el); + } + return this.trigger('messageInserted', view.el); + }, + + markFollowups (el) { + /* Given a message element, determine wether it should be + * marked as a followup message to the previous element. + * + * Also determine whether the element following it is a + * followup message or not. + * + * Followup messages are subsequent ones written by the same + * author with no other conversation elements inbetween and + * posted within 10 minutes of one another. + * + * Parameters: + * (HTMLElement) el - The message element. + */ + const from = el.getAttribute('data-from'), + previous_el = el.previousElementSibling, + date = moment(el.getAttribute('data-isodate')), + next_el = el.nextElementSibling; + + if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) && + previous_el.getAttribute('data-from') === from && + date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) && + el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) { + u.addClass('chat-msg--followup', el); + } + if (!next_el) { return; } + + if (!u.hasClass('chat-msg--action', 'el') && + next_el.getAttribute('data-from') === from && + moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && + el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) { + u.addClass('chat-msg--followup', next_el); + } else { + u.removeClass('chat-msg--followup', next_el); + } + }, + + async showMessage (message) { + /* Inserts a chat message into the content area of the chat box. + * + * Will also insert a new day indicator if the message is on a + * different day. + * + * Parameters: + * (Backbone.Model) message: The message object + */ + const view = new _converse.MessageView({'model': message}); + await view.render(); + + this.clearChatStateNotification(message); + this.insertMessage(view); + this.insertDayIndicator(view.el); + this.setScrollPosition(view.el); + + if (u.isNewMessage(message)) { + if (message.get('sender') === 'me') { + // We remove the "scrolled" flag so that the chat area + // gets scrolled down. We always want to scroll down + // when the user writes a message as opposed to when a + // message is received. + this.model.set('scrolled', false); + } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) { + this.showNewMessagesIndicator(); + } + } + if (this.shouldShowOnTextMessage()) { + this.show(); + } else { + this.scrollDown(); + } + }, + + onMessageAdded (message) { + /* Handler that gets called when a new message object is created. + * + * Parameters: + * (Object) message - The message Backbone object that was added. + */ + this.showMessage(message); + if (message.get('correcting')) { + this.insertIntoTextArea(message.get('message'), true, true); + } + _converse.emit('messageAdded', { + 'message': message, + 'chatbox': this.model + }); + }, + + parseMessageForCommands (text) { + const match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/); + if (match) { + if (match[1] === "clear") { + this.clearMessages(); + return true; + } + else if (match[1] === "help") { + const msgs = [ + `/clear: ${__('Remove messages')}`, + `/me: ${__('Write in the third person')}`, + `/help: ${__('Show this menu')}` + ]; + this.showHelpMessages(msgs); + return true; + } + } + }, + + onMessageSubmitted (text, spoiler_hint) { + /* This method gets called once the user has typed a message + * and then pressed enter in a chat box. + * + * Parameters: + * (String) text - The chat message text. + * (String) spoiler_hint - A hint in case the message + * text is a hidden/spoiler message. See XEP-0382 + */ + if (!_converse.connection.authenticated) { + return this.showHelpMessages( + ['Sorry, the connection has been lost, '+ + 'and your message could not be sent'], + 'error' + ); + } + if (this.parseMessageForCommands(text)) { + return; + } + const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); + this.model.sendMessage(attrs); + }, + + setChatState (state, options) { + /* Mutator for setting the chat state of this chat session. + * Handles clearing of any chat state notification timeouts and + * setting new ones if necessary. + * Timeouts are set when the state being set is COMPOSING or PAUSED. + * After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE. + * See XEP-0085 Chat State Notifications. + * + * Parameters: + * (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE) + */ + if (!_.isUndefined(this.chat_state_timeout)) { + window.clearTimeout(this.chat_state_timeout); + delete this.chat_state_timeout; + } + if (state === _converse.COMPOSING) { + this.chat_state_timeout = window.setTimeout( + this.setChatState.bind(this), + _converse.TIMEOUTS.PAUSED, + _converse.PAUSED + ); + } else if (state === _converse.PAUSED) { + this.chat_state_timeout = window.setTimeout( + this.setChatState.bind(this), + _converse.TIMEOUTS.INACTIVE, + _converse.INACTIVE + ); + } + this.model.set('chat_state', state, options); + return this; + }, + + onFormSubmitted (ev) { + ev.preventDefault(); + const textarea = this.el.querySelector('.chat-textarea'), + message = textarea.value; + + if (!message.replace(/\s/g, '').length) { + return; + } + let spoiler_hint; + if (this.model.get('composing_spoiler')) { + const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); + spoiler_hint = hint_el.value; + hint_el.value = ''; + } + textarea.value = ''; + u.removeClass('correcting', textarea); + textarea.focus(); + // Trigger input event, so that the textarea resizes + const event = document.createEvent('Event'); + event.initEvent('input', true, true); + textarea.dispatchEvent(event); + + this.onMessageSubmitted(message, spoiler_hint); + _converse.emit('messageSend', message); + // Suppress events, otherwise superfluous CSN gets set + // immediately after the message, causing rate-limiting issues. + this.setChatState(_converse.ACTIVE, {'silent': true}); + }, + + keyPressed (ev) { + /* Event handler for when a key is pressed in a chat box textarea. + */ + if (ev.ctrlKey) { + // When ctrl is pressed, no chars are entered into the textarea. + return; + } + if (!ev.shiftKey && !ev.altKey) { + if (ev.keyCode === _converse.keycodes.FORWARD_SLASH) { + // Forward slash is used to run commands. Nothing to do here. + return; + } else if (ev.keyCode === _converse.keycodes.ESCAPE) { + return this.onEscapePressed(ev); + } else if (ev.keyCode === _converse.keycodes.ENTER) { + if (this.emoji_dropdown && u.isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) { + this.emoji_dropdown.toggle(); + } + return this.onFormSubmitted(ev); + } else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) { + return this.editEarlierMessage(); + } else if (ev.keyCode === _converse.keycodes.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) { + return this.editLaterMessage(); + } + } + if (_.includes([ + _converse.keycodes.SHIFT, + _converse.keycodes.META, + _converse.keycodes.META_RIGHT, + _converse.keycodes.ESCAPE, + _converse.keycodes.ALT] + , ev.keyCode)) { + return; + } + if (this.model.get('chat_state') !== _converse.COMPOSING) { + // Set chat state to composing if keyCode is not a forward-slash + // (which would imply an internal command and not a message). + this.setChatState(_converse.COMPOSING); + } + }, + + getOwnMessages () { + return f(this.model.messages.filter({'sender': 'me'})); + }, + + onEscapePressed (ev) { + ev.preventDefault(); + const idx = this.model.messages.findLastIndex('correcting'), + message = idx >=0 ? this.model.messages.at(idx) : null; + + if (message) { + message.save('correcting', false); + } + this.insertIntoTextArea('', true, false); + }, + + onMessageEditButtonClicked (ev) { + ev.preventDefault(); + const idx = this.model.messages.findLastIndex('correcting'), + currently_correcting = idx >=0 ? this.model.messages.at(idx) : null, + message_el = u.ancestor(ev.target, '.chat-msg'), + message = this.model.messages.findWhere({'msgid': message_el.getAttribute('data-msgid')}); + + if (currently_correcting !== message) { + if (!_.isNil(currently_correcting)) { + currently_correcting.save('correcting', false); + } + message.save('correcting', true); + this.insertIntoTextArea(u.prefixMentions(message), true, true); + } else { + message.save('correcting', false); + this.insertIntoTextArea('', true, false); + } + }, + + editLaterMessage () { + let message; + let idx = this.model.messages.findLastIndex('correcting'); + if (idx >= 0) { + this.model.messages.at(idx).save('correcting', false); + while (idx < this.model.messages.length-1) { + idx += 1; + const candidate = this.model.messages.at(idx); + if (candidate.get('sender') === 'me' && candidate.get('message')) { + message = candidate; + break; + } + } + } + if (message) { + this.insertIntoTextArea(message.get('message'), true, true); + message.save('correcting', true); + } else { + this.insertIntoTextArea('', true, false); + } + }, + + editEarlierMessage () { + let message; + let idx = this.model.messages.findLastIndex('correcting'); + if (idx >= 0) { + this.model.messages.at(idx).save('correcting', false); + while (idx > 0) { + idx -= 1; + const candidate = this.model.messages.at(idx); + if (candidate.get('sender') === 'me' && candidate.get('message')) { + message = candidate; + break; + } + } + } + message = message || this.getOwnMessages().findLast((msg) => msg.get('message')); + if (message) { + this.insertIntoTextArea(message.get('message'), true, true); + message.save('correcting', true); + } + }, + + inputChanged (ev) { + ev.target.style.height = 'auto'; // Fixes weirdness + ev.target.style.height = (ev.target.scrollHeight) + 'px'; + }, + + clearMessages (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + const result = confirm(__("Are you sure you want to clear the messages from this conversation?")); + if (result === true) { + this.content.innerHTML = ''; + this.model.messages.reset(); + this.model.messages.browserStorage._clear(); + } + return this; + }, + + insertIntoTextArea (value, replace=false, correcting=false) { + const textarea = this.el.querySelector('.chat-textarea'); + if (correcting) { + u.addClass('correcting', textarea); + } else { + u.removeClass('correcting', textarea); + } + if (replace) { + textarea.value = ''; + textarea.value = value; + } else { + let existing = textarea.value; + if (existing && (existing[existing.length-1] !== ' ')) { + existing = existing + ' '; + } + textarea.value = ''; + textarea.value = existing+value+' '; + } + u.putCurserAtEnd(textarea); + }, + + createEmojiPicker () { + if (_.isUndefined(_converse.emojipicker)) { + const storage = _converse.config.get('storage'), + id = `converse.emoji-${_converse.bare_jid}`; + _converse.emojipicker = new _converse.EmojiPicker({'id': id}); + _converse.emojipicker.browserStorage = new Backbone.BrowserStorage[storage](id); + _converse.emojipicker.fetch(); + } + this.emoji_picker_view = new _converse.EmojiPickerView({ + 'model': _converse.emojipicker + }); + }, + + insertEmoji (ev) { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; + this.insertIntoTextArea(target.getAttribute('data-emoji')); + }, + + toggleEmojiMenu (ev) { + if (_.isUndefined(this.emoji_dropdown)) { + ev.stopPropagation(); + this.createEmojiPicker(); + this.insertEmojiPicker(); + this.renderEmojiPicker(); + + const dropdown_el = this.el.querySelector('.toggle-smiley.dropup'); + this.emoji_dropdown = new bootstrap.Dropdown(dropdown_el, true); + this.emoji_dropdown.el = dropdown_el; + this.emoji_dropdown.toggle(); + } + }, + + toggleCall (ev) { + ev.stopPropagation(); + _converse.emit('callButtonClicked', { + connection: _converse.connection, + model: this.model + }); + }, + + toggleComposeSpoilerMessage () { + this.model.set('composing_spoiler', !this.model.get('composing_spoiler')); + this.renderMessageForm(); + this.focus(); + }, + + toggleSpoilerMessage (ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + const toggle_el = ev.target, + icon_el = toggle_el.firstElementChild; + + u.slideToggleElement( + toggle_el.parentElement.parentElement.querySelector('.spoiler') + ); + if (toggle_el.getAttribute("data-toggle-state") == "closed") { + toggle_el.textContent = 'Show less'; + icon_el.classList.remove("fa-eye"); + icon_el.classList.add("fa-eye-slash"); + toggle_el.insertAdjacentElement('afterBegin', icon_el); + toggle_el.setAttribute("data-toggle-state", "open"); + } else { + toggle_el.textContent = 'Show more'; + icon_el.classList.remove("fa-eye-slash"); + icon_el.classList.add("fa-eye"); + toggle_el.insertAdjacentElement('afterBegin', icon_el); + toggle_el.setAttribute("data-toggle-state", "closed"); + } + }, + + onPresenceChanged (item) { + const show = item.get('show'), + fullname = this.model.getDisplayName(); + + let text; + if (u.isVisible(this.el)) { + if (show === 'offline') { + text = __('%1$s has gone offline', fullname); + } else if (show === 'away') { + text = __('%1$s has gone away', fullname); + } else if ((show === 'dnd')) { + text = __('%1$s is busy', fullname); + } else if (show === 'online') { + text = __('%1$s is online', fullname); + } + if (text) { + this.content.insertAdjacentHTML( + 'beforeend', + tpl_status_message({ + 'message': text, + 'isodate': moment().format(), + })); + this.scrollDown(); + } + } + }, + + close (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) { + _converse.router.navigate(''); + } + if (_converse.connection.connected) { + // Immediately sending the chat state, because the + // model is going to be destroyed afterwards. + this.setChatState(_converse.INACTIVE); + this.model.sendChatState(); + } + try { + this.model.destroy(); + } catch (e) { + _converse.log(e, Strophe.LogLevel.ERROR); + } + this.remove(); + _converse.emit('chatBoxClosed', this); + return this; + }, + + renderEmojiPicker () { + this.emoji_picker_view.render(); + }, + + insertEmojiPicker () { + var picker_el = this.el.querySelector('.emoji-picker'); + if (!_.isNull(picker_el)) { + picker_el.innerHTML = ''; + picker_el.appendChild(this.emoji_picker_view.el); + } + }, + + focus () { + const textarea_el = this.el.querySelector('.chat-textarea'); + if (!_.isNull(textarea_el)) { + textarea_el.focus(); + _converse.emit('chatBoxFocused', this); + } + return this; + }, + + hide () { + this.el.classList.add('hidden'); + return this; + }, + + afterShown () { + this.model.clearUnreadMsgCounter(); + this.setChatState(_converse.ACTIVE); + this.scrollDown(); + this.focus(); + }, + + _show (f) { + /* Inner show method that gets debounced */ + if (u.isVisible(this.el)) { + this.focus(); + return; + } + u.fadeIn(this.el, _.bind(this.afterShown, this)); + }, + + showNewMessagesIndicator () { + u.showElement(this.el.querySelector('.new-msgs-indicator')); + }, + + hideNewMessagesIndicator () { + const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator'); + if (!_.isNull(new_msgs_indicator)) { + new_msgs_indicator.classList.add('hidden'); + } + }, + + _markScrolled: function (ev) { + /* Called when the chat content is scrolled up or down. + * We want to record when the user has scrolled away from + * the bottom, so that we don't automatically scroll away + * from what the user is reading when new messages are + * received. + */ + if (ev && ev.preventDefault) { ev.preventDefault(); } + let scrolled = true; + const is_at_bottom = + (this.content.scrollTop + this.content.clientHeight) >= + this.content.scrollHeight - 62; // sigh... + + if (is_at_bottom) { + scrolled = false; + this.onScrolledDown(); + } + u.safeSave(this.model, { + 'scrolled': scrolled, + 'top_visible_message': null + }); + }, + + viewUnreadMessages () { + this.model.save({ + 'scrolled': false, + 'top_visible_message': null + }); + this.scrollDown(); + }, + + _scrollDown () { + /* Inner method that gets debounced */ + if (_.isUndefined(this.content)) { + return; + } + if (u.isVisible(this.content) && !this.model.get('scrolled')) { + this.content.scrollTop = this.content.scrollHeight; + } + }, + + onScrolledDown () { + this.hideNewMessagesIndicator(); + if (_converse.windowState !== 'hidden') { + this.model.clearUnreadMsgCounter(); + } + _converse.emit('chatBoxScrolledDown', {'chatbox': this.model}); + }, + + onWindowStateChanged (state) { + if (state === 'visible') { + if (!this.model.isHidden()) { + this.setChatState(_converse.ACTIVE); + if (this.model.get('num_unread', 0)) { + this.model.clearUnreadMsgCounter(); + } + } + } else if (state === 'hidden') { + this.setChatState(_converse.INACTIVE, {'silent': true}); + this.model.sendChatState(); + _converse.connection.flush(); + } + } + }); + + _converse.on('chatBoxViewsInitialized', () => { + const that = _converse.chatboxviews; + _converse.chatboxes.on('add', item => { + if (!that.get(item.get('id')) && item.get('type') === _converse.PRIVATE_CHAT_TYPE) { + that.add(item.get('id'), new _converse.ChatBoxView({model: item})); + } + }); + }); + + _converse.on('connected', () => { + // Advertise that we support XEP-0382 Message Spoilers + _converse.api.disco.own.features.add(Strophe.NS.SPOILER); + }); + + /************************ BEGIN API ************************/ + _.extend(_converse.api, { + /** + * The "chatview" namespace groups methods pertaining to views + * for one-on-one chats. + * + * @namespace _converse.api.chatviews + * @memberOf _converse.api + */ + 'chatviews': { + /** + * Get the view of an already open chat. + * + * @method _converse.api.chatviews.get + * @returns {ChatBoxView} A [Backbone.View](http://backbonejs.org/#View) instance. + * The chat should already be open, otherwise `undefined` will be returned. + * + * @example + * // To return a single view, provide the JID of the contact: + * _converse.api.chatviews.get('buddy@example.com') + * + * @example + * // To return an array of views, provide an array of JIDs: + * _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com']) + */ + 'get' (jids) { + if (_.isUndefined(jids)) { + _converse.log( + "chats.create: You need to provide at least one JID", + Strophe.LogLevel.ERROR + ); + return null; + } + if (_.isString(jids)) { + return _converse.chatboxviews.get(jids); + } + return _.map(jids, (jid) => _converse.chatboxviews.get(jids)); + } + } + }); + /************************ END API ************************/ + } +}); diff --git a/src/converse-controlbox.js b/src/converse-controlbox.js index fbe36308a..c9f673629 100644 --- a/src/converse-controlbox.js +++ b/src/converse-controlbox.js @@ -6,623 +6,609 @@ // /*global define */ -(function (root, factory) { - define(["@converse/headless/converse-core", - "bootstrap", - "formdata-polyfill", - "@converse/headless/lodash.fp", - "templates/converse_brand_heading.html", - "templates/controlbox.html", - "templates/controlbox_toggle.html", - "templates/login_panel.html", - "converse-chatview", - "converse-rosterview", - "converse-profile" - ], factory); -}(this, function ( - converse, - bootstrap, - _FormData, - fp, - tpl_brand_heading, - tpl_controlbox, - tpl_controlbox_toggle, - tpl_login_panel - ) { - "use strict"; +import "converse-chatview"; +import "converse-profile"; +import "converse-rosterview"; +import _FormData from "formdata-polyfill"; +import bootstrap from "bootstrap"; +import converse from "@converse/headless/converse-core"; +import fp from "@converse/headless/lodash.fp"; +import tpl_brand_heading from "templates/converse_brand_heading.html"; +import tpl_controlbox from "templates/controlbox.html"; +import tpl_controlbox_toggle from "templates/controlbox_toggle.html"; +import tpl_login_panel from "templates/login_panel.html"; - const CHATBOX_TYPE = 'chatbox'; - const { Strophe, Backbone, Promise, _, moment } = converse.env; - const u = converse.env.utils; +const CHATBOX_TYPE = 'chatbox'; +const { Strophe, Backbone, Promise, _, moment } = converse.env; +const u = converse.env.utils; - const CONNECTION_STATUS_CSS_CLASS = { - 'Error': 'error', - 'Connecting': 'info', - 'Connection failure': 'error', - 'Authenticating': 'info', - 'Authentication failure': 'error', - 'Connected': 'info', - 'Disconnected': 'error', - 'Disconnecting': 'warn', - 'Attached': 'info', - 'Redirect': 'info', - 'Reconnecting': 'warn' - }; +const CONNECTION_STATUS_CSS_CLASS = { + 'Error': 'error', + 'Connecting': 'info', + 'Connection failure': 'error', + 'Authenticating': 'info', + 'Authentication failure': 'error', + 'Connected': 'info', + 'Disconnected': 'error', + 'Disconnecting': 'warn', + 'Attached': 'info', + 'Redirect': 'info', + 'Reconnecting': 'warn' +}; - const PRETTY_CONNECTION_STATUS = { - 0: 'Error', - 1: 'Connecting', - 2: 'Connection failure', - 3: 'Authenticating', - 4: 'Authentication failure', - 5: 'Connected', - 6: 'Disconnected', - 7: 'Disconnecting', - 8: 'Attached', - 9: 'Redirect', - 10: 'Reconnecting' - }; +const PRETTY_CONNECTION_STATUS = { + 0: 'Error', + 1: 'Connecting', + 2: 'Connection failure', + 3: 'Authenticating', + 4: 'Authentication failure', + 5: 'Connected', + 6: 'Disconnected', + 7: 'Disconnecting', + 8: 'Attached', + 9: 'Redirect', + 10: 'Reconnecting' +}; - const REPORTABLE_STATUSES = [ - 0, // ERROR' - 1, // CONNECTING - 2, // CONNFAIL - 3, // AUTHENTICATING - 4, // AUTHFAIL - 7, // DISCONNECTING - 10 // RECONNECTING - ]; +const REPORTABLE_STATUSES = [ + 0, // ERROR' + 1, // CONNECTING + 2, // CONNFAIL + 3, // AUTHENTICATING + 4, // AUTHFAIL + 7, // DISCONNECTING + 10 // RECONNECTING +]; - converse.plugins.add('converse-controlbox', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-modal", "converse-chatboxes", "converse-rosterview", "converse-chatview"], +converse.plugins.add('converse-controlbox', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-modal", "converse-chatboxes", "converse-rosterview", "converse-chatview"], - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. - tearDown () { - this.__super__.tearDown.apply(this, arguments); - if (this.rosterview) { - // Removes roster groups - this.rosterview.model.off().reset(); - this.rosterview.each(function (groupview) { - groupview.removeAll(); - groupview.remove(); - }); - this.rosterview.removeAll().remove(); - } + tearDown () { + this.__super__.tearDown.apply(this, arguments); + if (this.rosterview) { + // Removes roster groups + this.rosterview.model.off().reset(); + this.rosterview.each(function (groupview) { + groupview.removeAll(); + groupview.remove(); + }); + this.rosterview.removeAll().remove(); + } + }, + + ChatBoxes: { + chatBoxMayBeShown (chatbox) { + return this.__super__.chatBoxMayBeShown.apply(this, arguments) && + chatbox.get('id') !== 'controlbox'; }, + }, - ChatBoxes: { - chatBoxMayBeShown (chatbox) { - return this.__super__.chatBoxMayBeShown.apply(this, arguments) && - chatbox.get('id') !== 'controlbox'; - }, - }, - - ChatBoxViews: { - closeAllChatBoxes () { - const { _converse } = this.__super__; - this.each(function (view) { - if (view.model.get('id') === 'controlbox' && - (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) { - return; - } - view.close(); - }); - return this; - }, - - getChatBoxWidth (view) { - const { _converse } = this.__super__; - const controlbox = this.get('controlbox'); - if (view.model.get('id') === 'controlbox') { - /* We return the width of the controlbox or its toggle, - * depending on which is visible. - */ - if (!controlbox || !u.isVisible(controlbox.el)) { - return u.getOuterWidth(_converse.controlboxtoggle.el, true); - } else { - return u.getOuterWidth(controlbox.el, true); - } - } else { - return this.__super__.getChatBoxWidth.apply(this, arguments); + ChatBoxViews: { + closeAllChatBoxes () { + const { _converse } = this.__super__; + this.each(function (view) { + if (view.model.get('id') === 'controlbox' && + (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) { + return; } - } + view.close(); + }); + return this; }, - ChatBox: { - initialize () { - if (this.get('id') === 'controlbox') { - this.set({'time_opened': moment(0).valueOf()}); + getChatBoxWidth (view) { + const { _converse } = this.__super__; + const controlbox = this.get('controlbox'); + if (view.model.get('id') === 'controlbox') { + /* We return the width of the controlbox or its toggle, + * depending on which is visible. + */ + if (!controlbox || !u.isVisible(controlbox.el)) { + return u.getOuterWidth(_converse.controlboxtoggle.el, true); } else { - this.__super__.initialize.apply(this, arguments); + return u.getOuterWidth(controlbox.el, true); } - }, - }, - - ChatBoxView: { - insertIntoDOM () { - const view = this.__super__._converse.chatboxviews.get("controlbox"); - if (view) { - view.el.insertAdjacentElement('afterend', this.el) - } else { - this.__super__.insertIntoDOM.apply(this, arguments); - } - return this; + } else { + return this.__super__.getChatBoxWidth.apply(this, arguments); } } }, - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; + ChatBox: { + initialize () { + if (this.get('id') === 'controlbox') { + this.set({'time_opened': moment(0).valueOf()}); + } else { + this.__super__.initialize.apply(this, arguments); + } + }, + }, - _converse.api.settings.update({ - allow_logout: true, - default_domain: undefined, - locked_domain: undefined, - show_controlbox_by_default: false, - sticky_controlbox: false - }); - - _converse.api.promises.add('controlboxInitialized'); - - _converse.addControlBox = () => { - return _converse.chatboxes.add({ - 'id': 'controlbox', - 'box_id': 'controlbox', - 'type': _converse.CONTROLBOX_TYPE, - 'closed': !_converse.show_controlbox_by_default - }) + ChatBoxView: { + insertIntoDOM () { + const view = this.__super__._converse.chatboxviews.get("controlbox"); + if (view) { + view.el.insertAdjacentElement('afterend', this.el) + } else { + this.__super__.insertIntoDOM.apply(this, arguments); + } + return this; } + } + }, + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; + + _converse.api.settings.update({ + allow_logout: true, + default_domain: undefined, + locked_domain: undefined, + show_controlbox_by_default: false, + sticky_controlbox: false + }); + + _converse.api.promises.add('controlboxInitialized'); + + _converse.addControlBox = () => { + return _converse.chatboxes.add({ + 'id': 'controlbox', + 'box_id': 'controlbox', + 'type': _converse.CONTROLBOX_TYPE, + 'closed': !_converse.show_controlbox_by_default + }) + } - _converse.ControlBoxView = _converse.ChatBoxView.extend({ - tagName: 'div', - className: 'chatbox', - id: 'controlbox', - events: { - 'click a.close-chatbox-button': 'close' - }, + _converse.ControlBoxView = _converse.ChatBoxView.extend({ + tagName: 'div', + className: 'chatbox', + id: 'controlbox', + events: { + 'click a.close-chatbox-button': 'close' + }, - initialize () { - if (_.isUndefined(_converse.controlboxtoggle)) { - _converse.controlboxtoggle = new _converse.ControlBoxToggle(); + initialize () { + if (_.isUndefined(_converse.controlboxtoggle)) { + _converse.controlboxtoggle = new _converse.ControlBoxToggle(); + } + _converse.controlboxtoggle.el.insertAdjacentElement('afterend', this.el); + + this.model.on('change:connected', this.onConnected, this); + this.model.on('destroy', this.hide, this); + this.model.on('hide', this.hide, this); + this.model.on('show', this.show, this); + this.model.on('change:closed', this.ensureClosedState, this); + this.render(); + if (this.model.get('connected')) { + this.insertRoster(); + } + _converse.emit('controlboxInitialized', this); + }, + + render () { + if (this.model.get('connected')) { + if (_.isUndefined(this.model.get('closed'))) { + this.model.set('closed', !_converse.show_controlbox_by_default); } - _converse.controlboxtoggle.el.insertAdjacentElement('afterend', this.el); + } + this.el.innerHTML = tpl_controlbox(_.extend(this.model.toJSON())); - this.model.on('change:connected', this.onConnected, this); - this.model.on('destroy', this.hide, this); - this.model.on('hide', this.hide, this); - this.model.on('show', this.show, this); - this.model.on('change:closed', this.ensureClosedState, this); + if (!this.model.get('closed')) { + this.show(); + } else { + this.hide(); + } + if (!_converse.connection.connected || + !_converse.connection.authenticated || + _converse.connection.disconnecting) { + this.renderLoginPanel(); + } else if (this.model.get('connected') && + (!this.controlbox_pane || !u.isVisible(this.controlbox_pane.el))) { + this.renderControlBoxPane(); + } + return this; + }, + + onConnected () { + if (this.model.get('connected')) { this.render(); - if (this.model.get('connected')) { - this.insertRoster(); - } - _converse.emit('controlboxInitialized', this); - }, + this.insertRoster(); + } + }, - render () { - if (this.model.get('connected')) { - if (_.isUndefined(this.model.get('closed'))) { - this.model.set('closed', !_converse.show_controlbox_by_default); - } - } - this.el.innerHTML = tpl_controlbox(_.extend(this.model.toJSON())); - - if (!this.model.get('closed')) { - this.show(); - } else { - this.hide(); - } - if (!_converse.connection.connected || - !_converse.connection.authenticated || - _converse.connection.disconnecting) { - this.renderLoginPanel(); - } else if (this.model.get('connected') && - (!this.controlbox_pane || !u.isVisible(this.controlbox_pane.el))) { - this.renderControlBoxPane(); - } - return this; - }, - - onConnected () { - if (this.model.get('connected')) { - this.render(); - this.insertRoster(); - } - }, - - insertRoster () { - if (_converse.authentication === _converse.ANONYMOUS) { - return; - } - /* Place the rosterview inside the "Contacts" panel. */ - _converse.api.waitUntil('rosterViewInitialized') - .then(() => this.controlbox_pane.el.insertAdjacentElement('beforeEnd', _converse.rosterview.el)) - .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - createBrandHeadingHTML () { - return tpl_brand_heading({ - 'sticky_controlbox': _converse.sticky_controlbox - }); - }, - - insertBrandHeading () { - const heading_el = this.el.querySelector('.brand-heading-container'); - if (_.isNull(heading_el)) { - const el = this.el.querySelector('.controlbox-head'); - el.insertAdjacentHTML('beforeend', this.createBrandHeadingHTML()); - } else { - heading_el.outerHTML = this.createBrandHeadingHTML(); - } - }, - - renderLoginPanel () { - this.el.classList.add("logged-out"); - if (_.isNil(this.loginpanel)) { - this.loginpanel = new _converse.LoginPanel({ - 'model': new _converse.LoginPanelModel() - }); - const panes = this.el.querySelector('.controlbox-panes'); - panes.innerHTML = ''; - panes.appendChild(this.loginpanel.render().el); - this.insertBrandHeading(); - } else { - this.loginpanel.render(); - } - this.loginpanel.initPopovers(); - return this; - }, - - renderControlBoxPane () { - /* Renders the "Contacts" panel of the controlbox. - * - * This will only be called after the user has already been - * logged in. - */ - if (this.loginpanel) { - this.loginpanel.remove(); - delete this.loginpanel; - } - this.el.classList.remove("logged-out"); - this.controlbox_pane = new _converse.ControlBoxPane(); - this.el.querySelector('.controlbox-panes').insertAdjacentElement( - 'afterBegin', - this.controlbox_pane.el - ) - }, - - close (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - if (_converse.sticky_controlbox) { - return; - } - if (_converse.connection.connected && !_converse.connection.disconnecting) { - this.model.save({'closed': true}); - } else { - this.model.trigger('hide'); - } - _converse.emit('controlBoxClosed', this); - return this; - }, - - ensureClosedState () { - if (this.model.get('closed')) { - this.hide(); - } else { - this.show(); - } - }, - - hide (callback) { - if (_converse.sticky_controlbox) { - return; - } - u.addClass('hidden', this.el); - _converse.emit('chatBoxClosed', this); - if (!_converse.connection.connected) { - _converse.controlboxtoggle.render(); - } - _converse.controlboxtoggle.show(callback); - return this; - }, - - onControlBoxToggleHidden () { - this.model.set('closed', false); - this.el.classList.remove('hidden'); - _converse.emit('controlBoxOpened', this); - }, - - show () { - _converse.controlboxtoggle.hide( - this.onControlBoxToggleHidden.bind(this) - ); - return this; - }, - - showHelpMessages () { - /* Override showHelpMessages in ChatBoxView, for now do nothing. - * - * Parameters: - * (Array) msgs: Array of messages - */ + insertRoster () { + if (_converse.authentication === _converse.ANONYMOUS) { return; } - }); + /* Place the rosterview inside the "Contacts" panel. */ + _converse.api.waitUntil('rosterViewInitialized') + .then(() => this.controlbox_pane.el.insertAdjacentElement('beforeEnd', _converse.rosterview.el)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, - _converse.LoginPanelModel = Backbone.Model.extend({ - defaults: { - // Passed-by-reference. Fine in this case because there's - // only one such model. - 'errors': [], - } - }); - - _converse.LoginPanel = Backbone.VDOMView.extend({ - tagName: 'div', - id: "converse-login-panel", - className: 'controlbox-pane fade-in', - events: { - 'submit form#converse-login': 'authenticate', - 'change input': 'validate' - }, - - initialize (cfg) { - this.model.on('change', this.render, this); - this.listenTo(_converse.connfeedback, 'change', this.render); - this.render(); - }, - - toHTML () { - const connection_status = _converse.connfeedback.get('connection_status'); - let feedback_class, pretty_status; - if (_.includes(REPORTABLE_STATUSES, connection_status)) { - pretty_status = PRETTY_CONNECTION_STATUS[connection_status]; - feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status]; - } - return tpl_login_panel( - _.extend(this.model.toJSON(), { - '__': __, - '_converse': _converse, - 'ANONYMOUS': _converse.ANONYMOUS, - 'EXTERNAL': _converse.EXTERNAL, - 'LOGIN': _converse.LOGIN, - 'PREBIND': _converse.PREBIND, - 'auto_login': _converse.auto_login, - 'authentication': _converse.authentication, - 'connection_status': connection_status, - 'conn_feedback_class': feedback_class, - 'conn_feedback_subject': pretty_status, - 'conn_feedback_message': _converse.connfeedback.get('message'), - 'placeholder_username': (_converse.locked_domain || _converse.default_domain) && - __('Username') || __('user@domain'), - }) - ); - }, - - initPopovers () { - _.forEach(this.el.querySelectorAll('[data-title]'), el => { - const popover = new bootstrap.Popover(el, { - 'trigger': _converse.view_mode === 'mobile' && 'click' || 'hover', - 'dismissible': _converse.view_mode === 'mobile' && true || false, - 'container': this.el.parentElement.parentElement.parentElement - }) - }); - }, - - validate () { - const form = this.el.querySelector('form'); - const jid_element = form.querySelector('input[name=jid]'); - if (jid_element.value && - !_converse.locked_domain && - !_converse.default_domain && - !u.isValidJID(jid_element.value)) { - jid_element.setCustomValidity(__('Please enter a valid XMPP address')); - return false; - } - jid_element.setCustomValidity(''); - return true; - }, - - authenticate (ev) { - /* Authenticate the user based on a form submission event. - */ - if (ev && ev.preventDefault) { ev.preventDefault(); } - if (_converse.authentication === _converse.ANONYMOUS) { - this.connect(_converse.jid, null); - return; - } - if (!this.validate()) { return; } - - const form_data = new FormData(ev.target); - _converse.config.save({ - 'trusted': form_data.get('trusted') && true || false, - 'storage': form_data.get('trusted') ? 'local' : 'session' - }); - - let jid = form_data.get('jid'); - if (_converse.locked_domain) { - const last_part = '@' + _converse.locked_domain; - if (jid.endsWith(last_part)) { - jid = jid.substr(0, jid.length - last_part.length); - } - jid = Strophe.escapeNode(jid) + last_part; - } else if (_converse.default_domain && !_.includes(jid, '@')) { - jid = jid + '@' + _converse.default_domain; - } - this.connect(jid, form_data.get('password')); - }, - - connect (jid, password) { - if (jid) { - const resource = Strophe.getResourceFromJid(jid); - if (!resource) { - jid = jid.toLowerCase() + _converse.generateResource(); - } else { - jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource; - } - } - if (_.includes(["converse/login", "converse/register"], - Backbone.history.getFragment())) { - _converse.router.navigate('', {'replace': true}); - } - _converse.connection.reset(); - _converse.connection.connect(jid, password, _converse.onConnectStatusChanged); - } - }); - - - _converse.ControlBoxPane = Backbone.NativeView.extend({ - tagName: 'div', - className: 'controlbox-pane', - - initialize () { - _converse.xmppstatusview = new _converse.XMPPStatusView({ - 'model': _converse.xmppstatus - }); - this.el.insertAdjacentElement( - 'afterBegin', - _converse.xmppstatusview.render().el - ); - } - }); - - - _converse.ControlBoxToggle = Backbone.NativeView.extend({ - tagName: 'a', - className: 'toggle-controlbox hidden', - id: 'toggle-controlbox', - events: { - 'click': 'onClick' - }, - attributes: { - 'href': "#" - }, - - initialize () { - _converse.chatboxviews.insertRowColumn(this.render().el); - _converse.api.waitUntil('initialized') - .then(this.render.bind(this)) - .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - render () { - // We let the render method of ControlBoxView decide whether - // the ControlBox or the Toggle must be shown. This prevents - // artifacts (i.e. on page load the toggle is shown only to then - // seconds later be hidden in favor of the control box). - this.el.innerHTML = tpl_controlbox_toggle({ - 'label_toggle': _converse.connection.connected ? __('Chat Contacts') : __('Toggle chat') - }) - return this; - }, - - hide (callback) { - u.hideElement(this.el); - callback(); - }, - - show (callback) { - u.fadeIn(this.el, callback); - }, - - showControlBox () { - let controlbox = _converse.chatboxes.get('controlbox'); - if (!controlbox) { - controlbox = _converse.addControlBox(); - } - if (_converse.connection.connected) { - controlbox.save({closed: false}); - } else { - controlbox.trigger('show'); - } - }, - - onClick (e) { - e.preventDefault(); - if (u.isVisible(_converse.root.querySelector("#controlbox"))) { - const controlbox = _converse.chatboxes.get('controlbox'); - if (_converse.connection.connected) { - controlbox.save({closed: true}); - } else { - controlbox.trigger('hide'); - } - } else { - this.showControlBox(); - } - } - }); - - _converse.on('chatBoxViewsInitialized', () => { - const that = _converse.chatboxviews; - _converse.chatboxes.on('add', item => { - if (item.get('type') === _converse.CONTROLBOX_TYPE) { - const view = that.get(item.get('id')); - if (view) { - view.model = item; - view.initialize(); - } else { - that.add(item.get('id'), new _converse.ControlBoxView({model: item})); - } - } + createBrandHeadingHTML () { + return tpl_brand_heading({ + 'sticky_controlbox': _converse.sticky_controlbox }); - }); + }, - _converse.on('clearSession', () => { - if (_converse.config.get('trusted')) { - const chatboxes = _.get(_converse, 'chatboxes', null); - if (!_.isNil(chatboxes)) { - const controlbox = chatboxes.get('controlbox'); - if (controlbox && - controlbox.collection && - controlbox.collection.browserStorage) { - controlbox.save({'connected': false}); - } + insertBrandHeading () { + const heading_el = this.el.querySelector('.brand-heading-container'); + if (_.isNull(heading_el)) { + const el = this.el.querySelector('.controlbox-head'); + el.insertAdjacentHTML('beforeend', this.createBrandHeadingHTML()); + } else { + heading_el.outerHTML = this.createBrandHeadingHTML(); + } + }, + + renderLoginPanel () { + this.el.classList.add("logged-out"); + if (_.isNil(this.loginpanel)) { + this.loginpanel = new _converse.LoginPanel({ + 'model': new _converse.LoginPanelModel() + }); + const panes = this.el.querySelector('.controlbox-panes'); + panes.innerHTML = ''; + panes.appendChild(this.loginpanel.render().el); + this.insertBrandHeading(); + } else { + this.loginpanel.render(); + } + this.loginpanel.initPopovers(); + return this; + }, + + renderControlBoxPane () { + /* Renders the "Contacts" panel of the controlbox. + * + * This will only be called after the user has already been + * logged in. + */ + if (this.loginpanel) { + this.loginpanel.remove(); + delete this.loginpanel; + } + this.el.classList.remove("logged-out"); + this.controlbox_pane = new _converse.ControlBoxPane(); + this.el.querySelector('.controlbox-panes').insertAdjacentElement( + 'afterBegin', + this.controlbox_pane.el + ) + }, + + close (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + if (_converse.sticky_controlbox) { + return; + } + if (_converse.connection.connected && !_converse.connection.disconnecting) { + this.model.save({'closed': true}); + } else { + this.model.trigger('hide'); + } + _converse.emit('controlBoxClosed', this); + return this; + }, + + ensureClosedState () { + if (this.model.get('closed')) { + this.hide(); + } else { + this.show(); + } + }, + + hide (callback) { + if (_converse.sticky_controlbox) { + return; + } + u.addClass('hidden', this.el); + _converse.emit('chatBoxClosed', this); + if (!_converse.connection.connected) { + _converse.controlboxtoggle.render(); + } + _converse.controlboxtoggle.show(callback); + return this; + }, + + onControlBoxToggleHidden () { + this.model.set('closed', false); + this.el.classList.remove('hidden'); + _converse.emit('controlBoxOpened', this); + }, + + show () { + _converse.controlboxtoggle.hide( + this.onControlBoxToggleHidden.bind(this) + ); + return this; + }, + + showHelpMessages () { + /* Override showHelpMessages in ChatBoxView, for now do nothing. + * + * Parameters: + * (Array) msgs: Array of messages + */ + return; + } + }); + + _converse.LoginPanelModel = Backbone.Model.extend({ + defaults: { + // Passed-by-reference. Fine in this case because there's + // only one such model. + 'errors': [], + } + }); + + _converse.LoginPanel = Backbone.VDOMView.extend({ + tagName: 'div', + id: "converse-login-panel", + className: 'controlbox-pane fade-in', + events: { + 'submit form#converse-login': 'authenticate', + 'change input': 'validate' + }, + + initialize (cfg) { + this.model.on('change', this.render, this); + this.listenTo(_converse.connfeedback, 'change', this.render); + this.render(); + }, + + toHTML () { + const connection_status = _converse.connfeedback.get('connection_status'); + let feedback_class, pretty_status; + if (_.includes(REPORTABLE_STATUSES, connection_status)) { + pretty_status = PRETTY_CONNECTION_STATUS[connection_status]; + feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status]; + } + return tpl_login_panel( + _.extend(this.model.toJSON(), { + '__': __, + '_converse': _converse, + 'ANONYMOUS': _converse.ANONYMOUS, + 'EXTERNAL': _converse.EXTERNAL, + 'LOGIN': _converse.LOGIN, + 'PREBIND': _converse.PREBIND, + 'auto_login': _converse.auto_login, + 'authentication': _converse.authentication, + 'connection_status': connection_status, + 'conn_feedback_class': feedback_class, + 'conn_feedback_subject': pretty_status, + 'conn_feedback_message': _converse.connfeedback.get('message'), + 'placeholder_username': (_converse.locked_domain || _converse.default_domain) && + __('Username') || __('user@domain'), + }) + ); + }, + + initPopovers () { + _.forEach(this.el.querySelectorAll('[data-title]'), el => { + const popover = new bootstrap.Popover(el, { + 'trigger': _converse.view_mode === 'mobile' && 'click' || 'hover', + 'dismissible': _converse.view_mode === 'mobile' && true || false, + 'container': this.el.parentElement.parentElement.parentElement + }) + }); + }, + + validate () { + const form = this.el.querySelector('form'); + const jid_element = form.querySelector('input[name=jid]'); + if (jid_element.value && + !_converse.locked_domain && + !_converse.default_domain && + !u.isValidJID(jid_element.value)) { + jid_element.setCustomValidity(__('Please enter a valid XMPP address')); + return false; + } + jid_element.setCustomValidity(''); + return true; + }, + + authenticate (ev) { + /* Authenticate the user based on a form submission event. + */ + if (ev && ev.preventDefault) { ev.preventDefault(); } + if (_converse.authentication === _converse.ANONYMOUS) { + this.connect(_converse.jid, null); + return; + } + if (!this.validate()) { return; } + + const form_data = new FormData(ev.target); + _converse.config.save({ + 'trusted': form_data.get('trusted') && true || false, + 'storage': form_data.get('trusted') ? 'local' : 'session' + }); + + let jid = form_data.get('jid'); + if (_converse.locked_domain) { + const last_part = '@' + _converse.locked_domain; + if (jid.endsWith(last_part)) { + jid = jid.substr(0, jid.length - last_part.length); + } + jid = Strophe.escapeNode(jid) + last_part; + } else if (_converse.default_domain && !_.includes(jid, '@')) { + jid = jid + '@' + _converse.default_domain; + } + this.connect(jid, form_data.get('password')); + }, + + connect (jid, password) { + if (jid) { + const resource = Strophe.getResourceFromJid(jid); + if (!resource) { + jid = jid.toLowerCase() + _converse.generateResource(); + } else { + jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource; + } + } + if (_.includes(["converse/login", "converse/register"], + Backbone.history.getFragment())) { + _converse.router.navigate('', {'replace': true}); + } + _converse.connection.reset(); + _converse.connection.connect(jid, password, _converse.onConnectStatusChanged); + } + }); + + + _converse.ControlBoxPane = Backbone.NativeView.extend({ + tagName: 'div', + className: 'controlbox-pane', + + initialize () { + _converse.xmppstatusview = new _converse.XMPPStatusView({ + 'model': _converse.xmppstatus + }); + this.el.insertAdjacentElement( + 'afterBegin', + _converse.xmppstatusview.render().el + ); + } + }); + + + _converse.ControlBoxToggle = Backbone.NativeView.extend({ + tagName: 'a', + className: 'toggle-controlbox hidden', + id: 'toggle-controlbox', + events: { + 'click': 'onClick' + }, + attributes: { + 'href': "#" + }, + + initialize () { + _converse.chatboxviews.insertRowColumn(this.render().el); + _converse.api.waitUntil('initialized') + .then(this.render.bind(this)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, + + render () { + // We let the render method of ControlBoxView decide whether + // the ControlBox or the Toggle must be shown. This prevents + // artifacts (i.e. on page load the toggle is shown only to then + // seconds later be hidden in favor of the control box). + this.el.innerHTML = tpl_controlbox_toggle({ + 'label_toggle': _converse.connection.connected ? __('Chat Contacts') : __('Toggle chat') + }) + return this; + }, + + hide (callback) { + u.hideElement(this.el); + callback(); + }, + + show (callback) { + u.fadeIn(this.el, callback); + }, + + showControlBox () { + let controlbox = _converse.chatboxes.get('controlbox'); + if (!controlbox) { + controlbox = _converse.addControlBox(); + } + if (_converse.connection.connected) { + controlbox.save({closed: false}); + } else { + controlbox.trigger('show'); + } + }, + + onClick (e) { + e.preventDefault(); + if (u.isVisible(_converse.root.querySelector("#controlbox"))) { + const controlbox = _converse.chatboxes.get('controlbox'); + if (_converse.connection.connected) { + controlbox.save({closed: true}); + } else { + controlbox.trigger('hide'); + } + } else { + this.showControlBox(); + } + } + }); + + _converse.on('chatBoxViewsInitialized', () => { + const that = _converse.chatboxviews; + _converse.chatboxes.on('add', item => { + if (item.get('type') === _converse.CONTROLBOX_TYPE) { + const view = that.get(item.get('id')); + if (view) { + view.model = item; + view.initialize(); + } else { + that.add(item.get('id'), new _converse.ControlBoxView({model: item})); } } }); + }); - Promise.all([ - _converse.api.waitUntil('connectionInitialized'), - _converse.api.waitUntil('chatBoxViewsInitialized') - ]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + _converse.on('clearSession', () => { + if (_converse.config.get('trusted')) { + const chatboxes = _.get(_converse, 'chatboxes', null); + if (!_.isNil(chatboxes)) { + const controlbox = chatboxes.get('controlbox'); + if (controlbox && + controlbox.collection && + controlbox.collection.browserStorage) { + controlbox.save({'connected': false}); + } + } + } + }); - _converse.on('chatBoxesFetched', () => { - const controlbox = _converse.chatboxes.get('controlbox') || _converse.addControlBox(); - controlbox.save({connected:true}); - }); + Promise.all([ + _converse.api.waitUntil('connectionInitialized'), + _converse.api.waitUntil('chatBoxViewsInitialized') + ]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - const disconnect = function () { - /* Upon disconnection, set connected to `false`, so that if - * we reconnect, "onConnected" will be called, - * to fetch the roster again and to send out a presence stanza. - */ - const view = _converse.chatboxviews.get('controlbox'); - view.model.set({'connected': false}); - return view; - }; - _converse.on('disconnected', () => disconnect().renderLoginPanel()); - _converse.on('will-reconnect', disconnect); - } - }); -})); + _converse.on('chatBoxesFetched', () => { + const controlbox = _converse.chatboxes.get('controlbox') || _converse.addControlBox(); + controlbox.save({connected:true}); + }); + + const disconnect = function () { + /* Upon disconnection, set connected to `false`, so that if + * we reconnect, "onConnected" will be called, + * to fetch the roster again and to send out a presence stanza. + */ + const view = _converse.chatboxviews.get('controlbox'); + view.model.set({'connected': false}); + return view; + }; + _converse.on('disconnected', () => disconnect().renderLoginPanel()); + _converse.on('will-reconnect', disconnect); + } +}); diff --git a/src/converse-dragresize.js b/src/converse-dragresize.js index 64942289b..9dac62493 100644 --- a/src/converse-dragresize.js +++ b/src/converse-dragresize.js @@ -6,366 +6,363 @@ // /*global define, window, document */ -(function (root, factory) { - define(["@converse/headless/converse-core", - "templates/dragresize.html", - "converse-chatview", - "converse-controlbox" - ], factory); -}(this, function (converse, tpl_dragresize) { - "use strict"; - const { _ } = converse.env; +import "converse-chatview"; +import "converse-controlbox"; +import converse from "@converse/headless/converse-core"; +import tpl_dragresize from "templates/dragresize.html"; - function renderDragResizeHandles (_converse, view) { - const flyout = view.el.querySelector('.box-flyout'); - const div = document.createElement('div'); - div.innerHTML = tpl_dragresize(); - flyout.insertBefore( - div, - flyout.firstChild - ); - } +const { _ } = converse.env; + +function renderDragResizeHandles (_converse, view) { + const flyout = view.el.querySelector('.box-flyout'); + const div = document.createElement('div'); + div.innerHTML = tpl_dragresize(); + flyout.insertBefore( + div, + flyout.firstChild + ); +} - converse.plugins.add('converse-dragresize', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatview", "converse-headline", "converse-muc-views"], +converse.plugins.add('converse-dragresize', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatview", "converse-headline", "converse-muc-views"], - enabled (_converse) { - return _converse.view_mode == 'overlayed'; + enabled (_converse) { + return _converse.view_mode == 'overlayed'; + }, + + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + + registerGlobalEventHandlers () { + const that = this; + + document.addEventListener('mousemove', function (ev) { + if (!that.resizing || !that.allow_dragresize) { return true; } + ev.preventDefault(); + that.resizing.chatbox.resizeChatBox(ev); + }); + + document.addEventListener('mouseup', function (ev) { + if (!that.resizing || !that.allow_dragresize) { return true; } + ev.preventDefault(); + const height = that.applyDragResistance( + that.resizing.chatbox.height, + that.resizing.chatbox.model.get('default_height') + ); + const width = that.applyDragResistance( + that.resizing.chatbox.width, + that.resizing.chatbox.model.get('default_width') + ); + if (that.connection.connected) { + that.resizing.chatbox.model.save({'height': height}); + that.resizing.chatbox.model.save({'width': width}); + } else { + that.resizing.chatbox.model.set({'height': height}); + that.resizing.chatbox.model.set({'width': width}); + } + that.resizing = null; + }); + + return this.__super__.registerGlobalEventHandlers.apply(this, arguments); }, - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - - registerGlobalEventHandlers () { - const that = this; - - document.addEventListener('mousemove', function (ev) { - if (!that.resizing || !that.allow_dragresize) { return true; } - ev.preventDefault(); - that.resizing.chatbox.resizeChatBox(ev); + ChatBox: { + initialize () { + const { _converse } = this.__super__; + const result = this.__super__.initialize.apply(this, arguments), + height = this.get('height'), width = this.get('width'), + save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this); + save({ + 'height': _converse.applyDragResistance(height, this.get('default_height')), + 'width': _converse.applyDragResistance(width, this.get('default_width')), }); + return result; + } + }, - document.addEventListener('mouseup', function (ev) { - if (!that.resizing || !that.allow_dragresize) { return true; } - ev.preventDefault(); - const height = that.applyDragResistance( - that.resizing.chatbox.height, - that.resizing.chatbox.model.get('default_height') - ); - const width = that.applyDragResistance( - that.resizing.chatbox.width, - that.resizing.chatbox.model.get('default_width') - ); - if (that.connection.connected) { - that.resizing.chatbox.model.save({'height': height}); - that.resizing.chatbox.model.save({'width': width}); - } else { - that.resizing.chatbox.model.set({'height': height}); - that.resizing.chatbox.model.set({'width': width}); - } - that.resizing = null; - }); - - return this.__super__.registerGlobalEventHandlers.apply(this, arguments); + ChatBoxView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' }, - ChatBox: { - initialize () { - const { _converse } = this.__super__; - const result = this.__super__.initialize.apply(this, arguments), - height = this.get('height'), width = this.get('width'), - save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this); - save({ - 'height': _converse.applyDragResistance(height, this.get('default_height')), - 'width': _converse.applyDragResistance(width, this.get('default_width')), - }); - return result; + initialize () { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + this.__super__.initialize.apply(this, arguments); + }, + + render () { + const result = this.__super__.render.apply(this, arguments); + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + }, + + setWidth () { + // If a custom width is applied (due to drag-resizing), + // then we need to set the width of the .chatbox element as well. + if (this.model.get('width')) { + this.el.style.width = this.model.get('width'); } }, - ChatBoxView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, + _show () { + this.initDragResize().setDimensions(); + this.__super__._show.apply(this, arguments); + }, - initialize () { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - this.__super__.initialize.apply(this, arguments); - }, + initDragResize () { + /* Determine and store the default box size. + * We need this information for the drag-resizing feature. + */ + const { _converse } = this.__super__, + flyout = this.el.querySelector('.box-flyout'), + style = window.getComputedStyle(flyout); - render () { - const result = this.__super__.render.apply(this, arguments); - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; - }, + if (_.isUndefined(this.model.get('height'))) { + const height = parseInt(style.height.replace(/px$/, ''), 10), + width = parseInt(style.width.replace(/px$/, ''), 10); + this.model.set('height', height); + this.model.set('default_height', height); + this.model.set('width', width); + this.model.set('default_width', width); + } + const min_width = style['min-width']; + const min_height = style['min-height']; + this.model.set('min_width', min_width.endsWith('px') ? Number(min_width.replace(/px$/, '')) :0); + this.model.set('min_height', min_height.endsWith('px') ? Number(min_height.replace(/px$/, '')) :0); + // Initialize last known mouse position + this.prev_pageY = 0; + this.prev_pageX = 0; + if (_converse.connection.connected) { + this.height = this.model.get('height'); + this.width = this.model.get('width'); + } + return this; + }, - setWidth () { - // If a custom width is applied (due to drag-resizing), - // then we need to set the width of the .chatbox element as well. - if (this.model.get('width')) { - this.el.style.width = this.model.get('width'); - } - }, + setDimensions () { + // Make sure the chat box has the right height and width. + this.adjustToViewport(); + this.setChatBoxHeight(this.model.get('height')); + this.setChatBoxWidth(this.model.get('width')); + }, - _show () { - this.initDragResize().setDimensions(); - this.__super__._show.apply(this, arguments); - }, - - initDragResize () { - /* Determine and store the default box size. - * We need this information for the drag-resizing feature. - */ - const { _converse } = this.__super__, - flyout = this.el.querySelector('.box-flyout'), - style = window.getComputedStyle(flyout); - - if (_.isUndefined(this.model.get('height'))) { - const height = parseInt(style.height.replace(/px$/, ''), 10), - width = parseInt(style.width.replace(/px$/, ''), 10); - this.model.set('height', height); - this.model.set('default_height', height); - this.model.set('width', width); - this.model.set('default_width', width); - } - const min_width = style['min-width']; - const min_height = style['min-height']; - this.model.set('min_width', min_width.endsWith('px') ? Number(min_width.replace(/px$/, '')) :0); - this.model.set('min_height', min_height.endsWith('px') ? Number(min_height.replace(/px$/, '')) :0); - // Initialize last known mouse position - this.prev_pageY = 0; - this.prev_pageX = 0; - if (_converse.connection.connected) { - this.height = this.model.get('height'); - this.width = this.model.get('width'); - } - return this; - }, - - setDimensions () { - // Make sure the chat box has the right height and width. - this.adjustToViewport(); - this.setChatBoxHeight(this.model.get('height')); - this.setChatBoxWidth(this.model.get('width')); - }, - - setChatBoxHeight (height) { - const { _converse } = this.__super__; - if (height) { - height = _converse.applyDragResistance(height, this.model.get('default_height'))+'px'; - } else { - height = ""; - } - const flyout_el = this.el.querySelector('.box-flyout'); - if (!_.isNull(flyout_el)) { - flyout_el.style.height = height; - } - }, - - setChatBoxWidth (width) { - const { _converse } = this.__super__; - if (width) { - width = _converse.applyDragResistance(width, this.model.get('default_width'))+'px'; - } else { - width = ""; - } - this.el.style.width = width; - const flyout_el = this.el.querySelector('.box-flyout'); - if (!_.isNull(flyout_el)) { - flyout_el.style.width = width; - } - }, - - adjustToViewport () { - /* Event handler called when viewport gets resized. We remove - * custom width/height from chat boxes. - */ - const viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - if (viewport_width <= 480) { - this.model.set('height', undefined); - this.model.set('width', undefined); - } else if (viewport_width <= this.model.get('width')) { - this.model.set('width', undefined); - } else if (viewport_height <= this.model.get('height')) { - this.model.set('height', undefined); - } - }, - - onStartVerticalResize (ev) { - const { _converse } = this.__super__; - if (!_converse.allow_dragresize) { return true; } - // Record element attributes for mouseMove(). - const flyout = this.el.querySelector('.box-flyout'), - style = window.getComputedStyle(flyout); - this.height = parseInt(style.height.replace(/px$/, ''), 10); - _converse.resizing = { - 'chatbox': this, - 'direction': 'top' - }; - this.prev_pageY = ev.pageY; - }, - - onStartHorizontalResize (ev) { - const { _converse } = this.__super__; - if (!_converse.allow_dragresize) { return true; } - const flyout = this.el.querySelector('.box-flyout'), - style = window.getComputedStyle(flyout); - this.width = parseInt(style.width.replace(/px$/, ''), 10); - _converse.resizing = { - 'chatbox': this, - 'direction': 'left' - }; - this.prev_pageX = ev.pageX; - }, - - onStartDiagonalResize (ev) { - const { _converse } = this.__super__; - this.onStartHorizontalResize(ev); - this.onStartVerticalResize(ev); - _converse.resizing.direction = 'topleft'; - }, - - resizeChatBox (ev) { - let diff; - const { _converse } = this.__super__; - if (_converse.resizing.direction.indexOf('top') === 0) { - diff = ev.pageY - this.prev_pageY; - if (diff) { - this.height = ((this.height-diff) > (this.model.get('min_height') || 0)) ? (this.height-diff) : this.model.get('min_height'); - this.prev_pageY = ev.pageY; - this.setChatBoxHeight(this.height); - } - } - if (_.includes(_converse.resizing.direction, 'left')) { - diff = this.prev_pageX - ev.pageX; - if (diff) { - this.width = ((this.width+diff) > (this.model.get('min_width') || 0)) ? (this.width+diff) : this.model.get('min_width'); - this.prev_pageX = ev.pageX; - this.setChatBoxWidth(this.width); - } - } + setChatBoxHeight (height) { + const { _converse } = this.__super__; + if (height) { + height = _converse.applyDragResistance(height, this.model.get('default_height'))+'px'; + } else { + height = ""; + } + const flyout_el = this.el.querySelector('.box-flyout'); + if (!_.isNull(flyout_el)) { + flyout_el.style.height = height; } }, - HeadlinesBoxView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, - - initialize () { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - return this.__super__.initialize.apply(this, arguments); - }, - - render () { - const result = this.__super__.render.apply(this, arguments); - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; + setChatBoxWidth (width) { + const { _converse } = this.__super__; + if (width) { + width = _converse.applyDragResistance(width, this.model.get('default_width'))+'px'; + } else { + width = ""; + } + this.el.style.width = width; + const flyout_el = this.el.querySelector('.box-flyout'); + if (!_.isNull(flyout_el)) { + flyout_el.style.width = width; } }, - ControlBoxView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, - - initialize () { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - this.__super__.initialize.apply(this, arguments); - }, - - render () { - const result = this.__super__.render.apply(this, arguments); - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; - }, - - renderLoginPanel () { - const result = this.__super__.renderLoginPanel.apply(this, arguments); - this.initDragResize().setDimensions(); - return result; - }, - - renderControlBoxPane () { - const result = this.__super__.renderControlBoxPane.apply(this, arguments); - this.initDragResize().setDimensions(); - return result; + adjustToViewport () { + /* Event handler called when viewport gets resized. We remove + * custom width/height from chat boxes. + */ + const viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + if (viewport_width <= 480) { + this.model.set('height', undefined); + this.model.set('width', undefined); + } else if (viewport_width <= this.model.get('width')) { + this.model.set('width', undefined); + } else if (viewport_height <= this.model.get('height')) { + this.model.set('height', undefined); } }, - ChatRoomView: { - events: { - 'mousedown .dragresize-top': 'onStartVerticalResize', - 'mousedown .dragresize-left': 'onStartHorizontalResize', - 'mousedown .dragresize-topleft': 'onStartDiagonalResize' - }, + onStartVerticalResize (ev) { + const { _converse } = this.__super__; + if (!_converse.allow_dragresize) { return true; } + // Record element attributes for mouseMove(). + const flyout = this.el.querySelector('.box-flyout'), + style = window.getComputedStyle(flyout); + this.height = parseInt(style.height.replace(/px$/, ''), 10); + _converse.resizing = { + 'chatbox': this, + 'direction': 'top' + }; + this.prev_pageY = ev.pageY; + }, - initialize () { - window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); - this.__super__.initialize.apply(this, arguments); - }, + onStartHorizontalResize (ev) { + const { _converse } = this.__super__; + if (!_converse.allow_dragresize) { return true; } + const flyout = this.el.querySelector('.box-flyout'), + style = window.getComputedStyle(flyout); + this.width = parseInt(style.width.replace(/px$/, ''), 10); + _converse.resizing = { + 'chatbox': this, + 'direction': 'left' + }; + this.prev_pageX = ev.pageX; + }, - render () { - const result = this.__super__.render.apply(this, arguments); - renderDragResizeHandles(this.__super__._converse, this); - this.setWidth(); - return result; + onStartDiagonalResize (ev) { + const { _converse } = this.__super__; + this.onStartHorizontalResize(ev); + this.onStartVerticalResize(ev); + _converse.resizing.direction = 'topleft'; + }, + + resizeChatBox (ev) { + let diff; + const { _converse } = this.__super__; + if (_converse.resizing.direction.indexOf('top') === 0) { + diff = ev.pageY - this.prev_pageY; + if (diff) { + this.height = ((this.height-diff) > (this.model.get('min_height') || 0)) ? (this.height-diff) : this.model.get('min_height'); + this.prev_pageY = ev.pageY; + this.setChatBoxHeight(this.height); + } + } + if (_.includes(_converse.resizing.direction, 'left')) { + diff = this.prev_pageX - ev.pageX; + if (diff) { + this.width = ((this.width+diff) > (this.model.get('min_width') || 0)) ? (this.width+diff) : this.model.get('min_width'); + this.prev_pageX = ev.pageX; + this.setChatBoxWidth(this.width); + } } } }, - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this; + HeadlinesBoxView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, - _converse.api.settings.update({ - allow_dragresize: true, - }); + initialize () { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + return this.__super__.initialize.apply(this, arguments); + }, - _converse.applyDragResistance = function (value, default_value) { - /* This method applies some resistance around the - * default_value. If value is close enough to - * default_value, then default_value is returned instead. - */ - if (_.isUndefined(value)) { - return undefined; - } else if (_.isUndefined(default_value)) { - return value; - } - const resistance = 10; - if ((value !== default_value) && - (Math.abs(value- default_value) < resistance)) { - return default_value; - } - return value; - }; + render () { + const result = this.__super__.render.apply(this, arguments); + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + } + }, + + ControlBoxView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, + + initialize () { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + this.__super__.initialize.apply(this, arguments); + }, + + render () { + const result = this.__super__.render.apply(this, arguments); + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + }, + + renderLoginPanel () { + const result = this.__super__.renderLoginPanel.apply(this, arguments); + this.initDragResize().setDimensions(); + return result; + }, + + renderControlBoxPane () { + const result = this.__super__.renderControlBoxPane.apply(this, arguments); + this.initDragResize().setDimensions(); + return result; + } + }, + + ChatRoomView: { + events: { + 'mousedown .dragresize-top': 'onStartVerticalResize', + 'mousedown .dragresize-left': 'onStartHorizontalResize', + 'mousedown .dragresize-topleft': 'onStartDiagonalResize' + }, + + initialize () { + window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); + this.__super__.initialize.apply(this, arguments); + }, + + render () { + const result = this.__super__.render.apply(this, arguments); + renderDragResizeHandles(this.__super__._converse, this); + this.setWidth(); + return result; + } } - }); -})); + }, + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this; + + _converse.api.settings.update({ + allow_dragresize: true, + }); + + _converse.applyDragResistance = function (value, default_value) { + /* This method applies some resistance around the + * default_value. If value is close enough to + * default_value, then default_value is returned instead. + */ + if (_.isUndefined(value)) { + return undefined; + } else if (_.isUndefined(default_value)) { + return value; + } + const resistance = 10; + if ((value !== default_value) && + (Math.abs(value- default_value) < resistance)) { + return default_value; + } + return value; + }; + } +}); + diff --git a/src/converse-embedded.js b/src/converse-embedded.js index 14a765542..a95657546 100644 --- a/src/converse-embedded.js +++ b/src/converse-embedded.js @@ -1,40 +1,38 @@ // Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - define(["@converse/headless/converse-core", "@converse/headless/converse-muc"], factory); -}(this, function (converse) { - "use strict"; - const { Backbone, _ } = converse.env; +import "@converse/headless/converse-muc"; +import converse from "@converse/headless/converse-core"; - converse.plugins.add('converse-embedded', { +const { Backbone, _ } = converse.env; - enabled (_converse) { - return _converse.view_mode === 'embedded'; - }, +converse.plugins.add('converse-embedded', { - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - this._converse.api.settings.update({ - 'allow_logout': false, // No point in logging out when we have auto_login as true. - 'allow_muc_invitations': false, // Doesn't make sense to allow because only - // roster contacts can be invited - 'hide_muc_server': true - }); - const { _converse } = this; - if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) { - throw new Error("converse-embedded: auto_join_rooms must be an Array"); - } - if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) { - throw new Error("converse-embedded: It doesn't make "+ - "sense to have the auto_join_rooms setting more then one, "+ - "since only one chat room can be open at any time."); - } + enabled (_converse) { + return _converse.view_mode === 'embedded'; + }, + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + this._converse.api.settings.update({ + 'allow_logout': false, // No point in logging out when we have auto_login as true. + 'allow_muc_invitations': false, // Doesn't make sense to allow because only + // roster contacts can be invited + 'hide_muc_server': true + }); + const { _converse } = this; + if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) { + throw new Error("converse-embedded: auto_join_rooms must be an Array"); } - }); -})); + if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) { + throw new Error("converse-embedded: It doesn't make "+ + "sense to have the auto_join_rooms setting more then one, "+ + "since only one chat room can be open at any time."); + } + } +}); diff --git a/src/converse-fullscreen.js b/src/converse-fullscreen.js index 571541ab5..8bd999209 100644 --- a/src/converse-fullscreen.js +++ b/src/converse-fullscreen.js @@ -4,57 +4,52 @@ // Copyright (c) JC Brand // Licensed under the Mozilla Public License (MPLv2) // -/*global define */ -(function (root, factory) { - define(["@converse/headless/converse-core", - "templates/inverse_brand_heading.html", - "converse-chatview", - "converse-controlbox", - "@converse/headless/converse-muc", - "converse-singleton" - ], factory); -}(this, function (converse, tpl_brand_heading) { - "use strict"; - const { Strophe, _ } = converse.env; +import "@converse/headless/converse-muc"; +import "converse-chatview"; +import "converse-controlbox"; +import "converse-singleton"; +import converse from "@converse/headless/converse-core"; +import tpl_brand_heading from "templates/inverse_brand_heading.html"; - converse.plugins.add('converse-fullscreen', { +const { Strophe, _ } = converse.env; - enabled (_converse) { - return _.includes(['fullscreen', 'embedded'], _converse.view_mode); - }, +converse.plugins.add('converse-fullscreen', { - overrides: { - // overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // new functions which don't exist yet can also be added. + enabled (_converse) { + return _.includes(['fullscreen', 'embedded'], _converse.view_mode); + }, - ControlBoxView: { - createBrandHeadingHTML() { - return tpl_brand_heading(); - }, + overrides: { + // overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // new functions which don't exist yet can also be added. - insertBrandHeading () { - const { _converse } = this.__super__; - const el = _converse.root.getElementById('converse-login-panel'); - el.parentNode.insertAdjacentHTML( - 'afterbegin', - this.createBrandHeadingHTML() - ); - } + ControlBoxView: { + createBrandHeadingHTML() { + return tpl_brand_heading(); + }, + + insertBrandHeading () { + const { _converse } = this.__super__; + const el = _converse.root.getElementById('converse-login-panel'); + el.parentNode.insertAdjacentHTML( + 'afterbegin', + this.createBrandHeadingHTML() + ); } - }, - - initialize () { - this._converse.api.settings.update({ - chatview_avatar_height: 50, - chatview_avatar_width: 50, - hide_open_bookmarks: true, - show_controlbox_by_default: true, - sticky_controlbox: true - }); } - }); -})); + }, + + initialize () { + this._converse.api.settings.update({ + chatview_avatar_height: 50, + chatview_avatar_width: 50, + hide_open_bookmarks: true, + show_controlbox_by_default: true, + sticky_controlbox: true + }); + } +}); diff --git a/src/converse-headline.js b/src/converse-headline.js index 988bd1f7e..77add00fb 100644 --- a/src/converse-headline.js +++ b/src/converse-headline.js @@ -3,154 +3,148 @@ // // Copyright (c) 2012-2017, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) -// -/*global define */ -(function (root, factory) { - define([ - "@converse/headless/converse-core", - "templates/chatbox.html", - "converse-chatview", - ], factory); -}(this, function (converse, tpl_chatbox) { - "use strict"; - const { _, utils } = converse.env; +import "converse-chatview"; +import converse from "@converse/headless/converse-core"; +import tpl_chatbox from "templates/chatbox.html"; - converse.plugins.add('converse-headline', { - /* Plugin dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. - * - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. By default it's - * false, which means these plugins are only loaded opportunistically. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatview"], - - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - - ChatBoxes: { - model (attrs, options) { - const { _converse } = this.__super__; - if (attrs.type == _converse.HEADLINES_TYPE) { - return new _converse.HeadlinesBox(attrs, options); - } else { - return this.__super__.model.apply(this, arguments); - } - }, - } - }, +const { _, utils } = converse.env; - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; +converse.plugins.add('converse-headline', { + /* Plugin dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. + * + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. By default it's + * false, which means these plugins are only loaded opportunistically. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatview"], - _converse.HeadlinesBox = _converse.ChatBox.extend({ - defaults: { - 'type': _converse.HEADLINES_TYPE, - 'bookmarked': false, - 'chat_state': undefined, - 'num_unread': 0, - 'url': '' - }, - }); + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. - - _converse.HeadlinesBoxView = _converse.ChatBoxView.extend({ - className: 'chatbox headlines', - - events: { - 'click .close-chatbox-button': 'close', - 'click .toggle-chatbox-button': 'minimize', - 'keypress textarea.chat-textarea': 'keyPressed' - }, - - initialize () { - this.initDebounced(); - - this.disable_mam = true; // Don't do MAM queries for this box - this.model.messages.on('add', this.onMessageAdded, this); - this.model.on('show', this.show, this); - this.model.on('destroy', this.hide, this); - this.model.on('change:minimized', this.onMinimizedChanged, this); - - this.render().insertHeading().fetchMessages().insertIntoDOM().hide(); - _converse.emit('chatBoxOpened', this); - _converse.emit('chatBoxInitialized', this); - }, - - render () { - this.el.setAttribute('id', this.model.get('box_id')) - this.el.innerHTML = tpl_chatbox( - _.extend(this.model.toJSON(), { - info_close: '', - label_personal_message: '', - show_send_button: false, - show_toolbar: false, - unread_msgs: '' - } - )); - this.content = this.el.querySelector('.chat-content'); - return this; - }, - - // Override to avoid the methods in converse-chatview.js - 'renderMessageForm': _.noop, - 'afterShown': _.noop - }); - - function onHeadlineMessage (message) { - /* Handler method for all incoming messages of type "headline". */ - const from_jid = message.getAttribute('from'); - if (utils.isHeadlineMessage(_converse, message)) { - if (_.includes(from_jid, '@') && - !_converse.api.contacts.get(from_jid) && - !_converse.allow_non_roster_messaging) { - return; - } - if (_.isNull(message.querySelector('body'))) { - // Avoid creating a chat box if we have nothing to show - // inside it. - return; - } - const chatbox = _converse.chatboxes.create({ - 'id': from_jid, - 'jid': from_jid, - 'type': _converse.HEADLINES_TYPE, - 'from': from_jid - }); - chatbox.createMessage(message, message); - _converse.emit('message', {'chatbox': chatbox, 'stanza': message}); + ChatBoxes: { + model (attrs, options) { + const { _converse } = this.__super__; + if (attrs.type == _converse.HEADLINES_TYPE) { + return new _converse.HeadlinesBox(attrs, options); + } else { + return this.__super__.model.apply(this, arguments); } - return true; - } - - function registerHeadlineHandler () { - _converse.connection.addHandler(onHeadlineMessage, null, 'message'); - } - _converse.on('connected', registerHeadlineHandler); - _converse.on('reconnected', registerHeadlineHandler); - - - _converse.on('chatBoxViewsInitialized', () => { - const that = _converse.chatboxviews; - _converse.chatboxes.on('add', item => { - if (!that.get(item.get('id')) && item.get('type') === _converse.HEADLINES_TYPE) { - that.add(item.get('id'), new _converse.HeadlinesBoxView({model: item})); - } - }); - }); + }, } - }); -})); + }, + + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; + + _converse.HeadlinesBox = _converse.ChatBox.extend({ + defaults: { + 'type': _converse.HEADLINES_TYPE, + 'bookmarked': false, + 'chat_state': undefined, + 'num_unread': 0, + 'url': '' + }, + }); + + + _converse.HeadlinesBoxView = _converse.ChatBoxView.extend({ + className: 'chatbox headlines', + + events: { + 'click .close-chatbox-button': 'close', + 'click .toggle-chatbox-button': 'minimize', + 'keypress textarea.chat-textarea': 'keyPressed' + }, + + initialize () { + this.initDebounced(); + + this.disable_mam = true; // Don't do MAM queries for this box + this.model.messages.on('add', this.onMessageAdded, this); + this.model.on('show', this.show, this); + this.model.on('destroy', this.hide, this); + this.model.on('change:minimized', this.onMinimizedChanged, this); + + this.render().insertHeading().fetchMessages().insertIntoDOM().hide(); + _converse.emit('chatBoxOpened', this); + _converse.emit('chatBoxInitialized', this); + }, + + render () { + this.el.setAttribute('id', this.model.get('box_id')) + this.el.innerHTML = tpl_chatbox( + _.extend(this.model.toJSON(), { + info_close: '', + label_personal_message: '', + show_send_button: false, + show_toolbar: false, + unread_msgs: '' + } + )); + this.content = this.el.querySelector('.chat-content'); + return this; + }, + + // Override to avoid the methods in converse-chatview.js + 'renderMessageForm': _.noop, + 'afterShown': _.noop + }); + + function onHeadlineMessage (message) { + /* Handler method for all incoming messages of type "headline". */ + const from_jid = message.getAttribute('from'); + if (utils.isHeadlineMessage(_converse, message)) { + if (_.includes(from_jid, '@') && + !_converse.api.contacts.get(from_jid) && + !_converse.allow_non_roster_messaging) { + return; + } + if (_.isNull(message.querySelector('body'))) { + // Avoid creating a chat box if we have nothing to show + // inside it. + return; + } + const chatbox = _converse.chatboxes.create({ + 'id': from_jid, + 'jid': from_jid, + 'type': _converse.HEADLINES_TYPE, + 'from': from_jid + }); + chatbox.createMessage(message, message); + _converse.emit('message', {'chatbox': chatbox, 'stanza': message}); + } + return true; + } + + function registerHeadlineHandler () { + _converse.connection.addHandler(onHeadlineMessage, null, 'message'); + } + _converse.on('connected', registerHeadlineHandler); + _converse.on('reconnected', registerHeadlineHandler); + + + _converse.on('chatBoxViewsInitialized', () => { + const that = _converse.chatboxviews; + _converse.chatboxes.on('add', item => { + if (!that.get(item.get('id')) && item.get('type') === _converse.HEADLINES_TYPE) { + that.add(item.get('id'), new _converse.HeadlinesBoxView({model: item})); + } + }); + }); + } +}); diff --git a/src/converse-message-view.js b/src/converse-message-view.js index 9bdf2b0f6..93bf6a9be 100644 --- a/src/converse-message-view.js +++ b/src/converse-message-view.js @@ -4,273 +4,256 @@ // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - define([ - "./utils/html", - "utils/emoji", - "@converse/headless/converse-core", - "xss", - "filesize", - "templates/csn.html", - "templates/file_progress.html", - "templates/info.html", - "templates/message.html", - "templates/message_versions_modal.html", - ], factory); -}(this, function ( - html, - u, - converse, - xss, - filesize, - tpl_csn, - tpl_file_progress, - tpl_info, - tpl_message, - tpl_message_versions_modal - ) { - "use strict"; - const { Backbone, _, moment } = converse.env; +import converse from "@converse/headless/converse-core"; +import filesize from "filesize"; +import html from "./utils/html"; +import tpl_csn from "templates/csn.html"; +import tpl_file_progress from "templates/file_progress.html"; +import tpl_info from "templates/info.html"; +import tpl_message from "templates/message.html"; +import tpl_message_versions_modal from "templates/message_versions_modal.html"; +import u from "utils/emoji"; +import xss from "xss"; + +const { Backbone, _, moment } = converse.env; - converse.plugins.add('converse-message-view', { +converse.plugins.add('converse-message-view', { - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; - _converse.api.settings.update({ - 'show_images_inline': true - }); + _converse.api.settings.update({ + 'show_images_inline': true + }); - _converse.MessageVersionsModal = _converse.BootstrapModal.extend({ - toHTML () { - return tpl_message_versions_modal(_.extend( + _converse.MessageVersionsModal = _converse.BootstrapModal.extend({ + toHTML () { + return tpl_message_versions_modal(_.extend( + this.model.toJSON(), { + '__': __ + })); + } + }); + + + _converse.MessageView = _converse.ViewWithAvatar.extend({ + events: { + 'click .chat-msg__edit-modal': 'showMessageVersionsModal' + }, + + initialize () { + if (this.model.vcard) { + this.model.vcard.on('change', this.render, this); + } + this.model.on('change', this.onChanged, this); + this.model.on('destroy', this.remove, this); + }, + + async render () { + const is_followup = u.hasClass('chat-msg--followup', this.el); + if (this.model.isOnlyChatStateNotification()) { + this.renderChatStateNotification() + } else if (this.model.get('file') && !this.model.get('oob_url')) { + this.renderFileUploadProgresBar(); + } else if (this.model.get('type') === 'error') { + this.renderErrorMessage(); + } else { + await this.renderChatMessage(); + } + if (is_followup) { + u.addClass('chat-msg--followup', this.el); + } + return this.el; + }, + + async onChanged (item) { + // Jot down whether it was edited because the `changed` + // attr gets removed when this.render() gets called further + // down. + const edited = item.changed.edited; + if (this.model.changed.progress) { + return this.renderFileUploadProgresBar(); + } + if (_.filter(['correcting', 'message', 'type', 'upload'], + prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) { + await this.render(); + } + if (edited) { + this.onMessageEdited(); + } + }, + + onMessageEdited () { + if (this.model.get('is_archived')) { + return; + } + this.el.addEventListener('animationend', () => u.removeClass('onload', this.el)); + u.addClass('onload', this.el); + }, + + replaceElement (msg) { + if (!_.isNil(this.el.parentElement)) { + this.el.parentElement.replaceChild(msg, this.el); + } + this.setElement(msg); + return this.el; + }, + + async renderChatMessage () { + const is_me_message = this.isMeCommand(), + moment_time = moment(this.model.get('time')), + role = this.model.vcard ? this.model.vcard.get('role') : null, + roles = role ? role.split(',') : []; + + const msg = u.stringToElement(tpl_message( + _.extend( this.model.toJSON(), { - '__': __ - })); + '__': __, + 'is_me_message': is_me_message, + 'roles': roles, + 'pretty_time': moment_time.format(_converse.time_format), + 'time': moment_time.format(), + 'extra_classes': this.getExtraMessageClasses(), + 'label_show': __('Show more'), + 'username': this.model.getDisplayName() + }) + )); + + const url = this.model.get('oob_url'); + if (url) { + msg.querySelector('.chat-msg__media').innerHTML = _.flow( + _.partial(u.renderFileURL, _converse), + _.partial(u.renderMovieURL, _converse), + _.partial(u.renderAudioURL, _converse), + _.partial(u.renderImageURL, _converse))(url); } - }); - - _converse.MessageView = _converse.ViewWithAvatar.extend({ - events: { - 'click .chat-msg__edit-modal': 'showMessageVersionsModal' - }, - - initialize () { - if (this.model.vcard) { - this.model.vcard.on('change', this.render, this); + let text = this.getMessageText(); + const msg_content = msg.querySelector('.chat-msg__text'); + if (text && text !== url) { + if (is_me_message) { + text = text.replace(/^\/me/, ''); } - this.model.on('change', this.onChanged, this); - this.model.on('destroy', this.remove, this); - }, - - async render () { - const is_followup = u.hasClass('chat-msg--followup', this.el); - if (this.model.isOnlyChatStateNotification()) { - this.renderChatStateNotification() - } else if (this.model.get('file') && !this.model.get('oob_url')) { - this.renderFileUploadProgresBar(); - } else if (this.model.get('type') === 'error') { - this.renderErrorMessage(); - } else { - await this.renderChatMessage(); - } - if (is_followup) { - u.addClass('chat-msg--followup', this.el); - } - return this.el; - }, - - async onChanged (item) { - // Jot down whether it was edited because the `changed` - // attr gets removed when this.render() gets called further - // down. - const edited = item.changed.edited; - if (this.model.changed.progress) { - return this.renderFileUploadProgresBar(); - } - if (_.filter(['correcting', 'message', 'type', 'upload'], - prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) { - await this.render(); - } - if (edited) { - this.onMessageEdited(); - } - }, - - onMessageEdited () { - if (this.model.get('is_archived')) { - return; - } - this.el.addEventListener('animationend', () => u.removeClass('onload', this.el)); - u.addClass('onload', this.el); - }, - - replaceElement (msg) { - if (!_.isNil(this.el.parentElement)) { - this.el.parentElement.replaceChild(msg, this.el); - } - this.setElement(msg); - return this.el; - }, - - async renderChatMessage () { - const is_me_message = this.isMeCommand(), - moment_time = moment(this.model.get('time')), - role = this.model.vcard ? this.model.vcard.get('role') : null, - roles = role ? role.split(',') : []; - - const msg = u.stringToElement(tpl_message( - _.extend( - this.model.toJSON(), { - '__': __, - 'is_me_message': is_me_message, - 'roles': roles, - 'pretty_time': moment_time.format(_converse.time_format), - 'time': moment_time.format(), - 'extra_classes': this.getExtraMessageClasses(), - 'label_show': __('Show more'), - 'username': this.model.getDisplayName() - }) - )); - - const url = this.model.get('oob_url'); - if (url) { - msg.querySelector('.chat-msg__media').innerHTML = _.flow( - _.partial(u.renderFileURL, _converse), - _.partial(u.renderMovieURL, _converse), - _.partial(u.renderAudioURL, _converse), - _.partial(u.renderImageURL, _converse))(url); - } - - let text = this.getMessageText(); - const msg_content = msg.querySelector('.chat-msg__text'); - if (text && text !== url) { - if (is_me_message) { - text = text.replace(/^\/me/, ''); - } - text = xss.filterXSS(text, {'whiteList': {}}); - msg_content.innerHTML = _.flow( - _.partial(u.geoUriToHttp, _, _converse.geouri_replacement), - _.partial(u.addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), - u.addHyperlinks, - u.renderNewLines, - _.partial(u.addEmoji, _converse, _) - )(text); - } - const promises = []; - promises.push(u.renderImageURLs(_converse, msg_content)); - if (this.model.get('type') !== 'headline') { - promises.push(this.renderAvatar(msg)); - } - await Promise.all(promises); - this.replaceElement(msg); - this.model.collection.trigger('rendered', this); - }, - - renderErrorMessage () { - const moment_time = moment(this.model.get('time')), - msg = u.stringToElement( - tpl_info(_.extend(this.model.toJSON(), { - 'extra_classes': 'chat-error', - 'isodate': moment_time.format() - }))); - return this.replaceElement(msg); - }, - - renderChatStateNotification () { - let text; - const from = this.model.get('from'), - name = this.model.getDisplayName(); - - if (this.model.get('chat_state') === _converse.COMPOSING) { - if (this.model.get('sender') === 'me') { - text = __('Typing from another device'); - } else { - text = __('%1$s is typing', name); - } - } else if (this.model.get('chat_state') === _converse.PAUSED) { - if (this.model.get('sender') === 'me') { - text = __('Stopped typing on the other device'); - } else { - text = __('%1$s has stopped typing', name); - } - } else if (this.model.get('chat_state') === _converse.GONE) { - text = __('%1$s has gone away', name); - } else { - return; - } - const isodate = moment().format(); - this.replaceElement( - u.stringToElement( - tpl_csn({ - 'message': text, - 'from': from, - 'isodate': isodate - }))); - }, - - renderFileUploadProgresBar () { - const msg = u.stringToElement(tpl_file_progress( - _.extend(this.model.toJSON(), { - 'filesize': filesize(this.model.get('file').size), - }))); - this.replaceElement(msg); - this.renderAvatar(); - }, - - showMessageVersionsModal (ev) { - ev.preventDefault(); - if (_.isUndefined(this.model.message_versions_modal)) { - this.model.message_versions_modal = new _converse.MessageVersionsModal({'model': this.model}); - } - this.model.message_versions_modal.show(ev); - }, - - getMessageText () { - if (this.model.get('is_encrypted')) { - return this.model.get('plaintext') || - (_converse.debug ? __('Unencryptable OMEMO message') : null); - } - return this.model.get('message'); - }, - - isMeCommand () { - const text = this.getMessageText(); - if (!text) { - return false; - } - const match = text.match(/^\/(.*?)(?: (.*))?$/); - return match && match[1] === 'me'; - }, - - processMessageText () { - var text = this.get('message'); - text = u.geoUriToHttp(text, _converse.geouri_replacement); - }, - - getExtraMessageClasses () { - let extra_classes = this.model.get('is_delayed') && 'delayed' || ''; - if (this.model.get('type') === 'groupchat' && this.model.get('sender') === 'them') { - if (this.model.collection.chatbox.isUserMentioned(this.model)) { - // Add special class to mark groupchat messages - // in which we are mentioned. - extra_classes += ' mentioned'; - } - } - if (this.model.get('correcting')) { - extra_classes += ' correcting'; - } - return extra_classes; + text = xss.filterXSS(text, {'whiteList': {}}); + msg_content.innerHTML = _.flow( + _.partial(u.geoUriToHttp, _, _converse.geouri_replacement), + _.partial(u.addMentionsMarkup, _, this.model.get('references'), this.model.collection.chatbox), + u.addHyperlinks, + u.renderNewLines, + _.partial(u.addEmoji, _converse, _) + )(text); } - }); - } - }); - return converse; -})); + const promises = []; + promises.push(u.renderImageURLs(_converse, msg_content)); + if (this.model.get('type') !== 'headline') { + promises.push(this.renderAvatar(msg)); + } + await Promise.all(promises); + this.replaceElement(msg); + this.model.collection.trigger('rendered', this); + }, + + renderErrorMessage () { + const moment_time = moment(this.model.get('time')), + msg = u.stringToElement( + tpl_info(_.extend(this.model.toJSON(), { + 'extra_classes': 'chat-error', + 'isodate': moment_time.format() + }))); + return this.replaceElement(msg); + }, + + renderChatStateNotification () { + let text; + const from = this.model.get('from'), + name = this.model.getDisplayName(); + + if (this.model.get('chat_state') === _converse.COMPOSING) { + if (this.model.get('sender') === 'me') { + text = __('Typing from another device'); + } else { + text = __('%1$s is typing', name); + } + } else if (this.model.get('chat_state') === _converse.PAUSED) { + if (this.model.get('sender') === 'me') { + text = __('Stopped typing on the other device'); + } else { + text = __('%1$s has stopped typing', name); + } + } else if (this.model.get('chat_state') === _converse.GONE) { + text = __('%1$s has gone away', name); + } else { + return; + } + const isodate = moment().format(); + this.replaceElement( + u.stringToElement( + tpl_csn({ + 'message': text, + 'from': from, + 'isodate': isodate + }))); + }, + + renderFileUploadProgresBar () { + const msg = u.stringToElement(tpl_file_progress( + _.extend(this.model.toJSON(), { + 'filesize': filesize(this.model.get('file').size), + }))); + this.replaceElement(msg); + this.renderAvatar(); + }, + + showMessageVersionsModal (ev) { + ev.preventDefault(); + if (_.isUndefined(this.model.message_versions_modal)) { + this.model.message_versions_modal = new _converse.MessageVersionsModal({'model': this.model}); + } + this.model.message_versions_modal.show(ev); + }, + + getMessageText () { + if (this.model.get('is_encrypted')) { + return this.model.get('plaintext') || + (_converse.debug ? __('Unencryptable OMEMO message') : null); + } + return this.model.get('message'); + }, + + isMeCommand () { + const text = this.getMessageText(); + if (!text) { + return false; + } + const match = text.match(/^\/(.*?)(?: (.*))?$/); + return match && match[1] === 'me'; + }, + + processMessageText () { + var text = this.get('message'); + text = u.geoUriToHttp(text, _converse.geouri_replacement); + }, + + getExtraMessageClasses () { + let extra_classes = this.model.get('is_delayed') && 'delayed' || ''; + if (this.model.get('type') === 'groupchat' && this.model.get('sender') === 'them') { + if (this.model.collection.chatbox.isUserMentioned(this.model)) { + // Add special class to mark groupchat messages + // in which we are mentioned. + extra_classes += ' mentioned'; + } + } + if (this.model.get('correcting')) { + extra_classes += ' correcting'; + } + return extra_classes; + } + }); + } +}); diff --git a/src/converse-minimize.js b/src/converse-minimize.js index 1eb9212a2..ec0d48b60 100644 --- a/src/converse-minimize.js +++ b/src/converse-minimize.js @@ -1,552 +1,540 @@ // Converse.js (A browser based XMPP chat client) // http://conversejs.org // -// Copyright (c) 2012-2017, Jan-Carel Brand +// Copyright (c) 2013-2018, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) -// -/*global define, window, document */ -(function (root, factory) { - define(["@converse/headless/converse-core", - "templates/chatbox_minimize.html", - "templates/toggle_chats.html", - "templates/trimmed_chat.html", - "templates/chats_panel.html", - "converse-chatview" - ], factory); -}(this, function ( - converse, - tpl_chatbox_minimize, - tpl_toggle_chats, - tpl_trimmed_chat, - tpl_chats_panel - ) { - "use strict"; +import "converse-chatview"; +import converse from "@converse/headless/converse-core"; +import tpl_chatbox_minimize from "templates/chatbox_minimize.html"; +import tpl_chats_panel from "templates/chats_panel.html"; +import tpl_toggle_chats from "templates/toggle_chats.html"; +import tpl_trimmed_chat from "templates/trimmed_chat.html"; - const { _ , Backbone, Promise, Strophe, b64_sha1, moment } = converse.env; - const u = converse.env.utils; - converse.plugins.add('converse-minimize', { - /* Optional dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. They are called "optional" because they might not be - * available, in which case any overrides applicable to them will be - * ignored. - * - * It's possible however to make optional dependencies non-optional. - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. - * - * NB: These plugins need to have already been loaded via require.js. - */ - dependencies: ["converse-chatview", "converse-controlbox", "@converse/headless/converse-muc", "converse-muc-views", "converse-headline"], +const { _ , Backbone, Promise, Strophe, b64_sha1, moment } = converse.env; +const u = converse.env.utils; - enabled (_converse) { - return _converse.view_mode == 'overlayed'; +converse.plugins.add('converse-minimize', { + /* Optional dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. They are called "optional" because they might not be + * available, in which case any overrides applicable to them will be + * ignored. + * + * It's possible however to make optional dependencies non-optional. + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. + * + * NB: These plugins need to have already been loaded via require.js. + */ + dependencies: ["converse-chatview", "converse-controlbox", "converse-muc", "converse-muc-views", "converse-headline"], + + enabled (_converse) { + return _converse.view_mode == 'overlayed'; + }, + + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + // + // New functions which don't exist yet can also be added. + + ChatBox: { + initialize () { + this.__super__.initialize.apply(this, arguments); + this.on('show', this.maximize, this); + + if (this.get('id') === 'controlbox') { + return; + } + this.save({ + 'minimized': this.get('minimized') || false, + 'time_minimized': this.get('time_minimized') || moment(), + }); + }, + + maximize () { + u.safeSave(this, { + 'minimized': false, + 'time_opened': moment().valueOf() + }); + }, + + minimize () { + u.safeSave(this, { + 'minimized': true, + 'time_minimized': moment().format() + }); + }, }, - overrides: { - // Overrides mentioned here will be picked up by converse.js's - // plugin architecture they will replace existing methods on the - // relevant objects or classes. - // - // New functions which don't exist yet can also be added. - - ChatBox: { - initialize () { - this.__super__.initialize.apply(this, arguments); - this.on('show', this.maximize, this); - - if (this.get('id') === 'controlbox') { - return; - } - this.save({ - 'minimized': this.get('minimized') || false, - 'time_minimized': this.get('time_minimized') || moment(), - }); - }, - - maximize () { - u.safeSave(this, { - 'minimized': false, - 'time_opened': moment().valueOf() - }); - }, - - minimize () { - u.safeSave(this, { - 'minimized': true, - 'time_minimized': moment().format() - }); - }, + ChatBoxView: { + events: { + 'click .toggle-chatbox-button': 'minimize', }, - ChatBoxView: { - events: { - 'click .toggle-chatbox-button': 'minimize', - }, - - initialize () { - this.model.on('change:minimized', this.onMinimizedChanged, this); - return this.__super__.initialize.apply(this, arguments); - }, - - _show () { - const { _converse } = this.__super__; - if (!this.model.get('minimized')) { - this.__super__._show.apply(this, arguments); - _converse.chatboxviews.trimChats(this); - } else { - this.minimize(); - } - }, - - isNewMessageHidden () { - return this.model.get('minimized') || - this.__super__.isNewMessageHidden.apply(this, arguments); - }, - - shouldShowOnTextMessage () { - return !this.model.get('minimized') && - this.__super__.shouldShowOnTextMessage.apply(this, arguments); - }, - - setChatBoxHeight (height) { - if (!this.model.get('minimized')) { - return this.__super__.setChatBoxHeight.apply(this, arguments); - } - }, - - setChatBoxWidth (width) { - if (!this.model.get('minimized')) { - return this.__super__.setChatBoxWidth.apply(this, arguments); - } - }, - - onMinimizedChanged (item) { - if (item.get('minimized')) { - this.minimize(); - } else { - this.maximize(); - } - }, - - maximize () { - // Restores a minimized chat box - const { _converse } = this.__super__; - this.insertIntoDOM(); - - if (!this.model.isScrolledUp()) { - this.model.clearUnreadMsgCounter(); - } - this.show(); - this.__super__._converse.emit('chatBoxMaximized', this); - return this; - }, - - minimize (ev) { - const { _converse } = this.__super__; - if (ev && ev.preventDefault) { ev.preventDefault(); } - // save the scroll position to restore it on maximize - if (this.model.collection && this.model.collection.browserStorage) { - this.model.save({'scroll': this.content.scrollTop}); - } else { - this.model.set({'scroll': this.content.scrollTop}); - } - this.setChatState(_converse.INACTIVE).model.minimize(); - this.hide(); - _converse.emit('chatBoxMinimized', this); - }, + initialize () { + this.model.on('change:minimized', this.onMinimizedChanged, this); + return this.__super__.initialize.apply(this, arguments); }, - ChatBoxHeading: { - - render () { - const { _converse } = this.__super__, - { __ } = _converse; - const result = this.__super__.render.apply(this, arguments); - const new_html = tpl_chatbox_minimize( - {info_minimize: __('Minimize this chat box')} - ); - const el = this.el.querySelector('.toggle-chatbox-button'); - if (el) { - el.outerHTML = new_html; - } else { - const button = this.el.querySelector('.close-chatbox-button'); - button.insertAdjacentHTML('afterEnd', new_html); - } + _show () { + const { _converse } = this.__super__; + if (!this.model.get('minimized')) { + this.__super__._show.apply(this, arguments); + _converse.chatboxviews.trimChats(this); + } else { + this.minimize(); } }, - ChatRoomView: { - events: { - 'click .toggle-chatbox-button': 'minimize', - }, + isNewMessageHidden () { + return this.model.get('minimized') || + this.__super__.isNewMessageHidden.apply(this, arguments); + }, - initialize () { - this.model.on('change:minimized', function (item) { - if (item.get('minimized')) { - this.hide(); - } else { - this.maximize(); - } - }, this); - const result = this.__super__.initialize.apply(this, arguments); - if (this.model.get('minimized')) { - this.hide(); - } - return result; - }, + shouldShowOnTextMessage () { + return !this.model.get('minimized') && + this.__super__.shouldShowOnTextMessage.apply(this, arguments); + }, - generateHeadingHTML () { - const { _converse } = this.__super__, - { __ } = _converse; - const html = this.__super__.generateHeadingHTML.apply(this, arguments); - const div = document.createElement('div'); - div.innerHTML = html; - const button = div.querySelector('.close-chatbox-button'); - button.insertAdjacentHTML('afterend', - tpl_chatbox_minimize({ - 'info_minimize': __('Minimize this chat box') - }) - ); - return div.innerHTML; + setChatBoxHeight (height) { + if (!this.model.get('minimized')) { + return this.__super__.setChatBoxHeight.apply(this, arguments); } }, - ChatBoxes: { - chatBoxMayBeShown (chatbox) { - return this.__super__.chatBoxMayBeShown.apply(this, arguments) && - !chatbox.get('minimized'); - }, + setChatBoxWidth (width) { + if (!this.model.get('minimized')) { + return this.__super__.setChatBoxWidth.apply(this, arguments); + } }, - ChatBoxViews: { - getChatBoxWidth (view) { - if (!view.model.get('minimized') && u.isVisible(view.el)) { - return u.getOuterWidth(view.el, true); - } - return 0; - }, + onMinimizedChanged (item) { + if (item.get('minimized')) { + this.minimize(); + } else { + this.maximize(); + } + }, - getShownChats () { - return this.filter((view) => - // The controlbox can take a while to close, - // so we need to check its state. That's why we checked - // the 'closed' state. - !view.model.get('minimized') && - !view.model.get('closed') && - u.isVisible(view.el) - ); - }, + maximize () { + // Restores a minimized chat box + const { _converse } = this.__super__; + this.insertIntoDOM(); - trimChats (newchat) { - /* This method is called when a newly created chat box will - * be shown. - * - * It checks whether there is enough space on the page to show - * another chat box. Otherwise it minimizes the oldest chat box - * to create space. - */ - const { _converse } = this.__super__, - shown_chats = this.getShownChats(), - body_width = u.getOuterWidth(document.querySelector('body'), true); + if (!this.model.isScrolledUp()) { + this.model.clearUnreadMsgCounter(); + } + this.show(); + this.__super__._converse.emit('chatBoxMaximized', this); + return this; + }, - if (_converse.no_trimming || shown_chats.length <= 1) { - return; - } - if (this.getChatBoxWidth(shown_chats[0]) === body_width) { - // If the chats shown are the same width as the body, - // then we're in responsive mode and the chats are - // fullscreen. In this case we don't trim. - return; - } - _converse.api.waitUntil('minimizedChatsInitialized').then(() => { - const minimized_el = _.get(_converse.minimized_chats, 'el'), - new_id = newchat ? newchat.model.get('id') : null; + minimize (ev) { + const { _converse } = this.__super__; + if (ev && ev.preventDefault) { ev.preventDefault(); } + // save the scroll position to restore it on maximize + if (this.model.collection && this.model.collection.browserStorage) { + this.model.save({'scroll': this.content.scrollTop}); + } else { + this.model.set({'scroll': this.content.scrollTop}); + } + this.setChatState(_converse.INACTIVE).model.minimize(); + this.hide(); + _converse.emit('chatBoxMinimized', this); + }, + }, - if (minimized_el) { - const minimized_width = _.includes(this.model.pluck('minimized'), true) ? - u.getOuterWidth(minimized_el, true) : 0; + ChatBoxHeading: { - const boxes_width = _.reduce( - this.xget(new_id), - (memo, view) => memo + this.getChatBoxWidth(view), - newchat ? u.getOuterWidth(newchat.el, true) : 0 - ); - if ((minimized_width + boxes_width) > body_width) { - const oldest_chat = this.getOldestMaximizedChat([new_id]); - if (oldest_chat) { - // We hide the chat immediately, because waiting - // for the event to fire (and letting the - // ChatBoxView hide it then) causes race - // conditions. - const view = this.get(oldest_chat.get('id')); - if (view) { - view.hide(); - } - oldest_chat.minimize(); - } - } - } - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }, - - getOldestMaximizedChat (exclude_ids) { - // Get oldest view (if its id is not excluded) - exclude_ids.push('controlbox'); - let i = 0; - let model = this.model.sort().at(i); - while (_.includes(exclude_ids, model.get('id')) || - model.get('minimized') === true) { - i++; - model = this.model.at(i); - if (!model) { - return null; - } - } - return model; + render () { + const { _converse } = this.__super__, + { __ } = _converse; + const result = this.__super__.render.apply(this, arguments); + const new_html = tpl_chatbox_minimize( + {info_minimize: __('Minimize this chat box')} + ); + const el = this.el.querySelector('.toggle-chatbox-button'); + if (el) { + el.outerHTML = new_html; + } else { + const button = this.el.querySelector('.close-chatbox-button'); + button.insertAdjacentHTML('afterEnd', new_html); } } }, + ChatRoomView: { + events: { + 'click .toggle-chatbox-button': 'minimize', + }, - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by Converse.js's plugin machinery. - */ - const { _converse } = this, - { __ } = _converse; - - // Add new HTML templates. - _converse.templates.chatbox_minimize = tpl_chatbox_minimize; - _converse.templates.toggle_chats = tpl_toggle_chats; - _converse.templates.trimmed_chat = tpl_trimmed_chat; - _converse.templates.chats_panel = tpl_chats_panel; - - _converse.api.settings.update({ - no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width) - }); - - _converse.api.promises.add('minimizedChatsInitialized'); - - _converse.MinimizedChatBoxView = Backbone.NativeView.extend({ - tagName: 'div', - className: 'chat-head row no-gutters', - events: { - 'click .close-chatbox-button': 'close', - 'click .restore-chat': 'restore' - }, - - initialize () { - this.model.on('change:num_unread', this.render, this); - }, - - render () { - const data = _.extend( - this.model.toJSON(), - { 'tooltip': __('Click to restore this chat') } - ); - if (this.model.get('type') === 'chatroom') { - data.title = this.model.get('name'); - u.addClass('chat-head-chatroom', this.el); - } else { - data.title = this.model.get('fullname'); - u.addClass('chat-head-chatbox', this.el); - } - this.el.innerHTML = tpl_trimmed_chat(data); - return this.el; - }, - - close (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - this.remove(); - const view = _converse.chatboxviews.get(this.model.get('id')); - if (view) { - // This will call model.destroy(), removing it from the - // collection and will also emit 'chatBoxClosed' - view.close(); - } else { - this.model.destroy(); - _converse.emit('chatBoxClosed', this); - } - return this; - }, - - restore: _.debounce(function (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - this.model.off('change:num_unread', null, this); - this.remove(); - this.model.maximize(); - }, 200, {'leading': true}) - }); - - - _converse.MinimizedChats = Backbone.Overview.extend({ - tagName: 'div', - id: "minimized-chats", - className: 'hidden', - events: { - "click #toggle-minimized-chats": "toggle" - }, - - initialize () { - this.render(); - this.initToggle(); - this.addMultipleChats(this.model.where({'minimized': true})); - this.model.on("add", this.onChanged, this); - this.model.on("destroy", this.removeChat, this); - this.model.on("change:minimized", this.onChanged, this); - this.model.on('change:num_unread', this.updateUnreadMessagesCounter, this); - }, - - render () { - if (!this.el.parentElement) { - this.el.innerHTML = tpl_chats_panel(); - _converse.chatboxviews.insertRowColumn(this.el); - } - if (this.keys().length === 0) { - this.el.classList.add('hidden'); - } else if (this.keys().length > 0 && !u.isVisible(this.el)) { - this.el.classList.remove('hidden'); - _converse.chatboxviews.trimChats(); - } - return this.el; - }, - - tearDown () { - this.model.off("add", this.onChanged); - this.model.off("destroy", this.removeChat); - this.model.off("change:minimized", this.onChanged); - this.model.off('change:num_unread', this.updateUnreadMessagesCounter); - return this; - }, - - initToggle () { - const storage = _converse.config.get('storage'), - id = b64_sha1(`converse.minchatstoggle${_converse.bare_jid}`); - this.toggleview = new _converse.MinimizedChatsToggleView({ - 'model': new _converse.MinimizedChatsToggle({'id': id}) - }); - this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id); - this.toggleview.model.fetch(); - }, - - toggle (ev) { - if (ev && ev.preventDefault) { ev.preventDefault(); } - this.toggleview.model.save({'collapsed': !this.toggleview.model.get('collapsed')}); - u.slideToggleElement(this.el.querySelector('.minimized-chats-flyout'), 200); - }, - - onChanged (item) { - if (item.get('id') === 'controlbox') { - // The ControlBox has it's own minimize toggle - return; - } + initialize () { + this.model.on('change:minimized', function (item) { if (item.get('minimized')) { - this.addChat(item); - } else if (this.get(item.get('id'))) { - this.removeChat(item); - } - }, - - addChatView (item) { - const existing = this.get(item.get('id')); - if (existing && existing.el.parentNode) { - return; - } - const view = new _converse.MinimizedChatBoxView({model: item}); - this.el.querySelector('.minimized-chats-flyout').insertAdjacentElement('beforeEnd', view.render()); - this.add(item.get('id'), view); - }, - - addMultipleChats (items) { - _.each(items, this.addChatView.bind(this)); - this.toggleview.model.set({'num_minimized': this.keys().length}); - this.render(); - }, - - addChat (item) { - this.addChatView(item); - this.toggleview.model.set({'num_minimized': this.keys().length}); - this.render(); - }, - - removeChat (item) { - this.remove(item.get('id')); - this.toggleview.model.set({'num_minimized': this.keys().length}); - this.render(); - }, - - updateUnreadMessagesCounter () { - const ls = this.model.pluck('num_unread'); - let count = 0, i; - for (i=0; i { - _converse.minimized_chats = new _converse.MinimizedChats({ - model: _converse.chatboxes - }); - _converse.emit('minimizedChatsInitialized'); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + generateHeadingHTML () { + const { _converse } = this.__super__, + { __ } = _converse; + const html = this.__super__.generateHeadingHTML.apply(this, arguments); + const div = document.createElement('div'); + div.innerHTML = html; + const button = div.querySelector('.close-chatbox-button'); + button.insertAdjacentHTML('afterend', + tpl_chatbox_minimize({ + 'info_minimize': __('Minimize this chat box') + }) + ); + return div.innerHTML; + } + }, + ChatBoxes: { + chatBoxMayBeShown (chatbox) { + return this.__super__.chatBoxMayBeShown.apply(this, arguments) && + !chatbox.get('minimized'); + }, + }, - _converse.on('registeredGlobalEventHandlers', function () { - window.addEventListener("resize", _.debounce(function (ev) { - if (_converse.connection.connected) { - _converse.chatboxviews.trimChats(); + ChatBoxViews: { + getChatBoxWidth (view) { + if (!view.model.get('minimized') && u.isVisible(view.el)) { + return u.getOuterWidth(view.el, true); + } + return 0; + }, + + getShownChats () { + return this.filter((view) => + // The controlbox can take a while to close, + // so we need to check its state. That's why we checked + // the 'closed' state. + !view.model.get('minimized') && + !view.model.get('closed') && + u.isVisible(view.el) + ); + }, + + trimChats (newchat) { + /* This method is called when a newly created chat box will + * be shown. + * + * It checks whether there is enough space on the page to show + * another chat box. Otherwise it minimizes the oldest chat box + * to create space. + */ + const { _converse } = this.__super__, + shown_chats = this.getShownChats(), + body_width = u.getOuterWidth(document.querySelector('body'), true); + + if (_converse.no_trimming || shown_chats.length <= 1) { + return; + } + if (this.getChatBoxWidth(shown_chats[0]) === body_width) { + // If the chats shown are the same width as the body, + // then we're in responsive mode and the chats are + // fullscreen. In this case we don't trim. + return; + } + _converse.api.waitUntil('minimizedChatsInitialized').then(() => { + const minimized_el = _.get(_converse.minimized_chats, 'el'), + new_id = newchat ? newchat.model.get('id') : null; + + if (minimized_el) { + const minimized_width = _.includes(this.model.pluck('minimized'), true) ? + u.getOuterWidth(minimized_el, true) : 0; + + const boxes_width = _.reduce( + this.xget(new_id), + (memo, view) => memo + this.getChatBoxWidth(view), + newchat ? u.getOuterWidth(newchat.el, true) : 0 + ); + if ((minimized_width + boxes_width) > body_width) { + const oldest_chat = this.getOldestMaximizedChat([new_id]); + if (oldest_chat) { + // We hide the chat immediately, because waiting + // for the event to fire (and letting the + // ChatBoxView hide it then) causes race + // conditions. + const view = this.get(oldest_chat.get('id')); + if (view) { + view.hide(); + } + oldest_chat.minimize(); + } + } } - }, 200)); - }); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }, - _converse.on('controlBoxOpened', function (chatbox) { - // Wrapped in anon method because at scan time, chatboxviews - // attr not set yet. - if (_converse.connection.connected) { - _converse.chatboxviews.trimChats(chatbox); + getOldestMaximizedChat (exclude_ids) { + // Get oldest view (if its id is not excluded) + exclude_ids.push('controlbox'); + let i = 0; + let model = this.model.sort().at(i); + while (_.includes(exclude_ids, model.get('id')) || + model.get('minimized') === true) { + i++; + model = this.model.at(i); + if (!model) { + return null; + } } - }); + return model; + } } - }); -})); + }, + + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by Converse.js's plugin machinery. + */ + const { _converse } = this, + { __ } = _converse; + + // Add new HTML templates. + _converse.templates.chatbox_minimize = tpl_chatbox_minimize; + _converse.templates.toggle_chats = tpl_toggle_chats; + _converse.templates.trimmed_chat = tpl_trimmed_chat; + _converse.templates.chats_panel = tpl_chats_panel; + + _converse.api.settings.update({ + no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width) + }); + + _converse.api.promises.add('minimizedChatsInitialized'); + + _converse.MinimizedChatBoxView = Backbone.NativeView.extend({ + tagName: 'div', + className: 'chat-head row no-gutters', + events: { + 'click .close-chatbox-button': 'close', + 'click .restore-chat': 'restore' + }, + + initialize () { + this.model.on('change:num_unread', this.render, this); + }, + + render () { + const data = _.extend( + this.model.toJSON(), + { 'tooltip': __('Click to restore this chat') } + ); + if (this.model.get('type') === 'chatroom') { + data.title = this.model.get('name'); + u.addClass('chat-head-chatroom', this.el); + } else { + data.title = this.model.get('fullname'); + u.addClass('chat-head-chatbox', this.el); + } + this.el.innerHTML = tpl_trimmed_chat(data); + return this.el; + }, + + close (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + this.remove(); + const view = _converse.chatboxviews.get(this.model.get('id')); + if (view) { + // This will call model.destroy(), removing it from the + // collection and will also emit 'chatBoxClosed' + view.close(); + } else { + this.model.destroy(); + _converse.emit('chatBoxClosed', this); + } + return this; + }, + + restore: _.debounce(function (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + this.model.off('change:num_unread', null, this); + this.remove(); + this.model.maximize(); + }, 200, {'leading': true}) + }); + + + _converse.MinimizedChats = Backbone.Overview.extend({ + tagName: 'div', + id: "minimized-chats", + className: 'hidden', + events: { + "click #toggle-minimized-chats": "toggle" + }, + + initialize () { + this.render(); + this.initToggle(); + this.addMultipleChats(this.model.where({'minimized': true})); + this.model.on("add", this.onChanged, this); + this.model.on("destroy", this.removeChat, this); + this.model.on("change:minimized", this.onChanged, this); + this.model.on('change:num_unread', this.updateUnreadMessagesCounter, this); + }, + + render () { + if (!this.el.parentElement) { + this.el.innerHTML = tpl_chats_panel(); + _converse.chatboxviews.insertRowColumn(this.el); + } + if (this.keys().length === 0) { + this.el.classList.add('hidden'); + } else if (this.keys().length > 0 && !u.isVisible(this.el)) { + this.el.classList.remove('hidden'); + _converse.chatboxviews.trimChats(); + } + return this.el; + }, + + tearDown () { + this.model.off("add", this.onChanged); + this.model.off("destroy", this.removeChat); + this.model.off("change:minimized", this.onChanged); + this.model.off('change:num_unread', this.updateUnreadMessagesCounter); + return this; + }, + + initToggle () { + const storage = _converse.config.get('storage'), + id = b64_sha1(`converse.minchatstoggle${_converse.bare_jid}`); + this.toggleview = new _converse.MinimizedChatsToggleView({ + 'model': new _converse.MinimizedChatsToggle({'id': id}) + }); + this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id); + this.toggleview.model.fetch(); + }, + + toggle (ev) { + if (ev && ev.preventDefault) { ev.preventDefault(); } + this.toggleview.model.save({'collapsed': !this.toggleview.model.get('collapsed')}); + u.slideToggleElement(this.el.querySelector('.minimized-chats-flyout'), 200); + }, + + onChanged (item) { + if (item.get('id') === 'controlbox') { + // The ControlBox has it's own minimize toggle + return; + } + if (item.get('minimized')) { + this.addChat(item); + } else if (this.get(item.get('id'))) { + this.removeChat(item); + } + }, + + addChatView (item) { + const existing = this.get(item.get('id')); + if (existing && existing.el.parentNode) { + return; + } + const view = new _converse.MinimizedChatBoxView({model: item}); + this.el.querySelector('.minimized-chats-flyout').insertAdjacentElement('beforeEnd', view.render()); + this.add(item.get('id'), view); + }, + + addMultipleChats (items) { + _.each(items, this.addChatView.bind(this)); + this.toggleview.model.set({'num_minimized': this.keys().length}); + this.render(); + }, + + addChat (item) { + this.addChatView(item); + this.toggleview.model.set({'num_minimized': this.keys().length}); + this.render(); + }, + + removeChat (item) { + this.remove(item.get('id')); + this.toggleview.model.set({'num_minimized': this.keys().length}); + this.render(); + }, + + updateUnreadMessagesCounter () { + const ls = this.model.pluck('num_unread'); + let count = 0, i; + for (i=0; i { + _converse.minimized_chats = new _converse.MinimizedChats({ + model: _converse.chatboxes + }); + _converse.emit('minimizedChatsInitialized'); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + + + _converse.on('registeredGlobalEventHandlers', function () { + window.addEventListener("resize", _.debounce(function (ev) { + if (_converse.connection.connected) { + _converse.chatboxviews.trimChats(); + } + }, 200)); + }); + + _converse.on('controlBoxOpened', function (chatbox) { + // Wrapped in anon method because at scan time, chatboxviews + // attr not set yet. + if (_converse.connection.connected) { + _converse.chatboxviews.trimChats(chatbox); + } + }); + } +}); diff --git a/src/converse-modal.js b/src/converse-modal.js index 8a020323f..69d99f49f 100644 --- a/src/converse-modal.js +++ b/src/converse-modal.js @@ -1,117 +1,111 @@ // Converse.js // http://conversejs.org // -// Copyright (c) 2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([ - "@converse/headless/converse-core", - "templates/alert_modal.html", - "bootstrap", - "backbone.vdomview" - ], factory); - } -}(this, function (converse, tpl_alert_modal, bootstrap) { - "use strict"; +import "backbone.vdomview"; +import bootstrap from "bootstrap"; +import converse from "@converse/headless/converse-core"; +import tpl_alert_modal from "templates/alert_modal.html"; - const { Strophe, Backbone, _ } = converse.env; +const { Strophe, Backbone, _ } = converse.env; - converse.plugins.add('converse-modal', { - initialize () { - const { _converse } = this; +converse.plugins.add('converse-modal', { - _converse.BootstrapModal = Backbone.VDOMView.extend({ + initialize () { + const { _converse } = this; - initialize () { - this.render().insertIntoDOM(); - this.modal = new bootstrap.Modal(this.el, { - backdrop: 'static', - keyboard: true - }); - this.el.addEventListener('hide.bs.modal', (event) => { - if (!_.isNil(this.trigger_el)) { - this.trigger_el.classList.remove('selected'); - } - }, false); - }, + _converse.BootstrapModal = Backbone.VDOMView.extend({ - insertIntoDOM () { - const container_el = _converse.chatboxviews.el.querySelector("#converse-modals"); - container_el.insertAdjacentElement('beforeEnd', this.el); - }, - - show (ev) { - if (ev) { - ev.preventDefault(); - this.trigger_el = ev.target; - this.trigger_el.classList.add('selected'); + initialize () { + this.render().insertIntoDOM(); + this.modal = new bootstrap.Modal(this.el, { + backdrop: 'static', + keyboard: true + }); + this.el.addEventListener('hide.bs.modal', (event) => { + if (!_.isNil(this.trigger_el)) { + this.trigger_el.classList.remove('selected'); } - this.modal.show(); + }, false); + }, + + insertIntoDOM () { + const container_el = _converse.chatboxviews.el.querySelector("#converse-modals"); + container_el.insertAdjacentElement('beforeEnd', this.el); + }, + + show (ev) { + if (ev) { + ev.preventDefault(); + this.trigger_el = ev.target; + this.trigger_el.classList.add('selected'); } - }); + this.modal.show(); + } + }); - _converse.Alert = _converse.BootstrapModal.extend({ + _converse.Alert = _converse.BootstrapModal.extend({ - initialize () { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - this.model.on('change', this.render, this); - }, + initialize () { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + this.model.on('change', this.render, this); + }, - toHTML () { - return tpl_alert_modal(this.model.toJSON()); - } - }); + toHTML () { + return tpl_alert_modal(this.model.toJSON()); + } + }); - _converse.api.listen.on('afterTearDown', () => { - if (!_converse.chatboxviews) { - return; - } - const container = _converse.chatboxviews.el.querySelector("#converse-modals"); - if (container) { - container.innerHTML = ''; - } - }); + _converse.api.listen.on('afterTearDown', () => { + if (!_converse.chatboxviews) { + return; + } + const container = _converse.chatboxviews.el.querySelector("#converse-modals"); + if (container) { + container.innerHTML = ''; + } + }); - /************************ BEGIN API ************************/ - // We extend the default converse.js API to add methods specific to MUC chat rooms. - let alert + /************************ BEGIN API ************************/ + // We extend the default converse.js API to add methods specific to MUC chat rooms. + let alert - _.extend(_converse.api, { - 'alert': { - 'show' (type, title, messages) { - if (_.isString(messages)) { - messages = [messages]; - } - if (type === Strophe.LogLevel.ERROR) { - type = 'alert-danger'; - } else if (type === Strophe.LogLevel.INFO) { - type = 'alert-info'; - } else if (type === Strophe.LogLevel.WARN) { - type = 'alert-warning'; - } - - if (_.isUndefined(alert)) { - const model = new Backbone.Model({ - 'title': title, - 'messages': messages, - 'type': type - }) - alert = new _converse.Alert({'model': model}); - } else { - alert.model.set({ - 'title': title, - 'messages': messages, - 'type': type - }); - } - alert.show(); + _.extend(_converse.api, { + 'alert': { + 'show' (type, title, messages) { + if (_.isString(messages)) { + messages = [messages]; } + if (type === Strophe.LogLevel.ERROR) { + type = 'alert-danger'; + } else if (type === Strophe.LogLevel.INFO) { + type = 'alert-info'; + } else if (type === Strophe.LogLevel.WARN) { + type = 'alert-warning'; + } + + if (_.isUndefined(alert)) { + const model = new Backbone.Model({ + 'title': title, + 'messages': messages, + 'type': type + }) + alert = new _converse.Alert({'model': model}); + } else { + alert.model.set({ + 'title': title, + 'messages': messages, + 'type': type + }); + } + alert.show(); } - }); - } - }); -})); + } + }); + } +}); + diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index 2b8f1177f..1ed69450c 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -4,2146 +4,2115 @@ // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - define([ - "@converse/headless/converse-core", - "formdata-polyfill", - "utils/muc", - "xss", - "templates/add_chatroom_modal.html", - "templates/chatarea.html", - "templates/chatroom.html", - "templates/chatroom_details_modal.html", - "templates/chatroom_destroyed.html", - "templates/chatroom_disconnect.html", - "templates/chatroom_features.html", - "templates/chatroom_form.html", - "templates/chatroom_head.html", - "templates/chatroom_invite.html", - "templates/chatroom_nickname_form.html", - "templates/chatroom_password_form.html", - "templates/chatroom_sidebar.html", - "templates/info.html", - "templates/list_chatrooms_modal.html", - "templates/occupant.html", - "templates/room_description.html", - "templates/room_item.html", - "templates/room_panel.html", - "templates/rooms_results.html", - "templates/spinner.html", - "awesomplete", - "converse-modal" - ], factory); -}(this, function ( - converse, - _FormData, - muc_utils, - xss, - tpl_add_chatroom_modal, - tpl_chatarea, - tpl_chatroom, - tpl_chatroom_details_modal, - tpl_chatroom_destroyed, - tpl_chatroom_disconnect, - tpl_chatroom_features, - tpl_chatroom_form, - tpl_chatroom_head, - tpl_chatroom_invite, - tpl_chatroom_nickname_form, - tpl_chatroom_password_form, - tpl_chatroom_sidebar, - tpl_info, - tpl_list_chatrooms_modal, - tpl_occupant, - tpl_room_description, - tpl_room_item, - tpl_room_panel, - tpl_rooms_results, - tpl_spinner, - Awesomplete -) { - "use strict"; +import "converse-modal"; +import Awesomplete from "awesomplete"; +import _FormData from "formdata-polyfill"; +import converse from "@converse/headless/converse-core"; +import muc_utils from "utils/muc"; +import tpl_add_chatroom_modal from "templates/add_chatroom_modal.html"; +import tpl_chatarea from "templates/chatarea.html"; +import tpl_chatroom from "templates/chatroom.html"; +import tpl_chatroom_destroyed from "templates/chatroom_destroyed.html"; +import tpl_chatroom_details_modal from "templates/chatroom_details_modal.html"; +import tpl_chatroom_disconnect from "templates/chatroom_disconnect.html"; +import tpl_chatroom_features from "templates/chatroom_features.html"; +import tpl_chatroom_form from "templates/chatroom_form.html"; +import tpl_chatroom_head from "templates/chatroom_head.html"; +import tpl_chatroom_invite from "templates/chatroom_invite.html"; +import tpl_chatroom_nickname_form from "templates/chatroom_nickname_form.html"; +import tpl_chatroom_password_form from "templates/chatroom_password_form.html"; +import tpl_chatroom_sidebar from "templates/chatroom_sidebar.html"; +import tpl_info from "templates/info.html"; +import tpl_list_chatrooms_modal from "templates/list_chatrooms_modal.html"; +import tpl_occupant from "templates/occupant.html"; +import tpl_room_description from "templates/room_description.html"; +import tpl_room_item from "templates/room_item.html"; +import tpl_room_panel from "templates/room_panel.html"; +import tpl_rooms_results from "templates/rooms_results.html"; +import tpl_spinner from "templates/spinner.html"; +import xss from "xss"; - const { Backbone, Promise, Strophe, b64_sha1, moment, f, sizzle, _, $build, $iq, $msg, $pres } = converse.env; - const u = converse.env.utils; - const ROOM_FEATURES_MAP = { - 'passwordprotected': 'unsecured', - 'unsecured': 'passwordprotected', - 'hidden': 'publicroom', - 'publicroom': 'hidden', - 'membersonly': 'open', - 'open': 'membersonly', - 'persistent': 'temporary', - 'temporary': 'persistent', - 'nonanonymous': 'semianonymous', - 'semianonymous': 'nonanonymous', - 'moderated': 'unmoderated', - 'unmoderated': 'moderated' - }; +const { Backbone, Promise, Strophe, b64_sha1, moment, f, sizzle, _, $build, $iq, $msg, $pres } = converse.env; +const u = converse.env.utils; - converse.plugins.add('converse-muc-views', { - /* Dependencies are other plugins which might be - * overridden or relied upon, and therefore need to be loaded before - * this plugin. They are "optional" because they might not be - * available, in which case any overrides applicable to them will be - * ignored. - * - * NB: These plugins need to have already been loaded via require.js. - * - * It's possible to make these dependencies "non-optional". - * If the setting "strict_plugin_dependencies" is set to true, - * an error will be raised if the plugin is not found. +const ROOM_FEATURES_MAP = { + 'passwordprotected': 'unsecured', + 'unsecured': 'passwordprotected', + 'hidden': 'publicroom', + 'publicroom': 'hidden', + 'membersonly': 'open', + 'open': 'membersonly', + 'persistent': 'temporary', + 'temporary': 'persistent', + 'nonanonymous': 'semianonymous', + 'semianonymous': 'nonanonymous', + 'moderated': 'unmoderated', + 'unmoderated': 'moderated' +}; + +converse.plugins.add('converse-muc-views', { + /* Dependencies are other plugins which might be + * overridden or relied upon, and therefore need to be loaded before + * this plugin. They are "optional" because they might not be + * available, in which case any overrides applicable to them will be + * ignored. + * + * NB: These plugins need to have already been loaded via require.js. + * + * It's possible to make these dependencies "non-optional". + * If the setting "strict_plugin_dependencies" is set to true, + * an error will be raised if the plugin is not found. + */ + dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"], + + overrides: { + + ControlBoxView: { + + renderRoomsPanel () { + const { _converse } = this.__super__; + this.roomspanel = new _converse.RoomsPanel({ + 'model': new (_converse.RoomsPanelModel.extend({ + 'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage + 'browserStorage': new Backbone.BrowserStorage[_converse.config.get('storage')]( + b64_sha1(`converse.roomspanel${_converse.bare_jid}`)) + }))() + }); + this.roomspanel.model.fetch(); + this.el.querySelector('.controlbox-pane').insertAdjacentElement( + 'beforeEnd', this.roomspanel.render().el); + + if (!this.roomspanel.model.get('nick')) { + this.roomspanel.model.save({ + nick: _converse.xmppstatus.vcard.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid) + }); + } + _converse.emit('roomsPanelRendered'); + }, + + renderControlBoxPane () { + const { _converse } = this.__super__; + this.__super__.renderControlBoxPane.apply(this, arguments); + if (_converse.allow_muc) { + this.renderRoomsPanel(); + } + }, + } + }, + + initialize () { + const { _converse } = this, + { __ } = _converse; + + _converse.api.promises.add(['roomsPanelRendered']); + + // Configuration values for this plugin + // ==================================== + // Refer to docs/source/configuration.rst for explanations of these + // configuration settings. + _converse.api.settings.update({ + 'auto_list_rooms': false, + 'hide_muc_server': false, // TODO: no longer implemented... + 'muc_disable_moderator_commands': false, + 'visible_toolbar_buttons': { + 'toggle_occupants': true + } + }); + + + function ___ (str) { + /* This is part of a hack to get gettext to scan strings to be + * translated. Strings we cannot send to the function above because + * they require variable interpolation and we don't yet have the + * variables at scan time. + * + * See actionInfoMessages further below. + */ + return str; + } + + /* http://xmpp.org/extensions/xep-0045.html + * ---------------------------------------- + * 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID + * 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat + * 102 message Configuration change Inform occupants that groupchat now shows unavailable members + * 103 message Configuration change Inform occupants that groupchat now does not show unavailable members + * 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred + * 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants + * 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled + * 171 message Configuration change Inform occupants that groupchat logging is now disabled + * 172 message Configuration change Inform occupants that the groupchat is now non-anonymous + * 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous + * 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous + * 201 presence Entering a groupchat Inform user that a new groupchat has been created + * 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick + * 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat + * 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname + * 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat + * 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change + * 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member + * 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown */ - dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"], + _converse.muc = { + info_messages: { + 100: __('This groupchat is not anonymous'), + 102: __('This groupchat now shows unavailable members'), + 103: __('This groupchat does not show unavailable members'), + 104: __('The groupchat configuration has changed'), + 170: __('groupchat logging is now enabled'), + 171: __('groupchat logging is now disabled'), + 172: __('This groupchat is now no longer anonymous'), + 173: __('This groupchat is now semi-anonymous'), + 174: __('This groupchat is now fully-anonymous'), + 201: __('A new groupchat has been created') + }, - overrides: { + disconnect_messages: { + 301: __('You have been banned from this groupchat'), + 307: __('You have been kicked from this groupchat'), + 321: __("You have been removed from this groupchat because of an affiliation change"), + 322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"), + 332: __("You have been removed from this groupchat because the service hosting it is being shut down") + }, - ControlBoxView: { - - renderRoomsPanel () { - const { _converse } = this.__super__; - this.roomspanel = new _converse.RoomsPanel({ - 'model': new (_converse.RoomsPanelModel.extend({ - 'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage - 'browserStorage': new Backbone.BrowserStorage[_converse.config.get('storage')]( - b64_sha1(`converse.roomspanel${_converse.bare_jid}`)) - }))() - }); - this.roomspanel.model.fetch(); - this.el.querySelector('.controlbox-pane').insertAdjacentElement( - 'beforeEnd', this.roomspanel.render().el); - - if (!this.roomspanel.model.get('nick')) { - this.roomspanel.model.save({ - nick: _converse.xmppstatus.vcard.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid) - }); - } - _converse.emit('roomsPanelRendered'); - }, - - renderControlBoxPane () { - const { _converse } = this.__super__; - this.__super__.renderControlBoxPane.apply(this, arguments); - if (_converse.allow_muc) { - this.renderRoomsPanel(); - } - }, - } - }, - - initialize () { - const { _converse } = this, - { __ } = _converse; - - _converse.api.promises.add(['roomsPanelRendered']); - - // Configuration values for this plugin - // ==================================== - // Refer to docs/source/configuration.rst for explanations of these - // configuration settings. - _converse.api.settings.update({ - 'auto_list_rooms': false, - 'hide_muc_server': false, // TODO: no longer implemented... - 'muc_disable_moderator_commands': false, - 'visible_toolbar_buttons': { - 'toggle_occupants': true - } - }); - - - function ___ (str) { - /* This is part of a hack to get gettext to scan strings to be - * translated. Strings we cannot send to the function above because - * they require variable interpolation and we don't yet have the - * variables at scan time. + action_info_messages: { + /* XXX: Note the triple underscore function and not double + * underscore. * - * See actionInfoMessages further below. + * This is a hack. We can't pass the strings to __ because we + * don't yet know what the variable to interpolate is. + * + * Triple underscore will just return the string again, but we + * can then at least tell gettext to scan for it so that these + * strings are picked up by the translation machinery. */ - return str; - } + 301: ___("%1$s has been banned"), + 303: ___("%1$s's nickname has changed"), + 307: ___("%1$s has been kicked out"), + 321: ___("%1$s has been removed because of an affiliation change"), + 322: ___("%1$s has been removed for not being a member") + }, - /* http://xmpp.org/extensions/xep-0045.html - * ---------------------------------------- - * 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID - * 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat - * 102 message Configuration change Inform occupants that groupchat now shows unavailable members - * 103 message Configuration change Inform occupants that groupchat now does not show unavailable members - * 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred - * 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants - * 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled - * 171 message Configuration change Inform occupants that groupchat logging is now disabled - * 172 message Configuration change Inform occupants that the groupchat is now non-anonymous - * 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous - * 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous - * 201 presence Entering a groupchat Inform user that a new groupchat has been created - * 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick - * 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat - * 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname - * 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat - * 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change - * 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member - * 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown + new_nickname_messages: { + 210: ___('Your nickname has been automatically set to %1$s'), + 303: ___('Your nickname has been changed to %1$s') + } + }; + + + function insertRoomInfo (el, stanza) { + /* Insert groupchat info (based on returned #disco IQ stanza) + * + * Parameters: + * (HTMLElement) el: The HTML DOM element that should + * contain the info. + * (XMLElement) stanza: The IQ stanza containing the groupchat + * info. */ - _converse.muc = { - info_messages: { - 100: __('This groupchat is not anonymous'), - 102: __('This groupchat now shows unavailable members'), - 103: __('This groupchat does not show unavailable members'), - 104: __('The groupchat configuration has changed'), - 170: __('groupchat logging is now enabled'), - 171: __('groupchat logging is now disabled'), - 172: __('This groupchat is now no longer anonymous'), - 173: __('This groupchat is now semi-anonymous'), - 174: __('This groupchat is now fully-anonymous'), - 201: __('A new groupchat has been created') - }, + // All MUC features found here: http://xmpp.org/registrar/disco-features.html + el.querySelector('span.spinner').remove(); + el.querySelector('a.room-info').classList.add('selected'); + el.insertAdjacentHTML( + 'beforeEnd', + tpl_room_description({ + 'jid': stanza.getAttribute('from'), + 'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'), + 'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'), + 'hidden': sizzle('feature[var="muc_hidden"]', stanza).length, + 'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length, + 'moderated': sizzle('feature[var="muc_moderated"]', stanza).length, + 'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length, + 'open': sizzle('feature[var="muc_open"]', stanza).length, + 'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length, + 'persistent': sizzle('feature[var="muc_persistent"]', stanza).length, + 'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length, + 'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length, + 'temporary': sizzle('feature[var="muc_temporary"]', stanza).length, + 'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length, + 'label_desc': __('Description:'), + 'label_jid': __('Groupchat Address (JID):'), + 'label_occ': __('Participants:'), + 'label_features': __('Features:'), + 'label_requires_auth': __('Requires authentication'), + 'label_hidden': __('Hidden'), + 'label_requires_invite': __('Requires an invitation'), + 'label_moderated': __('Moderated'), + 'label_non_anon': __('Non-anonymous'), + 'label_open_room': __('Open'), + 'label_permanent_room': __('Permanent'), + 'label_public': __('Public'), + 'label_semi_anon': __('Semi-anonymous'), + 'label_temp_room': __('Temporary'), + 'label_unmoderated': __('Unmoderated') + })); + } - disconnect_messages: { - 301: __('You have been banned from this groupchat'), - 307: __('You have been kicked from this groupchat'), - 321: __("You have been removed from this groupchat because of an affiliation change"), - 322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"), - 332: __("You have been removed from this groupchat because the service hosting it is being shut down") - }, - - action_info_messages: { - /* XXX: Note the triple underscore function and not double - * underscore. - * - * This is a hack. We can't pass the strings to __ because we - * don't yet know what the variable to interpolate is. - * - * Triple underscore will just return the string again, but we - * can then at least tell gettext to scan for it so that these - * strings are picked up by the translation machinery. - */ - 301: ___("%1$s has been banned"), - 303: ___("%1$s's nickname has changed"), - 307: ___("%1$s has been kicked out"), - 321: ___("%1$s has been removed because of an affiliation change"), - 322: ___("%1$s has been removed for not being a member") - }, - - new_nickname_messages: { - 210: ___('Your nickname has been automatically set to %1$s'), - 303: ___('Your nickname has been changed to %1$s') - } - }; - - - function insertRoomInfo (el, stanza) { - /* Insert groupchat info (based on returned #disco IQ stanza) - * - * Parameters: - * (HTMLElement) el: The HTML DOM element that should - * contain the info. - * (XMLElement) stanza: The IQ stanza containing the groupchat - * info. - */ - // All MUC features found here: http://xmpp.org/registrar/disco-features.html - el.querySelector('span.spinner').remove(); - el.querySelector('a.room-info').classList.add('selected'); - el.insertAdjacentHTML( - 'beforeEnd', - tpl_room_description({ - 'jid': stanza.getAttribute('from'), - 'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'), - 'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'), - 'hidden': sizzle('feature[var="muc_hidden"]', stanza).length, - 'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length, - 'moderated': sizzle('feature[var="muc_moderated"]', stanza).length, - 'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length, - 'open': sizzle('feature[var="muc_open"]', stanza).length, - 'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length, - 'persistent': sizzle('feature[var="muc_persistent"]', stanza).length, - 'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length, - 'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length, - 'temporary': sizzle('feature[var="muc_temporary"]', stanza).length, - 'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length, - 'label_desc': __('Description:'), - 'label_jid': __('Groupchat Address (JID):'), - 'label_occ': __('Participants:'), - 'label_features': __('Features:'), - 'label_requires_auth': __('Requires authentication'), - 'label_hidden': __('Hidden'), - 'label_requires_invite': __('Requires an invitation'), - 'label_moderated': __('Moderated'), - 'label_non_anon': __('Non-anonymous'), - 'label_open_room': __('Open'), - 'label_permanent_room': __('Permanent'), - 'label_public': __('Public'), - 'label_semi_anon': __('Semi-anonymous'), - 'label_temp_room': __('Temporary'), - 'label_unmoderated': __('Unmoderated') - })); - } - - function toggleRoomInfo (ev) { - /* Show/hide extra information about a groupchat in a listing. */ - const parent_el = u.ancestor(ev.target, '.room-item'), - div_el = parent_el.querySelector('div.room-info'); - if (div_el) { - u.slideIn(div_el).then(u.removeElement) - parent_el.querySelector('a.room-info').classList.remove('selected'); - } else { - parent_el.insertAdjacentHTML('beforeend', tpl_spinner()); - _converse.api.disco.info(ev.target.getAttribute('data-room-jid'), null) - .then((stanza) => insertRoomInfo(parent_el, stanza)) - .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - } + function toggleRoomInfo (ev) { + /* Show/hide extra information about a groupchat in a listing. */ + const parent_el = u.ancestor(ev.target, '.room-item'), + div_el = parent_el.querySelector('div.room-info'); + if (div_el) { + u.slideIn(div_el).then(u.removeElement) + parent_el.querySelector('a.room-info').classList.remove('selected'); + } else { + parent_el.insertAdjacentHTML('beforeend', tpl_spinner()); + _converse.api.disco.info(ev.target.getAttribute('data-room-jid'), null) + .then((stanza) => insertRoomInfo(parent_el, stanza)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); } + } - _converse.ListChatRoomsModal = _converse.BootstrapModal.extend({ + _converse.ListChatRoomsModal = _converse.BootstrapModal.extend({ - events: { - 'submit form': 'showRooms', - 'click a.room-info': 'toggleRoomInfo', - 'change input[name=nick]': 'setNick', - 'change input[name=server]': 'setDomain', - 'click .open-room': 'openRoom' - }, + events: { + 'submit form': 'showRooms', + 'click a.room-info': 'toggleRoomInfo', + 'change input[name=nick]': 'setNick', + 'change input[name=server]': 'setDomain', + 'click .open-room': 'openRoom' + }, - initialize () { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - this.model.on('change:muc_domain', this.onDomainChange, this); - }, + initialize () { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + this.model.on('change:muc_domain', this.onDomainChange, this); + }, - toHTML () { - return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), { - 'heading_list_chatrooms': __('Query for Groupchats'), - 'label_server_address': __('Server address'), - 'label_query': __('Show groupchats'), - 'server_placeholder': __('conference.example.org') - })); - }, + toHTML () { + return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), { + 'heading_list_chatrooms': __('Query for Groupchats'), + 'label_server_address': __('Server address'), + 'label_query': __('Show groupchats'), + 'server_placeholder': __('conference.example.org') + })); + }, - afterRender () { - this.el.addEventListener('shown.bs.modal', () => { - this.el.querySelector('input[name="server"]').focus(); - }, false); - }, + afterRender () { + this.el.addEventListener('shown.bs.modal', () => { + this.el.querySelector('input[name="server"]').focus(); + }, false); + }, - openRoom (ev) { - ev.preventDefault(); - const jid = ev.target.getAttribute('data-room-jid'); - const name = ev.target.getAttribute('data-room-name'); - this.modal.hide(); - _converse.api.rooms.open(jid, {'name': name}); - }, + openRoom (ev) { + ev.preventDefault(); + const jid = ev.target.getAttribute('data-room-jid'); + const name = ev.target.getAttribute('data-room-name'); + this.modal.hide(); + _converse.api.rooms.open(jid, {'name': name}); + }, - toggleRoomInfo (ev) { - ev.preventDefault(); - toggleRoomInfo(ev); - }, + toggleRoomInfo (ev) { + ev.preventDefault(); + toggleRoomInfo(ev); + }, - onDomainChange (model) { - if (_converse.auto_list_rooms) { - this.updateRoomsList(); - } - }, - - roomStanzaItemToHTMLElement (groupchat) { - const name = Strophe.unescapeNode(groupchat.getAttribute('name') || groupchat.getAttribute('jid')); - const div = document.createElement('div'); - div.innerHTML = tpl_room_item({ - 'name': Strophe.xmlunescape(name), - 'jid': groupchat.getAttribute('jid'), - 'open_title': __('Click to open this groupchat'), - 'info_title': __('Show more information on this groupchat') - }); - return div.firstElementChild; - }, - - removeSpinner () { - _.each(this.el.querySelectorAll('span.spinner'), - (el) => el.parentNode.removeChild(el) - ); - }, - - informNoRoomsFound () { - const chatrooms_el = this.el.querySelector('.available-chatrooms'); - chatrooms_el.innerHTML = tpl_rooms_results({ - 'feedback_text': __('No groupchats found') - }); - const input_el = this.el.querySelector('input[name="server"]'); - input_el.classList.remove('hidden') - this.removeSpinner(); - }, - - onRoomsFound (iq) { - /* Handle the IQ stanza returned from the server, containing - * all its public groupchats. - */ - const available_chatrooms = this.el.querySelector('.available-chatrooms'); - this.rooms = iq.querySelectorAll('query item'); - if (this.rooms.length) { - // For translators: %1$s is a variable and will be - // replaced with the XMPP server name - available_chatrooms.innerHTML = tpl_rooms_results({ - 'feedback_text': __('Groupchats found:') - }); - const fragment = document.createDocumentFragment(); - const children = _.reject(_.map(this.rooms, this.roomStanzaItemToHTMLElement), _.isNil) - _.each(children, (child) => fragment.appendChild(child)); - available_chatrooms.appendChild(fragment); - this.removeSpinner(); - } else { - this.informNoRoomsFound(); - } - return true; - }, - - updateRoomsList () { - /* Send an IQ stanza to the server asking for all groupchats - */ - _converse.connection.sendIQ( - $iq({ - 'to': this.model.get('muc_domain'), - 'from': _converse.connection.jid, - 'type': "get" - }).c("query", {xmlns: Strophe.NS.DISCO_ITEMS}), - this.onRoomsFound.bind(this), - this.informNoRoomsFound.bind(this), - 5000 - ); - }, - - showRooms (ev) { - ev.preventDefault(); - const data = new FormData(ev.target); - this.model.save('muc_domain', Strophe.getDomainFromJid(data.get('server'))); + onDomainChange (model) { + if (_converse.auto_list_rooms) { this.updateRoomsList(); - }, - - setDomain (ev) { - this.model.save('muc_domain', Strophe.getDomainFromJid(ev.target.value)); - }, - - setNick (ev) { - this.model.save({nick: ev.target.value}); } - }); + }, + roomStanzaItemToHTMLElement (groupchat) { + const name = Strophe.unescapeNode(groupchat.getAttribute('name') || groupchat.getAttribute('jid')); + const div = document.createElement('div'); + div.innerHTML = tpl_room_item({ + 'name': Strophe.xmlunescape(name), + 'jid': groupchat.getAttribute('jid'), + 'open_title': __('Click to open this groupchat'), + 'info_title': __('Show more information on this groupchat') + }); + return div.firstElementChild; + }, - _converse.AddChatRoomModal = _converse.BootstrapModal.extend({ + removeSpinner () { + _.each(this.el.querySelectorAll('span.spinner'), + (el) => el.parentNode.removeChild(el) + ); + }, - events: { - 'submit form.add-chatroom': 'openChatRoom' - }, + informNoRoomsFound () { + const chatrooms_el = this.el.querySelector('.available-chatrooms'); + chatrooms_el.innerHTML = tpl_rooms_results({ + 'feedback_text': __('No groupchats found') + }); + const input_el = this.el.querySelector('input[name="server"]'); + input_el.classList.remove('hidden') + this.removeSpinner(); + }, - toHTML () { - return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), { - 'heading_new_chatroom': __('Enter a new Groupchat'), - 'label_room_address': __('Groupchat address'), - 'label_nickname': __('Optional nickname'), - 'chatroom_placeholder': __('name@conference.example.org'), - 'label_join': __('Join'), - })); - }, - - afterRender () { - this.el.addEventListener('shown.bs.modal', () => { - this.el.querySelector('input[name="chatroom"]').focus(); - }, false); - }, - - parseRoomDataFromEvent (form) { - const data = new FormData(form); - const jid = data.get('chatroom'); - this.model.save('muc_domain', Strophe.getDomainFromJid(jid)); - return { - 'jid': jid, - 'nick': data.get('nickname') - } - }, - - openChatRoom (ev) { - ev.preventDefault(); - const data = this.parseRoomDataFromEvent(ev.target); - if (data.nick === "") { - // Make sure defaults apply if no nick is provided. - data.nick = undefined; - } - _converse.api.rooms.open(data.jid, data); - this.modal.hide(); - ev.target.reset(); - } - }); - - - _converse.RoomDetailsModal = _converse.BootstrapModal.extend({ - - initialize () { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - this.model.on('change', this.render, this); - this.model.occupants.on('add', this.render, this); - this.model.occupants.on('change', this.render, this); - }, - - toHTML () { - return tpl_chatroom_details_modal(_.extend( - this.model.toJSON(), { - '_': _, - '__': __, - 'topic': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), - 'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()), - 'num_occupants': this.model.occupants.length - }) - ); - } - }); - - - _converse.ChatRoomView = _converse.ChatBoxView.extend({ - /* Backbone.NativeView which renders a groupchat, based upon the view - * for normal one-on-one chat boxes. + onRoomsFound (iq) { + /* Handle the IQ stanza returned from the server, containing + * all its public groupchats. */ - length: 300, - tagName: 'div', - className: 'chatbox chatroom hidden', - is_chatroom: true, - events: { - 'change input.fileupload': 'onFileSelection', - 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', - 'click .chatbox-navback': 'showControlBox', - 'click .close-chatbox-button': 'close', - 'click .configure-chatroom-button': 'getAndRenderConfigurationForm', - 'click .hide-occupants': 'hideOccupants', - 'click .new-msgs-indicator': 'viewUnreadMessages', - 'click .occupant-nick': 'onOccupantClicked', - 'click .send-button': 'onFormSubmitted', - 'click .show-room-details-modal': 'showRoomDetailsModal', - 'click .toggle-call': 'toggleCall', - 'click .toggle-occupants': 'toggleOccupants', - 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', - 'click .toggle-smiley': 'toggleEmojiMenu', - 'click .upload-file': 'toggleFileUpload', - 'keydown .chat-textarea': 'keyPressed', - 'keyup .chat-textarea': 'keyUp', - 'input .chat-textarea': 'inputChanged' - }, + const available_chatrooms = this.el.querySelector('.available-chatrooms'); + this.rooms = iq.querySelectorAll('query item'); + if (this.rooms.length) { + // For translators: %1$s is a variable and will be + // replaced with the XMPP server name + available_chatrooms.innerHTML = tpl_rooms_results({ + 'feedback_text': __('Groupchats found:') + }); + const fragment = document.createDocumentFragment(); + const children = _.reject(_.map(this.rooms, this.roomStanzaItemToHTMLElement), _.isNil) + _.each(children, (child) => fragment.appendChild(child)); + available_chatrooms.appendChild(fragment); + this.removeSpinner(); + } else { + this.informNoRoomsFound(); + } + return true; + }, - initialize () { - this.initDebounced(); + updateRoomsList () { + /* Send an IQ stanza to the server asking for all groupchats + */ + _converse.connection.sendIQ( + $iq({ + 'to': this.model.get('muc_domain'), + 'from': _converse.connection.jid, + 'type': "get" + }).c("query", {xmlns: Strophe.NS.DISCO_ITEMS}), + this.onRoomsFound.bind(this), + this.informNoRoomsFound.bind(this), + 5000 + ); + }, - this.model.messages.on('add', this.onMessageAdded, this); - this.model.messages.on('rendered', this.scrollDown, this); + showRooms (ev) { + ev.preventDefault(); + const data = new FormData(ev.target); + this.model.save('muc_domain', Strophe.getDomainFromJid(data.get('server'))); + this.updateRoomsList(); + }, - this.model.on('change:affiliation', this.renderHeading, this); - this.model.on('change:connection_status', this.afterConnected, this); - this.model.on('change:jid', this.renderHeading, this); - this.model.on('change:name', this.renderHeading, this); - this.model.on('change:subject', this.renderHeading, this); - this.model.on('change:subject', this.setChatRoomSubject, this); - this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this); - this.model.on('destroy', this.hide, this); - this.model.on('show', this.show, this); + setDomain (ev) { + this.model.save('muc_domain', Strophe.getDomainFromJid(ev.target.value)); + }, - this.model.occupants.on('add', this.onOccupantAdded, this); - this.model.occupants.on('remove', this.onOccupantRemoved, this); - this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this); - this.model.occupants.on('change:role', this.informOfOccupantsRoleChange, this); - this.model.occupants.on('change:affiliation', this.informOfOccupantsAffiliationChange, this); + setNick (ev) { + this.model.save({nick: ev.target.value}); + } + }); - this.createEmojiPicker(); - this.createOccupantsView(); - this.render().insertIntoDOM(); - this.registerHandlers(); - this.enterRoom(); - }, - enterRoom (ev) { - if (ev) { ev.preventDefault(); } - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - const handler = () => { - if (!u.isPersistableModel(this.model)) { - // Happens during tests, nothing to do if this - // is a hanging chatbox (i.e. not in the collection anymore). - return; - } - this.populateAndJoin(); - _converse.emit('chatRoomOpened', this); + _converse.AddChatRoomModal = _converse.BootstrapModal.extend({ + + events: { + 'submit form.add-chatroom': 'openChatRoom' + }, + + toHTML () { + return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), { + 'heading_new_chatroom': __('Enter a new Groupchat'), + 'label_room_address': __('Groupchat address'), + 'label_nickname': __('Optional nickname'), + 'chatroom_placeholder': __('name@conference.example.org'), + 'label_join': __('Join'), + })); + }, + + afterRender () { + this.el.addEventListener('shown.bs.modal', () => { + this.el.querySelector('input[name="chatroom"]').focus(); + }, false); + }, + + parseRoomDataFromEvent (form) { + const data = new FormData(form); + const jid = data.get('chatroom'); + this.model.save('muc_domain', Strophe.getDomainFromJid(jid)); + return { + 'jid': jid, + 'nick': data.get('nickname') + } + }, + + openChatRoom (ev) { + ev.preventDefault(); + const data = this.parseRoomDataFromEvent(ev.target); + if (data.nick === "") { + // Make sure defaults apply if no nick is provided. + data.nick = undefined; + } + _converse.api.rooms.open(data.jid, data); + this.modal.hide(); + ev.target.reset(); + } + }); + + + _converse.RoomDetailsModal = _converse.BootstrapModal.extend({ + + initialize () { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + this.model.on('change', this.render, this); + this.model.occupants.on('add', this.render, this); + this.model.occupants.on('change', this.render, this); + }, + + toHTML () { + return tpl_chatroom_details_modal(_.extend( + this.model.toJSON(), { + '_': _, + '__': __, + 'topic': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), + 'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()), + 'num_occupants': this.model.occupants.length + }) + ); + } + }); + + + _converse.ChatRoomView = _converse.ChatBoxView.extend({ + /* Backbone.NativeView which renders a groupchat, based upon the view + * for normal one-on-one chat boxes. + */ + length: 300, + tagName: 'div', + className: 'chatbox chatroom hidden', + is_chatroom: true, + events: { + 'change input.fileupload': 'onFileSelection', + 'click .chat-msg__action-edit': 'onMessageEditButtonClicked', + 'click .chatbox-navback': 'showControlBox', + 'click .close-chatbox-button': 'close', + 'click .configure-chatroom-button': 'getAndRenderConfigurationForm', + 'click .hide-occupants': 'hideOccupants', + 'click .new-msgs-indicator': 'viewUnreadMessages', + 'click .occupant-nick': 'onOccupantClicked', + 'click .send-button': 'onFormSubmitted', + 'click .show-room-details-modal': 'showRoomDetailsModal', + 'click .toggle-call': 'toggleCall', + 'click .toggle-occupants': 'toggleOccupants', + 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', + 'click .toggle-smiley': 'toggleEmojiMenu', + 'click .upload-file': 'toggleFileUpload', + 'keydown .chat-textarea': 'keyPressed', + 'keyup .chat-textarea': 'keyUp', + 'input .chat-textarea': 'inputChanged' + }, + + initialize () { + this.initDebounced(); + + this.model.messages.on('add', this.onMessageAdded, this); + this.model.messages.on('rendered', this.scrollDown, this); + + this.model.on('change:affiliation', this.renderHeading, this); + this.model.on('change:connection_status', this.afterConnected, this); + this.model.on('change:jid', this.renderHeading, this); + this.model.on('change:name', this.renderHeading, this); + this.model.on('change:subject', this.renderHeading, this); + this.model.on('change:subject', this.setChatRoomSubject, this); + this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this); + this.model.on('destroy', this.hide, this); + this.model.on('show', this.show, this); + + this.model.occupants.on('add', this.onOccupantAdded, this); + this.model.occupants.on('remove', this.onOccupantRemoved, this); + this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this); + this.model.occupants.on('change:role', this.informOfOccupantsRoleChange, this); + this.model.occupants.on('change:affiliation', this.informOfOccupantsAffiliationChange, this); + + this.createEmojiPicker(); + this.createOccupantsView(); + this.render().insertIntoDOM(); + this.registerHandlers(); + this.enterRoom(); + }, + + enterRoom (ev) { + if (ev) { ev.preventDefault(); } + if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { + const handler = () => { + if (!u.isPersistableModel(this.model)) { + // Happens during tests, nothing to do if this + // is a hanging chatbox (i.e. not in the collection anymore). + return; } - this.model.getRoomFeatures().then(handler, handler); - } else { - this.fetchMessages(); + this.populateAndJoin(); _converse.emit('chatRoomOpened', this); } - }, - - render () { - this.el.setAttribute('id', this.model.get('box_id')); - this.el.innerHTML = tpl_chatroom(); - this.renderHeading(); - this.renderChatArea(); - this.renderMessageForm(); - this.initAutoComplete(); - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - this.showSpinner(); - } - return this; - }, - - renderHeading () { - /* Render the heading UI of the groupchat. */ - this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML(); - }, - - renderChatArea () { - /* Render the UI container in which groupchat messages will appear. - */ - if (_.isNull(this.el.querySelector('.chat-area'))) { - const container_el = this.el.querySelector('.chatroom-body'); - container_el.insertAdjacentHTML('beforeend', tpl_chatarea({ - 'show_send_button': _converse.show_send_button - })); - container_el.insertAdjacentElement('beforeend', this.occupantsview.el); - this.content = this.el.querySelector('.chat-content'); - this.toggleOccupants(null, true); - } - return this; - }, - - initAutoComplete () { - this.auto_complete = new _converse.AutoComplete(this.el, { - 'auto_first': true, - 'auto_evaluate': false, - 'min_chars': 1, - 'match_current_word': true, - 'match_on_tab': true, - 'list': () => this.model.occupants.map(o => ({'label': o.getDisplayName(), 'value': `@${o.getDisplayName()}`})), - 'filter': _converse.FILTER_STARTSWITH, - 'trigger_on_at': true - }); - this.auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false)); - }, - - keyPressed (ev) { - if (this.auto_complete.keyPressed(ev)) { - return; - } - return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments); - }, - - keyUp (ev) { - this.auto_complete.evaluate(ev); - }, - - showRoomDetailsModal (ev) { - ev.preventDefault(); - if (_.isUndefined(this.model.room_details_modal)) { - this.model.room_details_modal = new _converse.RoomDetailsModal({'model': this.model}); - } - this.model.room_details_modal.show(ev); - }, - - showChatStateNotification (message) { - if (message.get('sender') === 'me') { - return; - } - return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments); - }, - - createOccupantsView () { - /* Create the ChatRoomOccupantsView Backbone.NativeView - */ - this.model.occupants.chatroomview = this; - this.occupantsview = new _converse.ChatRoomOccupantsView({'model': this.model.occupants}); - return this; - }, - - informOfOccupantsAffiliationChange(occupant, changed) { - const previous_affiliation = occupant._previousAttributes.affiliation, - current_affiliation = occupant.get('affiliation'); - - if (previous_affiliation === 'admin') { - this.showChatEvent(__("%1$s is no longer an admin of this groupchat", occupant.get('nick'))) - } else if (previous_affiliation === 'owner') { - this.showChatEvent(__("%1$s is no longer an owner of this groupchat", occupant.get('nick'))) - } else if (previous_affiliation === 'outcast') { - this.showChatEvent(__("%1$s is no longer banned from this groupchat", occupant.get('nick'))) - } - - if (current_affiliation === 'none' && previous_affiliation === 'member') { - this.showChatEvent(__("%1$s is no longer a permanent member of this groupchat", occupant.get('nick'))) - } if (current_affiliation === 'member') { - this.showChatEvent(__("%1$s is now a permanent member of this groupchat", occupant.get('nick'))) - } else if (current_affiliation === 'outcast') { - this.showChatEvent(__("%1$s has been banned from this groupchat", occupant.get('nick'))) - } else if (current_affiliation === 'admin' || current_affiliation == 'owner') { - this.showChatEvent(__(`%1$s is now an ${current_affiliation} of this groupchat`, occupant.get('nick'))) - } - }, - - informOfOccupantsRoleChange (occupant, changed) { - const previous_role = occupant._previousAttributes.role; - if (previous_role === 'moderator') { - this.showChatEvent(__("%1$s is no longer a moderator", occupant.get('nick'))) - } - if (previous_role === 'visitor') { - this.showChatEvent(__("%1$s has been given a voice again", occupant.get('nick'))) - } - if (occupant.get('role') === 'visitor') { - this.showChatEvent(__("%1$s has been muted", occupant.get('nick'))) - } - if (occupant.get('role') === 'moderator') { - this.showChatEvent(__("%1$s is now a moderator", occupant.get('nick'))) - } - }, - - generateHeadingHTML () { - /* Returns the heading HTML to be rendered. - */ - return tpl_chatroom_head( - _.extend(this.model.toJSON(), { - 'Strophe': Strophe, - 'info_close': __('Close and leave this groupchat'), - 'info_configure': __('Configure this groupchat'), - 'info_details': __('Show more details about this groupchat'), - 'description': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), - })); - }, - - afterShown () { - /* Override from converse-chatview, specifically to avoid - * the 'active' chat state from being sent out prematurely. - * - * This is instead done in `afterConnected` below. - */ - if (u.isPersistableModel(this.model)) { - this.model.clearUnreadMsgCounter(); - this.model.save(); - } - this.occupantsview.setOccupantsHeight(); - this.scrollDown(); - this.renderEmojiPicker(); - }, - - show () { - if (u.isVisible(this.el)) { - this.focus(); - return; - } - // Override from converse-chatview in order to not use - // "fadeIn", which causes flashing. - u.showElement(this.el); - this.afterShown(); - }, - - afterConnected () { - if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) { - this.hideSpinner(); - this.setChatState(_converse.ACTIVE); - this.scrollDown(); - this.focus(); - } - }, - - getToolbarOptions () { - return _.extend( - _converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), - { - 'label_hide_occupants': __('Hide the list of participants'), - 'show_occupants_toggle': this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants - } - ); - }, - - close (ev) { - /* Close this chat box, which implies leaving the groupchat as - * well. - */ - this.hide(); - if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) { - _converse.router.navigate(''); - } - this.model.leave(); - _converse.ChatBoxView.prototype.close.apply(this, arguments); - }, - - setOccupantsVisibility () { - const icon_el = this.el.querySelector('.toggle-occupants'); - if (this.model.get('hidden_occupants')) { - u.removeClass('fa-angle-double-right', icon_el); - u.addClass('fa-angle-double-left', icon_el); - u.addClass('full', this.el.querySelector('.chat-area')); - u.hideElement(this.el.querySelector('.occupants')); - } else { - u.addClass('fa-angle-double-right', icon_el); - u.removeClass('fa-angle-double-left', icon_el); - u.removeClass('full', this.el.querySelector('.chat-area')); - u.removeClass('hidden', this.el.querySelector('.occupants')); - } - this.occupantsview.setOccupantsHeight(); - }, - - hideOccupants (ev, preserve_state) { - /* Show or hide the right sidebar containing the chat - * occupants (and the invite widget). - */ - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - this.model.save({'hidden_occupants': true}); - this.setOccupantsVisibility(); - this.scrollDown(); - }, - - toggleOccupants (ev, preserve_state) { - /* Show or hide the right sidebar containing the chat - * occupants (and the invite widget). - */ - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - if (!preserve_state) { - this.model.set({'hidden_occupants': !this.model.get('hidden_occupants')}); - } - this.setOccupantsVisibility(); - this.scrollDown(); - }, - - onOccupantClicked (ev) { - /* When an occupant is clicked, insert their nickname into - * the chat textarea input. - */ - this.insertIntoTextArea(ev.target.textContent); - }, - - handleChatStateNotification (message) { - /* Override the method on the ChatBoxView base class to - * ignore notifications in groupchats. - * - * As laid out in the business rules in XEP-0085 - * http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat - */ - if (message.get('fullname') === this.model.get('nick')) { - // Don't know about other servers, but OpenFire sends - // back to you your own chat state notifications. - // We ignore them here... - return; - } - if (message.get('chat_state') !== _converse.GONE) { - _converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments); - } - }, - - modifyRole (groupchat, nick, role, reason, onSuccess, onError) { - const item = $build("item", {nick, role}); - const iq = $iq({to: groupchat, type: "set"}).c("query", {xmlns: Strophe.NS.MUC_ADMIN}).cnode(item.node); - if (reason !== null) { iq.c("reason", reason); } - return _converse.connection.sendIQ(iq, onSuccess, onError); - }, - - verifyRoles (roles) { - const me = this.model.occupants.findWhere({'jid': _converse.bare_jid}); - if (!_.includes(roles, me.get('role'))) { - this.showErrorMessage(__(`Forbidden: you do not have the necessary role in order to do that.`)) - return false; - } - return true; - }, - - verifyAffiliations (affiliations) { - const me = this.model.occupants.findWhere({'jid': _converse.bare_jid}); - if (!_.includes(affiliations, me.get('affiliation'))) { - this.showErrorMessage(__(`Forbidden: you do not have the necessary affiliation in order to do that.`)) - return false; - } - return true; - }, - - validateRoleChangeCommand (command, args) { - /* Check that a command to change a groupchat user's role or - * affiliation has anough arguments. - */ - if (args.length < 1 || args.length > 2) { - this.showErrorMessage( - __('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', command) - ); - return false; - } - if (!this.model.occupants.findWhere({'nick': args[0]}) && !this.model.occupants.findWhere({'jid': args[0]})) { - this.showErrorMessage(__('Error: couldn\'t find a groupchat participant "%1$s"', args[0])); - return false; - } - return true; - }, - - onCommandError (err) { - _converse.log(err, Strophe.LogLevel.FATAL); - this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details.")); - }, - - parseMessageForCommands (text) { - if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) { - return true; - } - if (_converse.muc_disable_moderator_commands) { - return false; - } - const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''], - args = match[2] && match[2].splitOnce(' ').filter(s => s) || [], - command = match[1].toLowerCase(); - switch (command) { - case 'admin': - if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.model.setAffiliation('admin', [{ - 'jid': args[0], - 'reason': args[1] - }]).then( - () => this.model.occupants.fetchMembers(), - (err) => this.onCommandError(err) - ); - break; - case 'ban': - if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.model.setAffiliation('outcast', [{ - 'jid': args[0], - 'reason': args[1] - }]).then( - () => this.model.occupants.fetchMembers(), - (err) => this.onCommandError(err) - ); - break; - case 'deop': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.modifyRole( - this.model.get('jid'), args[0], 'participant', args[1], - undefined, this.onCommandError.bind(this)); - break; - case 'help': - this.showHelpMessages([ - `/admin: ${__("Change user's affiliation to admin")}`, - `/ban: ${__('Ban user from groupchat')}`, - `/clear: ${__('Remove messages')}`, - `/deop: ${__('Change user role to participant')}`, - `/help: ${__('Show this menu')}`, - `/kick: ${__('Kick user from groupchat')}`, - `/me: ${__('Write in 3rd person')}`, - `/member: ${__('Grant membership to a user')}`, - `/mute: ${__("Remove user's ability to post messages")}`, - `/nick: ${__('Change your nickname')}`, - `/op: ${__('Grant moderator role to user')}`, - `/owner: ${__('Grant ownership of this groupchat')}`, - `/register: ${__("Register a nickname for this room")}`, - `/revoke: ${__("Revoke user's membership")}`, - `/subject: ${__('Set groupchat subject')}`, - `/topic: ${__('Set groupchat subject (alias for /subject)')}`, - `/voice: ${__('Allow muted user to post messages')}` - ]); - break; - case 'kick': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.modifyRole( - this.model.get('jid'), args[0], 'none', args[1], - undefined, this.onCommandError.bind(this)); - break; - case 'mute': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.modifyRole( - this.model.get('jid'), args[0], 'visitor', args[1], - undefined, this.onCommandError.bind(this)); - break; - case 'member': { - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - const occupant = this.model.occupants.findWhere({'nick': args[0]}) || - this.model.occupants.findWhere({'jid': args[0]}), - attrs = { - 'jid': occupant.get('jid'), - 'reason': args[1] - }; - if (_converse.auto_register_muc_nickname) { - attrs['nick'] = occupant.get('nick'); - } - this.model.setAffiliation('member', [attrs]) - .then(() => this.model.occupants.fetchMembers()) - .catch(err => this.onCommandError(err)); - break; - } case 'nick': - if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { - break; - } - _converse.connection.send($pres({ - from: _converse.connection.jid, - to: this.model.getRoomJIDAndNick(match[2]), - id: _converse.connection.getUniqueId() - }).tree()); - break; - case 'owner': - if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.model.setAffiliation('owner', [{ - 'jid': args[0], - 'reason': args[1] - }]).then( - () => this.model.occupants.fetchMembers(), - (err) => this.onCommandError(err) - ); - break; - case 'op': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.modifyRole( - this.model.get('jid'), args[0], 'moderator', args[1], - undefined, this.onCommandError.bind(this)); - break; - case 'register': - if (args.length > 1) { - this.showErrorMessage(__(`Error: invalid number of arguments`)) - } else { - this.model.registerNickname().then(err_msg => { - if (err_msg) this.showErrorMessage(err_msg) - }); - } - break; - case 'revoke': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.model.setAffiliation('none', [{ - 'jid': args[0], - 'reason': args[1] - }]).then( - () => this.model.occupants.fetchMembers(), - (err) => this.onCommandError(err) - ); - break; - case 'topic': - case 'subject': - // TODO: should be done via API call to _converse.api.rooms - _converse.connection.send( - $msg({ - to: this.model.get('jid'), - from: _converse.connection.jid, - type: "groupchat" - }).c("subject", {xmlns: "jabber:client"}).t(match[2] || "").tree() - ); - break; - case 'voice': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { - break; - } - this.modifyRole( - this.model.get('jid'), args[0], 'participant', args[1], - undefined, this.onCommandError.bind(this)); - break; - default: - return false; - } - return true; - }, - - registerHandlers () { - /* Register presence and message handlers for this chat - * groupchat - */ - // XXX: Ideally this can be refactored out so that we don't - // need to do stanza processing inside the views in this - // module. See the comment in "onPresence" for more info. - this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this)); - // XXX instead of having a method showStatusMessages, we could instead - // create message models in converse-muc.js and then give them views in this module. - this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this)); - }, - - onPresence (pres) { - /* Handles all MUC presence stanzas. - * - * Parameters: - * (XMLElement) pres: The stanza - */ - // XXX: Current thinking is that excessive stanza - // processing inside a view is a "code smell". - // Instead stanza processing should happen inside the - // models/collections. - if (pres.getAttribute('type') === 'error') { - this.showErrorMessageFromPresence(pres); - } else { - // Instead of doing it this way, we could perhaps rather - // create StatusMessage objects inside the messages - // Collection and then simply render those. Then stanza - // processing is done on the model and rendering in the - // view(s). - this.showStatusMessages(pres); - } - }, - - populateAndJoin () { - this.model.occupants.fetchMembers(); - this.join(); + this.model.getRoomFeatures().then(handler, handler); + } else { this.fetchMessages(); - }, + _converse.emit('chatRoomOpened', this); + } + }, - join (nick, password) { - /* Join the groupchat. - * - * Parameters: - * (String) nick: The user's nickname - * (String) password: Optional password, if required by - * the groupchat. - */ - if (!nick && !this.model.get('nick')) { - this.checkForReservedNick(); - return this; - } - this.model.join(nick, password); - return this; - }, - - renderConfigurationForm (stanza) { - /* Renders a form given an IQ stanza containing the current - * groupchat configuration. - * - * Returns a promise which resolves once the user has - * either submitted the form, or canceled it. - * - * Parameters: - * (XMLElement) stanza: The IQ stanza containing the groupchat - * config. - */ - const container_el = this.el.querySelector('.chatroom-body'); - _.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement); - _.each(container_el.children, u.hideElement); - container_el.insertAdjacentHTML('beforeend', tpl_chatroom_form()); - - const form_el = container_el.querySelector('form.chatroom-form'), - fieldset_el = form_el.querySelector('fieldset'), - fields = stanza.querySelectorAll('field'), - title = _.get(stanza.querySelector('title'), 'textContent'), - instructions = _.get(stanza.querySelector('instructions'), 'textContent'); - - u.removeElement(fieldset_el.querySelector('span.spinner')); - fieldset_el.insertAdjacentHTML('beforeend', `${title}`); - - if (instructions && instructions !== title) { - fieldset_el.insertAdjacentHTML('beforeend', `

${instructions}

`); - } - _.each(fields, function (field) { - fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza)); - }); - - // Render save/cancel buttons - const last_fieldset_el = document.createElement('fieldset'); - last_fieldset_el.insertAdjacentHTML( - 'beforeend', - ``); - last_fieldset_el.insertAdjacentHTML( - 'beforeend', - ``); - form_el.insertAdjacentElement('beforeend', last_fieldset_el); - - last_fieldset_el.querySelector('input[type=button]').addEventListener('click', (ev) => { - ev.preventDefault(); - this.closeForm(); - }); - - form_el.addEventListener('submit', - (ev) => { - ev.preventDefault(); - this.model.saveConfiguration(ev.target) - .then(() => this.model.refreshRoomFeatures()); - this.closeForm(); - }, - false - ); - }, - - closeForm () { - /* Remove the configuration form without submitting and - * return to the chat view. - */ - u.removeElement(this.el.querySelector('.chatroom-form-container')); - this.renderAfterTransition(); - }, - - getAndRenderConfigurationForm (ev) { - /* Start the process of configuring a groupchat, either by - * rendering a configuration form, or by auto-configuring - * based on the "roomconfig" data stored on the - * Backbone.Model. - * - * Stores the new configuration on the Backbone.Model once - * completed. - * - * Paremeters: - * (Event) ev: DOM event that might be passed in if this - * method is called due to a user action. In this - * case, auto-configure won't happen, regardless of - * the settings. - */ + render () { + this.el.setAttribute('id', this.model.get('box_id')); + this.el.innerHTML = tpl_chatroom(); + this.renderHeading(); + this.renderChatArea(); + this.renderMessageForm(); + this.initAutoComplete(); + if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { this.showSpinner(); - this.model.fetchRoomConfiguration() - .then(this.renderConfigurationForm.bind(this)) - .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - }, + } + return this; + }, - submitNickname (ev) { - /* Get the nickname value from the form and then join the - * groupchat with it. - */ + renderHeading () { + /* Render the heading UI of the groupchat. */ + this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML(); + }, + + renderChatArea () { + /* Render the UI container in which groupchat messages will appear. + */ + if (_.isNull(this.el.querySelector('.chat-area'))) { + const container_el = this.el.querySelector('.chatroom-body'); + container_el.insertAdjacentHTML('beforeend', tpl_chatarea({ + 'show_send_button': _converse.show_send_button + })); + container_el.insertAdjacentElement('beforeend', this.occupantsview.el); + this.content = this.el.querySelector('.chat-content'); + this.toggleOccupants(null, true); + } + return this; + }, + + initAutoComplete () { + this.auto_complete = new _converse.AutoComplete(this.el, { + 'auto_first': true, + 'auto_evaluate': false, + 'min_chars': 1, + 'match_current_word': true, + 'match_on_tab': true, + 'list': () => this.model.occupants.map(o => ({'label': o.getDisplayName(), 'value': `@${o.getDisplayName()}`})), + 'filter': _converse.FILTER_STARTSWITH, + 'trigger_on_at': true + }); + this.auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false)); + }, + + keyPressed (ev) { + if (this.auto_complete.keyPressed(ev)) { + return; + } + return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments); + }, + + keyUp (ev) { + this.auto_complete.evaluate(ev); + }, + + showRoomDetailsModal (ev) { + ev.preventDefault(); + if (_.isUndefined(this.model.room_details_modal)) { + this.model.room_details_modal = new _converse.RoomDetailsModal({'model': this.model}); + } + this.model.room_details_modal.show(ev); + }, + + showChatStateNotification (message) { + if (message.get('sender') === 'me') { + return; + } + return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments); + }, + + createOccupantsView () { + /* Create the ChatRoomOccupantsView Backbone.NativeView + */ + this.model.occupants.chatroomview = this; + this.occupantsview = new _converse.ChatRoomOccupantsView({'model': this.model.occupants}); + return this; + }, + + informOfOccupantsAffiliationChange(occupant, changed) { + const previous_affiliation = occupant._previousAttributes.affiliation, + current_affiliation = occupant.get('affiliation'); + + if (previous_affiliation === 'admin') { + this.showChatEvent(__("%1$s is no longer an admin of this groupchat", occupant.get('nick'))) + } else if (previous_affiliation === 'owner') { + this.showChatEvent(__("%1$s is no longer an owner of this groupchat", occupant.get('nick'))) + } else if (previous_affiliation === 'outcast') { + this.showChatEvent(__("%1$s is no longer banned from this groupchat", occupant.get('nick'))) + } + + if (current_affiliation === 'none' && previous_affiliation === 'member') { + this.showChatEvent(__("%1$s is no longer a permanent member of this groupchat", occupant.get('nick'))) + } if (current_affiliation === 'member') { + this.showChatEvent(__("%1$s is now a permanent member of this groupchat", occupant.get('nick'))) + } else if (current_affiliation === 'outcast') { + this.showChatEvent(__("%1$s has been banned from this groupchat", occupant.get('nick'))) + } else if (current_affiliation === 'admin' || current_affiliation == 'owner') { + this.showChatEvent(__(`%1$s is now an ${current_affiliation} of this groupchat`, occupant.get('nick'))) + } + }, + + informOfOccupantsRoleChange (occupant, changed) { + const previous_role = occupant._previousAttributes.role; + if (previous_role === 'moderator') { + this.showChatEvent(__("%1$s is no longer a moderator", occupant.get('nick'))) + } + if (previous_role === 'visitor') { + this.showChatEvent(__("%1$s has been given a voice again", occupant.get('nick'))) + } + if (occupant.get('role') === 'visitor') { + this.showChatEvent(__("%1$s has been muted", occupant.get('nick'))) + } + if (occupant.get('role') === 'moderator') { + this.showChatEvent(__("%1$s is now a moderator", occupant.get('nick'))) + } + }, + + generateHeadingHTML () { + /* Returns the heading HTML to be rendered. + */ + return tpl_chatroom_head( + _.extend(this.model.toJSON(), { + 'Strophe': Strophe, + 'info_close': __('Close and leave this groupchat'), + 'info_configure': __('Configure this groupchat'), + 'info_details': __('Show more details about this groupchat'), + 'description': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), + })); + }, + + afterShown () { + /* Override from converse-chatview, specifically to avoid + * the 'active' chat state from being sent out prematurely. + * + * This is instead done in `afterConnected` below. + */ + if (u.isPersistableModel(this.model)) { + this.model.clearUnreadMsgCounter(); + this.model.save(); + } + this.occupantsview.setOccupantsHeight(); + this.scrollDown(); + this.renderEmojiPicker(); + }, + + show () { + if (u.isVisible(this.el)) { + this.focus(); + return; + } + // Override from converse-chatview in order to not use + // "fadeIn", which causes flashing. + u.showElement(this.el); + this.afterShown(); + }, + + afterConnected () { + if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) { + this.hideSpinner(); + this.setChatState(_converse.ACTIVE); + this.scrollDown(); + this.focus(); + } + }, + + getToolbarOptions () { + return _.extend( + _converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), + { + 'label_hide_occupants': __('Hide the list of participants'), + 'show_occupants_toggle': this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants + } + ); + }, + + close (ev) { + /* Close this chat box, which implies leaving the groupchat as + * well. + */ + this.hide(); + if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) { + _converse.router.navigate(''); + } + this.model.leave(); + _converse.ChatBoxView.prototype.close.apply(this, arguments); + }, + + setOccupantsVisibility () { + const icon_el = this.el.querySelector('.toggle-occupants'); + if (this.model.get('hidden_occupants')) { + u.removeClass('fa-angle-double-right', icon_el); + u.addClass('fa-angle-double-left', icon_el); + u.addClass('full', this.el.querySelector('.chat-area')); + u.hideElement(this.el.querySelector('.occupants')); + } else { + u.addClass('fa-angle-double-right', icon_el); + u.removeClass('fa-angle-double-left', icon_el); + u.removeClass('full', this.el.querySelector('.chat-area')); + u.removeClass('hidden', this.el.querySelector('.occupants')); + } + this.occupantsview.setOccupantsHeight(); + }, + + hideOccupants (ev, preserve_state) { + /* Show or hide the right sidebar containing the chat + * occupants (and the invite widget). + */ + if (ev) { ev.preventDefault(); - const nick_el = ev.target.nick; - const nick = nick_el.value; - if (!nick) { - nick_el.classList.add('error'); + ev.stopPropagation(); + } + this.model.save({'hidden_occupants': true}); + this.setOccupantsVisibility(); + this.scrollDown(); + }, + + toggleOccupants (ev, preserve_state) { + /* Show or hide the right sidebar containing the chat + * occupants (and the invite widget). + */ + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + if (!preserve_state) { + this.model.set({'hidden_occupants': !this.model.get('hidden_occupants')}); + } + this.setOccupantsVisibility(); + this.scrollDown(); + }, + + onOccupantClicked (ev) { + /* When an occupant is clicked, insert their nickname into + * the chat textarea input. + */ + this.insertIntoTextArea(ev.target.textContent); + }, + + handleChatStateNotification (message) { + /* Override the method on the ChatBoxView base class to + * ignore notifications in groupchats. + * + * As laid out in the business rules in XEP-0085 + * http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat + */ + if (message.get('fullname') === this.model.get('nick')) { + // Don't know about other servers, but OpenFire sends + // back to you your own chat state notifications. + // We ignore them here... + return; + } + if (message.get('chat_state') !== _converse.GONE) { + _converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments); + } + }, + + modifyRole (groupchat, nick, role, reason, onSuccess, onError) { + const item = $build("item", {nick, role}); + const iq = $iq({to: groupchat, type: "set"}).c("query", {xmlns: Strophe.NS.MUC_ADMIN}).cnode(item.node); + if (reason !== null) { iq.c("reason", reason); } + return _converse.connection.sendIQ(iq, onSuccess, onError); + }, + + verifyRoles (roles) { + const me = this.model.occupants.findWhere({'jid': _converse.bare_jid}); + if (!_.includes(roles, me.get('role'))) { + this.showErrorMessage(__(`Forbidden: you do not have the necessary role in order to do that.`)) + return false; + } + return true; + }, + + verifyAffiliations (affiliations) { + const me = this.model.occupants.findWhere({'jid': _converse.bare_jid}); + if (!_.includes(affiliations, me.get('affiliation'))) { + this.showErrorMessage(__(`Forbidden: you do not have the necessary affiliation in order to do that.`)) + return false; + } + return true; + }, + + validateRoleChangeCommand (command, args) { + /* Check that a command to change a groupchat user's role or + * affiliation has anough arguments. + */ + if (args.length < 1 || args.length > 2) { + this.showErrorMessage( + __('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', command) + ); + return false; + } + if (!this.model.occupants.findWhere({'nick': args[0]}) && !this.model.occupants.findWhere({'jid': args[0]})) { + this.showErrorMessage(__('Error: couldn\'t find a groupchat participant "%1$s"', args[0])); + return false; + } + return true; + }, + + onCommandError (err) { + _converse.log(err, Strophe.LogLevel.FATAL); + this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details.")); + }, + + parseMessageForCommands (text) { + if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) { + return true; + } + if (_converse.muc_disable_moderator_commands) { + return false; + } + const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''], + args = match[2] && match[2].splitOnce(' ').filter(s => s) || [], + command = match[1].toLowerCase(); + switch (command) { + case 'admin': + if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.model.setAffiliation('admin', [{ + 'jid': args[0], + 'reason': args[1] + }]).then( + () => this.model.occupants.fetchMembers(), + (err) => this.onCommandError(err) + ); + break; + case 'ban': + if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.model.setAffiliation('outcast', [{ + 'jid': args[0], + 'reason': args[1] + }]).then( + () => this.model.occupants.fetchMembers(), + (err) => this.onCommandError(err) + ); + break; + case 'deop': + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.modifyRole( + this.model.get('jid'), args[0], 'participant', args[1], + undefined, this.onCommandError.bind(this)); + break; + case 'help': + this.showHelpMessages([ + `/admin: ${__("Change user's affiliation to admin")}`, + `/ban: ${__('Ban user from groupchat')}`, + `/clear: ${__('Remove messages')}`, + `/deop: ${__('Change user role to participant')}`, + `/help: ${__('Show this menu')}`, + `/kick: ${__('Kick user from groupchat')}`, + `/me: ${__('Write in 3rd person')}`, + `/member: ${__('Grant membership to a user')}`, + `/mute: ${__("Remove user's ability to post messages")}`, + `/nick: ${__('Change your nickname')}`, + `/op: ${__('Grant moderator role to user')}`, + `/owner: ${__('Grant ownership of this groupchat')}`, + `/register: ${__("Register a nickname for this room")}`, + `/revoke: ${__("Revoke user's membership")}`, + `/subject: ${__('Set groupchat subject')}`, + `/topic: ${__('Set groupchat subject (alias for /subject)')}`, + `/voice: ${__('Allow muted user to post messages')}` + ]); + break; + case 'kick': + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.modifyRole( + this.model.get('jid'), args[0], 'none', args[1], + undefined, this.onCommandError.bind(this)); + break; + case 'mute': + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.modifyRole( + this.model.get('jid'), args[0], 'visitor', args[1], + undefined, this.onCommandError.bind(this)); + break; + case 'member': { + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + const occupant = this.model.occupants.findWhere({'nick': args[0]}) || + this.model.occupants.findWhere({'jid': args[0]}), + attrs = { + 'jid': occupant.get('jid'), + 'reason': args[1] + }; + if (_converse.auto_register_muc_nickname) { + attrs['nick'] = occupant.get('nick'); + } + this.model.setAffiliation('member', [attrs]) + .then(() => this.model.occupants.fetchMembers()) + .catch(err => this.onCommandError(err)); + break; + } case 'nick': + if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { + break; + } + _converse.connection.send($pres({ + from: _converse.connection.jid, + to: this.model.getRoomJIDAndNick(match[2]), + id: _converse.connection.getUniqueId() + }).tree()); + break; + case 'owner': + if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.model.setAffiliation('owner', [{ + 'jid': args[0], + 'reason': args[1] + }]).then( + () => this.model.occupants.fetchMembers(), + (err) => this.onCommandError(err) + ); + break; + case 'op': + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.modifyRole( + this.model.get('jid'), args[0], 'moderator', args[1], + undefined, this.onCommandError.bind(this)); + break; + case 'register': + if (args.length > 1) { + this.showErrorMessage(__(`Error: invalid number of arguments`)) + } else { + this.model.registerNickname().then(err_msg => { + if (err_msg) this.showErrorMessage(err_msg) + }); + } + break; + case 'revoke': + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.model.setAffiliation('none', [{ + 'jid': args[0], + 'reason': args[1] + }]).then( + () => this.model.occupants.fetchMembers(), + (err) => this.onCommandError(err) + ); + break; + case 'topic': + case 'subject': + // TODO: should be done via API call to _converse.api.rooms + _converse.connection.send( + $msg({ + to: this.model.get('jid'), + from: _converse.connection.jid, + type: "groupchat" + }).c("subject", {xmlns: "jabber:client"}).t(match[2] || "").tree() + ); + break; + case 'voice': + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + this.modifyRole( + this.model.get('jid'), args[0], 'participant', args[1], + undefined, this.onCommandError.bind(this)); + break; + default: + return false; + } + return true; + }, + + registerHandlers () { + /* Register presence and message handlers for this chat + * groupchat + */ + // XXX: Ideally this can be refactored out so that we don't + // need to do stanza processing inside the views in this + // module. See the comment in "onPresence" for more info. + this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this)); + // XXX instead of having a method showStatusMessages, we could instead + // create message models in converse-muc.js and then give them views in this module. + this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this)); + }, + + onPresence (pres) { + /* Handles all MUC presence stanzas. + * + * Parameters: + * (XMLElement) pres: The stanza + */ + // XXX: Current thinking is that excessive stanza + // processing inside a view is a "code smell". + // Instead stanza processing should happen inside the + // models/collections. + if (pres.getAttribute('type') === 'error') { + this.showErrorMessageFromPresence(pres); + } else { + // Instead of doing it this way, we could perhaps rather + // create StatusMessage objects inside the messages + // Collection and then simply render those. Then stanza + // processing is done on the model and rendering in the + // view(s). + this.showStatusMessages(pres); + } + }, + + populateAndJoin () { + this.model.occupants.fetchMembers(); + this.join(); + this.fetchMessages(); + }, + + join (nick, password) { + /* Join the groupchat. + * + * Parameters: + * (String) nick: The user's nickname + * (String) password: Optional password, if required by + * the groupchat. + */ + if (!nick && !this.model.get('nick')) { + this.checkForReservedNick(); + return this; + } + this.model.join(nick, password); + return this; + }, + + renderConfigurationForm (stanza) { + /* Renders a form given an IQ stanza containing the current + * groupchat configuration. + * + * Returns a promise which resolves once the user has + * either submitted the form, or canceled it. + * + * Parameters: + * (XMLElement) stanza: The IQ stanza containing the groupchat + * config. + */ + const container_el = this.el.querySelector('.chatroom-body'); + _.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement); + _.each(container_el.children, u.hideElement); + container_el.insertAdjacentHTML('beforeend', tpl_chatroom_form()); + + const form_el = container_el.querySelector('form.chatroom-form'), + fieldset_el = form_el.querySelector('fieldset'), + fields = stanza.querySelectorAll('field'), + title = _.get(stanza.querySelector('title'), 'textContent'), + instructions = _.get(stanza.querySelector('instructions'), 'textContent'); + + u.removeElement(fieldset_el.querySelector('span.spinner')); + fieldset_el.insertAdjacentHTML('beforeend', `${title}`); + + if (instructions && instructions !== title) { + fieldset_el.insertAdjacentHTML('beforeend', `

${instructions}

`); + } + _.each(fields, function (field) { + fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza)); + }); + + // Render save/cancel buttons + const last_fieldset_el = document.createElement('fieldset'); + last_fieldset_el.insertAdjacentHTML( + 'beforeend', + ``); + last_fieldset_el.insertAdjacentHTML( + 'beforeend', + ``); + form_el.insertAdjacentElement('beforeend', last_fieldset_el); + + last_fieldset_el.querySelector('input[type=button]').addEventListener('click', (ev) => { + ev.preventDefault(); + this.closeForm(); + }); + + form_el.addEventListener('submit', + (ev) => { + ev.preventDefault(); + this.model.saveConfiguration(ev.target) + .then(() => this.model.refreshRoomFeatures()); + this.closeForm(); + }, + false + ); + }, + + closeForm () { + /* Remove the configuration form without submitting and + * return to the chat view. + */ + u.removeElement(this.el.querySelector('.chatroom-form-container')); + this.renderAfterTransition(); + }, + + getAndRenderConfigurationForm (ev) { + /* Start the process of configuring a groupchat, either by + * rendering a configuration form, or by auto-configuring + * based on the "roomconfig" data stored on the + * Backbone.Model. + * + * Stores the new configuration on the Backbone.Model once + * completed. + * + * Paremeters: + * (Event) ev: DOM event that might be passed in if this + * method is called due to a user action. In this + * case, auto-configure won't happen, regardless of + * the settings. + */ + this.showSpinner(); + this.model.fetchRoomConfiguration() + .then(this.renderConfigurationForm.bind(this)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }, + + submitNickname (ev) { + /* Get the nickname value from the form and then join the + * groupchat with it. + */ + ev.preventDefault(); + const nick_el = ev.target.nick; + const nick = nick_el.value; + if (!nick) { + nick_el.classList.add('error'); + return; + } + else { + nick_el.classList.remove('error'); + } + this.el.querySelector('.chatroom-form-container').outerHTML = tpl_spinner(); + this.join(nick); + }, + + checkForReservedNick () { + /* User service-discovery to ask the XMPP server whether + * this user has a reserved nickname for this groupchat. + * If so, we'll use that, otherwise we render the nickname form. + */ + this.showSpinner(); + this.model.checkForReservedNick() + .then(this.onReservedNickFound.bind(this)) + .catch(this.onReservedNickNotFound.bind(this)); + }, + + onReservedNickFound (iq) { + if (this.model.get('nick')) { + this.join(); + } else { + this.onReservedNickNotFound(); + } + }, + + onReservedNickNotFound (message) { + const nick = this.model.getDefaultNick(); + if (nick) { + this.join(nick); + } else { + this.renderNicknameForm(message); + } + }, + + onNicknameClash (presence) { + /* When the nickname is already taken, we either render a + * form for the user to choose a new nickname, or we + * try to make the nickname unique by adding an integer to + * it. So john will become john-2, and then john-3 and so on. + * + * Which option is take depends on the value of + * muc_nickname_from_jid. + */ + if (_converse.muc_nickname_from_jid) { + const nick = presence.getAttribute('from').split('/')[1]; + if (nick === this.model.getDefaultNick()) { + this.join(nick + '-2'); + } else { + const del= nick.lastIndexOf("-"); + const num = nick.substring(del+1, nick.length); + this.join(nick.substring(0, del+1) + String(Number(num)+1)); + } + } else { + this.renderNicknameForm( + __("The nickname you chose is reserved or "+ + "currently in use, please choose a different one.") + ); + } + }, + + hideChatRoomContents () { + const container_el = this.el.querySelector('.chatroom-body'); + if (!_.isNull(container_el)) { + _.each(container_el.children, (child) => { child.classList.add('hidden'); }); + } + }, + + renderNicknameForm (message) { + /* Render a form which allows the user to choose their + * nickname. + */ + this.hideChatRoomContents(); + _.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement); + if (!_.isString(message)) { + message = ''; + } + const container_el = this.el.querySelector('.chatroom-body'); + container_el.insertAdjacentHTML( + 'beforeend', + tpl_chatroom_nickname_form({ + heading: __('Please choose your nickname'), + label_nickname: __('Nickname'), + label_join: __('Enter groupchat'), + validation_message: message + })); + this.model.save('connection_status', converse.ROOMSTATUS.NICKNAME_REQUIRED); + + const form_el = this.el.querySelector('.chatroom-form'); + form_el.addEventListener('submit', this.submitNickname.bind(this), false); + }, + + submitPassword (ev) { + ev.preventDefault(); + const password = this.el.querySelector('.chatroom-form input[type=password]').value; + this.showSpinner(); + this.join(this.model.get('nick'), password); + }, + + renderPasswordForm () { + const container_el = this.el.querySelector('.chatroom-body'); + _.each(container_el.children, u.hideElement); + _.each(this.el.querySelectorAll('.spinner'), u.removeElement); + _.each(this.el.querySelectorAll('.chatroom-form-container'), u.removeElement); + + container_el.insertAdjacentHTML('beforeend', + tpl_chatroom_password_form({ + 'heading': __('This groupchat requires a password'), + 'label_password': __('Password: '), + 'label_submit': __('Submit') + })); + + this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED); + this.el.querySelector('.chatroom-form') + .addEventListener('submit', ev => this.submitPassword(ev), false); + }, + + showDestroyedMessage (error) { + u.hideElement(this.el.querySelector('.chat-area')); + u.hideElement(this.el.querySelector('.occupants')); + _.each(this.el.querySelectorAll('.spinner'), u.removeElement); + const container = this.el.querySelector('.disconnect-container'); + const moved_jid = _.get( + sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), + 'textContent' + ).replace(/^xmpp:/, '').replace(/\?join$/, ''); + const reason = _.get( + sizzle('text[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), + 'textContent' + ); + container.innerHTML = tpl_chatroom_destroyed({ + '_': _, + '__':__, + 'jid': moved_jid, + 'reason': reason ? `"${reason}"` : null + }); + + const switch_el = container.querySelector('a.switch-chat'); + if (switch_el) { + switch_el.addEventListener('click', ev => { + ev.preventDefault(); + this.model.save('jid', moved_jid); + container.innerHTML = ''; + this.showSpinner(); + this.enterRoom(); + }); + } + u.showElement(container); + }, + + showDisconnectMessages (msgs) { + if (_.isString(msgs)) { + msgs = [msgs]; + } + u.hideElement(this.el.querySelector('.chat-area')); + u.hideElement(this.el.querySelector('.occupants')); + _.each(this.el.querySelectorAll('.spinner'), u.removeElement); + const container = this.el.querySelector('.disconnect-container'); + container.innerHTML = tpl_chatroom_disconnect({ + '_': _, + 'disconnect_messages': msgs + }) + u.showElement(container); + }, + + getMessageFromStatus (stat, stanza, is_self) { + /* Parameters: + * (XMLElement) stat: A element. + * (Boolean) is_self: Whether the element refers to the + * current user. + * (XMLElement) stanza: The original stanza received. + */ + const code = stat.getAttribute('code'); + if (code === '110' || (code === '100' && !is_self)) { return; } + if (code in _converse.muc.info_messages) { + return _converse.muc.info_messages[code]; + } + let nick; + if (!is_self) { + if (code in _converse.muc.action_info_messages) { + nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); + return __(_converse.muc.action_info_messages[code], nick); + } + } else if (code in _converse.muc.new_nickname_messages) { + if (is_self && code === "210") { + nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); + } else if (is_self && code === "303") { + nick = stanza.querySelector('x item').getAttribute('nick'); + } + return __(_converse.muc.new_nickname_messages[code], nick); + } + return; + }, + + getNotificationWithMessage (message) { + let el = this.content.lastElementChild; + while (!_.isNil(el)) { + const data = _.get(el, 'dataset', {}); + if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { return; } - else { - nick_el.classList.remove('error'); + if (el.textContent === message) { + return el; } - this.el.querySelector('.chatroom-form-container').outerHTML = tpl_spinner(); - this.join(nick); - }, + el = el.previousElementSibling; + } + }, - checkForReservedNick () { - /* User service-discovery to ask the XMPP server whether - * this user has a reserved nickname for this groupchat. - * If so, we'll use that, otherwise we render the nickname form. - */ - this.showSpinner(); - this.model.checkForReservedNick() - .then(this.onReservedNickFound.bind(this)) - .catch(this.onReservedNickNotFound.bind(this)); - }, - - onReservedNickFound (iq) { - if (this.model.get('nick')) { - this.join(); - } else { - this.onReservedNickNotFound(); + parseXUserElement (x, stanza, is_self) { + /* Parse the passed-in + * element and construct a map containing relevant + * information. + */ + // 1. Get notification messages based on the elements. + const statuses = x.querySelectorAll('status'); + const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self); + const notification = {}; + const messages = _.reject( + _.reject(_.map(statuses, mapper), _.isUndefined), + message => this.getNotificationWithMessage(message) + ); + if (messages.length) { + notification.messages = messages; + } + // 2. Get disconnection messages based on the elements + const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code'); + const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages)); + const disconnected = is_self && disconnection_codes.length > 0; + if (disconnected) { + notification.disconnected = true; + notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]]; + } + // 3. Find the reason and actor from the element + const item = x.querySelector('item'); + // By using querySelector above, we assume here there is + // one per + // element. This appears to be a safe assumption, since + // each element pertains to a single user. + if (!_.isNull(item)) { + const reason = item.querySelector('reason'); + if (reason) { + notification.reason = reason ? reason.textContent : undefined; } - }, - - onReservedNickNotFound (message) { - const nick = this.model.getDefaultNick(); - if (nick) { - this.join(nick); - } else { - this.renderNicknameForm(message); + const actor = item.querySelector('actor'); + if (actor) { + notification.actor = actor ? actor.getAttribute('nick') : undefined; } - }, + } + return notification; + }, - onNicknameClash (presence) { - /* When the nickname is already taken, we either render a - * form for the user to choose a new nickname, or we - * try to make the nickname unique by adding an integer to - * it. So john will become john-2, and then john-3 and so on. - * - * Which option is take depends on the value of - * muc_nickname_from_jid. - */ - if (_converse.muc_nickname_from_jid) { - const nick = presence.getAttribute('from').split('/')[1]; - if (nick === this.model.getDefaultNick()) { - this.join(nick + '-2'); - } else { - const del= nick.lastIndexOf("-"); - const num = nick.substring(del+1, nick.length); - this.join(nick.substring(0, del+1) + String(Number(num)+1)); - } - } else { - this.renderNicknameForm( - __("The nickname you chose is reserved or "+ - "currently in use, please choose a different one.") - ); + showNotificationsforUser (notification) { + /* Given the notification object generated by + * parseXUserElement, display any relevant messages and + * information to the user. + */ + if (notification.disconnected) { + const messages = []; + messages.push(notification.disconnection_message); + if (notification.actor) { + messages.push(__('This action was done by %1$s.', notification.actor)); } - }, - - hideChatRoomContents () { - const container_el = this.el.querySelector('.chatroom-body'); - if (!_.isNull(container_el)) { - _.each(container_el.children, (child) => { child.classList.add('hidden'); }); - } - }, - - renderNicknameForm (message) { - /* Render a form which allows the user to choose their - * nickname. - */ - this.hideChatRoomContents(); - _.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement); - if (!_.isString(message)) { - message = ''; - } - const container_el = this.el.querySelector('.chatroom-body'); - container_el.insertAdjacentHTML( - 'beforeend', - tpl_chatroom_nickname_form({ - heading: __('Please choose your nickname'), - label_nickname: __('Nickname'), - label_join: __('Enter groupchat'), - validation_message: message - })); - this.model.save('connection_status', converse.ROOMSTATUS.NICKNAME_REQUIRED); - - const form_el = this.el.querySelector('.chatroom-form'); - form_el.addEventListener('submit', this.submitNickname.bind(this), false); - }, - - submitPassword (ev) { - ev.preventDefault(); - const password = this.el.querySelector('.chatroom-form input[type=password]').value; - this.showSpinner(); - this.join(this.model.get('nick'), password); - }, - - renderPasswordForm () { - const container_el = this.el.querySelector('.chatroom-body'); - _.each(container_el.children, u.hideElement); - _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - _.each(this.el.querySelectorAll('.chatroom-form-container'), u.removeElement); - - container_el.insertAdjacentHTML('beforeend', - tpl_chatroom_password_form({ - 'heading': __('This groupchat requires a password'), - 'label_password': __('Password: '), - 'label_submit': __('Submit') - })); - - this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED); - this.el.querySelector('.chatroom-form') - .addEventListener('submit', ev => this.submitPassword(ev), false); - }, - - showDestroyedMessage (error) { - u.hideElement(this.el.querySelector('.chat-area')); - u.hideElement(this.el.querySelector('.occupants')); - _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - const container = this.el.querySelector('.disconnect-container'); - const moved_jid = _.get( - sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), - 'textContent' - ).replace(/^xmpp:/, '').replace(/\?join$/, ''); - const reason = _.get( - sizzle('text[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(), - 'textContent' - ); - container.innerHTML = tpl_chatroom_destroyed({ - '_': _, - '__':__, - 'jid': moved_jid, - 'reason': reason ? `"${reason}"` : null - }); - - const switch_el = container.querySelector('a.switch-chat'); - if (switch_el) { - switch_el.addEventListener('click', ev => { - ev.preventDefault(); - this.model.save('jid', moved_jid); - container.innerHTML = ''; - this.showSpinner(); - this.enterRoom(); - }); - } - u.showElement(container); - }, - - showDisconnectMessages (msgs) { - if (_.isString(msgs)) { - msgs = [msgs]; - } - u.hideElement(this.el.querySelector('.chat-area')); - u.hideElement(this.el.querySelector('.occupants')); - _.each(this.el.querySelectorAll('.spinner'), u.removeElement); - const container = this.el.querySelector('.disconnect-container'); - container.innerHTML = tpl_chatroom_disconnect({ - '_': _, - 'disconnect_messages': msgs - }) - u.showElement(container); - }, - - getMessageFromStatus (stat, stanza, is_self) { - /* Parameters: - * (XMLElement) stat: A element. - * (Boolean) is_self: Whether the element refers to the - * current user. - * (XMLElement) stanza: The original stanza received. - */ - const code = stat.getAttribute('code'); - if (code === '110' || (code === '100' && !is_self)) { return; } - if (code in _converse.muc.info_messages) { - return _converse.muc.info_messages[code]; - } - let nick; - if (!is_self) { - if (code in _converse.muc.action_info_messages) { - nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); - return __(_converse.muc.action_info_messages[code], nick); - } - } else if (code in _converse.muc.new_nickname_messages) { - if (is_self && code === "210") { - nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); - } else if (is_self && code === "303") { - nick = stanza.querySelector('x item').getAttribute('nick'); - } - return __(_converse.muc.new_nickname_messages[code], nick); + if (notification.reason) { + messages.push(__('The reason given is: "%1$s".', notification.reason)); } + this.showDisconnectMessages(messages); + this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); return; - }, + } + _.each(notification.messages, (message) => { + this.content.insertAdjacentHTML( + 'beforeend', + tpl_info({ + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + })); + }); + if (notification.reason) { + this.showChatEvent(__('The reason given is: "%1$s".', notification.reason)); + } + if (_.get(notification.messages, 'length')) { + this.scrollDown(); + } + }, - getNotificationWithMessage (message) { - let el = this.content.lastElementChild; - while (!_.isNil(el)) { - const data = _.get(el, 'dataset', {}); - if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { - return; - } - if (el.textContent === message) { - return el; - } + onOccupantAdded (occupant) { + if (occupant.get('show') === 'online') { + this.showJoinNotification(occupant); + } + }, + + onOccupantRemoved (occupant) { + if (occupant.get('show') === 'online') { + this.showLeaveNotification(occupant); + } + }, + + showJoinOrLeaveNotification (occupant) { + if (_.includes(occupant.get('states'), '303')) { + return; + } + if (occupant.get('show') === 'offline') { + this.showLeaveNotification(occupant); + } else if (occupant.get('show') === 'online') { + this.showJoinNotification(occupant); + } + }, + + getPreviousJoinOrLeaveNotification (el, nick) { + /* Working backwards, get the first join/leave notification + * from the same user, on the same day and BEFORE any chat + * messages were received. + */ + while (!_.isNil(el)) { + const data = _.get(el, 'dataset', {}); + if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { + return; + } + if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { el = el.previousElementSibling; + continue; } - }, + if (data.join === nick || + data.leave === nick || + data.leavejoin === nick || + data.joinleave === nick) { + return el; + } + el = el.previousElementSibling; + } + }, - parseXUserElement (x, stanza, is_self) { - /* Parse the passed-in - * element and construct a map containing relevant - * information. - */ - // 1. Get notification messages based on the elements. - const statuses = x.querySelectorAll('status'); - const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self); - const notification = {}; - const messages = _.reject( - _.reject(_.map(statuses, mapper), _.isUndefined), - message => this.getNotificationWithMessage(message) - ); - if (messages.length) { - notification.messages = messages; + showJoinNotification (occupant) { + if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { + return; + } + const nick = occupant.get('nick'), + stat = occupant.get('status'), + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + data = _.get(prev_info_el, 'dataset', {}); + + if (data.leave === nick) { + let message; + if (_.isNil(stat)) { + message = __('%1$s has left and re-entered the groupchat', nick); + } else { + message = __('%1$s has left and re-entered the groupchat. "%2$s"', nick, stat); } - // 2. Get disconnection messages based on the elements - const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code'); - const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages)); - const disconnected = is_self && disconnection_codes.length > 0; - if (disconnected) { - notification.disconnected = true; - notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]]; + const data = { + 'data_name': 'leavejoin', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; + setTimeout(() => u.addClass('fade-out', el), 5000); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); + } else { + let message; + if (_.isNil(stat)) { + message = __('%1$s has entered the groupchat', nick); + } else { + message = __('%1$s has entered the groupchat. "%2$s"', nick, stat); } - // 3. Find the reason and actor from the element - const item = x.querySelector('item'); - // By using querySelector above, we assume here there is - // one per - // element. This appears to be a safe assumption, since - // each element pertains to a single user. - if (!_.isNull(item)) { - const reason = item.querySelector('reason'); + const data = { + 'data_name': 'join', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + } else { + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); + } + } + this.scrollDown(); + }, + + showLeaveNotification (occupant) { + if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) { + return; + } + const nick = occupant.get('nick'), + stat = occupant.get('status'), + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), + dataset = _.get(prev_info_el, 'dataset', {}); + + if (dataset.join === nick) { + let message; + if (_.isNil(stat)) { + message = __('%1$s has entered and left the groupchat', nick); + } else { + message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); + } + const data = { + 'data_name': 'joinleave', + 'data_value': nick, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'message': message + }; + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; + setTimeout(() => u.addClass('fade-out', el), 5000); + setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); + } else { + let message; + if (_.isNil(stat)) { + message = __('%1$s has left the groupchat', nick); + } else { + message = __('%1$s has left the groupchat. "%2$s"', nick, stat); + } + const data = { + 'message': message, + 'isodate': moment().format(), + 'extra_classes': 'chat-event', + 'data_name': 'leave', + 'data_value': nick + } + if (prev_info_el) { + this.content.removeChild(prev_info_el); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + } else { + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); + } + } + this.scrollDown(); + }, + + showStatusMessages (stanza) { + /* Check for status codes and communicate their purpose to the user. + * See: http://xmpp.org/registrar/mucstatus.html + * + * Parameters: + * (XMLElement) stanza: The message or presence stanza + * containing the status codes. + */ + const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza); + const is_self = stanza.querySelectorAll("status[code='110']").length; + const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self); + const notifications = _.reject(_.map(elements, iteratee), _.isEmpty); + _.each(notifications, this.showNotificationsforUser.bind(this)); + }, + + showErrorMessageFromPresence (presence) { + // We didn't enter the groupchat, so we must remove it from the MUC add-on + const error = presence.querySelector('error'); + if (error.getAttribute('type') === 'auth') { + if (!_.isNull(error.querySelector('not-authorized'))) { + this.renderPasswordForm(); + } else if (!_.isNull(error.querySelector('registration-required'))) { + this.showDisconnectMessages(__('You are not on the member list of this groupchat.')); + } else if (!_.isNull(error.querySelector('forbidden'))) { + this.showDisconnectMessages(__('You have been banned from this groupchat.')); + } + } else if (error.getAttribute('type') === 'modify') { + if (!_.isNull(error.querySelector('jid-malformed'))) { + this.showDisconnectMessages(__('No nickname was specified.')); + } + } else if (error.getAttribute('type') === 'cancel') { + if (!_.isNull(error.querySelector('not-allowed'))) { + this.showDisconnectMessages(__('You are not allowed to create new groupchats.')); + } else if (!_.isNull(error.querySelector('not-acceptable'))) { + this.showDisconnectMessages(__("Your nickname doesn't conform to this groupchat's policies.")); + } else if (sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).length) { + this.showDestroyedMessage(error); + } else if (!_.isNull(error.querySelector('conflict'))) { + this.onNicknameClash(presence); + } else if (!_.isNull(error.querySelector('item-not-found'))) { + this.showDisconnectMessages(__("This groupchat does not (yet) exist.")); + } else if (!_.isNull(error.querySelector('service-unavailable'))) { + this.showDisconnectMessages(__("This groupchat has reached its maximum number of participants.")); + } else if (!_.isNull(error.querySelector('remote-server-not-found'))) { + const messages = [__("Remote server not found")]; + const reason = _.get(error.querySelector('text'), 'textContent'); if (reason) { - notification.reason = reason ? reason.textContent : undefined; - } - const actor = item.querySelector('actor'); - if (actor) { - notification.actor = actor ? actor.getAttribute('nick') : undefined; - } - } - return notification; - }, - - showNotificationsforUser (notification) { - /* Given the notification object generated by - * parseXUserElement, display any relevant messages and - * information to the user. - */ - if (notification.disconnected) { - const messages = []; - messages.push(notification.disconnection_message); - if (notification.actor) { - messages.push(__('This action was done by %1$s.', notification.actor)); - } - if (notification.reason) { - messages.push(__('The reason given is: "%1$s".', notification.reason)); + messages.push(__('The explanation given is: "%1$s".', reason)); } this.showDisconnectMessages(messages); - this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); - return; } - _.each(notification.messages, (message) => { - this.content.insertAdjacentHTML( - 'beforeend', - tpl_info({ - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - })); - }); - if (notification.reason) { - this.showChatEvent(__('The reason given is: "%1$s".', notification.reason)); - } - if (_.get(notification.messages, 'length')) { - this.scrollDown(); - } - }, + } + }, - onOccupantAdded (occupant) { - if (occupant.get('show') === 'online') { - this.showJoinNotification(occupant); - } - }, - - onOccupantRemoved (occupant) { - if (occupant.get('show') === 'online') { - this.showLeaveNotification(occupant); - } - }, - - showJoinOrLeaveNotification (occupant) { - if (_.includes(occupant.get('states'), '303')) { - return; - } - if (occupant.get('show') === 'offline') { - this.showLeaveNotification(occupant); - } else if (occupant.get('show') === 'online') { - this.showJoinNotification(occupant); - } - }, - - getPreviousJoinOrLeaveNotification (el, nick) { - /* Working backwards, get the first join/leave notification - * from the same user, on the same day and BEFORE any chat - * messages were received. - */ - while (!_.isNil(el)) { - const data = _.get(el, 'dataset', {}); - if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { - return; - } - if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { - el = el.previousElementSibling; - continue; - } - if (data.join === nick || - data.leave === nick || - data.leavejoin === nick || - data.joinleave === nick) { - return el; - } - el = el.previousElementSibling; - } - }, - - showJoinNotification (occupant) { - if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { - return; - } - const nick = occupant.get('nick'), - stat = occupant.get('status'), - prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), - data = _.get(prev_info_el, 'dataset', {}); - - if (data.leave === nick) { - let message; - if (_.isNil(stat)) { - message = __('%1$s has left and re-entered the groupchat', nick); - } else { - message = __('%1$s has left and re-entered the groupchat. "%2$s"', nick, stat); - } - const data = { - 'data_name': 'leavejoin', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }; - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - const el = this.content.lastElementChild; - setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); - } else { - let message; - if (_.isNil(stat)) { - message = __('%1$s has entered the groupchat', nick); - } else { - message = __('%1$s has entered the groupchat. "%2$s"', nick, stat); - } - const data = { - 'data_name': 'join', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }; - if (prev_info_el) { - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - } else { - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - this.insertDayIndicator(this.content.lastElementChild); - } - } + renderAfterTransition () { + /* Rerender the groupchat after some kind of transition. For + * example after the spinner has been removed or after a + * form has been submitted and removed. + */ + if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) { + this.renderNicknameForm(); + } else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) { + this.renderPasswordForm(); + } else { + this.el.querySelector('.chat-area').classList.remove('hidden'); + this.setOccupantsVisibility(); this.scrollDown(); - }, + } + }, - showLeaveNotification (occupant) { - if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) { - return; - } - const nick = occupant.get('nick'), - stat = occupant.get('status'), - prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), - dataset = _.get(prev_info_el, 'dataset', {}); + showSpinner () { + u.removeElement(this.el.querySelector('.spinner')); - if (dataset.join === nick) { - let message; - if (_.isNil(stat)) { - message = __('%1$s has entered and left the groupchat', nick); - } else { - message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat); - } - const data = { - 'data_name': 'joinleave', - 'data_value': nick, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'message': message - }; - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - const el = this.content.lastElementChild; - setTimeout(() => u.addClass('fade-out', el), 5000); - setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); - } else { - let message; - if (_.isNil(stat)) { - message = __('%1$s has left the groupchat', nick); - } else { - message = __('%1$s has left the groupchat. "%2$s"', nick, stat); - } - const data = { - 'message': message, - 'isodate': moment().format(), - 'extra_classes': 'chat-event', - 'data_name': 'leave', - 'data_value': nick - } - if (prev_info_el) { - this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - } else { - this.content.insertAdjacentHTML('beforeend', tpl_info(data)); - this.insertDayIndicator(this.content.lastElementChild); - } - } - this.scrollDown(); - }, + const container_el = this.el.querySelector('.chatroom-body'); + const children = Array.prototype.slice.call(container_el.children, 0); + container_el.insertAdjacentHTML('afterbegin', tpl_spinner()); + _.each(children, u.hideElement); - showStatusMessages (stanza) { - /* Check for status codes and communicate their purpose to the user. - * See: http://xmpp.org/registrar/mucstatus.html - * - * Parameters: - * (XMLElement) stanza: The message or presence stanza - * containing the status codes. - */ - const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza); - const is_self = stanza.querySelectorAll("status[code='110']").length; - const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self); - const notifications = _.reject(_.map(elements, iteratee), _.isEmpty); - _.each(notifications, this.showNotificationsforUser.bind(this)); - }, + }, - showErrorMessageFromPresence (presence) { - // We didn't enter the groupchat, so we must remove it from the MUC add-on - const error = presence.querySelector('error'); - if (error.getAttribute('type') === 'auth') { - if (!_.isNull(error.querySelector('not-authorized'))) { - this.renderPasswordForm(); - } else if (!_.isNull(error.querySelector('registration-required'))) { - this.showDisconnectMessages(__('You are not on the member list of this groupchat.')); - } else if (!_.isNull(error.querySelector('forbidden'))) { - this.showDisconnectMessages(__('You have been banned from this groupchat.')); - } - } else if (error.getAttribute('type') === 'modify') { - if (!_.isNull(error.querySelector('jid-malformed'))) { - this.showDisconnectMessages(__('No nickname was specified.')); - } - } else if (error.getAttribute('type') === 'cancel') { - if (!_.isNull(error.querySelector('not-allowed'))) { - this.showDisconnectMessages(__('You are not allowed to create new groupchats.')); - } else if (!_.isNull(error.querySelector('not-acceptable'))) { - this.showDisconnectMessages(__("Your nickname doesn't conform to this groupchat's policies.")); - } else if (sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).length) { - this.showDestroyedMessage(error); - } else if (!_.isNull(error.querySelector('conflict'))) { - this.onNicknameClash(presence); - } else if (!_.isNull(error.querySelector('item-not-found'))) { - this.showDisconnectMessages(__("This groupchat does not (yet) exist.")); - } else if (!_.isNull(error.querySelector('service-unavailable'))) { - this.showDisconnectMessages(__("This groupchat has reached its maximum number of participants.")); - } else if (!_.isNull(error.querySelector('remote-server-not-found'))) { - const messages = [__("Remote server not found")]; - const reason = _.get(error.querySelector('text'), 'textContent'); - if (reason) { - messages.push(__('The explanation given is: "%1$s".', reason)); - } - this.showDisconnectMessages(messages); - } - } - }, + hideSpinner () { + /* Check if the spinner is being shown and if so, hide it. + * Also make sure then that the chat area and occupants + * list are both visible. + */ + const spinner = this.el.querySelector('.spinner'); + if (!_.isNull(spinner)) { + u.removeElement(spinner); + this.renderAfterTransition(); + } + return this; + }, - renderAfterTransition () { - /* Rerender the groupchat after some kind of transition. For - * example after the spinner has been removed or after a - * form has been submitted and removed. - */ - if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) { - this.renderNicknameForm(); - } else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) { - this.renderPasswordForm(); - } else { - this.el.querySelector('.chat-area').classList.remove('hidden'); - this.setOccupantsVisibility(); - this.scrollDown(); - } - }, + setChatRoomSubject () { + // For translators: the %1$s and %2$s parts will get + // replaced by the user and topic text respectively + // Example: Topic set by JC Brand to: Hello World! + const subject = this.model.get('subject'), + message = subject.text ? __('Topic set by %1$s', subject.author) : + __('Topic cleared by %1$s', subject.author), + date = moment().format(); + this.content.insertAdjacentHTML( + 'beforeend', + tpl_info({ + 'isodate': date, + 'extra_classes': 'chat-event', + 'message': message + })); - showSpinner () { - u.removeElement(this.el.querySelector('.spinner')); - - const container_el = this.el.querySelector('.chatroom-body'); - const children = Array.prototype.slice.call(container_el.children, 0); - container_el.insertAdjacentHTML('afterbegin', tpl_spinner()); - _.each(children, u.hideElement); - - }, - - hideSpinner () { - /* Check if the spinner is being shown and if so, hide it. - * Also make sure then that the chat area and occupants - * list are both visible. - */ - const spinner = this.el.querySelector('.spinner'); - if (!_.isNull(spinner)) { - u.removeElement(spinner); - this.renderAfterTransition(); - } - return this; - }, - - setChatRoomSubject () { - // For translators: the %1$s and %2$s parts will get - // replaced by the user and topic text respectively - // Example: Topic set by JC Brand to: Hello World! - const subject = this.model.get('subject'), - message = subject.text ? __('Topic set by %1$s', subject.author) : - __('Topic cleared by %1$s', subject.author), - date = moment().format(); + if (subject.text) { this.content.insertAdjacentHTML( 'beforeend', tpl_info({ 'isodate': date, - 'extra_classes': 'chat-event', - 'message': message + 'extra_classes': 'chat-topic', + 'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), + 'render_message': true })); - - if (subject.text) { - this.content.insertAdjacentHTML( - 'beforeend', - tpl_info({ - 'isodate': date, - 'extra_classes': 'chat-topic', - 'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), - 'render_message': true - })); - } - this.scrollDown(); } - }); + this.scrollDown(); + } + }); - _converse.RoomsPanel = Backbone.NativeView.extend({ - /* Backbone.NativeView which renders MUC section of the control box. - */ - tagName: 'div', - className: 'controlbox-section', - id: 'chatrooms', - events: { - 'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal', - 'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal' - }, + _converse.RoomsPanel = Backbone.NativeView.extend({ + /* Backbone.NativeView which renders MUC section of the control box. + */ + tagName: 'div', + className: 'controlbox-section', + id: 'chatrooms', + events: { + 'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal', + 'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal' + }, - render () { - this.el.innerHTML = tpl_room_panel({ - 'heading_chatrooms': __('Groupchats'), - 'title_new_room': __('Add a new groupchat'), - 'title_list_rooms': __('Query for groupchats') - }); - return this; - }, + render () { + this.el.innerHTML = tpl_room_panel({ + 'heading_chatrooms': __('Groupchats'), + 'title_new_room': __('Add a new groupchat'), + 'title_list_rooms': __('Query for groupchats') + }); + return this; + }, - showAddRoomModal (ev) { - if (_.isUndefined(this.add_room_modal)) { - this.add_room_modal = new _converse.AddChatRoomModal({'model': this.model}); - } - this.add_room_modal.show(ev); - }, - - showListRoomsModal(ev) { - if (_.isUndefined(this.list_rooms_modal)) { - this.list_rooms_modal = new _converse.ListChatRoomsModal({'model': this.model}); - } - this.list_rooms_modal.show(ev); + showAddRoomModal (ev) { + if (_.isUndefined(this.add_room_modal)) { + this.add_room_modal = new _converse.AddChatRoomModal({'model': this.model}); } - }); + this.add_room_modal.show(ev); + }, - - _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({ - tagName: 'li', - initialize () { - this.model.on('change', this.render, this); - }, - - toHTML () { - const show = this.model.get('show'); - return tpl_occupant( - _.extend( - { '_': _, // XXX Normally this should already be included, - // but with the current webpack build, - // we only get a subset of the _ methods. - 'jid': '', - 'show': show, - 'hint_show': _converse.PRETTY_CHAT_STATUS[show], - 'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')), - 'desc_moderator': __('This user is a moderator.'), - 'desc_participant': __('This user can send messages in this groupchat.'), - 'desc_visitor': __('This user can NOT send messages in this groupchat.'), - 'label_moderator': __('Moderator'), - 'label_visitor': __('Visitor'), - 'label_owner': __('Owner'), - 'label_member': __('Member'), - 'label_admin': __('Admin') - }, this.model.toJSON()) - ); - }, - - destroy () { - this.el.parentElement.removeChild(this.el); + showListRoomsModal(ev) { + if (_.isUndefined(this.list_rooms_modal)) { + this.list_rooms_modal = new _converse.ListChatRoomsModal({'model': this.model}); } - }); + this.list_rooms_modal.show(ev); + } + }); - _converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({ - tagName: 'div', - className: 'occupants col-md-3 col-4', - listItems: 'model', - sortEvent: 'change:role', - listSelector: '.occupant-list', + _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({ + tagName: 'li', + initialize () { + this.model.on('change', this.render, this); + }, - ItemView: _converse.ChatRoomOccupantView, + toHTML () { + const show = this.model.get('show'); + return tpl_occupant( + _.extend( + { '_': _, // XXX Normally this should already be included, + // but with the current webpack build, + // we only get a subset of the _ methods. + 'jid': '', + 'show': show, + 'hint_show': _converse.PRETTY_CHAT_STATUS[show], + 'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')), + 'desc_moderator': __('This user is a moderator.'), + 'desc_participant': __('This user can send messages in this groupchat.'), + 'desc_visitor': __('This user can NOT send messages in this groupchat.'), + 'label_moderator': __('Moderator'), + 'label_visitor': __('Visitor'), + 'label_owner': __('Owner'), + 'label_member': __('Member'), + 'label_admin': __('Admin') + }, this.model.toJSON()) + ); + }, - initialize () { - Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + destroy () { + this.el.parentElement.removeChild(this.el); + } + }); - this.chatroomview = this.model.chatroomview; - this.chatroomview.model.on('change:open', this.renderInviteWidget, this); - this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this); - this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this); - this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this); - this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this); - this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this); - this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this); - this.chatroomview.model.on('change:open', this.onFeatureChanged, this); - this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this); - this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this); - this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this); - this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this); - this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this); - this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this); - this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this); - this.render(); - this.model.fetch({ - 'add': true, - 'silent': true, - 'success': this.sortAndPositionAllItems.bind(this) - }); - }, + _converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({ + tagName: 'div', + className: 'occupants col-md-3 col-4', + listItems: 'model', + sortEvent: 'change:role', + listSelector: '.occupant-list', - render () { - this.el.innerHTML = tpl_chatroom_sidebar( - _.extend(this.chatroomview.model.toJSON(), { - 'allow_muc_invitations': _converse.allow_muc_invitations, - 'label_occupants': __('Participants') - }) + ItemView: _converse.ChatRoomOccupantView, + + initialize () { + Backbone.OrderedListView.prototype.initialize.apply(this, arguments); + + this.chatroomview = this.model.chatroomview; + this.chatroomview.model.on('change:open', this.renderInviteWidget, this); + this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this); + this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this); + this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this); + this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this); + this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this); + this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this); + this.chatroomview.model.on('change:open', this.onFeatureChanged, this); + this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this); + this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this); + this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this); + this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this); + this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this); + this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this); + this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this); + + this.render(); + this.model.fetch({ + 'add': true, + 'silent': true, + 'success': this.sortAndPositionAllItems.bind(this) + }); + }, + + render () { + this.el.innerHTML = tpl_chatroom_sidebar( + _.extend(this.chatroomview.model.toJSON(), { + 'allow_muc_invitations': _converse.allow_muc_invitations, + 'label_occupants': __('Participants') + }) + ); + if (_converse.allow_muc_invitations) { + _converse.api.waitUntil('rosterContactsFetched').then( + this.renderInviteWidget.bind(this) ); - if (_converse.allow_muc_invitations) { - _converse.api.waitUntil('rosterContactsFetched').then( - this.renderInviteWidget.bind(this) - ); - } - return this.renderRoomFeatures(); - }, + } + return this.renderRoomFeatures(); + }, - renderInviteWidget () { - const form = this.el.querySelector('form.room-invite'); - if (this.shouldInviteWidgetBeShown()) { - if (_.isNull(form)) { - const heading = this.el.querySelector('.occupants-heading'); - heading.insertAdjacentHTML( - 'afterend', - tpl_chatroom_invite({ - 'error_message': null, - 'label_invitation': __('Invite'), - }) - ); - this.initInviteWidget(); - } - } else if (!_.isNull(form)) { - form.remove(); - } - return this; - }, - - renderRoomFeatures () { - const picks = _.pick(this.chatroomview.model.attributes, converse.ROOM_FEATURES), - iteratee = (a, v) => a || v, - el = this.el.querySelector('.chatroom-features'); - - el.innerHTML = tpl_chatroom_features( - _.extend(this.chatroomview.model.toJSON(), { - '__': __, - 'has_features': _.reduce(_.values(picks), iteratee) - })); - this.setOccupantsHeight(); - return this; - }, - - onFeatureChanged (model) { - /* When a feature has been changed, it's logical opposite - * must be set to the opposite value. - * - * So for example, if "temporary" was set to "false", then - * "persistent" will be set to "true" in this method. - * - * Additionally a debounced render method is called to make - * sure the features widget gets updated. - */ - if (_.isUndefined(this.debouncedRenderRoomFeatures)) { - this.debouncedRenderRoomFeatures = _.debounce( - this.renderRoomFeatures, 100, {'leading': false} - ); - } - const changed_features = {}; - _.each(_.keys(model.changed), function (k) { - if (!_.isNil(ROOM_FEATURES_MAP[k])) { - changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k]; - } - }); - this.chatroomview.model.save(changed_features, {'silent': true}); - this.debouncedRenderRoomFeatures(); - }, - - setOccupantsHeight () { - const el = this.el.querySelector('.chatroom-features'); - this.el.querySelector('.occupant-list').style.cssText = - `height: calc(100% - ${el.offsetHeight}px - 5em);`; - }, - - - promptForInvite (suggestion) { - const reason = prompt( - __('You are about to invite %1$s to the groupchat "%2$s". '+ - 'You may optionally include a message, explaining the reason for the invitation.', - suggestion.text.label, this.model.get('id')) - ); - if (reason !== null) { - this.chatroomview.model.directInvite(suggestion.text.value, reason); - } - const form = suggestion.target.form, - error = form.querySelector('.pure-form-message.error'); - if (!_.isNull(error)) { - error.parentNode.removeChild(error); - } - suggestion.target.value = ''; - }, - - inviteFormSubmitted (evt) { - evt.preventDefault(); - const el = evt.target.querySelector('input.invited-contact'), - jid = el.value; - if (!jid || _.compact(jid.split('@')).length < 2) { - evt.target.outerHTML = tpl_chatroom_invite({ - 'error_message': __('Please enter a valid XMPP username'), - 'label_invitation': __('Invite'), - }); - this.initInviteWidget(); - return; - } - this.promptForInvite({ - 'target': el, - 'text': { - 'label': jid, - 'value': jid - }}); - }, - - shouldInviteWidgetBeShown () { - return _converse.allow_muc_invitations && - (this.chatroomview.model.get('open') || - this.chatroomview.model.get('affiliation') === "owner" - ); - }, - - initInviteWidget () { - const form = this.el.querySelector('form.room-invite'); + renderInviteWidget () { + const form = this.el.querySelector('form.room-invite'); + if (this.shouldInviteWidgetBeShown()) { if (_.isNull(form)) { - return; + const heading = this.el.querySelector('.occupants-heading'); + heading.insertAdjacentHTML( + 'afterend', + tpl_chatroom_invite({ + 'error_message': null, + 'label_invitation': __('Invite'), + }) + ); + this.initInviteWidget(); } - form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false); - const el = this.el.querySelector('input.invited-contact'); - const list = _converse.roster.map(function (item) { - const label = item.get('fullname') || item.get('jid'); - return {'label': label, 'value':item.get('jid')}; - }); - const awesomplete = new Awesomplete(el, { - 'minChars': 1, - 'list': list - }); - el.addEventListener('awesomplete-selectcomplete', - this.promptForInvite.bind(this)); + } else if (!_.isNull(form)) { + form.remove(); } - }); + return this; + }, + renderRoomFeatures () { + const picks = _.pick(this.chatroomview.model.attributes, converse.ROOM_FEATURES), + iteratee = (a, v) => a || v, + el = this.el.querySelector('.chatroom-features'); - function setMUCDomain (domain, controlboxview) { - _converse.muc_domain = domain; - controlboxview.roomspanel.model.save('muc_domain', Strophe.getDomainFromJid(domain)); - } + el.innerHTML = tpl_chatroom_features( + _.extend(this.chatroomview.model.toJSON(), { + '__': __, + 'has_features': _.reduce(_.values(picks), iteratee) + })); + this.setOccupantsHeight(); + return this; + }, - function setMUCDomainFromDisco (controlboxview) { - /* Check whether service discovery for the user's domain - * returned MUC information and use that to automatically - * set the MUC domain in the "Add groupchat" modal. + onFeatureChanged (model) { + /* When a feature has been changed, it's logical opposite + * must be set to the opposite value. + * + * So for example, if "temporary" was set to "false", then + * "persistent" will be set to "true" in this method. + * + * Additionally a debounced render method is called to make + * sure the features widget gets updated. */ - function featureAdded (feature) { - if (!feature) { return; } - if (feature.get('var') === Strophe.NS.MUC) { - feature.entity.getIdentity('conference', 'text').then(identity => { - if (identity) { - setMUCDomain(feature.get('from'), controlboxview); - } - }); - } + if (_.isUndefined(this.debouncedRenderRoomFeatures)) { + this.debouncedRenderRoomFeatures = _.debounce( + this.renderRoomFeatures, 100, {'leading': false} + ); } - _converse.api.waitUntil('discoInitialized').then(() => { - _converse.api.listen.on('serviceDiscovered', featureAdded); - // Features could have been added before the controlbox was - // initialized. We're only interested in MUC - _converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({'var': Strophe.NS.MUC }))); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); - } - - function fetchAndSetMUCDomain (controlboxview) { - if (controlboxview.model.get('connected')) { - if (!controlboxview.roomspanel.model.get('muc_domain')) { - if (_.isUndefined(_converse.muc_domain)) { - setMUCDomainFromDisco(controlboxview); - } else { - setMUCDomain(_converse.muc_domain, controlboxview); - } - } - } - } - - /************************ BEGIN Event Handlers ************************/ - _converse.on('chatBoxViewsInitialized', () => { - - function openChatRoomFromURIClicked (ev) { - ev.preventDefault(); - _converse.api.rooms.open(ev.target.href); - } - _converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked); - - const that = _converse.chatboxviews; - _converse.chatboxes.on('add', item => { - if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) { - return that.add(item.get('id'), new _converse.ChatRoomView({'model': item})); + const changed_features = {}; + _.each(_.keys(model.changed), function (k) { + if (!_.isNil(ROOM_FEATURES_MAP[k])) { + changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k]; } }); - }); + this.chatroomview.model.save(changed_features, {'silent': true}); + this.debouncedRenderRoomFeatures(); + }, - _converse.on('controlboxInitialized', (view) => { - if (!_converse.allow_muc) { + setOccupantsHeight () { + const el = this.el.querySelector('.chatroom-features'); + this.el.querySelector('.occupant-list').style.cssText = + `height: calc(100% - ${el.offsetHeight}px - 5em);`; + }, + + + promptForInvite (suggestion) { + const reason = prompt( + __('You are about to invite %1$s to the groupchat "%2$s". '+ + 'You may optionally include a message, explaining the reason for the invitation.', + suggestion.text.label, this.model.get('id')) + ); + if (reason !== null) { + this.chatroomview.model.directInvite(suggestion.text.value, reason); + } + const form = suggestion.target.form, + error = form.querySelector('.pure-form-message.error'); + if (!_.isNull(error)) { + error.parentNode.removeChild(error); + } + suggestion.target.value = ''; + }, + + inviteFormSubmitted (evt) { + evt.preventDefault(); + const el = evt.target.querySelector('input.invited-contact'), + jid = el.value; + if (!jid || _.compact(jid.split('@')).length < 2) { + evt.target.outerHTML = tpl_chatroom_invite({ + 'error_message': __('Please enter a valid XMPP username'), + 'label_invitation': __('Invite'), + }); + this.initInviteWidget(); return; } - fetchAndSetMUCDomain(view); - view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view)); - }); + this.promptForInvite({ + 'target': el, + 'text': { + 'label': jid, + 'value': jid + }}); + }, - function reconnectToChatRooms () { - /* Upon a reconnection event from converse, join again - * all the open groupchats. - */ - _converse.chatboxviews.each(function (view) { - if (view.model.get('type') === _converse.CHATROOMS_TYPE) { - view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); - view.model.registerHandlers(); - view.populateAndJoin(); - } + shouldInviteWidgetBeShown () { + return _converse.allow_muc_invitations && + (this.chatroomview.model.get('open') || + this.chatroomview.model.get('affiliation') === "owner" + ); + }, + + initInviteWidget () { + const form = this.el.querySelector('form.room-invite'); + if (_.isNull(form)) { + return; + } + form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false); + const el = this.el.querySelector('input.invited-contact'); + const list = _converse.roster.map(function (item) { + const label = item.get('fullname') || item.get('jid'); + return {'label': label, 'value':item.get('jid')}; + }); + const awesomplete = new Awesomplete(el, { + 'minChars': 1, + 'list': list }); + el.addEventListener('awesomplete-selectcomplete', + this.promptForInvite.bind(this)); } - _converse.on('reconnected', reconnectToChatRooms); - /************************ END Event Handlers ************************/ + }); - /************************ BEGIN API ************************/ - _.extend(_converse.api, { - /** - * The "roomviews" namespace groups methods relevant to chatroom - * (aka groupchats) views. - * - * @namespace _converse.api.roomviews - * @memberOf _converse.api - */ - 'roomviews': { - /** - * Lets you close open chatrooms. - * - * You can call this method without any arguments to close - * all open chatrooms, or you can specify a single JID or - * an array of JIDs. - * - * @method _converse.api.roomviews.close - * @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s) - */ - 'close' (jids) { - if (_.isUndefined(jids)) { - _converse.chatboxviews.each(function (view) { - if (view.is_chatroom && view.model) { - view.close(); - } - }); - } else if (_.isString(jids)) { - const view = _converse.chatboxviews.get(jids); - if (view) { view.close(); } - } else { - _.each(jids, function (jid) { - const view = _converse.chatboxviews.get(jid); - if (view) { view.close(); } - }); + function setMUCDomain (domain, controlboxview) { + _converse.muc_domain = domain; + controlboxview.roomspanel.model.save('muc_domain', Strophe.getDomainFromJid(domain)); + } + + function setMUCDomainFromDisco (controlboxview) { + /* Check whether service discovery for the user's domain + * returned MUC information and use that to automatically + * set the MUC domain in the "Add groupchat" modal. + */ + function featureAdded (feature) { + if (!feature) { return; } + if (feature.get('var') === Strophe.NS.MUC) { + feature.entity.getIdentity('conference', 'text').then(identity => { + if (identity) { + setMUCDomain(feature.get('from'), controlboxview); } + }); + } + } + _converse.api.waitUntil('discoInitialized').then(() => { + _converse.api.listen.on('serviceDiscovered', featureAdded); + // Features could have been added before the controlbox was + // initialized. We're only interested in MUC + _converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({'var': Strophe.NS.MUC }))); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + } + + function fetchAndSetMUCDomain (controlboxview) { + if (controlboxview.model.get('connected')) { + if (!controlboxview.roomspanel.model.get('muc_domain')) { + if (_.isUndefined(_converse.muc_domain)) { + setMUCDomainFromDisco(controlboxview); + } else { + setMUCDomain(_converse.muc_domain, controlboxview); } } + } + } + + /************************ BEGIN Event Handlers ************************/ + _converse.on('chatBoxViewsInitialized', () => { + + function openChatRoomFromURIClicked (ev) { + ev.preventDefault(); + _converse.api.rooms.open(ev.target.href); + } + _converse.chatboxviews.delegate('click', 'a.open-chatroom', openChatRoomFromURIClicked); + + const that = _converse.chatboxviews; + _converse.chatboxes.on('add', item => { + if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) { + return that.add(item.get('id'), new _converse.ChatRoomView({'model': item})); + } + }); + }); + + _converse.on('controlboxInitialized', (view) => { + if (!_converse.allow_muc) { + return; + } + fetchAndSetMUCDomain(view); + view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view)); + }); + + function reconnectToChatRooms () { + /* Upon a reconnection event from converse, join again + * all the open groupchats. + */ + _converse.chatboxviews.each(function (view) { + if (view.model.get('type') === _converse.CHATROOMS_TYPE) { + view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED); + view.model.registerHandlers(); + view.populateAndJoin(); + } }); } - }); -})); + _converse.on('reconnected', reconnectToChatRooms); + /************************ END Event Handlers ************************/ + + + /************************ BEGIN API ************************/ + _.extend(_converse.api, { + /** + * The "roomviews" namespace groups methods relevant to chatroom + * (aka groupchats) views. + * + * @namespace _converse.api.roomviews + * @memberOf _converse.api + */ + 'roomviews': { + /** + * Lets you close open chatrooms. + * + * You can call this method without any arguments to close + * all open chatrooms, or you can specify a single JID or + * an array of JIDs. + * + * @method _converse.api.roomviews.close + * @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s) + */ + 'close' (jids) { + if (_.isUndefined(jids)) { + _converse.chatboxviews.each(function (view) { + if (view.is_chatroom && view.model) { + view.close(); + } + }); + } else if (_.isString(jids)) { + const view = _converse.chatboxviews.get(jids); + if (view) { view.close(); } + } else { + _.each(jids, function (jid) { + const view = _converse.chatboxviews.get(jid); + if (view) { view.close(); } + }); + } + } + } + }); + } +}); + diff --git a/src/converse-notification.js b/src/converse-notification.js index be66d50c4..c580176d0 100644 --- a/src/converse-notification.js +++ b/src/converse-notification.js @@ -6,292 +6,290 @@ // /*global define */ -(function (root, factory) { - define(["@converse/headless/converse-core"], factory); -}(this, function (converse) { - "use strict"; - const { Strophe, _, sizzle } = converse.env, - u = converse.env.utils; +import converse from "@converse/headless/converse-core"; - converse.plugins.add('converse-notification', { +const { Strophe, _, sizzle } = converse.env, + u = converse.env.utils; - dependencies: ["converse-chatboxes"], - initialize () { - /* The initialize function gets called as soon as the plugin is - * loaded by converse.js's plugin machinery. +converse.plugins.add('converse-notification', { + + dependencies: ["converse-chatboxes"], + + initialize () { + /* The initialize function gets called as soon as the plugin is + * loaded by converse.js's plugin machinery. + */ + const { _converse } = this; + const { __ } = _converse; + + _converse.supports_html5_notification = "Notification" in window; + + _converse.api.settings.update({ + notify_all_room_messages: false, + show_desktop_notifications: true, + show_chatstate_notifications: false, + chatstate_notification_blacklist: [], + // ^ a list of JIDs to ignore concerning chat state notifications + play_sounds: true, + sounds_path: '/sounds/', + notification_icon: '/logo/conversejs-filled.svg' + }); + + _converse.isOnlyChatStateNotification = (msg) => + // See XEP-0085 Chat State Notification + _.isNull(msg.querySelector('body')) && ( + _.isNull(msg.querySelector(_converse.ACTIVE)) || + _.isNull(msg.querySelector(_converse.COMPOSING)) || + _.isNull(msg.querySelector(_converse.INACTIVE)) || + _.isNull(msg.querySelector(_converse.PAUSED)) || + _.isNull(msg.querySelector(_converse.GONE)) + ) + ; + + _converse.shouldNotifyOfGroupMessage = function (message) { + /* Is this a group message worthy of notification? */ - const { _converse } = this; - const { __ } = _converse; + let notify_all = _converse.notify_all_room_messages; + const jid = message.getAttribute('from'), + resource = Strophe.getResourceFromJid(jid), + room_jid = Strophe.getBareJidFromJid(jid), + sender = resource && Strophe.unescapeNode(resource) || ''; + if (sender === '' || message.querySelectorAll('delay').length > 0) { + return false; + } + const room = _converse.chatboxes.get(room_jid); + const body = message.querySelector('body'); + if (_.isNull(body)) { + return false; + } + const mentioned = (new RegExp(`\\b${room.get('nick')}\\b`)).test(body.textContent); + notify_all = notify_all === true || + (_.isArray(notify_all) && _.includes(notify_all, room_jid)); + if (sender === room.get('nick') || (!notify_all && !mentioned)) { + return false; + } + return true; + }; - _converse.supports_html5_notification = "Notification" in window; + _converse.isMessageToHiddenChat = function (message) { + if (_.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode)) { + const jid = Strophe.getBareJidFromJid(message.getAttribute('from')), + view = _converse.chatboxviews.get(jid); - _converse.api.settings.update({ - notify_all_room_messages: false, - show_desktop_notifications: true, - show_chatstate_notifications: false, - chatstate_notification_blacklist: [], - // ^ a list of JIDs to ignore concerning chat state notifications - play_sounds: true, - sounds_path: '/sounds/', - notification_icon: '/logo/conversejs-filled.svg' - }); - - _converse.isOnlyChatStateNotification = (msg) => - // See XEP-0085 Chat State Notification - _.isNull(msg.querySelector('body')) && ( - _.isNull(msg.querySelector(_converse.ACTIVE)) || - _.isNull(msg.querySelector(_converse.COMPOSING)) || - _.isNull(msg.querySelector(_converse.INACTIVE)) || - _.isNull(msg.querySelector(_converse.PAUSED)) || - _.isNull(msg.querySelector(_converse.GONE)) - ) - ; - - _converse.shouldNotifyOfGroupMessage = function (message) { - /* Is this a group message worthy of notification? - */ - let notify_all = _converse.notify_all_room_messages; - const jid = message.getAttribute('from'), - resource = Strophe.getResourceFromJid(jid), - room_jid = Strophe.getBareJidFromJid(jid), - sender = resource && Strophe.unescapeNode(resource) || ''; - if (sender === '' || message.querySelectorAll('delay').length > 0) { - return false; - } - const room = _converse.chatboxes.get(room_jid); - const body = message.querySelector('body'); - if (_.isNull(body)) { - return false; - } - const mentioned = (new RegExp(`\\b${room.get('nick')}\\b`)).test(body.textContent); - notify_all = notify_all === true || - (_.isArray(notify_all) && _.includes(notify_all, room_jid)); - if (sender === room.get('nick') || (!notify_all && !mentioned)) { - return false; + if (!_.isNil(view)) { + return view.model.get('hidden') || _converse.windowState === 'hidden' || !u.isVisible(view.el); } return true; - }; - - _converse.isMessageToHiddenChat = function (message) { - if (_.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode)) { - const jid = Strophe.getBareJidFromJid(message.getAttribute('from')), - view = _converse.chatboxviews.get(jid); - - if (!_.isNil(view)) { - return view.model.get('hidden') || _converse.windowState === 'hidden' || !u.isVisible(view.el); - } - return true; - } - return _converse.windowState === 'hidden'; } - - _converse.shouldNotifyOfMessage = function (message) { - const forwarded = message.querySelector('forwarded'); - if (!_.isNull(forwarded)) { - return false; - } else if (message.getAttribute('type') === 'groupchat') { - return _converse.shouldNotifyOfGroupMessage(message); - } else if (u.isHeadlineMessage(_converse, message)) { - // We want to show notifications for headline messages. - return _converse.isMessageToHiddenChat(message); - } - const is_me = Strophe.getBareJidFromJid( - message.getAttribute('from')) === _converse.bare_jid; - return !_converse.isOnlyChatStateNotification(message) && - !is_me && - _converse.isMessageToHiddenChat(message); - }; - - _converse.playSoundNotification = function () { - /* Plays a sound to notify that a new message was recieved. - */ - // XXX Eventually this can be refactored to use Notification's sound - // feature, but no browser currently supports it. - // https://developer.mozilla.org/en-US/docs/Web/API/notification/sound - let audioOgg, audioMp3, canPlayOgg, canPlayMp3; - if (_converse.play_sounds && !_.isUndefined(window.Audio)) { - audioOgg = new Audio(_converse.sounds_path+"msg_received.ogg"); - canPlayOgg = audioOgg.canPlayType('audio/ogg'); - audioMp3 = new Audio(_converse.sounds_path+"msg_received.mp3"); - canPlayMp3 = audioMp3.canPlayType('audio/mp3'); - - // Prefer 'probably' over 'maybe'. - if ( canPlayOgg === 'probably') { - audioOgg.play(); - } else if ( canPlayMp3 === 'probably' ) { - audioMp3.play(); - } else if ( canPlayOgg === 'maybe' ) { - audioOgg.play(); - } else if ( canPlayMp3 === 'maybe' ) { - audioMp3.play(); - } - } - }; - - _converse.areDesktopNotificationsEnabled = function () { - return _converse.supports_html5_notification && - _converse.show_desktop_notifications && - Notification.permission === "granted"; - }; - - _converse.showMessageNotification = function (message) { - /* Shows an HTML5 Notification to indicate that a new chat - * message was received. - */ - let title, roster_item; - const full_from_jid = message.getAttribute('from'), - from_jid = Strophe.getBareJidFromJid(full_from_jid); - if (message.getAttribute('type') === 'headline') { - if (!_.includes(from_jid, '@') || _converse.allow_non_roster_messaging) { - title = __("Notification from %1$s", from_jid); - } else { - return; - } - } else if (!_.includes(from_jid, '@')) { - // workaround for Prosody which doesn't give type "headline" - title = __("Notification from %1$s", from_jid); - } else if (message.getAttribute('type') === 'groupchat') { - title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid)); - } else { - if (_.isUndefined(_converse.roster)) { - _converse.log( - "Could not send notification, because roster is undefined", - Strophe.LogLevel.ERROR); - return; - } - roster_item = _converse.roster.get(from_jid); - if (!_.isUndefined(roster_item)) { - title = __("%1$s says", roster_item.getDisplayName()); - } else { - if (_converse.allow_non_roster_messaging) { - title = __("%1$s says", from_jid); - } else { - return; - } - } - } - // TODO: we should suppress notifications if we cannot decrypt - // the message... - const body = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, message).length ? - __('OMEMO Message received') : - _.get(message.querySelector('body'), 'textContent'); - if (!body) { - return; - } - const n = new Notification(title, { - 'body': body, - 'lang': _converse.locale, - 'icon': _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - }; - - _converse.showChatStateNotification = function (contact) { - /* Creates an HTML5 Notification to inform of a change in a - * contact's chat state. - */ - if (_.includes(_converse.chatstate_notification_blacklist, contact.jid)) { - // Don't notify if the user is being ignored. - return; - } - const chat_state = contact.chat_status; - let message = null; - if (chat_state === 'offline') { - message = __('has gone offline'); - } else if (chat_state === 'away') { - message = __('has gone away'); - } else if ((chat_state === 'dnd')) { - message = __('is busy'); - } else if (chat_state === 'online') { - message = __('has come online'); - } - if (message === null) { - return; - } - const n = new Notification(contact.getDisplayName(), { - body: message, - lang: _converse.locale, - icon: _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - }; - - _converse.showContactRequestNotification = function (contact) { - const n = new Notification(contact.getDisplayName(), { - body: __('wants to be your contact'), - lang: _converse.locale, - icon: _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - }; - - _converse.showFeedbackNotification = function (data) { - if (data.klass === 'error' || data.klass === 'warn') { - const n = new Notification(data.subject, { - body: data.message, - lang: _converse.locale, - icon: _converse.notification_icon - }); - setTimeout(n.close.bind(n), 5000); - } - }; - - _converse.handleChatStateNotification = function (contact) { - /* Event handler for on('contactPresenceChanged'). - * Will show an HTML5 notification to indicate that the chat - * status has changed. - */ - if (_converse.areDesktopNotificationsEnabled() && - _converse.show_chatstate_notifications) { - _converse.showChatStateNotification(contact); - } - }; - - _converse.handleMessageNotification = function (data) { - /* Event handler for the on('message') event. Will call methods - * to play sounds and show HTML5 notifications. - */ - const message = data.stanza; - if (!_converse.shouldNotifyOfMessage(message)) { - return false; - } - _converse.playSoundNotification(); - if (_converse.areDesktopNotificationsEnabled()) { - _converse.showMessageNotification(message); - } - }; - - _converse.handleContactRequestNotification = function (contact) { - if (_converse.areDesktopNotificationsEnabled(true)) { - _converse.showContactRequestNotification(contact); - } - }; - - _converse.handleFeedback = function (data) { - if (_converse.areDesktopNotificationsEnabled(true)) { - _converse.showFeedbackNotification(data); - } - }; - - _converse.requestPermission = function () { - if (_converse.supports_html5_notification && - ! _.includes(['denied', 'granted'], Notification.permission)) { - // Ask user to enable HTML5 notifications - Notification.requestPermission(); - } - }; - - _converse.on('pluginsInitialized', function () { - // We only register event handlers after all plugins are - // registered, because other plugins might override some of our - // handlers. - _converse.on('contactRequest', _converse.handleContactRequestNotification); - _converse.on('contactPresenceChanged', _converse.handleChatStateNotification); - _converse.on('message', _converse.handleMessageNotification); - _converse.on('feedback', _converse.handleFeedback); - _converse.on('connected', _converse.requestPermission); - }); + return _converse.windowState === 'hidden'; } - }); -})); + + _converse.shouldNotifyOfMessage = function (message) { + const forwarded = message.querySelector('forwarded'); + if (!_.isNull(forwarded)) { + return false; + } else if (message.getAttribute('type') === 'groupchat') { + return _converse.shouldNotifyOfGroupMessage(message); + } else if (u.isHeadlineMessage(_converse, message)) { + // We want to show notifications for headline messages. + return _converse.isMessageToHiddenChat(message); + } + const is_me = Strophe.getBareJidFromJid( + message.getAttribute('from')) === _converse.bare_jid; + return !_converse.isOnlyChatStateNotification(message) && + !is_me && + _converse.isMessageToHiddenChat(message); + }; + + + _converse.playSoundNotification = function () { + /* Plays a sound to notify that a new message was recieved. + */ + // XXX Eventually this can be refactored to use Notification's sound + // feature, but no browser currently supports it. + // https://developer.mozilla.org/en-US/docs/Web/API/notification/sound + if (_converse.play_sounds && !_.isUndefined(window.Audio)) { + const audioOgg = new Audio(_converse.sounds_path+"msg_received.ogg"); + const canPlayOgg = audioOgg.canPlayType('audio/ogg'); + if (canPlayOgg === 'probably') { + return audioOgg.play(); + } + const audioMp3 = new Audio(_converse.sounds_path+"msg_received.mp3"); + const canPlayMp3 = audioMp3.canPlayType('audio/mp3'); + if (canPlayMp3 === 'probably') { + audioMp3.play(); + } else if (canPlayOgg === 'maybe') { + audioOgg.play(); + } else if (canPlayMp3 === 'maybe') { + audioMp3.play(); + } + } + }; + + _converse.areDesktopNotificationsEnabled = function () { + return _converse.supports_html5_notification && + _converse.show_desktop_notifications && + Notification.permission === "granted"; + }; + + _converse.showMessageNotification = function (message) { + /* Shows an HTML5 Notification to indicate that a new chat + * message was received. + */ + let title, roster_item; + const full_from_jid = message.getAttribute('from'), + from_jid = Strophe.getBareJidFromJid(full_from_jid); + if (message.getAttribute('type') === 'headline') { + if (!_.includes(from_jid, '@') || _converse.allow_non_roster_messaging) { + title = __("Notification from %1$s", from_jid); + } else { + return; + } + } else if (!_.includes(from_jid, '@')) { + // workaround for Prosody which doesn't give type "headline" + title = __("Notification from %1$s", from_jid); + } else if (message.getAttribute('type') === 'groupchat') { + title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid)); + } else { + if (_.isUndefined(_converse.roster)) { + _converse.log( + "Could not send notification, because roster is undefined", + Strophe.LogLevel.ERROR); + return; + } + roster_item = _converse.roster.get(from_jid); + if (!_.isUndefined(roster_item)) { + title = __("%1$s says", roster_item.getDisplayName()); + } else { + if (_converse.allow_non_roster_messaging) { + title = __("%1$s says", from_jid); + } else { + return; + } + } + } + // TODO: we should suppress notifications if we cannot decrypt + // the message... + const body = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, message).length ? + __('OMEMO Message received') : + _.get(message.querySelector('body'), 'textContent'); + if (!body) { + return; + } + const n = new Notification(title, { + 'body': body, + 'lang': _converse.locale, + 'icon': _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + }; + + _converse.showChatStateNotification = function (contact) { + /* Creates an HTML5 Notification to inform of a change in a + * contact's chat state. + */ + if (_.includes(_converse.chatstate_notification_blacklist, contact.jid)) { + // Don't notify if the user is being ignored. + return; + } + const chat_state = contact.chat_status; + let message = null; + if (chat_state === 'offline') { + message = __('has gone offline'); + } else if (chat_state === 'away') { + message = __('has gone away'); + } else if ((chat_state === 'dnd')) { + message = __('is busy'); + } else if (chat_state === 'online') { + message = __('has come online'); + } + if (message === null) { + return; + } + const n = new Notification(contact.getDisplayName(), { + body: message, + lang: _converse.locale, + icon: _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + }; + + _converse.showContactRequestNotification = function (contact) { + const n = new Notification(contact.getDisplayName(), { + body: __('wants to be your contact'), + lang: _converse.locale, + icon: _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + }; + + _converse.showFeedbackNotification = function (data) { + if (data.klass === 'error' || data.klass === 'warn') { + const n = new Notification(data.subject, { + body: data.message, + lang: _converse.locale, + icon: _converse.notification_icon + }); + setTimeout(n.close.bind(n), 5000); + } + }; + + _converse.handleChatStateNotification = function (contact) { + /* Event handler for on('contactPresenceChanged'). + * Will show an HTML5 notification to indicate that the chat + * status has changed. + */ + if (_converse.areDesktopNotificationsEnabled() && + _converse.show_chatstate_notifications) { + _converse.showChatStateNotification(contact); + } + }; + + _converse.handleMessageNotification = function (data) { + /* Event handler for the on('message') event. Will call methods + * to play sounds and show HTML5 notifications. + */ + const message = data.stanza; + if (!_converse.shouldNotifyOfMessage(message)) { + return false; + } + _converse.playSoundNotification(); + if (_converse.areDesktopNotificationsEnabled()) { + _converse.showMessageNotification(message); + } + }; + + _converse.handleContactRequestNotification = function (contact) { + if (_converse.areDesktopNotificationsEnabled(true)) { + _converse.showContactRequestNotification(contact); + } + }; + + _converse.handleFeedback = function (data) { + if (_converse.areDesktopNotificationsEnabled(true)) { + _converse.showFeedbackNotification(data); + } + }; + + _converse.requestPermission = function () { + if (_converse.supports_html5_notification && + ! _.includes(['denied', 'granted'], Notification.permission)) { + // Ask user to enable HTML5 notifications + Notification.requestPermission(); + } + }; + + _converse.on('pluginsInitialized', function () { + // We only register event handlers after all plugins are + // registered, because other plugins might override some of our + // handlers. + _converse.on('contactRequest', _converse.handleContactRequestNotification); + _converse.on('contactPresenceChanged', _converse.handleChatStateNotification); + _converse.on('message', _converse.handleMessageNotification); + _converse.on('feedback', _converse.handleFeedback); + _converse.on('connected', _converse.requestPermission); + }); + } +}); + diff --git a/src/converse-oauth.js b/src/converse-oauth.js index 3cc4aa063..4b6f0c8ef 100644 --- a/src/converse-oauth.js +++ b/src/converse-oauth.js @@ -4,143 +4,136 @@ // Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as a module called "myplugin" - define(["@converse/headless/converse-core", "templates/oauth_providers.html", "hellojs"], factory); - } else { - // Browser globals. If you're not using a module loader such as require.js, - // then this line below executes. Make sure that your plugin's