Add a title attribute to emojis in messages

so that you can see the shortname when you hover your mouse over an emoji.
This commit is contained in:
JC Brand 2020-06-24 14:07:28 +02:00
parent 1b520328fa
commit b7a40dad41
6 changed files with 98 additions and 79 deletions

View File

@ -117,7 +117,7 @@
"no-console": "off", "no-console": "off",
"no-catch-shadow": "off", "no-catch-shadow": "off",
"no-cond-assign": [ "no-cond-assign": [
"error", "off",
"except-parens" "except-parens"
], ],
"no-confusing-arrow": "off", "no-confusing-arrow": "off",

View File

@ -43,7 +43,7 @@ export class EmojiPicker extends CustomElement {
'query': this.query, 'query': this.query,
'search_results': this.search_results, 'search_results': this.search_results,
'shouldBeHidden': shortname => this.shouldBeHidden(shortname), 'shouldBeHidden': shortname => this.shouldBeHidden(shortname),
'transformCategory': shortname => u.getEmojiRenderer()(this.getTonedShortname(shortname)) 'transformCategory': shortname => u.shortnamesToEmojis(this.getTonedShortname(shortname))
}); });
} }

View File

@ -951,7 +951,7 @@ converse.plugins.add('converse-chat', {
getOutgoingMessageAttributes (text, spoiler_hint) { getOutgoingMessageAttributes (text, spoiler_hint) {
const is_spoiler = this.get('composing_spoiler'); const is_spoiler = this.get('composing_spoiler');
const origin_id = u.getUniqueId(); 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 { return {
'from': _converse.bare_jid, 'from': _converse.bare_jid,
'fullname': _converse.xmppstatus.get('fullname'), 'fullname': _converse.xmppstatus.get('fullname'),

View File

@ -34,8 +34,7 @@ const ASCII_REPLACE_REGEX = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*
function convert (unicode) { function convert (unicode) {
// Converts unicode code points and code pairs // Converts unicode code points and code pairs to their respective characters
// to their respective characters
if (unicode.indexOf("-") > -1) { if (unicode.indexOf("-") > -1) {
const parts = [], const parts = [],
s = unicode.split('-'); s = unicode.split('-');
@ -82,44 +81,80 @@ function convertASCII2Emoji (str) {
} }
function getEmojiMarkup (shortname, unicode_only) { function getEmojiMarkup (data, options={unicode_only: false, add_title_wrapper: false}) {
if ((typeof shortname === 'undefined') || (shortname === '') || (!_converse.emoji_shortnames.includes(shortname))) { const emoji = data.emoji;
// if the shortname doesnt exist just return the entire match const shortname = data.shortname;
return shortname; if (emoji) {
if (api.settings.get('use_system_emojis')) {
return options.add_title_wrapper ? html`<span title="${shortname}">${emoji}</span>` : emoji;
} else {
const path = api.settings.get('emoji_image_path');
return html`<img class="emoji"
draggable="false"
alt="${emoji}"
src="${path}/72x72/${data.cp}.png"/>`;
} }
const codepoint = _converse.emojis_map[shortname].cp; } else if (options.unicode_only) {
if (codepoint) {
return convert(codepoint.toUpperCase());
} else if (unicode_only) {
return shortname; return shortname;
} else { } else {
return html`<img class="emoji" draggable="false" title="${shortname}" alt="${shortname}" src="${_converse.emojis_map[shortname].url}">`; return html`<img class="emoji"
draggable="false"
title="${shortname}"
alt="${shortname}"
src="${_converse.emojis_by_sn[shortname].url}">`;
} }
} }
function addEmojisMarkup (text, unicode_only=false) { function getShortnameReferences (text) {
const original_text = text;
let list = [text];
const references = [...text.matchAll(shortnames_regex)]; const references = [...text.matchAll(shortnames_regex)];
if (references.length) { return references.map(ref => {
references.map(ref => { const cp = _converse.emojis_by_sn[ref[0]].cp;
ref.begin = ref.index; return {
ref.end = ref.index+ref[0].length; cp,
return ref; '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) .sort((a, b) => b.begin - a.begin)
.forEach(ref => { .forEach(ref => {
const text = list.shift(); const text = list.shift();
const shortname = original_text.slice(ref.begin, ref.end); const emoji = getEmojiMarkup(ref, options);
const emoji = getEmojiMarkup(shortname, unicode_only);
if (isString(emoji)) { if (isString(emoji)) {
list = [text.slice(0, ref.begin) + emoji + text.slice(ref.end), ...list]; list = [text.slice(0, ref.begin) + emoji + text.slice(ref.end), ...list];
} else { } else {
list = [text.slice(0, ref.begin), emoji, text.slice(ref.end), ...list]; list = [text.slice(0, ref.begin), emoji, text.slice(ref.end), ...list];
} }
}); });
}
return list; return list;
} }
@ -190,33 +225,16 @@ converse.plugins.add('converse-emoji', {
const emojis_by_attribute = {}; const emojis_by_attribute = {};
Object.assign(u, { 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 * Replaces emoji shortnames in the passed-in string with unicode or image-based emojis
* (based on the value of `use_system_emojis`). * (based on the value of `use_system_emojis`).
* @method u.addEmoji * @method u.addEmoji
* @param {string} text = The text * @param { String } text = The text
* @returns {string} The text with shortnames replaced with emoji * @returns { String } The text with shortnames replaced with emoji unicodes or images.
* unicodes or images.
*/ */
addEmoji (text) { 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. * an `url` attribute which points to the source for the image.
* *
* @method u.shortnamesToEmojis * @method u.shortnamesToEmojis
* @param {String} str - String containg the shortname(s) * @param { String } str - String containg the shortname(s)
* @param {Boolean} unicode_only - Whether emojis are rendered as * @param { Object } options
* @param { Boolean } options.unicode_only - Whether emojis are rendered as
* unicode codepoints. If so, the returned result will be an array * unicode codepoints. If so, the returned result will be an array
* with containing one string, because the emojis themselves will * with containing one string, because the emojis themselves will
* also be strings. If set to false, emojis will be represented by * also be strings. If set to false, emojis will be represented by
* lit-html TemplateResult objects. * lit-html TemplateResult objects.
* @param { Boolean } options.add_title_wrapper - Whether unicode
* codepoints should be wrapped with a `<span>` 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 * @returns {Array} An array of at least one string, or otherwise
* strings and lit-html TemplateResult objects. * strings and lit-html TemplateResult objects.
*/ */
shortnamesToEmojis (str, unicode_only=false) { shortnamesToEmojis (str, options={unicode_only: false, add_title_wrapper: false}) {
str = convertASCII2Emoji(str); str = convertASCII2Emoji(str);
return addEmojisMarkup(str, unicode_only); return addEmojisMarkup(str, options);
}, },
/** /**
* Returns unicode represented by the passed in shortname. * Replaces all shortnames in the passed in string with their
* @method u.shortnameToUnicode * unicode (emoji) representation.
* @param {string} str - String containing the shortname(s) * @method u.shortnamesToUnicode
* @param { String } str - String containing the shortname(s)
* @returns { String }
*/ */
shortnameToUnicode (str) { shortnamesToUnicode (str) {
return u.shortnamesToEmojis(str, true)[0]; return u.shortnamesToEmojis(str, {'unicode_only': true})[0];
}, },
/** /**
* Determines whether the passed in string is just a single emoji shortname; * Determines whether the passed in string is just a single emoji shortname;
* @method u.isOnlyEmojis * @method u.isOnlyEmojis
* @param {string} shortname - A string which migh be just an emoji shortname * @param { String } shortname - A string which migh be just an emoji shortname
* @returns {boolean} * @returns { Boolean }
*/ */
isOnlyEmojis (text) { isOnlyEmojis (text) {
const words = text.trim().split(/\s+/); const words = text.trim().split(/\s+/);
@ -266,7 +291,7 @@ converse.plugins.add('converse-emoji', {
return false; return false;
} }
const rejects = words.filter(text => { 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(/<img class="emoji" draggable="false" alt=".*?" src=".*?\.png"\/>/); const match = result.match(/<img class="emoji" draggable="false" alt=".*?" src=".*?\.png"\/>/);
return !match || match.length !== 1; return !match || match.length !== 1;
}); });
@ -275,9 +300,9 @@ converse.plugins.add('converse-emoji', {
/** /**
* @method u.getEmojisByAtrribute * @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. * 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. * as keys and a list of emojis for a particular category as values.
*/ */
getEmojisByAtrribute (attr) { 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. // We extend the default converse.js API to add methods specific to MUC groupchats.
Object.assign(api, { Object.assign(api, {
/** /**
* The "rooms" namespace groups methods relevant to chatrooms * @namespace api.emojis
* (aka groupchats).
*
* @namespace api.rooms
* @memberOf api * @memberOf api
*/ */
emojis: { emojis: {
@ -323,8 +345,8 @@ converse.plugins.add('converse-emoji', {
const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json'); const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json');
_converse.emojis.json = json; _converse.emojis.json = json;
_converse.emojis.categories = Object.keys(_converse.emojis.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_by_sn = _converse.emojis.categories.reduce((result, cat) => Object.assign(result, _converse.emojis.json[cat]), {});
_converse.emojis_list = Object.values(_converse.emojis_map); _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.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); _converse.emoji_shortnames = _converse.emojis_list.map(m => m.sn);

View File

@ -997,7 +997,7 @@ converse.plugins.add('converse-muc', {
const is_spoiler = this.get('composing_spoiler'); const is_spoiler = this.get('composing_spoiler');
const [text, references] = this.parseTextForReferences(original_message); const [text, references] = this.parseTextForReferences(original_message);
const origin_id = u.getUniqueId(); 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 { return {
body, body,
is_spoiler, is_spoiler,

View File

@ -1,5 +1,5 @@
import { __ } from '@converse/headless/i18n'; 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"; import { html } from "lit-html";
const u = converse.env.utils; const u = converse.env.utils;
@ -32,7 +32,7 @@ const emoji_picker_header = (o) => html`
const emoji_item = (o) => { const emoji_item = (o) => {
return html` return html`
<li class="emoji insert-emoji ${o.shouldBeHidden(o.emoji.sn) ? 'hidden' : ''}" data-emoji="${o.emoji.sn}" title="${o.emoji.sn}"> <li class="emoji insert-emoji ${o.shouldBeHidden(o.emoji.sn) ? 'hidden' : ''}" data-emoji="${o.emoji.sn}" title="${o.emoji.sn}">
<a href="#" @click=${o.onEmojiPicked} data-emoji="${o.emoji.sn}">${o.transform(o.emoji.sn)}</a> <a href="#" @click=${o.onEmojiPicked} data-emoji="${o.emoji.sn}">${u.shortnamesToEmojis(o.emoji.sn)}</a>
</li> </li>
`; `;
} }
@ -53,16 +53,14 @@ const emojis_for_category = (o) => html`
</ul> </ul>
`; `;
const skintone_emoji = (o) => { const skintone_emoji = (o) => {
return html` return html`
<li data-skintone="${o.skintone}" class="emoji-skintone ${(o.current_skintone === o.skintone) ? 'picked' : ''}"> <li data-skintone="${o.skintone}" class="emoji-skintone ${(o.current_skintone === o.skintone) ? 'picked' : ''}">
<a class="pick-skintone" href="#" data-skintone="${o.skintone}" @click=${o.onSkintonePicked}>${o.transform(':'+o.skintone+':')}</a> <a class="pick-skintone" href="#" data-skintone="${o.skintone}" @click=${o.onSkintonePicked}>${u.shortnamesToEmojis(':'+o.skintone+':')}</a>
</li> </li>
`; `;
} }
const all_emojis = (o) => html` const all_emojis = (o) => html`
<span ?hidden=${o.query} class="emoji-lists__container emoji-lists__container--browse"> <span ?hidden=${o.query} class="emoji-lists__container emoji-lists__container--browse">
${Object.keys(o.emoji_categories).map(category => (o.emoji_categories[category] ? emojis_for_category(Object.assign({category}, o)) : ''))} ${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) => { export default (o) => {
o.emoji_categories = api.settings.get('emoji_categories'); o.emoji_categories = api.settings.get('emoji_categories');
o.emojis_by_category = _converse.emojis.json; o.emojis_by_category = _converse.emojis.json;
o.transform = u.getEmojiRenderer();
o.toned_emojis = _converse.emojis.toned; o.toned_emojis = _converse.emojis.toned;
return html` return html`