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 @@ ...@@ -346,105 +346,6 @@
background-color: var(--chat-correcting-color); 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 { .dragresize {
background: transparent; background: transparent;
...@@ -530,19 +431,9 @@ ...@@ -530,19 +431,9 @@
max-height: var(--overlayed-max-chat-textarea-height); max-height: var(--overlayed-max-chat-textarea-height);
} }
.chatbox { .chatbox {
.sendXMPPMessage {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 235px;
}
}
}
}
.chat-body { .chat-body {
height: calc(100% - var(--overlayed-chat-head-height)); height: calc(100% - var(--overlayed-chat-head-height));
} }
.chatbox-title { .chatbox-title {
cursor: pointer; cursor: pointer;
padding: 0.5rem 0.75rem 0 0.75rem; padding: 0.5rem 0.75rem 0 0.75rem;
...@@ -550,7 +441,6 @@ ...@@ -550,7 +441,6 @@
.chatbox-title--no-desc { .chatbox-title--no-desc {
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
} }
converse-dropdown { converse-dropdown {
.btn--standalone { .btn--standalone {
padding: 0 0 0 0.5em; padding: 0 0 0 0.5em;
......
...@@ -351,7 +351,6 @@ ...@@ -351,7 +351,6 @@
} }
.muc-bottom-panel { .muc-bottom-panel {
border-top: var(--message-input-border-top);
height: 3em; height: 3em;
padding: 0.5em; padding: 0.5em;
text-align: center; text-align: center;
...@@ -376,17 +375,6 @@ ...@@ -376,17 +375,6 @@
.suggestion-box__results--above { .suggestion-box__results--above {
bottom: 4.5em; 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 { .chat-textarea {
&:active, &:focus{ &:active, &:focus{
outline-color: var(--chatroom-head-bg-color); outline-color: var(--chatroom-head-bg-color);
...@@ -396,9 +384,6 @@ ...@@ -396,9 +384,6 @@
background-color: var(--chatroom-correcting-color); background-color: var(--chatroom-correcting-color);
} }
} }
.send-button {
background-color: var(--message-input-color);
}
} }
.room-invite { .room-invite {
...@@ -467,15 +452,6 @@ ...@@ -467,15 +452,6 @@
min-width: var(--overlayed-chat-width); min-width: var(--overlayed-chat-width);
} }
} }
.sendXMPPMessage {
.chat-toolbar {
li {
.toolbar-menu {
min-width: 280px;
}
}
}
}
} }
} }
} }
......
...@@ -15,7 +15,14 @@ ...@@ -15,7 +15,14 @@
} }
} }
.emoji-picker.toolbar-menu { converse-emoji-dropdown {
display: inline-block;
.dropdown-menu {
padding: 0;
}
}
converse-emoji-picker {
width: 100%; width: 100%;
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
...@@ -84,6 +91,9 @@ ...@@ -84,6 +91,9 @@
list-style: none; list-style: none;
position: relative; position: relative;
&.insert-emoji { &.insert-emoji {
padding: 0 0.2em;
height: auto;
width: auto;
margin: 0; margin: 0;
display: block; display: block;
text-align: center; text-align: center;
...@@ -115,7 +125,7 @@ ...@@ -115,7 +125,7 @@
.emoji-picker__header { .emoji-picker__header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: 0.5em; padding: 0.1em 0;
background-color: var(--chat-head-color); background-color: var(--chat-head-color);
.emoji-search { .emoji-search {
width: auto; width: auto;
...@@ -154,7 +164,7 @@ ...@@ -154,7 +164,7 @@
} }
.chatroom { .chatroom {
.emoji-picker.toolbar-menu { converse-emoji-picker {
background-color: var(--chatroom-head-bg-color); background-color: var(--chatroom-head-bg-color);
background: white; background: white;
.emoji-skintone-picker { .emoji-skintone-picker {
...@@ -177,6 +187,11 @@ ...@@ -177,6 +187,11 @@
#conversejs.converse-overlayed { #conversejs.converse-overlayed {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 18em;
}
}
.chatbox { .chatbox {
.emoji-picker__header { .emoji-picker__header {
.emoji-category { .emoji-category {
...@@ -186,13 +201,7 @@ ...@@ -186,13 +201,7 @@
} }
} }
} }
.emoji-picker.toolbar-menu { converse-emoji-picker {
li {
&.insert-emoji {
height: calc(var(--font-size) * 1.5);
width: calc(var(--font-size) * 1.5);
}
}
.emoji-picker { .emoji-picker {
.insert-emoji { .insert-emoji {
a { a {
...@@ -223,11 +232,24 @@ ...@@ -223,11 +232,24 @@
} }
} }
#conversejs.converse-embedded {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 20em;
}
}
}
#conversejs.converse-fullscreen { #conversejs.converse-fullscreen {
converse-emoji-dropdown {
.dropdown-menu {
min-width: 22em;
}
}
.chatbox { .chatbox {
.toggle-smiley { .toggle-smiley {
} }
.emoji-picker.toolbar-menu { converse-emoji-picker {
.emoji-picker__lists { .emoji-picker__lists {
height: 12em; height: 12em;
} }
...@@ -238,7 +260,7 @@ ...@@ -238,7 +260,7 @@
@include media-breakpoint-up(m) { @include media-breakpoint-up(m) {
#conversejs { #conversejs {
.chatbox { .chatbox {
.emoji-picker.toolbar-menu { converse-emoji-picker {
max-width: 40em; 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; ...@@ -143,8 +143,8 @@ $mobile_portrait_length: 480px !default;
--chat-separator-border-bottom: 2px solid var(--chat-head-color); --chat-separator-border-bottom: 2px solid var(--chat-head-color);
--chatroom-separator-border-bottom: 2px solid var(--chatroom-head-bg-color); --chatroom-separator-border-bottom: 2px solid var(--chatroom-head-bg-color);
--message-input-border-top: 4px solid var(--chatroom-head-bg-color); --chatbox-message-input-border-top: 4px solid var(--chat-head-color);
--message-input-color: var(--chatroom-head-bg-color); --chatroom-message-input-border-top: 4px solid var(--chatroom-head-bg-color);
--line-height-small: 14px; --line-height-small: 14px;
--line-height: 16px; --line-height: 16px;
...@@ -238,8 +238,8 @@ $mobile_portrait_length: 480px !default; ...@@ -238,8 +238,8 @@ $mobile_portrait_length: 480px !default;
--chat-separator-border-bottom: 1px solid #AAA; --chat-separator-border-bottom: 1px solid #AAA;
--chatroom-separator-border-bottom: 1px solid #AAA; --chatroom-separator-border-bottom: 1px solid #AAA;
--message-input-border-top: 1px solid #CCC; --chatroom-message-input-border-top: 1px solid #CCC;
--message-input-color: #CCC; --chatbox-message-input-border-top: 1px solid #CCC;
--fullpage-chatbox-button-size: 24px; --fullpage-chatbox-button-size: 24px;
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
@import "core"; @import "core";
@import "forms"; @import "forms";
@import "toolbar";
@import "chatbox"; @import "chatbox";
@import "controlbox"; @import "controlbox";
@import "modal"; @import "modal";
......
...@@ -441,25 +441,6 @@ describe("Chatboxes", function () { ...@@ -441,25 +441,6 @@ describe("Chatboxes", function () {
describe("A chat toolbar", 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", it("shows the remaining character count if a message_limit is configured",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'message_limit': 200}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'message_limit': 200},
...@@ -476,7 +457,7 @@ describe("Chatboxes", function () { ...@@ -476,7 +457,7 @@ describe("Chatboxes", function () {
view.insertIntoTextArea('hello world'); view.insertIntoTextArea('hello world');
expect(counter.textContent).toBe('188'); 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 picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__lists'));
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a')); const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'));
item.click() item.click()
...@@ -532,7 +513,7 @@ describe("Chatboxes", function () { ...@@ -532,7 +513,7 @@ describe("Chatboxes", function () {
_converse.visible_toolbar_buttons.call = false; _converse.visible_toolbar_buttons.call = false;
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
let view = _converse.chatboxviews.get(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'); call_button = toolbar.querySelector('.toggle-call');
expect(call_button === null).toBeTruthy(); expect(call_button === null).toBeTruthy();
view.close(); view.close();
...@@ -541,7 +522,7 @@ describe("Chatboxes", function () { ...@@ -541,7 +522,7 @@ describe("Chatboxes", function () {
_converse.visible_toolbar_buttons.call = true; // enable the button _converse.visible_toolbar_buttons.call = true; // enable the button
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
view = _converse.chatboxviews.get(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 = toolbar.querySelector('.toggle-call');
call_button.click(); call_button.click();
expect(_converse.api.trigger).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
......
...@@ -20,15 +20,13 @@ describe("Emojis", function () { ...@@ -20,15 +20,13 @@ describe("Emojis", function () {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar')); const toolbar = await u.waitUntil(() => view.el.querySelector('converse-chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1); toolbar.querySelector('.toggle-emojis').click();
toolbar.querySelector('a.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000); 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 = view.el.querySelector('.emoji-picker li.insert-emoji a');
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'), 1000);
item.click() item.click()
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: '); 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(); done();
})); }));
...@@ -53,16 +51,15 @@ describe("Emojis", function () { ...@@ -53,16 +51,15 @@ describe("Emojis", function () {
'key': 'Tab' 'key': 'Tab'
} }
view.onKeyDown(tab_event); view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists'))); await u.waitUntil(() => view.el.querySelector('converse-emoji-picker .emoji-search').value === ':gri');
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container')); await u.waitUntil(() => sizzle('.emojis-lists__container--search .insert-emoji', view.el).length === 3, 1000);
const input = picker.querySelector('.emoji-search'); let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', view.el);
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);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:'); expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:'); expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:');
expect(visible_emojis[2].getAttribute('data-emoji')).toBe(':grinning:'); 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 // Test that TAB autocompletes the to first match
input.dispatchEvent(new KeyboardEvent('keydown', tab_event)); input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
...@@ -76,7 +73,7 @@ describe("Emojis", function () { ...@@ -76,7 +73,7 @@ describe("Emojis", function () {
input.dispatchEvent(new KeyboardEvent('keydown', enter_event)); input.dispatchEvent(new KeyboardEvent('keydown', enter_event));
await u.waitUntil(() => input.value === ''); 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 // Test that username starting with : doesn't cause issues
const presence = $pres({ const presence = $pres({
...@@ -110,15 +107,12 @@ describe("Emojis", function () { ...@@ -110,15 +107,12 @@ describe("Emojis", function () {
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
const toolbar = view.el.querySelector('ul.chat-toolbar'); const toolbar = view.el.querySelector('converse-chat-toolbar');
expect(toolbar.querySelectorAll('.toggle-smiley__container').length).toBe(1); toolbar.querySelector('.toggle-emojis').click();
toolbar.querySelector('.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists'))); await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container')); await u.waitUntil(() => sizzle('converse-chat-toolbar .insert-emoji:not(.hidden)', view.el).length === 1589);
const input = picker.querySelector('.emoji-search');
expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1589);
expect(view.emoji_picker_view.model.get('query')).toBeUndefined(); const input = view.el.querySelector('.emoji-search');
input.value = 'smiley'; input.value = 'smiley';
const event = { const event = {
'target': input, 'target': input,
...@@ -127,9 +121,8 @@ describe("Emojis", function () { ...@@ -127,9 +121,8 @@ describe("Emojis", function () {
}; };
input.dispatchEvent(new KeyboardEvent('keydown', event)); 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:not(.hidden)', view.el).length === 2, 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:not(.hidden)', view.el);
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:'); expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:'); expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
...@@ -143,8 +136,8 @@ describe("Emojis", function () { ...@@ -143,8 +136,8 @@ describe("Emojis", function () {
input.dispatchEvent(new KeyboardEvent('keydown', tab_event)); input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
await u.waitUntil(() => input.value === ':smiley:'); await u.waitUntil(() => input.value === ':smiley:');
await u.waitUntil(() => sizzle(".emojis-lists__container--search .insert-emoji:not('.hidden')", picker).length === 1); 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')", picker); 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[0].getAttribute('data-emoji')).toBe(':smiley:');
// Check that ENTER now inserts the match // Check that ENTER now inserts the match
...@@ -266,11 +259,10 @@ describe("Emojis", function () { ...@@ -266,11 +259,10 @@ describe("Emojis", function () {
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.api.chatviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar')); const toolbar = await u.waitUntil(() => view.el.querySelector('.chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1); toolbar.querySelector('.toggle-emojis').click();
toolbar.querySelector('a.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000); 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"]'); const custom_category = picker.querySelector('.pick-category[data-category="custom"]');
expect(custom_category.innerHTML.replace(/<!---->/g, '').trim()).toBe( expect(custom_category.innerHTML.replace(/<!---->/g, '').trim()).toBe(
'<img class="emoji" draggable="false" title=":xmpp:" alt=":xmpp:" src="/dist/images/custom_emojis/xmpp.png">'); '<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 () { ...@@ -159,7 +159,7 @@ describe("XEP-0363: HTTP File Upload", function () {
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items'); await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items');
const view = _converse.chatboxviews.get(contact_jid); 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(); done();
})); }));
...@@ -173,10 +173,10 @@ describe("XEP-0363: HTTP File Upload", function () { ...@@ -173,10 +173,10 @@ describe("XEP-0363: HTTP File Upload", function () {
[{'category': 'server', 'type':'IM'}], [{'category': 'server', 'type':'IM'}],
['http://jabber.org/protocol/disco#items'], [], 'info'); ['http://jabber.org/protocol/disco#items'], [], 'info');
await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items'); await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], [], 'items');
await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
const view = _converse.chatboxviews.get('lounge@montague.lit'); 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(); done();
})); }));
...@@ -199,8 +199,8 @@ describe("XEP-0363: HTTP File Upload", function () { ...@@ -199,8 +199,8 @@ describe("XEP-0363: HTTP File Upload", function () {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
u.waitUntil(() => view.el.querySelector('.upload-file')); const el = await u.waitUntil(() => view.el.querySelector('.chat-toolbar .fileupload'));
expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null); expect(el).not.toEqual(null);
done(); done();
})); }));
...@@ -216,9 +216,9 @@ describe("XEP-0363: HTTP File Upload", function () { ...@@ -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, _converse.domain, [], [], ['upload.montague.lit'], 'items');
await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []); await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); 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'); 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(); done();
})); }));
......
...@@ -199,11 +199,8 @@ describe("Message Archive Management", function () { ...@@ -199,11 +199,8 @@ describe("Message Archive Management", function () {
_converse.connection._dataRecv(mock.createRequest(result)); _converse.connection._dataRecv(mock.createRequest(result));
await u.waitUntil(() => view.model.messages.length === 5); await u.waitUntil(() => view.model.messages.length === 5);
await u.waitUntil(() => view.content.querySelectorAll('.chat-msg__text').length); await u.waitUntil(() => view.content.querySelectorAll('.chat-msg__text').length);
const msg_els = Array.from(view.content.querySelectorAll('.chat-msg__text')); await u.waitUntil(() => Array.from(view.content.querySelectorAll('.chat-msg__text'))
await u.waitUntil( .map(e => e.textContent).join(' ') === "2nd Message 3rd Message 4th Message 5th Message 6th Message", 1000);
() => msg_els.map(e => e.textContent).join(' ') === "2nd Message 3rd Message 4th Message 5th Message 6th Message",
1000
);
done(); done();
})); }));
}); });
......
This diff is collapsed.
...@@ -223,9 +223,7 @@ describe("A spoiler message", function () { ...@@ -223,9 +223,7 @@ describe("A spoiler message", function () {
`</message>` `</message>`
); );
const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]'); await u.waitUntil(() => stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]')?.textContent === 'This is the hint');
expect(spoiler_el === null).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
const spoiler = 'This is the spoiler' const spoiler = 'This is the spoiler'
const body_el = stanza.querySelector('body'); const body_el = stanza.querySelector('body');
......
...@@ -29,8 +29,9 @@ export class BaseDropdown extends CustomElement { ...@@ -29,8 +29,9 @@ export class BaseDropdown extends CustomElement {
this.button.setAttribute('aria-expanded', true); this.button.setAttribute('aria-expanded', true);
} }
toggleMenu (event) { toggleMenu (ev) {
event.stopPropagation(); ev.stopPropagation();
ev.preventDefault();
if (u.hasClass('show', this.menu)) { if (u.hasClass('show', this.menu)) {
this.hideMenu(); this.hideMenu();
} else { } else {
...@@ -41,7 +42,7 @@ export class BaseDropdown extends CustomElement { ...@@ -41,7 +42,7 @@ export class BaseDropdown extends CustomElement {
handleKeyUp (ev) { handleKeyUp (ev) {
if (ev.keyCode === converse.keycodes.ESCAPE) { if (ev.keyCode === converse.keycodes.ESCAPE) {
this.hideMenu(); 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); this.enableArrowNavigation(ev);
} }
} }
......
...@@ -81,7 +81,6 @@ export default class EmojiPickerContent extends CustomElement { ...@@ -81,7 +81,6 @@ export default class EmojiPickerContent extends CustomElement {
const position = this.model.get('position'); const position = this.model.get('position');
this.model.set({'autocompleting': null, 'position': null, 'query': ''}); this.model.set({'autocompleting': null, 'position': null, 'query': ''});
this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'), replace, false, position); this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'), replace, false, position);
this.chatview.emoji_dropdown.toggle();
} }
shouldBeHidden (shortname) { shouldBeHidden (shortname) {
......
import "./emoji-picker-content.js"; import "./emoji-picker-content.js";
import DOMNavigator from "../dom-navigator"; import DOMNavigator from "../dom-navigator";
import { BaseDropdown } from "./dropdown.js";
import { CustomElement } from './element.js'; import { CustomElement } from './element.js';
import { __ } from '@converse/headless/i18n';
import { _converse, api, converse } from "@converse/headless/converse-core"; import { _converse, api, converse } from "@converse/headless/converse-core";
import { debounce, find } from "lodash-es"; import { debounce, find } from "lodash-es";
import { html } from "lit-element";
import { tpl_emoji_picker } from "../templates/emoji_picker.js"; import { tpl_emoji_picker } from "../templates/emoji_picker.js";
import { until } from 'lit-html/directives/until.js';
const u = converse.env.utils; const u = converse.env.utils;
...@@ -13,13 +17,20 @@ export default class EmojiPicker extends CustomElement { ...@@ -13,13 +17,20 @@ export default class EmojiPicker extends CustomElement {
static get properties () { static get properties () {
return { return {
'chatview': { type: Object }, 'chatview': { type: Object },
'current_category': { type: String }, 'current_category': { type: String, 'reflect': true },
'current_skintone': { type: String }, 'current_skintone': { type: String, 'reflect': true },
'model': { type: Object }, '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 () { constructor () {
super(); super();
this.search_results = []; this.search_results = [];
...@@ -42,19 +53,22 @@ export default class EmojiPicker extends CustomElement { ...@@ -42,19 +53,22 @@ export default class EmojiPicker extends CustomElement {
'onSkintonePicked': ev => this.chooseSkinTone(ev), 'onSkintonePicked': ev => this.chooseSkinTone(ev),
'query': this.query, 'query': this.query,
'search_results': this.search_results, 'search_results': this.search_results,
'render_emojis': this.render_emojis,
'sn2Emoji': shortname => u.shortnamesToEmojis(this.getTonedShortname(shortname)) 'sn2Emoji': shortname => u.shortnamesToEmojis(this.getTonedShortname(shortname))
}); });
} }
firstUpdated () {
this.initArrowNavigation();
}
updated (changed) { updated (changed) {
changed.has('query') && this.updateSearchResults(); changed.has('query') && this.updateSearchResults();
changed.has('current_category') && this.setScrollPosition(); 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 () { setScrollPosition () {
if (this.preserve_scroll) { if (this.preserve_scroll) {
this.preserve_scroll = false; this.preserve_scroll = false;
...@@ -76,7 +90,7 @@ export default class EmojiPicker extends CustomElement { ...@@ -76,7 +90,7 @@ export default class EmojiPicker extends CustomElement {
} else if (this.old_query && this.query.includes(this.old_query)) { } else if (this.old_query && this.query.includes(this.old_query)) {
this.search_results = this.search_results.filter(e => contains(e.sn, this.query)); this.search_results = this.search_results.filter(e => contains(e.sn, this.query));
} else { } 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; this.old_query = this.query;
} else if (this.search_results.length) { } else if (this.search_results.length) {
...@@ -109,22 +123,15 @@ export default class EmojiPicker extends CustomElement { ...@@ -109,22 +123,15 @@ export default class EmojiPicker extends CustomElement {
setCategoryForElement (el) { setCategoryForElement (el) {
const old_category = this.current_category; 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) { if (old_category !== category) {
this.model.save({'current_category': category}); this.model.save({'current_category': category});
} }
} }
insertIntoTextArea (value) { insertIntoTextArea (value) {
const replace = this.model.get('autocompleting'); this.chatview.onEmojiReceivedFromPicker(value);
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.model.set({'query': ''}); this.model.set({'query': ''});
this.disableArrowNavigation();
} }
chooseSkinTone (ev) { chooseSkinTone (ev) {
...@@ -152,7 +159,7 @@ export default class EmojiPicker extends CustomElement { ...@@ -152,7 +159,7 @@ export default class EmojiPicker extends CustomElement {
if (ev.keyCode === converse.keycodes.TAB) { if (ev.keyCode === converse.keycodes.TAB) {
if (ev.target.value) { if (ev.target.value) {
ev.preventDefault(); 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}); match && this.model.set({'query': match});
} else if (!this.navigator.enabled) { } else if (!this.navigator.enabled) {
this.enableArrowNavigation(ev); this.enableArrowNavigation(ev);
...@@ -176,7 +183,7 @@ export default class EmojiPicker extends CustomElement { ...@@ -176,7 +183,7 @@ export default class EmojiPicker extends CustomElement {
onEnterPressed (ev) { onEnterPressed (ev) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
if (_converse.emoji_shortnames.includes(ev.target.value)) { if (converse.emojis.shortnames.includes(ev.target.value)) {
this.insertIntoTextArea(ev.target.value); this.insertIntoTextArea(ev.target.value);
} else if (this.search_results.length === 1) { } else if (this.search_results.length === 1) {
this.insertIntoTextArea(this.search_results[0].sn); this.insertIntoTextArea(this.search_results[0].sn);
...@@ -193,7 +200,7 @@ export default class EmojiPicker extends CustomElement { ...@@ -193,7 +200,7 @@ export default class EmojiPicker extends CustomElement {
} }
getTonedShortname (shortname) { 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.slice(0, shortname.length-1)}_${this.current_skintone}:`
} }
return shortname; return shortname;
...@@ -242,4 +249,82 @@ export default class EmojiPicker extends CustomElement { ...@@ -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); 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', { ...@@ -435,7 +435,6 @@ converse.plugins.add('converse-muc-views', {
className: 'chatbox chatroom hidden', className: 'chatbox chatroom hidden',
is_chatroom: true, is_chatroom: true,
events: { events: {
'change input.fileupload': 'onFileSelection',
'click .chatbox-navback': 'showControlBox', 'click .chatbox-navback': 'showControlBox',
'click .chatbox-title': 'minimize', 'click .chatbox-title': 'minimize',
'click .hide-occupants': 'hideOccupants', 'click .hide-occupants': 'hideOccupants',
...@@ -443,9 +442,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -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. // 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 .occupant-nick': function (ev) {this.insertIntoTextArea(ev.target.textContent) },
'click .send-button': 'onFormSubmitted', 'click .send-button': 'onFormSubmitted',
'click .toggle-call': 'toggleCall',
'click .toggle-occupants': 'toggleOccupants',
'click .upload-file': 'toggleFileUpload',
'dragover .chat-textarea': 'onDragOver', 'dragover .chat-textarea': 'onDragOver',
'drop .chat-textarea': 'onDrop', 'drop .chat-textarea': 'onDrop',
'input .chat-textarea': 'inputChanged', 'input .chat-textarea': 'inputChanged',
...@@ -460,7 +456,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -460,7 +456,7 @@ converse.plugins.add('converse-muc-views', {
this.initDebounced(); this.initDebounced();
this.listenTo(this.model, 'change', debounce(() => this.renderHeading(), 250)); 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, 'configurationNeeded', this.getAndRenderConfigurationForm);
this.listenTo(this.model, 'destroy', this.hide); this.listenTo(this.model, 'destroy', this.hide);
this.listenTo(this.model, 'show', this.show); this.listenTo(this.model, 'show', this.show);
...@@ -1079,8 +1075,8 @@ converse.plugins.add('converse-muc-views', { ...@@ -1079,8 +1075,8 @@ converse.plugins.add('converse-muc-views', {
getToolbarOptions () { getToolbarOptions () {
return Object.assign( return Object.assign(
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), _converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
{ 'is_groupchat': true,
'label_hide_occupants': __('Hide the list of participants'), 'label_hide_occupants': __('Hide the list of participants'),
'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants 'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
} }
...@@ -1101,20 +1097,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -1101,20 +1097,6 @@ converse.plugins.add('converse-muc-views', {
return _converse.ChatBoxView.prototype.close.apply(this, arguments); 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. * Hide the right sidebar containing the chat occupants.
* @private * @private
...@@ -1129,20 +1111,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -1129,20 +1111,6 @@ converse.plugins.add('converse-muc-views', {
this.scrollDown(); 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) { verifyRoles (roles, occupant, show_error=true) {
if (!Array.isArray(roles)) { if (!Array.isArray(roles)) {
throw new TypeError('roles must be an Array'); 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 ...@@ -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-chatview"; // Renders standalone chat boxes for single user chat
import "converse-controlbox"; // The control box import "converse-controlbox"; // The control box
import "converse-dragresize"; // Allows chat boxes to be resized by dragging them import "converse-dragresize"; // Allows chat boxes to be resized by dragging them
import "converse-emoji-views";
import "converse-fullscreen"; import "converse-fullscreen";
import "converse-mam-views"; import "converse-mam-views";
import "converse-minimize"; // Allows chat boxes to be minimized import "converse-minimize"; // Allows chat boxes to be minimized
...@@ -43,7 +42,6 @@ const WHITELISTED_PLUGINS = [ ...@@ -43,7 +42,6 @@ const WHITELISTED_PLUGINS = [
'converse-chatview', 'converse-chatview',
'converse-controlbox', 'converse-controlbox',
'converse-dragresize', 'converse-dragresize',
'converse-emoji-views',
'converse-fullscreen', 'converse-fullscreen',
'converse-mam-views', 'converse-mam-views',
'converse-minimize', 'converse-minimize',
......
...@@ -11,6 +11,11 @@ import { html } from 'lit-html'; ...@@ -11,6 +11,11 @@ import { html } from 'lit-html';
const u = converse.env.utils; const u = converse.env.utils;
converse.emojis = {
'initialized_promise': u.getResolveablePromise()
};
const ASCII_LIST = { const ASCII_LIST = {
'*\\0/*':'1f646', '*\\O/*':'1f646', '-___-':'1f611', ':\'-)':'1f602', '\':-)':'1f605', '\':-D':'1f605', '>:-)':'1f606', '\':-(':'1f613', '*\\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', '>:-(':'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) { ...@@ -56,14 +61,14 @@ function convert (unicode) {
function getTonedEmojis () { function getTonedEmojis () {
if (!_converse.toned_emojis) { if (!converse.emojis.toned) {
_converse.toned_emojis = uniq( converse.emojis.toned = uniq(
Object.values(_converse.emojis.json.people) Object.values(converse.emojis.json.people)
.filter(person => person.sn.includes('_tone')) .filter(person => person.sn.includes('_tone'))
.map(person => person.sn.replace(/_tone[1-5]/, '')) .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: ...@@ -101,7 +106,7 @@ function getEmojiMarkup (data, options={unicode_only: false, add_title_wrapper:
draggable="false" draggable="false"
title="${shortname}" title="${shortname}"
alt="${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: ...@@ -109,7 +114,7 @@ function getEmojiMarkup (data, options={unicode_only: false, add_title_wrapper:
function getShortnameReferences (text) { function getShortnameReferences (text) {
const references = [...text.matchAll(shortnames_regex)]; const references = [...text.matchAll(shortnames_regex)];
return references.map(ref => { return references.map(ref => {
const cp = _converse.emojis_by_sn[ref[0]].cp; const cp = converse.emojis.by_sn[ref[0]].cp;
return { return {
cp, cp,
'begin': ref.index, 'begin': ref.index,
...@@ -201,8 +206,6 @@ converse.plugins.add('converse-emoji', { ...@@ -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'); twemoji.default.base = api.settings.get('emoji_image_path');
...@@ -310,14 +313,14 @@ converse.plugins.add('converse-emoji', { ...@@ -310,14 +313,14 @@ converse.plugins.add('converse-emoji', {
return emojis_by_attribute[attr]; return emojis_by_attribute[attr];
} }
if (attr === 'category') { 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]) .map(e => e[attr])
.filter((c, i, arr) => arr.indexOf(c) == i); .filter((c, i, arr) => arr.indexOf(c) == i);
emojis_by_attribute[attr] = {}; 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]; return emojis_by_attribute[attr];
} }
}); });
...@@ -338,29 +341,20 @@ converse.plugins.add('converse-emoji', { ...@@ -338,29 +341,20 @@ converse.plugins.add('converse-emoji', {
* @returns {Promise} * @returns {Promise}
*/ */
async initialize () { async initialize () {
if (_converse.emojis.initialized) { if (!converse.emojis.initialized) {
return _converse.emojis.initialized; converse.emojis.initialized = true;
}
_converse.emojis.initialized = u.getResolveablePromise();
const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json'); const { default: json } = await import(/*webpackChunkName: "emojis" */ './emojis.json');
_converse.emojis.json = json; converse.emojis.json = json;
_converse.emojis.categories = Object.keys(_converse.emojis.json); converse.emojis.by_sn = Object.keys(json).reduce((result, cat) => Object.assign(result, json[cat]), {});
_converse.emojis_by_sn = _converse.emojis.categories.reduce((result, cat) => Object.assign(result, _converse.emojis.json[cat]), {}); converse.emojis.list = Object.values(converse.emojis.by_sn);
_converse.emojis_list = Object.values(_converse.emojis_by_sn); converse.emojis.list.sort((a, b) => a.sn < b.sn ? -1 : (a.sn > b.sn ? 1 : 0));
_converse.emojis_list.sort((a, b) => a.sn < b.sn ? -1 : (a.sn > b.sn ? 1 : 0)); converse.emojis.shortnames = converse.emojis.list.map(m => m.sn);
_converse.emoji_shortnames = _converse.emojis_list.map(m => m.sn); const getShortNames = () => converse.emojis.shortnames.map(s => s.replace(/[+]/g, "\\$&")).join('|');
const getShortNames = () => _converse.emoji_shortnames.map(s => s.replace(/[+]/g, "\\$&")).join('|');
shortnames_regex = new RegExp(getShortNames(), "gi"); shortnames_regex = new RegExp(getShortNames(), "gi");
converse.emojis.toned = getTonedEmojis();
_converse.emojis.toned = getTonedEmojis(); converse.emojis.initialized_promise.resolve();
_converse.emojis.initialized.resolve(); }
/** return converse.emojis.initialized_promise;
* 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');
} }
} }
}); });
......
...@@ -9,7 +9,6 @@ export default (o) => html` ...@@ -9,7 +9,6 @@ export default (o) => html`
<div class="chat-content__help"></div> <div class="chat-content__help"></div>
</div> </div>
<div class="bottom-panel"> <div class="bottom-panel">
<div class="emoji-picker__container dropup"></div>
<div class="message-form-container"> <div class="message-form-container">
</div> </div>
</div> </div>
......
import { html } from "lit-html"; import { html } from "lit-html";
import { __ } from '@converse/headless/i18n';
const i18n_send_message = __('Send the message');
export default (o) => html` export default (o) => html`
...@@ -10,12 +7,7 @@ export default (o) => html` ...@@ -10,12 +7,7 @@ export default (o) => html`
<input type="submit" class="btn btn-primary" name="join" value="Join"/> <input type="submit" class="btn btn-primary" name="join" value="Join"/>
</form> </form>
<form class="sendXMPPMessage"> <form class="sendXMPPMessage">
${ (o.show_toolbar || o.show_send_button) ? html` <span class="chat-toolbar no-text-select"></span>
<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>` : ''
}
<input type="text" placeholder="${o.label_spoiler_hint || ''}" value="${o.hint_value || ''}" class="${o.composing_spoiler ? '' : 'hidden'} spoiler-hint"/> <input type="text" placeholder="${o.label_spoiler_hint || ''}" value="${o.hint_value || ''}" class="${o.composing_spoiler ? '' : 'hidden'} spoiler-hint"/>
<div class="suggestion-box"> <div class="suggestion-box">
......
...@@ -32,7 +32,7 @@ class MessageBodyRenderer extends String { ...@@ -32,7 +32,7 @@ class MessageBodyRenderer extends String {
let list = await Promise.all(u.addHyperlinks(text)); 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], []); 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) 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` ...@@ -47,11 +47,10 @@ export const tpl_search_results = (o) => html`
`; `;
const emojis_for_category = (o) => { const emojis_for_category = (o) => {
const emojis_by_category = _converse.emojis.json;
return html` 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> <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}"> <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>`; </ul>`;
} }
...@@ -82,13 +81,13 @@ export const tpl_emoji_picker = (o) => { ...@@ -82,13 +81,13 @@ export const tpl_emoji_picker = (o) => {
@focus=${o.onSearchInputFocus}> @focus=${o.onSearchInputFocus}>
${ o.query ? '' : emoji_picker_header(o) } ${ o.query ? '' : emoji_picker_header(o) }
</div> </div>
<converse-emoji-picker-content ${ o.render_emojis ?
html`<converse-emoji-picker-content
.chatview=${o.chatview} .chatview=${o.chatview}
.model=${o.model} .model=${o.model}
.search_results="${o.search_results}" .search_results="${o.search_results}"
current_skintone="${o.current_skintone}" current_skintone="${o.current_skintone}"
query="${o.query}" query="${o.query}"></converse-emoji-picker-content>` : ''}
></converse-emoji-picker-content>
<div class="emoji-skintone-picker"> <div class="emoji-skintone-picker">
<label>Skin tone</label> <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 { html } from "lit-html";
import { api } from '@converse/headless/converse-core.js';
export default (o) => html` export default (o) => {
${ o.show_call_button ? html`<li class="toggle-call fa fa-phone" title="${o.label_start_call}"></li>` : '' } const message_limit = api.settings.get('message_limit');
const show_call_button = api.settings.get('visible_toolbar_buttons').call;
${ o.show_occupants_toggle ? const show_emoji_button = api.settings.get('visible_toolbar_buttons').emoji;
html` <li class="toggle-occupants float-right fa ${ o.hidden_occupants ? `fa-angle-double-left` : `fa-angle-double-right` }" const show_send_button = api.settings.get('show_send_button');
title="${o.label_hide_occupants}"></li>` : '' } const show_spoiler_button = api.settings.get('visible_toolbar_buttons').spoiler;
const show_toolbar = api.settings.get('show_toolbar');
${ o.message_limit ? html`<li class="message-limit font-weight-bold float-right" title="${o.label_message_limit}">${o.message_limit}</li>` : '' } 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 @@ ...@@ -9,6 +9,7 @@
<script src="3rdparty/libsignal-protocol.js"></script> <script src="3rdparty/libsignal-protocol.js"></script>
<link rel="manifest" href="./manifest.json"> <link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" type="image/ico" href="favicon.ico"/> <link rel="shortcut icon" type="image/ico" href="favicon.ico"/>
<script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
</head> </head>
<body class="reset"></body> <body class="reset"></body>
<script> <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