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-catch-shadow": "off",
"no-cond-assign": [
"error",
"off",
"except-parens"
],
"no-confusing-arrow": "off",

View File

@ -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))
});
}

View File

@ -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'),

View File

@ -34,8 +34,7 @@ const ASCII_REPLACE_REGEX = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*
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`<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"/>`;
}
} else if (options.unicode_only) {
return shortname;
} 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) {
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 `<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
* 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(/<img class="emoji" draggable="false" alt=".*?" src=".*?\.png"\/>/);
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);

View File

@ -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,

View File

@ -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`
<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>
`;
}
@ -53,16 +53,14 @@ const emojis_for_category = (o) => html`
</ul>
`;
const skintone_emoji = (o) => {
return html`
<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>
`;
}
const all_emojis = (o) => html`
<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)) : ''))}
@ -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`