Commit c3d6b64f authored by JC Brand's avatar JC Brand

Turn the chat toolbar into a component

- Declaratively render the emoji picker dropup
- Got rid of converse-emoji-views
- Adapt OMEMO to the new buttons stuff
- Make emojis json global, to try and speed up tests
- omemo: Move functions to the top of the module
parent b8be707d
......@@ -346,105 +346,6 @@
background-color: var(--chat-correcting-color);
}
}
.send-button {
border-radius: 0;
bottom: var(--send-button-bottom);
background-color: var(--chat-head-color);
color: var(--inverse-link-color);
}
.chat-toolbar--container {
display: flex;
flex-wrap: nowrap;
}
.chat-toolbar {
box-sizing: border-box;
margin: 0;
width: 100%;
padding: 0.25em;
display: block;
border-top: 4px solid var(--chat-head-color);
background-color: white;
color: var(--chat-head-color);
.fa, .fa:hover,
.far, .far:hover,
.fas, .fas:hover {
color: var(--chat-head-color);
font-size: var(--font-size-large);
}
.disabled {
color: var(--text-color-lighten-15-percent) !important;
}
.unencrypted a,
.unencrypted {
color: var(--text-color);
.toolbar-menu {
a {
color: var(--link-color);
}
}
}
.unverified a,
.unverified {
color: #cf5300;
}
.private a,
.private {
color: #4b7003;
}
li {
cursor: pointer;
display: inline-block;
list-style: none;
padding: 0 0.5em;
&:hover {
cursor: pointer;
}
.toolbar-menu {
background-color: #fff;
bottom: 1.7rem;
box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
height: auto;
margin-bottom: 0;
min-width: 21rem;
position: absolute;
right: 0;
top: auto;
z-index: $zindex-dropdown;
&.otr-menu {
left: -6em;
min-width: 15rem;
&.show {
display: flex;
flex-direction: column;
}
}
a {
color: var(--link-color);
}
}
&.toggle-otr {
ul {
z-index: 99;
li {
&:hover {
background-color: var(--highlight-color);
}
display: block;
padding: 7px;
a {
display: block;
}
}
}
}
}
}
}
.dragresize {
background: transparent;
......@@ -530,19 +431,9 @@
max-height: var(--overlayed-max-chat-textarea-height);
}
.chatbox {
.sendXMPPMessage {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 235px;
}
}
}
}
.chat-body {
height: calc(100% - var(--overlayed-chat-head-height));
}
.chatbox-title {
cursor: pointer;
padding: 0.5rem 0.75rem 0 0.75rem;
......@@ -550,7 +441,6 @@
.chatbox-title--no-desc {
padding: 0.5rem 0.75rem;
}
converse-dropdown {
.btn--standalone {
padding: 0 0 0 0.5em;
......
......@@ -351,7 +351,6 @@
}
.muc-bottom-panel {
border-top: var(--message-input-border-top);
height: 3em;
padding: 0.5em;
text-align: center;
......@@ -376,17 +375,6 @@
.suggestion-box__results--above {
bottom: 4.5em;
}
.chat-toolbar {
background-color: white;
border-top: var(--message-input-border-top);
color: var(--message-input-color);
.fas, .fas:hover,
.far, .far:hover,
.fa, .fa:hover {
color: var(--message-input-color);
}
}
.chat-textarea {
&:active, &:focus{
outline-color: var(--chatroom-head-bg-color);
......@@ -396,9 +384,6 @@
background-color: var(--chatroom-correcting-color);
}
}
.send-button {
background-color: var(--message-input-color);
}
}
.room-invite {
......@@ -467,15 +452,6 @@
min-width: var(--overlayed-chat-width);
}
}
.sendXMPPMessage {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 280px;
}
}
}
}
}
}
}
......
......@@ -15,7 +15,14 @@
}
}
.emoji-picker.toolbar-menu {
converse-emoji-dropdown {
display: inline-block;
.dropdown-menu {
padding: 0;
}
}
converse-emoji-picker {
width: 100%;
padding-top: 0;
padding-bottom: 0;
......@@ -84,6 +91,9 @@
list-style: none;
position: relative;
&.insert-emoji {
padding: 0 0.2em;
height: auto;
width: auto;
margin: 0;
display: block;
text-align: center;
......@@ -115,7 +125,7 @@
.emoji-picker__header {
display: flex;
flex-direction: column;
padding-top: 0.5em;
padding: 0.1em 0;
background-color: var(--chat-head-color);
.emoji-search {
width: auto;
......@@ -154,7 +164,7 @@
}
.chatroom {
.emoji-picker.toolbar-menu {
converse-emoji-picker {
background-color: var(--chatroom-head-bg-color);
background: white;
.emoji-skintone-picker {
......@@ -177,6 +187,11 @@
#conversejs.converse-overlayed {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 18em;
}
}
.chatbox {
.emoji-picker__header {
.emoji-category {
......@@ -186,13 +201,7 @@
}
}
}
.emoji-picker.toolbar-menu {
li {
&.insert-emoji {
height: calc(var(--font-size) * 1.5);
width: calc(var(--font-size) * 1.5);
}
}
converse-emoji-picker {
.emoji-picker {
.insert-emoji {
a {
......@@ -223,11 +232,24 @@
}
}
#conversejs.converse-embedded {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 20em;
}
}
}
#conversejs.converse-fullscreen {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 22em;
}
}
.chatbox {
.toggle-smiley {
}
.emoji-picker.toolbar-menu {
converse-emoji-picker {
.emoji-picker__lists {
height: 12em;
}
......@@ -238,7 +260,7 @@
@include media-breakpoint-up(m) {
#conversejs {
.chatbox {
.emoji-picker.toolbar-menu {
converse-emoji-picker {
max-width: 40em;
}
}
......
#conversejs {
.send-button {
border-radius: 0;
bottom: var(--send-button-bottom);
color: var(--inverse-link-color);
}
.chatbox {
.send-button {
background-color: var(--chat-head-color);
}
}
.chatroom {
.send-button {
background-color: var(--chatroom-head-bg-color);
}
}
.chat-toolbar {
converse-chat-toolbar {
background-color: white;
box-sizing: border-box;
color: var(--chat-head-color);
display: flex;
justify-content: space-between;
margin: 0;
width: 100%;
.fa, .fa:hover,
.far, .far:hover,
.fas, .fas:hover {
color: var(--chat-head-color);
font-size: var(--font-size-large);
svg {
fill: var(--chat-head-color);
}
}
.unencrypted a,
.unencrypted {
color: var(--text-color);
.toolbar-menu {
a {
color: var(--link-color);
}
}
}
}
.toolbar-buttons {
width: 100%;
display: inline-block;
.message-limit {
padding: 0.5em;
font-weight: bold;
}
}
button {
margin-top: 0.4em;
border: 1px transparent solid;
background-color: transparent;
&:disabled .fa {
color: grey;
&:hover {
color: grey;
}
svg, svg:hover {
fill: grey;
}
}
&.send-button {
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
margin-top: -1px;
}
}
.unverified a,
.unverified {
color: #cf5300;
}
.private a,
.private {
color: #4b7003;
}
li {
cursor: pointer;
display: inline-block;
list-style: none;
padding: 0 0.5em;
&:hover {
cursor: pointer;
}
.toolbar-menu {
background-color: #fff;
bottom: 1.7rem;
box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
height: auto;
margin-bottom: 0;
min-width: 21rem;
position: absolute;
right: 0;
top: auto;
z-index: $zindex-dropdown;
&.otr-menu {
left: -6em;
min-width: 15rem;
&.show {
display: flex;
flex-direction: column;
}
}
a {
color: var(--link-color);
}
}
&.toggle-otr {
ul {
z-index: 99;
li {
&:hover {
background-color: var(--highlight-color);
}
display: block;
padding: 7px;
a {
display: block;
}
}
}
}
}
}
.chatbox {
converse-chat-toolbar {
border-top: var(--chatbox-message-input-border-top);
color: var(--chat-head-color);
background-color: white;
.fas, .fas:hover,
.far, .far:hover,
.fa, .fa:hover {
color: var(--chat-head-color);
}
button {
&:focus {
outline-color: var(--chat-head-color) !important;
}
}
}
}
.chatroom {
converse-chat-toolbar {
border-top: var(--chatroom-message-input-border-top);
color: var(--chatroom-head-bg-color);
.fas, .fas:hover,
.far, .far:hover,
.fa, .fa:hover {
color: var(--chatroom-head-bg-color);
font-size: var(--font-size-large);
svg {
fill: var(--chatroom-head-bg-color);
}
}
button {
&:focus {
outline-color: var(--chatroom-head-bg-color) !important;
}
}
}
}
}
#conversejs.converse-overlayed {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 235px;
}
}
}
.chatroom {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 280px;
}
}
}
}
}
......@@ -143,8 +143,8 @@ $mobile_portrait_length: 480px !default;
--chat-separator-border-bottom: 2px solid var(--chat-head-color);
--chatroom-separator-border-bottom: 2px solid var(--chatroom-head-bg-color);
--message-input-border-top: 4px solid var(--chatroom-head-bg-color);
--message-input-color: var(--chatroom-head-bg-color);
--chatbox-message-input-border-top: 4px solid var(--chat-head-color);
--chatroom-message-input-border-top: 4px solid var(--chatroom-head-bg-color);
--line-height-small: 14px;
--line-height: 16px;
......@@ -238,8 +238,8 @@ $mobile_portrait_length: 480px !default;
--chat-separator-border-bottom: 1px solid #AAA;
--chatroom-separator-border-bottom: 1px solid #AAA;
--message-input-border-top: 1px solid #CCC;
--message-input-color: #CCC;
--chatroom-message-input-border-top: 1px solid #CCC;
--chatbox-message-input-border-top: 1px solid #CCC;
--fullpage-chatbox-button-size: 24px;
......
......@@ -39,6 +39,7 @@
@import "core";
@import "forms";
@import "toolbar";
@import "chatbox";
@import "controlbox";
@import "modal";
......
......@@ -441,25 +441,6 @@ describe("Chatboxes", function () {
describe("A chat toolbar", function () {
it("can be found on each chat box",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current', 3);
await mock.openControlBox(_converse);
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const chatbox = _converse.chatboxes.get(contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
expect(chatbox).toBeDefined();
expect(view).toBeDefined();
const toolbar = view.el.querySelector('ul.chat-toolbar');
expect(_.isElement(toolbar)).toBe(true);
expect(toolbar.querySelectorAll(':scope > li').length).toBe(2);
done();
}));
it("shows the remaining character count if a message_limit is configured",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'message_limit': 200},
......@@ -476,7 +457,7 @@ describe("Chatboxes", function () {
view.insertIntoTextArea('hello world');
expect(counter.textContent).toBe('188');
toolbar.querySelector('a.toggle-smiley').click();
toolbar.querySelector('.toggle-emojis').click();
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__lists'));
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'));
item.click()
......@@ -532,7 +513,7 @@ describe("Chatboxes", function () {
_converse.visible_toolbar_buttons.call = false;
await mock.openChatBoxFor(_converse, contact_jid);
let view = _converse.chatboxviews.get(contact_jid);
toolbar = view.el.querySelector('ul.chat-toolbar');
toolbar = view.el.querySelector('.chat-toolbar');
call_button = toolbar.querySelector('.toggle-call');
expect(call_button === null).toBeTruthy();
view.close();
......@@ -541,7 +522,7 @@ describe("Chatboxes", function () {
_converse.visible_toolbar_buttons.call = true; // enable the button
await mock.openChatBoxFor(_converse, contact_jid);
view = _converse.chatboxviews.get(contact_jid);
toolbar = view.el.querySelector('ul.chat-toolbar');
toolbar = view.el.querySelector('.chat-toolbar');
call_button = toolbar.querySelector('.toggle-call');
call_button.click();
expect(_converse.api.trigger).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
......
......@@ -20,15 +20,13 @@ describe("Emojis", function () {
await mock.openControlBox(_converse);
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('a.toggle-smiley').click();
const toolbar = await u.waitUntil(() => view.el.querySelector('converse-chat-toolbar'));
toolbar.querySelector('.toggle-emojis').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'), 1000);
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'), 1000);
const item = view.el.querySelector('.emoji-picker li.insert-emoji a');
item.click()
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
toolbar.querySelector('a.toggle-smiley').click(); // Close the panel again
toolbar.querySelector('.toggle-emojis').click(); // Close the panel again
done();
}));
......@@ -53,16 +51,15 @@ describe("Emojis", function () {
'key': 'Tab'
}
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
const input = picker.querySelector('.emoji-search');
expect(input.value).toBe(':gri');
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', picker).length === 3, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
await u.waitUntil(() => view.el.querySelector('converse-emoji-picker .emoji-search').value === ':gri');
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view.el).length === 3, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', view.el);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:');
expect(visible_emojis[2].getAttribute('data-emoji')).toBe(':grinning:');
const picker = view.el.querySelector('converse-emoji-picker');
const input = picker.querySelector('.emoji-search');
// Test that TAB autocompletes the to first match
input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
......@@ -76,7 +73,7 @@ describe("Emojis", function () {
input.dispatchEvent(new KeyboardEvent('keydown', enter_event));
await u.waitUntil(() => input.value === '');
expect(textarea.value).toBe(':grimacing: ');
await u.waitUntil(() => textarea.value === ':grimacing:');
// Test that username starting with : doesn't cause issues
const presence = $pres({
......@@ -110,15 +107,12 @@ describe("Emojis", function () {
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const toolbar = view.el.querySelector('ul.chat-toolbar');
expect(toolbar.querySelectorAll('.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('.toggle-smiley').click();
const toolbar = view.el.querySelector('converse-chat-toolbar');
toolbar.querySelector('.toggle-emojis').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
const input = picker.querySelector('.emoji-search');
expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1589);
await u.waitUntil(() => sizzle('converse-chat-toolbar .insert-emoji:not(.hidden)', view.el).length === 1589);
expect(view.emoji_picker_view.model.get('query')).toBeUndefined();
const input = view.el.querySelector('.emoji-search');
input.value = 'smiley';
const event = {
'target': input,
......@@ -127,9 +121,8 @@ describe("Emojis", function () {
};
input.dispatchEvent(new KeyboardEvent('keydown', event));
await u.waitUntil(() => view.emoji_picker_view.model.get('query') === 'smiley', 1000);
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', picker).length === 2, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el).length === 2, 1000);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji:not(.hidden)', view.el);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
......@@ -143,8 +136,8 @@ describe("Emojis", function () {
input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
await u.waitUntil(() => input.value === ':smiley:');
await u.waitUntil(() => sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", picker).length === 1);
visible_emojis = sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", picker);
await u.waitUntil(() => sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", view.el).length === 1, 1000);
visible_emojis = sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", view.el);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
// Check that ENTER now inserts the match
......@@ -266,11 +259,10 @@ describe("Emojis", function () {
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.api.chatviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('a.toggle-smiley').click();
const toolbar = await u.waitUntil(() => view.el.querySelector('.chat-toolbar'));
toolbar.querySelector('.toggle-emojis').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('converse-emoji-picker'), 1000);
const custom_category = picker.querySelector('.pick-category[data-category="custom"]');
expect(custom_category.innerHTML.replace(/<!---->/g, '').trim()).toBe(
'<img class="emoji" draggable="false" title=":xmpp:" alt=":xmpp:" src="/dist/images/custom_emojis/xmpp.png">');
......
......@@ -159,7 +159,7 @@ describe("XEP-0363: HTTP File Upload", function () {
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items');
const view = _converse.chatboxviews.get(contact_jid);
expect(view.el.querySelector('.chat-toolbar .upload-file')).toBe(null);
expect(view.el.querySelector('.chat-toolbar .fileupload')).toBe(null);
done();
}));
......@@ -173,10 +173,10 @@ describe("XEP-0363: HTTP File Upload", function () {
[{'category': 'server', 'type':'IM'}],
['http://jabber.org/protocol/disco#items'], [], 'info');
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items');
await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.el.querySelector('.chat-toolbar .upload-file')).toBe(null);
await u.waitUntil(() => view.el.querySelector('.chat-toolbar .fileupload') === null);
expect(1).toBe(1);
done();
}));
......@@ -199,8 +199,8 @@ describe("XEP-0363: HTTP File Upload", function () {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
u.waitUntil(() => view.el.querySelector('.upload-file'));
expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
const el = await u.waitUntil(() => view.el.querySelector('.chat-toolbar .fileupload'));
expect(el).not.toEqual(null);
done();
}));
......@@ -216,9 +216,9 @@ describe("XEP-0363: HTTP File Upload", function () {
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items');
await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
await u.waitUntil(() => _converse.chatboxviews.get('lounge@montague.lit').el.querySelector('.upload-file'));
await u.waitUntil(() => _converse.chatboxviews.get('lounge@montague.lit').el.querySelector('.fileupload'));
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null);
expect(view.el.querySelector('.chat-toolbar .fileupload')).not.toBe(null);
done();
}));
......
......@@ -199,11 +199,8 @@ describe("Message Archive Management", function () {
_converse.connection._dataRecv(mock.createRequest(result));
await u.waitUntil(() => view.model.messages.length === 5);
await u.waitUntil(() => view.content.querySelectorAll('.chat-msg__text').length);
const msg_els = Array.from(view.content.querySelectorAll('.chat-msg__text'));
await u.waitUntil(
() => msg_els.map(e => e.textContent).join(' ') === "2nd Message 3rd Message 4th Message 5th Message 6th Message",
1000
);
await u.waitUntil(() => Array.from(view.content.querySelectorAll('.chat-msg__text'))
.map(e => e.textContent).join(' ') === "2nd Message 3rd Message 4th Message 5th Message 6th Message", 1000);
done();
}));
});
......
This diff is collapsed.
......@@ -16,10 +16,10 @@ describe("A spoiler message", function () {
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message>
*/
* <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message>
*/
const spoiler_hint = "Love story end"
const spoiler = "And at the end of the story, both of them die! It is so tragic!";
const $msg = converse.env.$msg;
......@@ -223,9 +223,7 @@ describe("A spoiler message", function () {
`</message>`
);
const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(spoiler_el === null).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
await u.waitUntil(() => stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]')?.textContent === 'This is the hint');
const spoiler = 'This is the spoiler'
const body_el = stanza.querySelector('body');
......
......@@ -29,8 +29,9 @@ export class BaseDropdown extends CustomElement {
this.button.setAttribute('aria-expanded', true);
}
toggleMenu (event) {
event.stopPropagation();
toggleMenu (ev) {
ev.stopPropagation();
ev.preventDefault();
if (u.hasClass('show', this.menu)) {
this.hideMenu();
} else {
......@@ -41,7 +42,7 @@ export class BaseDropdown extends CustomElement {
handleKeyUp (ev) {
if (ev.keyCode === converse.keycodes.ESCAPE) {
this.hideMenu();
} else if (ev.keyCode === converse.keycodes.DOWN_ARROW && !this.navigator.enabled) {
} else if (ev.keyCode === converse.keycodes.DOWN_ARROW && this.navigator && !this.navigator.enabled) {
this.enableArrowNavigation(ev);
}
}
......
......@@ -81,7 +81,6 @@ export default class EmojiPickerContent extends CustomElement {
const position = this.model.get('position');
this.model.set({'autocompleting': null, 'position': null, 'query': ''});
this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'), replace, false, position);
this.chatview.emoji_dropdown.toggle();
}
shouldBeHidden (shortname) {
......
import "./emoji-picker-content.js";
import DOMNavigator from "../dom-navigator";
import { BaseDropdown } from "./dropdown.js";
import { CustomElement } from './element.js';
import { __ } from '@converse/headless/i18n';
import { _converse, api, converse } from "@converse/headless/converse-core";
import { debounce, find } from "lodash-es";
import { html } from "lit-element";
import { tpl_emoji_picker } from "../templates/emoji_picker.js";
import { until } from 'lit-html/directives/until.js';
const u = converse.env.utils;
......@@ -13,13 +17,20 @@ export default class EmojiPicker extends CustomElement {
static get properties () {
return {
'chatview': { type: Object },
'current_category': { type: String },
'current_skintone': { type: String },
'current_category': { type: String, 'reflect': true },
'current_skintone': { type: String, 'reflect': true },
'model': { type: Object },
'query': { type: String },
'query': { type: String, 'reflet': true },
// This is an optimization, we lazily render the emoji picker, otherwise tests slow to a crawl.
'render_emojis': { type: Boolean },
}
}
firstUpdated () {
this.listenTo(this.model, 'change', o => this.onModelChanged(o.changed));
this.initArrowNavigation();
}
constructor () {
super();
this.search_results = [];
......@@ -42,19 +53,22 @@ export default class EmojiPicker extends CustomElement {
'onSkintonePicked': ev => this.chooseSkinTone(ev),
'query': this.query,
'search_results': this.search_results,
'render_emojis': this.render_emojis,
'sn2Emoji': shortname => u.shortnamesToEmojis(this.getTonedShortname(shortname))
});
}
firstUpdated () {
this.initArrowNavigation();
}
updated (changed) {
changed.has('query') && this.updateSearchResults();
changed.has('current_category') && this.setScrollPosition();
}
onModelChanged (changed) {
if ('current_category' in changed) this.current_category = changed.current_category;
if ('current_skintone' in changed) this.current_skintone = changed.current_skintone;
if ('query' in changed) this.query = changed.query;
}
setScrollPosition () {
if (this.preserve_scroll) {
this.preserve_scroll = false;
......@@ -76,7 +90,7 @@ export default class EmojiPicker extends CustomElement {
} else if (this.old_query && this.query.includes(this.old_query)) {
this.search_results = this.search_results.filter(e => contains(e.sn, this.query));
} else {
this.search_results = _converse.emojis_list.filter(e => contains(e.sn, this.query));
this.search_results = converse.emojis.list.filter(e => contains(e.sn, this.query));
}
this.old_query = this.query;
} else if (this.search_results.length) {
......@@ -109,22 +123,15 @@ export default class EmojiPicker extends CustomElement {
setCategoryForElement (el) {
const old_category = this.current_category;
const category = el.getAttribute('data-category') || old_category;
const category = el?.getAttribute('data-category') || old_category;
if (old_category !== category) {
this.model.save({'current_category': category});
}
}
insertIntoTextArea (value) {
const replace = this.model.get('autocompleting');
const position = this.model.get('position');
this.model.set({'autocompleting': null, 'position': null});
this.chatview.insertIntoTextArea(value, replace, false, position);
if (this.chatview.emoji_dropdown) {
this.chatview.emoji_dropdown.toggle();
}
this.chatview.onEmojiReceivedFromPicker(value);
this.model.set({'query': ''});
this.disableArrowNavigation();
}
chooseSkinTone (ev) {
......@@ -152,7 +159,7 @@ export default class EmojiPicker extends CustomElement {
if (ev.keyCode === converse.keycodes.TAB) {
if (ev.target.value) {
ev.preventDefault();
const match = find(_converse.emoji_shortnames, sn => _converse.FILTER_CONTAINS(sn, ev.target.value));
const match = find(converse.emojis.shortnames, sn => _converse.FILTER_CONTAINS(sn, ev.target.value));
match && this.model.set({'query': match});
} else if (!this.navigator.enabled) {
this.enableArrowNavigation(ev);
......@@ -176,7 +183,7 @@ export default class EmojiPicker extends CustomElement {
onEnterPressed (ev) {
ev.preventDefault();
ev.stopPropagation();
if (_converse.emoji_shortnames.includes(ev.target.value)) {
if (converse.emojis.shortnames.includes(ev.target.value)) {
this.insertIntoTextArea(ev.target.value);
} else if (this.search_results.length === 1) {
this.insertIntoTextArea(this.search_results[0].sn);
......@@ -193,7 +200,7 @@ export default class EmojiPicker extends CustomElement {
}
getTonedShortname (shortname) {
if (_converse.emojis.toned.includes(shortname) && this.current_skintone) {
if (converse.emojis.toned.includes(shortname) && this.current_skintone) {
return `${shortname.slice(0, shortname.length-1)}_${this.current_skintone}:`
}
return shortname;
......@@ -242,4 +249,82 @@ export default class EmojiPicker extends CustomElement {
}
export class EmojiDropdown extends BaseDropdown {
static get properties() {
return {
chatview: { type: Object }
};
}
constructor () {
super();
// This is an optimization, we lazily render the emoji picker, otherwise tests slow to a crawl.
this.render_emojis = false;
}
initModel () {
if (!this.init_promise) {
this.init_promise = (async () => {
await api.emojis.initialize()
const id = `converse.emoji-${_converse.bare_jid}-${this.chatview.model.get('jid')}`;
this.model = new _converse.EmojiPicker({'id': id});
this.model.browserStorage = _converse.createStore(id);
await new Promise(resolve => this.model.fetch({'success': resolve, 'error': resolve}));
})();
}
return this.init_promise;
}
render() {
return html`
<div class="dropup">
<button class="toggle-emojis"
title="${__('Insert emojis')}"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<converse-icon class="fa fa-smile "
path-prefix="${api.settings.get('assets_path')}"
size="1em"></converse-icon>
</button>
<div class="dropdown-menu">
${until(this.initModel().then(() => html`
<converse-emoji-picker
.chatview=${this.chatview}
.model=${this.model}
?render_emojis=${this.render_emojis}
current_category="${this.model.get('current_category') || ''}"
current_skintone="${this.model.get('current_skintone') || ''}"
query="${this.model.get('query') || ''}"
></converse-emoji-picker>`), '')}
</div>
</div>`;
}
toggleMenu (ev) {
ev.stopPropagation();
ev.preventDefault();
if (u.hasClass('show', this.menu)) {
if (u.ancestor(ev.target, '.toggle-emojis')) {
this.hideMenu();
}
} else {
this.showMenu();
}
}
async showMenu () {
await this.init_promise;
if (!this.render_emojis) {
// Trigger an update so that emojis are rendered
this.render_emojis = true;
this.requestUpdate();
}
super.showMenu();
this.querySelector('.emoji-search')?.focus();
}
}
api.elements.define('converse-emoji-dropdown', EmojiDropdown);
api.elements.define('converse-emoji-picker', EmojiPicker);
import { html, css } from 'lit-element';
import { CustomElement } from './element.js';
class ConverseIcon extends CustomElement {
static get properties () {
return {
color: String,
class_name: { attribute: "class" },
style: String,
size: String
};
}
static get styles () {
return css`
:host {
display: inline-block;
padding: 0;
margin: 0;
}
:host svg {
fill: var(--fa-icon-fill-color, currentcolor);
width: var(--fa-icon-width, 19px);
height: var(--fa-icon-height, 19px);
}
`;
}
getSources () {
const get_prefix = class_name => {
const data = class_name.split(" ");
return ['solid', normalizeIconName(data[1])];
};
const normalizeIconName = name => {
const icon = name.replace("fa-", "");
return icon;
};
const data = get_prefix(this.class_name);
return `#${data[1]}`;
}
constructor () {
super();
this.class_name = "";
this.style = "";
this.size = "";
this.color = "";
}
firstUpdated () {
this.src = this.getSources();
}
_parseStyles () {
return `
${this.size ? `width: ${this.size};` : ''}
${this.size ? `height: ${this.size};` : ''}
${this.color ? `fill: ${this.color};` : ''}
${this.style}
`;
}
render () {
return html`<svg .style="${this._parseStyles()}"> <use href="${this.src}"> </use> </svg>`;
}
}
customElements.define("converse-icon", ConverseIcon);
import "./emoji-picker.js";
import { CustomElement } from './element.js';
import { __ } from '@converse/headless/i18n';
import { _converse, api, converse } from "@converse/headless/converse-core";
import { html } from 'lit-element';
import { until } from 'lit-html/directives/until.js';
const Strophe = converse.env.Strophe
const i18n_chars_remaining = __('Message characters remaining');
const i18n_choose_file = __('Choose a file to send')
const i18n_hide_occupants = __('Hide occupants');
const i18n_send_message = __('Send the message');
const i18n_show_occupants = __('Show occupants');
const i18n_start_call = __('Start a call');
export class ChatToolbar extends CustomElement {
static get properties () {
return {
chatview: { type: Object }, // Used by getToolbarButtons hooks
hidden_occupants: { type: Boolean },
is_groupchat: { type: Boolean },
message_limit: { type: Number },
model: { type: Object },
show_call_button: { type: Boolean },
show_emoji_button: { type: Boolean },
show_occupants_toggle: { type: Boolean },
show_send_button: { type: Boolean },
show_spoiler_button: { type: Boolean },
show_toolbar: { type: Boolean }
}
}
render () {
return html`
${ this.show_toolbar ? html`<span class="toolbar-buttons">${until(this.getButtons(), '')}</span>` : '' }
${ this.show_send_button ? html`<button type="submit" class="btn send-button fa fa-paper-plane" title="${ i18n_send_message }"></button>` : '' }
`;
}
getButtons () {
const buttons = [];
if (this.show_emoji_button) {
buttons.push(html`<converse-emoji-dropdown .chatview=${this.chatview}></converse-dropdown>`);
}
if (this.show_call_button) {
buttons.push(html`
<button class="toggle-call" @click=${this.toggleCall} title="${i18n_start_call}">
<converse-icon class="fa fa-phone" path-prefix="/dist" size="1em"></converse-icon>
</button>`
);
}
const message_limit = api.settings.get('message_limit');
if (message_limit) {
buttons.push(html`<span class="right message-limit" title="${i18n_chars_remaining}">${this.message_limit}</span>`);
}
if (this.show_spoiler_button) {
buttons.push(this.getSpoilerButton());
}
const http_upload_promise = api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain);
buttons.push(html`${until(http_upload_promise.then(is_supported => this.getHTTPUploadButton(is_supported)),'')}`);
if (this.show_occupants_toggle) {
buttons.push(html`
<button class="toggle_occupants right"
title="${this.hidden_occupants ? i18n_show_occupants : i18n_hide_occupants}"
@click=${this.toggleOccupants}>
<converse-icon class="fa ${this.hidden_occupants ? `fa-angle-double-left` : `fa-angle-double-right`}"
path-prefix="${api.settings.get('assets_path')}" size="1em"></converse-icon>
</button>`
);
}
/**
* *Hook* which allows plugins to add more buttons to a chat's toolbar
* @event _converse#getToolbarButtons
*/
return _converse.api.hook('getToolbarButtons', this, buttons);
}
getHTTPUploadButton (is_supported) {
if (is_supported) {
return html`
<button title="${i18n_choose_file}" @click=${this.toggleFileUpload}>
<converse-icon class="fa fa-paperclip"
path-prefix="${api.settings.get('assets_path')}"
size="1em"></converse-icon>
</button>
<input type="file" @change=${this.onFileSelection} class="fileupload" multiple="" style="display:none"/>`;
} else {
return '';
}
}
getSpoilerButton () {
if (!this.is_groupchat && this.model.presence.resources.length === 0) {
return;
}
let i18n_toggle_spoiler;
if (this.model.get('composing_spoiler')) {
i18n_toggle_spoiler = __("Click to write as a normal (non-spoiler) message");
} else {
i18n_toggle_spoiler = __("Click to write your message as a spoiler");
}
const markup = html`
<button class="toggle-compose-spoiler"
title="${i18n_toggle_spoiler}"
@click=${this.toggleComposeSpoilerMessage}>
<converse-icon class="fa ${this.composing_spoiler ? 'fa-eye-slash' : 'fa-eye'}"
path-prefix="${api.settings.get('assets_path')}"
size="1em"></converse-icon>
</button>`;
if (this.is_groupchat) {
return markup;
} else {
const contact_jid = this.model.get('jid');
const spoilers_promise = Promise.all(
this.model.presence.resources.map(
r => api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${r.get('name')}`)
)).then(results => results.reduce((acc, val) => (acc && val), true));
return html`${until(spoilers_promise.then(() => markup), '')}`;
}
}
toggleFileUpload (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
this.querySelector('.fileupload').click();
}
onFileSelection (evt) {
this.model.sendFiles(evt.target.files);
}
toggleComposeSpoilerMessage (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
this.model.set('composing_spoiler', !this.model.get('composing_spoiler'));
}
toggleOccupants (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')});
}
toggleCall (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
/**
* When a call button (i.e. with class .toggle-call) on a chatbox has been clicked.
* @event _converse#callButtonClicked
* @type { object }
* @property { Strophe.Connection } _converse.connection - The XMPP Connection object
* @property { _converse.ChatBox | _converse.ChatRoom } _converse.connection - The XMPP Connection object
* @example _converse.api.listen.on('callButtonClicked', (connection, model) => { ... });
*/
api.trigger('callButtonClicked', {
connection: _converse.connection,
model: this.model
});
}
}
window.customElements.define('converse-chat-toolbar', ChatToolbar);
This diff is collapsed.
/**
* @module converse-emoji-views
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import "./components/emoji-picker.js";
import "@converse/headless/converse-emoji";
import bootstrap from "bootstrap.native";
import tpl_emoji_button from "templates/emoji_button.html";
import { View } from "@converse/skeletor/src/view";
import { __ } from '@converse/headless/i18n';
import { _converse, api, converse } from '@converse/headless/converse-core';
import { html } from "lit-html";
const u = converse.env.utils;
converse.plugins.add('converse-emoji-views', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-emoji", "converse-chatview", "converse-muc-views"],
overrides: {
ChatBoxView: {
events: {
'click .toggle-smiley': 'toggleEmojiMenu',
},
onEnterPressed () {
if (this.emoji_dropdown && u.isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) {
this.emoji_dropdown.toggle();
}
this.__super__.onEnterPressed.apply(this, arguments);
},
onKeyDown (ev) {
if (ev.keyCode === converse.keycodes.TAB) {
const value = u.getCurrentWord(ev.target, null, /(:.*?:)/g);
if (value.startsWith(':')) {
ev.preventDefault();
ev.stopPropagation();
return this.autocompleteInPicker(ev.target, value);
}
}
return this.__super__.onKeyDown.call(this, ev);
}
},
ChatRoomView: {
events: {
'click .toggle-smiley': 'toggleEmojiMenu'
}
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
api.settings.extend({
'use_system_emojis': true,
'visible_toolbar_buttons': {
'emoji': true
},
});
const emoji_aware_chat_view = {
async autocompleteInPicker (input, value) {
await this.createEmojiDropdown();
this.emoji_picker_view.model.set({
'query': value,
'autocompleting': value,
'position': input.selectionStart
});
this.emoji_dropdown.toggle();
},
async createEmojiPicker () {
await api.emojis.initialize()
const id = `converse.emoji-${_converse.bare_jid}-${this.model.get('jid')}`;
const emojipicker = new _converse.EmojiPicker({'id': id});
emojipicker.browserStorage = _converse.createStore(id);
await new Promise(resolve => emojipicker.fetch({'success': resolve, 'error': resolve}));
this.emoji_picker_view = new _converse.EmojiPickerView({'model': emojipicker, 'chatview': this});
const el = this.el.querySelector('.emoji-picker__container');
el.innerHTML = '';
el.appendChild(this.emoji_picker_view.el);
},
async createEmojiDropdown () {
if (!this.emoji_dropdown) {
await this.createEmojiPicker();
const el = this.el.querySelector('.emoji-picker');
this.emoji_dropdown = new bootstrap.Dropdown(el, true);
this.emoji_dropdown.el = el;
}
},
async toggleEmojiMenu (ev) {
ev.stopPropagation();
await this.createEmojiDropdown();
this.emoji_dropdown.toggle();
}
};
Object.assign(_converse.ChatBoxView.prototype, emoji_aware_chat_view);
_converse.EmojiPickerView = View.extend({
className: 'emoji-picker dropdown-menu toolbar-menu',
initialize (config) {
this.chatview = config.chatview;
this.listenTo(this.model, 'change', o => {
if (['current_category', 'current_skintone', 'query'].some(k => k in o.changed)) {
this.render();
}
});
this.render();
},
toHTML () {
return html`<converse-emoji-picker
.chatview=${this.chatview}
.model=${this.model}
current_category="${this.model.get('current_category') || ''}"
current_skintone="${this.model.get('current_skintone') || ''}"
query="${this.model.get('query') || ''}"
></converse-emoji-picker>`;
}
});
/************************ BEGIN Event Handlers ************************/
api.listen.on('chatBoxClosed', view => view.emoji_picker_view && view.emoji_picker_view.remove());
api.listen.on('renderToolbar', view => {
if (api.settings.get('visible_toolbar_buttons').emoji) {
const html = tpl_emoji_button({'tooltip_insert_smiley': __('Insert emojis')});
view.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
}
});
api.listen.on('headlinesBoxInitialized', () => api.emojis.initialize());
api.listen.on('chatRoomInitialized', () => api.emojis.initialize());
api.listen.on('chatBoxInitialized', () => api.emojis.initialize());
/************************ END Event Handlers ************************/
}
});
......@@ -435,7 +435,6 @@ converse.plugins.add('converse-muc-views', {
className: 'chatbox chatroom hidden',
is_chatroom: true,
events: {
'change input.fileupload': 'onFileSelection',
'click .chatbox-navback': 'showControlBox',
'click .chatbox-title': 'minimize',
'click .hide-occupants': 'hideOccupants',
......@@ -443,9 +442,6 @@ converse.plugins.add('converse-muc-views', {
// Arrow functions don't work here because you can't bind a different `this` param to them.
'click .occupant-nick': function (ev) {this.insertIntoTextArea(ev.target.textContent) },
'click .send-button': 'onFormSubmitted',
'click .toggle-call': 'toggleCall',
'click .toggle-occupants': 'toggleOccupants',
'click .upload-file': 'toggleFileUpload',
'dragover .chat-textarea': 'onDragOver',
'drop .chat-textarea': 'onDrop',
'input .chat-textarea': 'inputChanged',
......@@ -460,7 +456,7 @@ converse.plugins.add('converse-muc-views', {
this.initDebounced();
this.listenTo(this.model, 'change', debounce(() => this.renderHeading(), 250));
this.listenTo(this.model, 'change:hidden_occupants', this.updateOccupantsToggle);
this.listenTo(this.model, 'change:hidden_occupants', this.renderToolbar);
this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
this.listenTo(this.model, 'destroy', this.hide);
this.listenTo(this.model, 'show', this.show);
......@@ -1079,10 +1075,10 @@ converse.plugins.add('converse-muc-views', {
getToolbarOptions () {
return Object.assign(
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
{
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
'is_groupchat': true,
'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
}
);
},
......@@ -1101,20 +1097,6 @@ converse.plugins.add('converse-muc-views', {
return _converse.ChatBoxView.prototype.close.apply(this, arguments);
},
updateOccupantsToggle () {
const icon_el = this.el.querySelector('.toggle-occupants');
const chat_area = this.el.querySelector('.chat-area');
if (this.model.get('hidden_occupants')) {
u.removeClass('fa-angle-double-right', icon_el);
u.addClass('fa-angle-double-left', icon_el);
u.addClass('full', chat_area);
} else {
u.addClass('fa-angle-double-right', icon_el);
u.removeClass('fa-angle-double-left', icon_el);
u.removeClass('full', chat_area);
}
},
/**
* Hide the right sidebar containing the chat occupants.
* @private
......@@ -1129,20 +1111,6 @@ converse.plugins.add('converse-muc-views', {
this.scrollDown();
},
/**
* Show or hide the right sidebar containing the chat occupants.
* @private
* @method _converse.ChatRoomView#toggleOccupants
*/
toggleOccupants (ev) {
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')});
this.scrollDown();
},
verifyRoles (roles, occupant, show_error=true) {
if (!Array.isArray(roles)) {
throw new TypeError('roles must be an Array');
......
This diff is collapsed.
......@@ -15,7 +15,6 @@ import "converse-bookmark-views"; // Views for XEP-0048 Bookmarks
import "converse-chatview"; // Renders standalone chat boxes for single user chat
import "converse-controlbox"; // The control box
import "converse-dragresize"; // Allows chat boxes to be resized by dragging them
import "converse-emoji-views";
import "converse-fullscreen";
import "converse-mam-views";
import "converse-minimize"; // Allows chat boxes to be minimized
......@@ -43,7 +42,6 @@ const WHITELISTED_PLUGINS = [
'converse-chatview',
'converse-controlbox',
'converse-dragresize',
'converse-emoji-views',
'converse-fullscreen',
'converse-mam-views',
'converse-minimize',
......
......@@ -11,6 +11,11 @@ import { html } from 'lit-html';
const u = converse.env.utils;
converse.emojis = {
'initialized_promise': u.getResolveablePromise()
};
const ASCII_LIST = {
'*\\0/*':'1f646', '*\\O/*':'1f646', '-___-':'1f611', ':\'-)':'1f602', '\':-)':'1f605', '\':-D':'1f605', '>:-)':'1f606', '\':-(':'1f613',
'>:-(':'1f620', ':\'-(':'1f622', 'O:-)':'1f607', '0:-3':'1f607', '0:-)':'1f607', '0;^)':'1f607', 'O;-)':'1f607', '0;-)':'1f607', 'O:-3':'1f607',
......@@ -56,14 +61,14 @@ function convert (unicode) {
function getTonedEmojis () {
if (!_converse.toned_emojis) {
_converse.toned_emojis = uniq(
Object.values(_converse.emojis.json.people)
if (!converse.emojis.toned) {
converse.emojis.toned = uniq(
Object.values(converse.emojis.json.people)
.filter(person => person.sn.includes('_tone'))
.map(person => person.sn.replace(/_tone[1-5]/, ''))
);
}
return _converse.toned_emojis;
return converse.emojis.toned;
}
......@@ -101,7 +106,7 @@ function getEmojiMarkup (data, options={unicode_only: false, add_title_wrapper:
draggable="false"
title="${shortname}"
alt="${shortname}"
src="${_converse.emojis_by_sn[shortname].url}">`;
src="${converse.emojis.by_sn[shortname].url}">`;
}
}
......@@ -109,7 +114,7 @@ function getEmojiMarkup (data, options={unicode_only: false, add_title_wrapper:
function getShortnameReferences (text) {
const references = [...text.matchAll(shortnames_regex)];
return references.map(ref => {
const cp = _converse.emojis_by_sn[ref[0]].cp;
const cp = converse.emojis.by_sn[ref[0]].cp;
return {
cp,
'begin': ref.index,
......@@ -201,8 +206,6 @@ converse.plugins.add('converse-emoji', {
}
});
_converse.emojis = {};
api.promises.add('emojisInitialized', false);
twemoji.default.base = api.settings.get('emoji_image_path');
......@@ -310,14 +313,14 @@ converse.plugins.add('converse-emoji', {
return emojis_by_attribute[attr];
}
if (attr === 'category') {
return _converse.emojis.json;
return converse.emojis.json;
}
const all_variants = _converse.emojis_list
const all_variants = converse.emojis.list
.map(e => e[attr])
.filter((c, i, arr) => arr.indexOf(c) == i);
emojis_by_attribute[attr] = {};
all_variants.forEach(v => (emojis_by_attribute[attr][v] = find(_converse.emojis_list, i => (i[attr] === v))));
all_variants.forEach(v => (emojis_by_attribute[attr][v] = find(converse.emojis.list, i => (i[attr] === v))));
return emojis_by_attribute[attr];
}
});
......@@ -338,29 +341,20 @@ converse.plugins.add('converse-emoji', {
* @returns {Promise}
*/
async initialize () {
if (_converse.emojis.initialized) {
return _converse.emojis.initialized;
if (!converse.emojis.initialized) {
converse.emojis.initialized = true;
const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json');
converse.emojis.json = json;
converse.emojis.by_sn = Object.keys(json).reduce((result, cat) => Object.assign(result, 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.emojis.shortnames = converse.emojis.list.map(m => m.sn);
const getShortNames = () => converse.emojis.shortnames.map(s => s.replace(/[+]/g, "\\$&")).join('|');
shortnames_regex = new RegExp(getShortNames(), "gi");
converse.emojis.toned = getTonedEmojis();
converse.emojis.initialized_promise.resolve();
}
_converse.emojis.initialized = u.getResolveablePromise();
const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json');
_converse.emojis.json = json;
_converse.emojis.categories = Object.keys(_converse.emojis.json);
_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);
const getShortNames = () => _converse.emoji_shortnames.map(s => s.replace(/[+]/g, "\\$&")).join('|');
shortnames_regex = new RegExp(getShortNames(), "gi");
_converse.emojis.toned = getTonedEmojis();
_converse.emojis.initialized.resolve();
/**
* Triggered once the JSON file representing emoji data has been
* fetched and its save to start calling emoji utility methods.
* @event _converse#emojisInitialized
*/
api.trigger('emojisInitialized');
return converse.emojis.initialized_promise;
}
}
});
......
......@@ -9,7 +9,6 @@ export default (o) => html`
<div class="chat-content__help"></div>
</div>
<div class="bottom-panel">
<div class="emoji-picker__container dropup"></div>
<div class="message-form-container">
</div>
</div>
......
import { html } from "lit-html";
import { __ } from '@converse/headless/i18n';
const i18n_send_message = __('Send the message');
export default (o) => html`
......@@ -10,12 +7,7 @@ export default (o) => html`
<input type="submit" class="btn btn-primary" name="join" value="Join"/>
</form>
<form class="sendXMPPMessage">
${ (o.show_toolbar || o.show_send_button) ? html`
<div class="chat-toolbar--container">
${ o.show_toolbar ? html`<ul class="chat-toolbar no-text-select"></ul>` : '' }
${ o.show_send_button ? html`<button type="submit" class="btn send-button fa fa-paper-plane" title="${ i18n_send_message }"></button>` : '' }
</div>` : ''
}
<span class="chat-toolbar no-text-select"></span>
<input type="text" placeholder="${o.label_spoiler_hint || ''}" value="${o.hint_value || ''}" class="${o.composing_spoiler ? '' : 'hidden'} spoiler-hint"/>
<div class="suggestion-box">
......
......@@ -32,7 +32,7 @@ class MessageBodyRenderer extends String {
let list = await Promise.all(u.addHyperlinks(text));
await api.waitUntil('emojisInitialized');
await api.emojis.initialize();
list = list.reduce((acc, i) => isString(i) ? [...acc, ...u.addEmoji(i)] : [...acc, i], []);
const addMentions = text => addMentionsMarkup(text, this.model.get('references'), this.model.collection.chatbox)
......
<li class="toggle-toolbar-menu toggle-smiley__container">
<a class="toggle-smiley far fa-smile"
title="{{{o.tooltip_insert_smiley}}}"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"></a>
</li>
......@@ -47,11 +47,10 @@ export const tpl_search_results = (o) => html`
`;
const emojis_for_category = (o) => {
const emojis_by_category = _converse.emojis.json;
return html`
<a id="emoji-picker-${o.category}" class="emoji-category__heading" data-category="${o.category}">${ __(api.settings.get('emoji_category_labels')[o.category]) }</a>
<ul class="emoji-picker" data-category="${o.category}">
${ Object.values(emojis_by_category[o.category]).map(emoji => emoji_item(Object.assign({emoji}, o))) }
${ Object.values(converse.emojis.json[o.category]).map(emoji => emoji_item(Object.assign({emoji}, o))) }
</ul>`;
}
......@@ -82,13 +81,13 @@ export const tpl_emoji_picker = (o) => {
@focus=${o.onSearchInputFocus}>
${ o.query ? '' : emoji_picker_header(o) }
</div>
<converse-emoji-picker-content
.chatview=${o.chatview}
.model=${o.model}
.search_results="${o.search_results}"
current_skintone="${o.current_skintone}"
query="${o.query}"
></converse-emoji-picker-content>
${ o.render_emojis ?
html`<converse-emoji-picker-content
.chatview=${o.chatview}
.model=${o.model}
.search_results="${o.search_results}"
current_skintone="${o.current_skintone}"
query="${o.query}"></converse-emoji-picker-content>` : ''}
<div class="emoji-skintone-picker">
<label>Skin tone</label>
......
<li class="toggle-compose-spoiler fa {[ if (o.composing_spoiler) { ]} fa-eye-slash {[ } ]} {[ if (!o.composing_spoiler) { ]} fa-eye {[ } ]}"
title="{{ o.label_toggle_spoiler }}">
</li>
import { html } from "lit-html";
import { api } from '@converse/headless/converse-core.js';
export default (o) => html`
${ o.show_call_button ? html`<li class="toggle-call fa fa-phone" title="${o.label_start_call}"></li>` : '' }
${ o.show_occupants_toggle ?
html` <li class="toggle-occupants float-right fa ${ o.hidden_occupants ? `fa-angle-double-left` : `fa-angle-double-right` }"
title="${o.label_hide_occupants}"></li>` : '' }
${ o.message_limit ? html`<li class="message-limit font-weight-bold float-right" title="${o.label_message_limit}">${o.message_limit}</li>` : '' }
`;
export default (o) => {
const message_limit = api.settings.get('message_limit');
const show_call_button = api.settings.get('visible_toolbar_buttons').call;
const show_emoji_button = api.settings.get('visible_toolbar_buttons').emoji;
const show_send_button = api.settings.get('show_send_button');
const show_spoiler_button = api.settings.get('visible_toolbar_buttons').spoiler;
const show_toolbar = api.settings.get('show_toolbar');
return html`
<converse-chat-toolbar
.chatview=${o.chatview}
.model=${o.model}
?hidden_occupants="${o.hidden_occupants}"
?is_groupchat="${o.is_groupchat}"
?show_call_button="${show_call_button}"
?show_emoji_button="${show_emoji_button}"
?show_occupants_toggle="${o.show_occupants_toggle}"
?show_send_button="${show_send_button}"
?show_spoiler_button="${show_spoiler_button}"
?show_toolbar="${show_toolbar}"
message_limit="${message_limit}"
></converse-chat-toolbar>
`;
}
<li class="toggle-omemo fa
{[ if (!o.omemo_supported) { ]} disabled {[ } ]}
{[ if (o.omemo_active) { ]} fa-lock {[ } else { ]} fa-unlock {[ } ]}"
title="{{{o.__('Messages are being sent in plaintext')}}}"></li>
......@@ -9,6 +9,7 @@
<script src="3rdparty/libsignal-protocol.js"></script>
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" type="image/ico" href="favicon.ico"/>
<script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
</head>
<body class="reset"></body>
<script>
......
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