diff --git a/.eslintrc.json b/.eslintrc.json index 1b7fb33fa..f7f2f948a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -117,7 +117,7 @@ "no-console": "off", "no-catch-shadow": "off", "no-cond-assign": [ - "error", + "off", "except-parens" ], "no-confusing-arrow": "off", diff --git a/src/components/emoji-picker.js b/src/components/emoji-picker.js index 10c98c76a..f3abd9979 100644 --- a/src/components/emoji-picker.js +++ b/src/components/emoji-picker.js @@ -43,7 +43,7 @@ export class EmojiPicker extends CustomElement { 'query': this.query, 'search_results': this.search_results, 'shouldBeHidden': shortname => this.shouldBeHidden(shortname), - 'transformCategory': shortname => u.getEmojiRenderer()(this.getTonedShortname(shortname)) + 'transformCategory': shortname => u.shortnamesToEmojis(this.getTonedShortname(shortname)) }); } diff --git a/src/headless/converse-chat.js b/src/headless/converse-chat.js index 6e6756413..7a2413e15 100644 --- a/src/headless/converse-chat.js +++ b/src/headless/converse-chat.js @@ -951,7 +951,7 @@ converse.plugins.add('converse-chat', { getOutgoingMessageAttributes (text, spoiler_hint) { const is_spoiler = this.get('composing_spoiler'); const origin_id = u.getUniqueId(); - const body = text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined; + const body = text ? u.httpToGeoUri(u.shortnamesToUnicode(text), _converse) : undefined; return { 'from': _converse.bare_jid, 'fullname': _converse.xmppstatus.get('fullname'), diff --git a/src/headless/converse-emoji.js b/src/headless/converse-emoji.js index 398fb4bee..e7e96014f 100644 --- a/src/headless/converse-emoji.js +++ b/src/headless/converse-emoji.js @@ -34,8 +34,7 @@ const ASCII_REPLACE_REGEX = new RegExp("]*>.*?<\/object>|]*>.* function convert (unicode) { - // Converts unicode code points and code pairs - // to their respective characters + // Converts unicode code points and code pairs to their respective characters if (unicode.indexOf("-") > -1) { const parts = [], s = unicode.split('-'); @@ -82,44 +81,80 @@ function convertASCII2Emoji (str) { } -function getEmojiMarkup (shortname, unicode_only) { - if ((typeof shortname === 'undefined') || (shortname === '') || (!_converse.emoji_shortnames.includes(shortname))) { - // if the shortname doesnt exist just return the entire match - return shortname; - } - const codepoint = _converse.emojis_map[shortname].cp; - if (codepoint) { - return convert(codepoint.toUpperCase()); - } else if (unicode_only) { +function getEmojiMarkup (data, options={unicode_only: false, add_title_wrapper: false}) { + const emoji = data.emoji; + const shortname = data.shortname; + if (emoji) { + if (api.settings.get('use_system_emojis')) { + return options.add_title_wrapper ? html`${emoji}` : emoji; + } else { + const path = api.settings.get('emoji_image_path'); + return html`${emoji}`; + } + } else if (options.unicode_only) { return shortname; } else { - return html`${shortname}`; + return html`${shortname}`; } } -function addEmojisMarkup (text, unicode_only=false) { - const original_text = text; - let list = [text]; +function getShortnameReferences (text) { const references = [...text.matchAll(shortnames_regex)]; - if (references.length) { - references.map(ref => { - ref.begin = ref.index; - ref.end = ref.index+ref[0].length; - return ref; - }) + return references.map(ref => { + const cp = _converse.emojis_by_sn[ref[0]].cp; + return { + cp, + 'begin': ref.index, + 'end': ref.index+ref[0].length, + 'shortname': ref[0], + 'emoji': cp ? convert(cp) : null + } + }); +} + + +function getCodePointReferences (text) { + const references = []; + const how = { + callback: (icon_id) => { + const emoji = convert(icon_id); + const begin = text.indexOf(emoji); + references.push({ + 'emoji': emoji, + 'end': begin + emoji.length, + 'shortname': u.getEmojisByAtrribute('cp')[icon_id]['sn'], + begin, + cp: icon_id + }); + return false; + } + }; + twemoji.default.parse(text, how); + return references; +} + + +function addEmojisMarkup (text, options) { + let list = [text]; + [...getShortnameReferences(text), ...getCodePointReferences(text)] .sort((a, b) => b.begin - a.begin) .forEach(ref => { const text = list.shift(); - const shortname = original_text.slice(ref.begin, ref.end); - const emoji = getEmojiMarkup(shortname, unicode_only); + const emoji = getEmojiMarkup(ref, options); if (isString(emoji)) { list = [text.slice(0, ref.begin) + emoji + text.slice(ref.end), ...list]; } else { list = [text.slice(0, ref.begin), emoji, text.slice(ref.end), ...list]; } }); - } return list; } @@ -190,33 +225,16 @@ converse.plugins.add('converse-emoji', { const emojis_by_attribute = {}; Object.assign(u, { - /** - * Based on the value of `use_system_emojis` will return either - * a function that converts emoji shortnames into unicode glyphs - * (see {@link u.shortnamesToEmojis} or one that converts them into images. - * @returns {function} - */ - getEmojiRenderer () { - const how = { - 'attributes': icon => { - const codepoint = twemoji.default.convert.toCodePoint(icon); - return {'title': `${u.getEmojisByAtrribute('cp')[codepoint]['sn']} ${icon}`} - } - }; - const transform = u.shortnamesToEmojis; - return api.settings.get('use_system_emojis') ? transform : text => twemoji.default.parse(transform(text), how); - }, - /** * Replaces emoji shortnames in the passed-in string with unicode or image-based emojis * (based on the value of `use_system_emojis`). * @method u.addEmoji - * @param {string} text = The text - * @returns {string} The text with shortnames replaced with emoji - * unicodes or images. + * @param { String } text = The text + * @returns { String } The text with shortnames replaced with emoji unicodes or images. */ addEmoji (text) { - return u.getEmojiRenderer()(text); + const options = {add_title_wrapper: true, unicode_only: false}; + return u.shortnamesToEmojis(text, options); }, /** @@ -231,34 +249,41 @@ converse.plugins.add('converse-emoji', { * an `url` attribute which points to the source for the image. * * @method u.shortnamesToEmojis - * @param {String} str - String containg the shortname(s) - * @param {Boolean} unicode_only - Whether emojis are rendered as - * unicode codepoints. If so, the returned result will be an array - * with containing one string, because the emojis themselves will - * also be strings. If set to false, emojis will be represented by - * lit-html TemplateResult objects. + * @param { String } str - String containg the shortname(s) + * @param { Object } options + * @param { Boolean } options.unicode_only - Whether emojis are rendered as + * unicode codepoints. If so, the returned result will be an array + * with containing one string, because the emojis themselves will + * also be strings. If set to false, emojis will be represented by + * lit-html TemplateResult objects. + * @param { Boolean } options.add_title_wrapper - Whether unicode + * codepoints should be wrapped with a `` element with a + * title, so that the shortname is shown upon hovering with the + * mouse. * @returns {Array} An array of at least one string, or otherwise * strings and lit-html TemplateResult objects. */ - shortnamesToEmojis (str, unicode_only=false) { + shortnamesToEmojis (str, options={unicode_only: false, add_title_wrapper: false}) { str = convertASCII2Emoji(str); - return addEmojisMarkup(str, unicode_only); + return addEmojisMarkup(str, options); }, /** - * Returns unicode represented by the passed in shortname. - * @method u.shortnameToUnicode - * @param {string} str - String containing the shortname(s) + * Replaces all shortnames in the passed in string with their + * unicode (emoji) representation. + * @method u.shortnamesToUnicode + * @param { String } str - String containing the shortname(s) + * @returns { String } */ - shortnameToUnicode (str) { - return u.shortnamesToEmojis(str, true)[0]; + shortnamesToUnicode (str) { + return u.shortnamesToEmojis(str, {'unicode_only': true})[0]; }, /** * Determines whether the passed in string is just a single emoji shortname; * @method u.isOnlyEmojis - * @param {string} shortname - A string which migh be just an emoji shortname - * @returns {boolean} + * @param { String } shortname - A string which migh be just an emoji shortname + * @returns { Boolean } */ isOnlyEmojis (text) { const words = text.trim().split(/\s+/); @@ -266,7 +291,7 @@ converse.plugins.add('converse-emoji', { return false; } const rejects = words.filter(text => { - const result = twemoji.default.parse(u.shortnameToUnicode(text)); + const result = twemoji.default.parse(u.shortnamesToUnicode(text)); const match = result.match(/.*?/); return !match || match.length !== 1; }); @@ -275,9 +300,9 @@ converse.plugins.add('converse-emoji', { /** * @method u.getEmojisByAtrribute - * @param {string} attr - The attribute according to which the + * @param { String } attr - The attribute according to which the * returned map should be keyed. - * @returns {object} - Map of emojis with the passed in attribute values + * @returns { Object } - Map of emojis with the passed in attribute values * as keys and a list of emojis for a particular category as values. */ getEmojisByAtrribute (attr) { @@ -303,10 +328,7 @@ converse.plugins.add('converse-emoji', { // We extend the default converse.js API to add methods specific to MUC groupchats. Object.assign(api, { /** - * The "rooms" namespace groups methods relevant to chatrooms - * (aka groupchats). - * - * @namespace api.rooms + * @namespace api.emojis * @memberOf api */ emojis: { @@ -323,8 +345,8 @@ converse.plugins.add('converse-emoji', { const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json'); _converse.emojis.json = json; _converse.emojis.categories = Object.keys(_converse.emojis.json); - _converse.emojis_map = _converse.emojis.categories.reduce((result, cat) => Object.assign(result, _converse.emojis.json[cat]), {}); - _converse.emojis_list = Object.values(_converse.emojis_map); + _converse.emojis_by_sn = _converse.emojis.categories.reduce((result, cat) => Object.assign(result, _converse.emojis.json[cat]), {}); + _converse.emojis_list = Object.values(_converse.emojis_by_sn); _converse.emojis_list.sort((a, b) => a.sn < b.sn ? -1 : (a.sn > b.sn ? 1 : 0)); _converse.emoji_shortnames = _converse.emojis_list.map(m => m.sn); diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index c555a4e8b..9d6271acc 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -997,7 +997,7 @@ converse.plugins.add('converse-muc', { const is_spoiler = this.get('composing_spoiler'); const [text, references] = this.parseTextForReferences(original_message); const origin_id = u.getUniqueId(); - const body = text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined; + const body = text ? u.httpToGeoUri(u.shortnamesToUnicode(text), _converse) : undefined; return { body, is_spoiler, diff --git a/src/templates/emoji_picker.js b/src/templates/emoji_picker.js index 3ec0037f6..799afb920 100644 --- a/src/templates/emoji_picker.js +++ b/src/templates/emoji_picker.js @@ -1,5 +1,5 @@ import { __ } from '@converse/headless/i18n'; -import { _converse, api } from "@converse/headless/converse-core"; +import { _converse, converse, api } from "@converse/headless/converse-core"; import { html } from "lit-html"; const u = converse.env.utils; @@ -32,7 +32,7 @@ const emoji_picker_header = (o) => html` const emoji_item = (o) => { return html`
  • - ${o.transform(o.emoji.sn)} + ${u.shortnamesToEmojis(o.emoji.sn)}
  • `; } @@ -53,16 +53,14 @@ const emojis_for_category = (o) => html` `; - const skintone_emoji = (o) => { return html`
  • - ${o.transform(':'+o.skintone+':')} + ${u.shortnamesToEmojis(':'+o.skintone+':')}
  • `; } - const all_emojis = (o) => html` ${Object.keys(o.emoji_categories).map(category => (o.emoji_categories[category] ? emojis_for_category(Object.assign({category}, o)) : ''))} @@ -73,7 +71,6 @@ const all_emojis = (o) => html` export default (o) => { o.emoji_categories = api.settings.get('emoji_categories'); o.emojis_by_category = _converse.emojis.json; - o.transform = u.getEmojiRenderer(); o.toned_emojis = _converse.emojis.toned; return html`