Commit 73c8002b authored by JC Brand's avatar JC Brand

Add search bar for emojis

parent 13d41910
...@@ -21,8 +21,10 @@ ...@@ -21,8 +21,10 @@
overflow-y: hidden; overflow-y: hidden;
background: white; background: white;
.emoji-picker__lists { .emoji-picker__lists {
height: 100%;
overflow-y: auto; overflow-y: auto;
.emoji-category__heading { .emoji-category__heading {
cursor: auto;
color: var(--subdued-color); color: var(--subdued-color);
font-size: var(--font-size); font-size: var(--font-size);
padding: 0.5em 0 0 0.5em; padding: 0.5em 0 0 0.5em;
...@@ -70,9 +72,17 @@ ...@@ -70,9 +72,17 @@
} }
} }
} }
.emoji-category-picker { .emoji-picker__header {
display: flex;
flex-direction: column;
padding-top: 0.5em; padding-top: 0.5em;
background-color: var(--chat-head-color); background-color: var(--chat-head-color);
.emoji-search {
width: auto;
margin: 0.25em;
height: 2em;
font-size: var(--font-size-small);
}
ul { ul {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
...@@ -105,7 +115,7 @@ ...@@ -105,7 +115,7 @@
.emoji-skintone-picker { .emoji-skintone-picker {
background-color: var(--chatroom-head-color); background-color: var(--chatroom-head-color);
} }
.emoji-category-picker { .emoji-picker__header {
background-color: var(--chatroom-head-color); background-color: var(--chatroom-head-color);
.emoji-category { .emoji-category {
&.picked { &.picked {
...@@ -145,7 +155,7 @@ ...@@ -145,7 +155,7 @@
.emoji-skintone-picker { .emoji-skintone-picker {
font-size: var(--font-size-small); font-size: var(--font-size-small);
} }
.emoji-category-picker { .emoji-picker__header {
.emoji-category { .emoji-category {
font-size: var(--font-size); font-size: var(--font-size);
} }
......
...@@ -13,7 +13,7 @@ import BrowserStorage from "backbone.browserStorage"; ...@@ -13,7 +13,7 @@ import BrowserStorage from "backbone.browserStorage";
import bootstrap from "bootstrap.native"; import bootstrap from "bootstrap.native";
import tpl_emoji_button from "templates/emoji_button.html"; import tpl_emoji_button from "templates/emoji_button.html";
import tpl_emojis from "templates/emojis.html"; import tpl_emojis from "templates/emojis.html";
const { Backbone } = converse.env; const { Backbone, _ } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
...@@ -106,32 +106,18 @@ converse.plugins.add('converse-emoji-views', { ...@@ -106,32 +106,18 @@ converse.plugins.add('converse-emoji-views', {
Object.assign(_converse.ChatBoxView.prototype, emoji_aware_chat_view); Object.assign(_converse.ChatBoxView.prototype, emoji_aware_chat_view);
function emojiShouldBeHidden (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 (shortname.includes('_tone')) {
if (!current_skintone || !shortname.includes(current_skintone)) {
return true;
}
} else {
if (current_skintone && toned_emojis.includes(shortname)) {
return true;
}
}
return false;
}
_converse.EmojiPickerView = Backbone.VDOMView.extend({ _converse.EmojiPickerView = Backbone.VDOMView.extend({
className: 'emoji-picker__container', className: 'emoji-picker__container',
events: { events: {
'click .emoji-category-picker li.emoji-category': 'chooseCategory', 'click .emoji-picker__header li.emoji-category': 'chooseCategory',
'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone', 'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone',
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji' 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'keydown .emoji-search': 'onKeyDown'
}, },
initialize () { initialize () {
this.debouncedFilter = _.debounce(input => this.filter(input), 100);
this.model.on('change:query', this.render, this);
this.model.on('change:current_skintone', this.render, this); this.model.on('change:current_skintone', this.render, this);
this.model.on('change:current_category', () => { this.model.on('change:current_category', () => {
this.render(); this.render();
...@@ -144,10 +130,11 @@ converse.plugins.add('converse-emoji-views', { ...@@ -144,10 +130,11 @@ converse.plugins.add('converse-emoji-views', {
const html = tpl_emojis( const html = tpl_emojis(
Object.assign( Object.assign(
this.model.toJSON(), { this.model.toJSON(), {
'__': __,
'_converse': _converse, '_converse': _converse,
'emoji_categories': _converse.emoji_categories, 'emoji_categories': _converse.emoji_categories,
'emojis_by_category': u.getEmojisByCategory(), 'emojis_by_category': u.getEmojisByCategory(),
'shouldBeHidden': emojiShouldBeHidden, 'shouldBeHidden': shortname => this.shouldBeHidden(shortname),
'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'], 'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'],
'toned_emojis': _converse.emojis.toned, 'toned_emojis': _converse.emojis.toned,
'transform': u.getEmojiRenderer(), 'transform': u.getEmojiRenderer(),
...@@ -158,6 +145,58 @@ converse.plugins.add('converse-emoji-views', { ...@@ -158,6 +145,58 @@ converse.plugins.add('converse-emoji-views', {
return html; return html;
}, },
filter (input) {
this.model.set({'query': input.value});
},
onKeyDown (ev) {
if (ev.keyCode === _converse.keycodes.TAB) {
ev.preventDefault();
const match = _.find(_converse.emoji_shortnames, sn => _converse.FILTER_CONTAINS(sn, ev.target.value));
if (match) {
// XXX: Ideally we would set `query` on the model and
// then let the view re-render, instead of doing it
// manually here. Unfortunately this doesn't work, the
// value gets set on the HTML element, but is not
// visible to the new user.
ev.target.value = match;
this.filter(ev.target);
}
} else if (ev.keyCode === _converse.keycodes.ENTER) {
ev.preventDefault();
ev.stopPropagation();
if (_converse.emoji_shortnames.includes(ev.target.value)) {
this.chatview.insertIntoTextArea(ev.target.value);
// XXX: See above
ev.target.value = '';
this.filter(ev.target);
}
} else {
this.debouncedFilter(ev.target);
}
},
shouldBeHidden (shortname) {
// Helper method for the template which decides whether an
// emoji should be hidden, based on which skin tone is
// currently being applied.
const current_skintone = this.model.get('current_skintone');
if (shortname.includes('_tone')) {
if (!current_skintone || !shortname.includes(current_skintone)) {
return true;
}
} else {
if (current_skintone && _converse.emojis.toned.includes(shortname)) {
return true;
}
}
const query = this.model.get('query');
if (query && !_converse.FILTER_CONTAINS(shortname, query)) {
return true;
}
return false;
},
getTonedShortname (shortname) { getTonedShortname (shortname) {
if (_converse.emojis.toned.includes(shortname) && this.model.get('current_skintone')) { if (_converse.emojis.toned.includes(shortname) && this.model.get('current_skintone')) {
return `${shortname.slice(0, shortname.length-1)}_${this.model.get('current_skintone')}:` return `${shortname.slice(0, shortname.length-1)}_${this.model.get('current_skintone')}:`
...@@ -197,6 +236,10 @@ converse.plugins.add('converse-emoji-views', { ...@@ -197,6 +236,10 @@ converse.plugins.add('converse-emoji-views', {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target; const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
// XXX: See above
const input = this.el.querySelector('.emoji-search');
input.value = '';
this.filter(input);
this.chatview.insertIntoTextArea(target.getAttribute('data-emoji')); this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'));
} }
}); });
......
...@@ -382,6 +382,7 @@ converse.plugins.add('converse-emoji', { ...@@ -382,6 +382,7 @@ converse.plugins.add('converse-emoji', {
await fetchEmojiJSON(); await fetchEmojiJSON();
_converse.emojis.shortnames_regex = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+getShortNames()+")", "gi"); _converse.emojis.shortnames_regex = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+getShortNames()+")", "gi");
_converse.emojis_list = Object.values(_converse.emojis.json); _converse.emojis_list = Object.values(_converse.emojis.json);
_converse.emoji_shortnames = _converse.emojis_list.map(m => m.sn);
const excluded_categories = ['modifier', 'regional']; const excluded_categories = ['modifier', 'regional'];
_converse.emojis.all_categories = _converse.emojis_list _converse.emojis.all_categories = _converse.emojis_list
......
<div class="emoji-picker__container"> <div class="emoji-picker__container">
<div class="emoji-category-picker"> <div class="emoji-picker__header">
<input class="form-control emoji-search" name="emoji-search" placeholder="{{{o.__('Search')}}}"/>
<ul> <ul>
{[ Object.keys(o.emoji_categories).forEach(function (category) { ]} {[ Object.keys(o.emoji_categories).forEach(function (category) { ]}
<li data-category="{{{category}}}" class="emoji-category {[ if (o.current_category === category) { ]} picked {[ } ]}"> <li data-category="{{{category}}}" class="emoji-category {[ if (o.current_category === category) { ]} picked {[ } ]}">
...@@ -8,18 +9,32 @@ ...@@ -8,18 +9,32 @@
{[ }); ]} {[ }); ]}
</ul> </ul>
</div> </div>
<div class="emoji-picker__lists"> <div class="emoji-picker__lists">
{[ Object.keys(o.emoji_categories).forEach(function (category) { ]} {[ if (o.query) { ]}
<a id="emoji-picker-{{{category}}}" class="emoji-category__heading">{{{o._converse.emoji_category_labels[category]}}}</a> <a id="emoji-picker-search-results" class="emoji-category__heading">{{{o.__('Search results')}}}</a>
<ul class="emoji-picker emoji-picker-{{{category}}}"> <ul class="emoji-picker">
{[ o.emojis_by_category[category].forEach(function (emoji) { ]} {[ o._converse.emojis_list.forEach(function (emoji) { ]}
<li class="emoji insert-emoji {[ if (o.shouldBeHidden(emoji.sn, o.current_skintone, o.toned_emojis)) { ]} hidden {[ }; ]}" <li class="emoji insert-emoji {[ if (o.shouldBeHidden(emoji.sn)) { ]} hidden {[ }; ]}"
data-emoji="{{{emoji.sn}}}" title="{{{emoji.sn}}}"> data-emoji="{{{emoji.sn}}}" title="{{{emoji.sn}}}">
<a href="#" data-emoji="{{{emoji.sn}}}"> {{ o.transform(emoji.sn) }} </a> <a href="#" data-emoji="{{{emoji.sn}}}"> {{ o.transform(emoji.sn) }} </a>
</li> </li>
{[ }); ]} {[ }); ]}
</ul> </ul>
{[ }); ]} {[ } else { ]}
{[ Object.keys(o.emoji_categories).forEach(function (category) { ]}
<a id="emoji-picker-{{{category}}}" class="emoji-category__heading">{{{o._converse.emoji_category_labels[category]}}}</a>
<ul class="emoji-picker">
{[ o.emojis_by_category[category].forEach(function (emoji) { ]}
<li class="emoji insert-emoji {[ if (o.shouldBeHidden(emoji.sn)) { ]} hidden {[ }; ]}"
data-emoji="{{{emoji.sn}}}" title="{{{emoji.sn}}}">
<a href="#" data-emoji="{{{emoji.sn}}}"> {{ o.transform(emoji.sn) }} </a>
</li>
{[ }); ]}
</ul>
{[ }); ]}
{[ } ]}
</div> </div>
<div class="emoji-skintone-picker"> <div class="emoji-skintone-picker">
<label>Skin tone</label> <label>Skin tone</label>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment