Add initial support for custom emojis
This commit is contained in:
parent
2ed8b4660e
commit
e6e23a1a82
|
@ -6,6 +6,9 @@
|
|||
- #1691 Fix `collection.chatbox is undefined` errors
|
||||
- #1733 New message notifications for a minimized chat stack on top of each other
|
||||
- Prevent editing of sent file uploads.
|
||||
- Initial support for sending custom emojis. Currently only between Converse
|
||||
instances. Still working out a wire protocol for compatibility with other clients.
|
||||
To add custom emojis, edit the `emojis.json` file.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -68,7 +68,7 @@ serve_bg: stamp-npm
|
|||
dist/converse-no-dependencies.js: src webpack.common.js webpack.nodeps.js stamp-npm @converse/headless
|
||||
npm run nodeps
|
||||
|
||||
GETTEXT = $(XGETTEXT) --from-code=UTF-8 --language=JavaScript --keyword=__ -keyword=___ --force-po --output=locale/converse.pot --package-name=Converse.js --copyright-holder="Jan-Carel Brand" --package-version=5.0.4 dist/converse-no-dependencies.js -c
|
||||
GETTEXT = $(XGETTEXT) --from-code=UTF-8 --language=JavaScript --keyword=__ --keyword=___ --force-po --output=locale/converse.pot --package-name=Converse.js --copyright-holder="Jan-Carel Brand" --package-version=5.0.4 dist/converse-no-dependencies.js -c
|
||||
|
||||
.PHONY: pot
|
||||
pot: dist/converse-no-dependencies.js
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
|
@ -664,6 +664,63 @@ domain_placeholder
|
|||
The placeholder text shown in the domain input on the registration form.
|
||||
|
||||
|
||||
emoji_categories
|
||||
----------------
|
||||
|
||||
* Default:::
|
||||
{
|
||||
"smileys": ":grinning:",
|
||||
"people": ":thumbsup:",
|
||||
"activity": ":soccer:",
|
||||
"travel": ":motorcycle:",
|
||||
"objects": ":bomb:",
|
||||
"nature": ":rainbow:",
|
||||
"food": ":hotdog:",
|
||||
"symbols": ":musical_note:",
|
||||
"flags": ":flag_ac:",
|
||||
"custom": ":converse:"
|
||||
}
|
||||
|
||||
This setting lets you define the categories that are available in the emoji
|
||||
picker, as well as the default image that's shown for each category.
|
||||
|
||||
The keys of the map are the categories and the values are the shortnames of the
|
||||
representative images.
|
||||
|
||||
If you want to remove a category, don't just remove the key, instead set its
|
||||
value to ``undefined``.
|
||||
|
||||
Due to restrictions intended to prevent addition of undeclared configuration
|
||||
settings, it's not possible to add new emoji categories. There is however a
|
||||
``custom`` category where you can put your own custom emojis (also known as
|
||||
"stickers").
|
||||
|
||||
To add custom emojis, you need to edit ``src/headless/emojis.json`` to add new
|
||||
entries to the map under the ``custom`` key.
|
||||
|
||||
|
||||
emoji_categories_label
|
||||
----------------------
|
||||
|
||||
* Default:::
|
||||
{
|
||||
"smileys": "Smileys and emotions",
|
||||
"people": "People",
|
||||
"activity": "Activities",
|
||||
"travel": "Travel",
|
||||
"objects": "Objects",
|
||||
"nature": "Animals and nature",
|
||||
"food": "Food and drink",
|
||||
"symbols": "Symbols",
|
||||
"flags": "Flags",
|
||||
"custom": "Stickers"
|
||||
}
|
||||
|
||||
This setting lets you pass in the text value that goes into the `title`
|
||||
attribute for the emoji categories. These strings will be translated, but for
|
||||
your custom text to be translatable, you'll need to wrap it in `__()``
|
||||
somewhere in your own code.
|
||||
|
||||
emoji_image_path
|
||||
----------------
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
209
sass/_emoji.scss
209
sass/_emoji.scss
|
@ -7,100 +7,103 @@
|
|||
vertical-align: -0.1em;
|
||||
}
|
||||
|
||||
.toggle-smiley {
|
||||
a.toggle-smiley {
|
||||
padding: 0;
|
||||
}
|
||||
.emoji-picker.toolbar-menu {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
background-color: var(--chat-head-color);
|
||||
.emoji-picker__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
background: white;
|
||||
.emoji-picker__lists {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
.emoji-category__heading {
|
||||
cursor: auto;
|
||||
color: var(--subdued-color);
|
||||
font-size: var(--font-size);
|
||||
padding: 0.5em 0 0 0.5em;
|
||||
}
|
||||
.sendXMPPMessage {
|
||||
.toggle-smiley {
|
||||
a.toggle-smiley {
|
||||
padding: 0;
|
||||
}
|
||||
.emoji-picker.toolbar-menu {
|
||||
min-width: 23rem;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
background-color: var(--chat-head-color);
|
||||
.emoji-picker__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.emoji-skintone-picker {
|
||||
display: flex;
|
||||
label {
|
||||
margin: 0;
|
||||
padding: 0 0.5em;
|
||||
white-space: nowrap;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--heading-color);
|
||||
}
|
||||
li {
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
padding: 0.5em 0;
|
||||
background-color: var(--chat-head-color);
|
||||
width: auto;
|
||||
font-size: var(--font-size);
|
||||
}
|
||||
}
|
||||
.emoji-picker {
|
||||
background-color: white;
|
||||
padding: 0.5em;
|
||||
li {
|
||||
margin-left: 0;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
&.insert-emoji {
|
||||
margin: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
&.picked {
|
||||
background-color: var(--highlight-color);
|
||||
overflow-y: hidden;
|
||||
background: white;
|
||||
.emoji-picker__lists {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
.emoji-category__heading {
|
||||
cursor: auto;
|
||||
color: var(--subdued-color);
|
||||
font-size: var(--font-size);
|
||||
padding: 0.5em 0 0 0.5em;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.emoji-skintone-picker {
|
||||
display: flex;
|
||||
label {
|
||||
margin: 0;
|
||||
padding: 0 0.5em;
|
||||
white-space: nowrap;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--heading-color);
|
||||
}
|
||||
li {
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
padding: 0.5em 0;
|
||||
background-color: var(--chat-head-color);
|
||||
width: auto;
|
||||
font-size: var(--font-size);
|
||||
}
|
||||
}
|
||||
.emoji-picker {
|
||||
background-color: white;
|
||||
padding: 0.5em;
|
||||
li {
|
||||
margin-left: 0;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
&.insert-emoji {
|
||||
margin: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
&.picked {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
a {
|
||||
&:hover {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
font-size: var(--font-size-huge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.emoji-picker__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0.5em;
|
||||
background-color: var(--chat-head-color);
|
||||
.emoji-search {
|
||||
width: auto;
|
||||
margin: 0.25em;
|
||||
height: 2em;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
.emoji-category {
|
||||
&.picked {
|
||||
background-color: white;
|
||||
border: 1px var(--chat-head-color) solid;
|
||||
border-bottom: none;
|
||||
}
|
||||
padding: 0.25em;
|
||||
font-size: var(--font-size-huge);
|
||||
&:hover {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
font-size: var(--font-size-huge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.emoji-picker__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0.5em;
|
||||
background-color: var(--chat-head-color);
|
||||
.emoji-search {
|
||||
width: auto;
|
||||
margin: 0.25em;
|
||||
height: 2em;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
.emoji-category {
|
||||
&.picked {
|
||||
background-color: white;
|
||||
border: 1px var(--chat-head-color) solid;
|
||||
border-bottom: none;
|
||||
}
|
||||
padding: 0.25em;
|
||||
font-size: var(--font-size-huge);
|
||||
&:hover {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,20 +113,22 @@
|
|||
}
|
||||
|
||||
.chatroom {
|
||||
.toggle-smiley {
|
||||
.emoji-picker.toolbar-menu {
|
||||
background-color: var(--chatroom-head-color);
|
||||
.emoji-picker__container {
|
||||
background: white;
|
||||
.emoji-skintone-picker {
|
||||
background-color: var(--chatroom-head-color);
|
||||
}
|
||||
.emoji-picker__header {
|
||||
background-color: var(--chatroom-head-color);
|
||||
.emoji-category {
|
||||
&.picked {
|
||||
border: 1px var(--chatroom-head-color) solid;
|
||||
border-bottom: none;
|
||||
.sendXMPPMessage {
|
||||
.toggle-smiley {
|
||||
.emoji-picker.toolbar-menu {
|
||||
background-color: var(--chatroom-head-color);
|
||||
.emoji-picker__container {
|
||||
background: white;
|
||||
.emoji-skintone-picker {
|
||||
background-color: var(--chatroom-head-color);
|
||||
}
|
||||
.emoji-picker__header {
|
||||
background-color: var(--chatroom-head-color);
|
||||
.emoji-category {
|
||||
&.picked {
|
||||
border: 1px var(--chatroom-head-color) solid;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker__container')));
|
||||
const picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
|
||||
const input = picker.querySelector('.emoji-search');
|
||||
expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1589);
|
||||
expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1591);
|
||||
|
||||
expect(view.emoji_picker_view.model.get('query')).toBeUndefined();
|
||||
input.value = 'smiley';
|
||||
|
|
|
@ -1026,9 +1026,10 @@
|
|||
// Non-https images aren't rendered
|
||||
base_url = document.URL.split(window.location.pathname)[0];
|
||||
message = base_url+"/logo/conversejs-filled.svg";
|
||||
expect(view.el.querySelectorAll('img').length).toBe(4);
|
||||
const chat_content = view.el.querySelector('.chat-content');
|
||||
expect(chat_content.querySelectorAll('img').length).toBe(4);
|
||||
test_utils.sendMessage(view, message);
|
||||
expect(view.el.querySelectorAll('img').length).toBe(4);
|
||||
expect(chat_content.querySelectorAll('img').length).toBe(4);
|
||||
done();
|
||||
}));
|
||||
|
||||
|
|
|
@ -313,6 +313,28 @@ _converse.__ = function (str) {
|
|||
return i18n.translate.apply(i18n, arguments);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A no-op method which is used to signal to gettext that the passed in string
|
||||
* should be included in the pot translation file.
|
||||
*
|
||||
* In contrast to the double-underscore method, the triple underscore method
|
||||
* doesn't actually translate the strings.
|
||||
*
|
||||
* One reason for this method might be because we're using strings we cannot
|
||||
* send to the translation function because they require variable interpolation
|
||||
* and we don't yet have the variables at scan time.
|
||||
*
|
||||
* @method ___
|
||||
* @private
|
||||
* @memberOf _converse
|
||||
* @param { String } str
|
||||
*/
|
||||
_converse.___ = function (str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
const __ = _converse.__;
|
||||
|
||||
const PROMISES = [
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as twemoji from "twemoji";
|
|||
import _ from "./lodash.noconflict";
|
||||
import converse from "./converse-core";
|
||||
|
||||
const { Backbone, } = converse.env;
|
||||
const { Backbone } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
const ASCII_LIST = {
|
||||
|
@ -166,7 +166,7 @@ converse.plugins.add('converse-emoji', {
|
|||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
const { _converse } = this;
|
||||
const { __ } = _converse;
|
||||
const { ___ } = _converse;
|
||||
|
||||
_converse.api.settings.update({
|
||||
'emoji_image_path': twemoji.default.base,
|
||||
|
@ -179,23 +179,32 @@ converse.plugins.add('converse-emoji', {
|
|||
"nature": ":rainbow:",
|
||||
"food": ":hotdog:",
|
||||
"symbols": ":musical_note:",
|
||||
"flags": ":flag_ac:"
|
||||
"flags": ":flag_ac:",
|
||||
"custom": ":converse:"
|
||||
},
|
||||
// We use the triple-underscore method which doesn't actually
|
||||
// translate but does signify to gettext that these strings should
|
||||
// go into the POT file. The translation then happens in the
|
||||
// template. We do this so that users can pass in their own
|
||||
// strings via converse.initialize, which is before __ is
|
||||
// available.
|
||||
'emoji_category_labels': {
|
||||
"smileys": ___("Smileys and emotions"),
|
||||
"people": ___("People"),
|
||||
"activity": ___("Activities"),
|
||||
"travel": ___("Travel"),
|
||||
"objects": ___("Objects"),
|
||||
"nature": ___("Animals and nature"),
|
||||
"food": ___("Food and drink"),
|
||||
"symbols": ___("Symbols"),
|
||||
"flags": ___("Flags"),
|
||||
"custom": ___("Stickers")
|
||||
}
|
||||
});
|
||||
|
||||
_converse.api.promises.add(['emojisInitialized']);
|
||||
twemoji.default.base = _converse.emoji_image_path;
|
||||
|
||||
_converse.emoji_category_labels = {
|
||||
"smileys": __("Smileys and emotions"),
|
||||
"people": __("People"),
|
||||
"activity": __("Activities"),
|
||||
"travel": __("Travel"),
|
||||
"objects": __("Objects"),
|
||||
"nature": __("Animals and nature"),
|
||||
"food": __("Food and drink"),
|
||||
"symbols": __("Symbols"),
|
||||
"flags": __("Flags")
|
||||
}
|
||||
|
||||
/**
|
||||
* Model for storing data related to the Emoji picker widget
|
||||
|
@ -253,28 +262,43 @@ converse.plugins.add('converse-emoji', {
|
|||
*/
|
||||
getEmojiRenderer () {
|
||||
const how = {
|
||||
'attributes': (icon) => {
|
||||
'attributes': icon => {
|
||||
const codepoint = twemoji.default.convert.toCodePoint(icon);
|
||||
return {'title': `${u.getEmojisByAtrribute('cp')[codepoint]['sn']} ${icon}`}
|
||||
}
|
||||
};
|
||||
const toUnicode = u.shortnameToUnicode;
|
||||
return _converse.use_system_emojis ? toUnicode: text => twemoji.default.parse(toUnicode(text), how);
|
||||
const transform = u.shortnamesToEmojis;
|
||||
return _converse.use_system_emojis ? transform : text => twemoji.default.parse(transform(text), how);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns unicode represented by the passed in shortname.
|
||||
* @method u.shortnameToUnicode
|
||||
* Returns an emoji represented by the passed in shortname.
|
||||
* Scans the passed in text for shortnames and replaces them with
|
||||
* emoji unicode glyphs or alternatively if it's a custom emoji
|
||||
* without unicode representation then markup for an HTML image tag
|
||||
* is returned.
|
||||
*
|
||||
* The shortname needs to be defined in `emojis.json`
|
||||
* and needs to have either a `cp` attribute for the codepoint, or
|
||||
* an `url` attribute which points to the source for the image.
|
||||
*
|
||||
* @method u.shortnamesToEmojis
|
||||
* @param {string} str - String containg the shortname(s)
|
||||
*/
|
||||
shortnameToUnicode (str) {
|
||||
shortnamesToEmojis (str, unicode_only=false) {
|
||||
str = str.replace(_converse.emojis.shortnames_regex, shortname => {
|
||||
if ((typeof shortname === 'undefined') || (shortname === '') || (!_converse.emoji_shortnames.includes(shortname))) {
|
||||
// if the shortname doesnt exist just return the entire match
|
||||
return shortname;
|
||||
}
|
||||
const unicode = _converse.emojis_map[shortname].cp.toUpperCase();
|
||||
return convert(unicode);
|
||||
const codepoint = _converse.emojis_map[shortname].cp;
|
||||
if (codepoint) {
|
||||
return convert(codepoint.toUpperCase());
|
||||
} else if (unicode_only) {
|
||||
return shortname;
|
||||
} else {
|
||||
return `<img class="emoji" draggable="false" alt="${shortname}" src="${_converse.emojis_map[shortname].url}"/>`;
|
||||
}
|
||||
});
|
||||
// Also replace ASCII smileys
|
||||
str = str.replace(ASCII_REPLACE_REGEX, (entire, m1, m2, m3) => {
|
||||
|
@ -289,6 +313,15 @@ converse.plugins.add('converse-emoji', {
|
|||
return str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns unicode represented by the passed in shortname.
|
||||
* @method u.shortnameToUnicode
|
||||
* @param {string} str - String containg the shortname(s)
|
||||
*/
|
||||
shortnameToUnicode (str) {
|
||||
return this.shortnamesToEmojis(str, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether the passed in string is just a single emoji shortname;
|
||||
* @method u.isSingleEmoji
|
||||
|
|
|
@ -100,8 +100,8 @@ converse.plugins.add('converse-muc', {
|
|||
/* The initialize function gets called as soon as the plugin is
|
||||
* loaded by converse.js's plugin machinery.
|
||||
*/
|
||||
const { _converse } = this,
|
||||
{ __ } = _converse;
|
||||
const { _converse } = this;
|
||||
const { __, ___ } = _converse;
|
||||
|
||||
// Configuration values for this plugin
|
||||
// ====================================
|
||||
|
@ -128,15 +128,6 @@ converse.plugins.add('converse-muc', {
|
|||
}
|
||||
|
||||
|
||||
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.
|
||||
*/
|
||||
return str;
|
||||
}
|
||||
|
||||
/* https://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
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
{
|
||||
"custom": {
|
||||
":converse:":{"sn":":converse:","url":"/dist/custom_emojis/converse.png","c":"custom"},
|
||||
":xmpp:":{"sn":":xmpp:","url":"/dist/custom_emojis/xmpp.png","c":"custom"}
|
||||
},
|
||||
"smileys": {
|
||||
":smiley:":{"sn":":smiley:","cp":"1f603","sns":[],"c":"smileys"},
|
||||
":smile:":{"sn":":smile:","cp":"1f604","sns":[],"c":"smileys"},
|
||||
|
@ -2692,4 +2696,3 @@
|
|||
":tone5:":{"sn":":tone5:","cp":"1f3ff","sns":[],"c":"modifier"}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
{[ if (!o.query) { ]}
|
||||
<ul>
|
||||
{[ Object.keys(o.emoji_categories).forEach(function (category) { ]}
|
||||
<li data-category="{{{category}}}" class="emoji-category {{{o.current_category}}} {{{ category}}} {[ if (o.current_category === category) { ]} picked {[ } ]}" title="{{{o._converse.emoji_category_labels[category]}}}">
|
||||
{[ if (o.emoji_categories[category]) { ]}
|
||||
<li data-category="{{{category}}}" class="emoji-category {{{o.current_category}}} {{{ category}}} {[ if (o.current_category === category) { ]} picked {[ } ]}"
|
||||
title="{{{ o.__(o._converse.emoji_category_labels[category]) }}}">
|
||||
<a class="pick-category" href="#emoji-picker-{{{category}}}" data-category="{{{category}}}"> {{ o.transformCategory(o.emoji_categories[category]) }} </a>
|
||||
</li>
|
||||
{[ } ]}
|
||||
{[ }); ]}
|
||||
</ul>
|
||||
{[ } ]}
|
||||
|
@ -24,15 +27,17 @@
|
|||
</ul>
|
||||
{[ } else { ]}
|
||||
{[ Object.keys(o.emoji_categories).forEach(function (category) { ]}
|
||||
<a id="emoji-picker-{{{category}}}" class="emoji-category__heading" data-category="{{{category}}}">{{{o._converse.emoji_category_labels[category]}}}</a>
|
||||
<ul class="emoji-picker" data-category="{{{category}}}">
|
||||
{[ Object.values(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>
|
||||
{[ if (o.emoji_categories[category]) { ]}
|
||||
<a id="emoji-picker-{{{category}}}" class="emoji-category__heading" data-category="{{{category}}}">{{{ o.__(o._converse.emoji_category_labels[category]) }}} </a>
|
||||
<ul class="emoji-picker" data-category="{{{category}}}">
|
||||
{[ Object.values(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>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* global __dirname, module, process */
|
||||
/* global __dirname, module */
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
|
|
|
@ -7,11 +7,11 @@ module.exports = merge(common, {
|
|||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
devServer: {
|
||||
contentBase: "./dist"
|
||||
contentBase: "./"
|
||||
},
|
||||
plugins: [
|
||||
new HTMLWebpackPlugin({
|
||||
title: 'Production',
|
||||
title: 'Converse.js Dev',
|
||||
template: 'webpack.html'
|
||||
})
|
||||
],
|
||||
|
|
Reference in New Issue