Re-add xhr_user_search_url and autocomplete when adding contacts

This commit is contained in:
JC Brand 2018-03-26 15:38:03 +02:00
parent 1e927294e0
commit 95406a149d
13 changed files with 340 additions and 248 deletions

View File

@ -149,7 +149,7 @@
"no-negated-condition": "off", "no-negated-condition": "off",
"no-negated-in-lhs": "error", "no-negated-in-lhs": "error",
"no-nested-ternary": "off", "no-nested-ternary": "off",
"no-new": "error", "no-new": "off",
"no-new-func": "error", "no-new-func": "error",
"no-new-object": "error", "no-new-object": "error",
"no-new-require": "error", "no-new-require": "error",

View File

@ -7,7 +7,7 @@ addons:
chrome: stable chrome: stable
node_js: node_js:
- 6 - 6
install: make node_modules install: make stamp-npm
before_script: make serve_bg before_script: make serve_bg
script: make check script: make check
sudo: false sudo: false

View File

@ -2,29 +2,19 @@
## 4.0.0 (Unreleased) ## 4.0.0 (Unreleased)
## Removed configuration settings ## UI changes
Due to rewriting parts of the code, we regrettably had to remove certain The UI is now based on Bootstrap4 and Flexbox is used extensively.
lesser-used configuration settings because the cost of adding them to the
new code was too high.
If you relied on any of these settings, you can reproduce their ## Configuration changes
functionality in your own 3rd party plugins, or you can [contact us](http://opkode.com/contact.html)
with regards to sponsoring development on reintroducing them.
* Removed the `xhr_custom_status` and `xhr_custom_status_url` configuration * Removed the `xhr_custom_status` and `xhr_custom_status_url` configuration
settings. If you relied on these settings, you can instead listen for the settings. If you relied on these settings, you can instead listen for the
[statusMessageChanged](https://conversejs.org/docs/html/events.html#contactstatusmessagechanged) [statusMessageChanged](https://conversejs.org/docs/html/events.html#contactstatusmessagechanged)
event and make the XMLHttpRequest yourself. event and make the XMLHttpRequest yourself.
* Removed the `xhr_user_search` and `xhr_user_search_url` configuration options. * Removed `xhr_user_search` in favor of only accepting `xhr_user_search_url` as configuration option.
* The data returned from the `xhr_user_search_url` must now include the user's
## Updated UI `jid` instead of just an `id`.
The UI is now rewritten with Bootstrap4 and Flexbox is used pretty much
everywhere. Unfortunately this means that in the overlayed view_mode, chat
boxes can no longer be resized horizontally (or diagonally). Perhaps a solution
for this can again be found, but time constraints meant that this feature had
to be removed.
### Bugfixes ### Bugfixes

View File

@ -8586,20 +8586,23 @@ body.reset {
#conversejs:not(.fullscreen) #minimized-chats .chat-head-message-count-hidden { #conversejs:not(.fullscreen) #minimized-chats .chat-head-message-count-hidden {
display: none; } display: none; }
#converse-embedded-chat, #converse-embedded-chat [hidden],
#conversejs { #conversejs [hidden] {
/* Pointer */ } display: none; }
#converse-embedded-chat [hidden], #converse-embedded-chat .visually-hidden,
#conversejs [hidden] { #conversejs .visually-hidden {
display: none; } position: absolute;
#converse-embedded-chat .visually-hidden, clip: rect(0, 0, 0, 0); }
#conversejs .visually-hidden { #converse-embedded-chat .form-group .awesomplete,
position: absolute; #conversejs .form-group .awesomplete {
clip: rect(0, 0, 0, 0); } width: 100%; }
#converse-embedded-chat div.awesomplete, #converse-embedded-chat div.awesomplete,
#conversejs div.awesomplete { #conversejs div.awesomplete {
display: inline-block; display: inline-block;
position: relative; } position: relative; }
#converse-embedded-chat div.awesomplete mark,
#conversejs div.awesomplete mark {
background: #FFB9A7; }
#converse-embedded-chat div.awesomplete > input, #converse-embedded-chat div.awesomplete > input,
#conversejs div.awesomplete > input { #conversejs div.awesomplete > input {
display: block; } display: block; }
@ -8620,62 +8623,60 @@ body.reset {
border: 1px solid rgba(0, 0, 0, 0.3); border: 1px solid rgba(0, 0, 0, 0.3);
box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2); box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2);
text-shadow: none; } text-shadow: none; }
#converse-embedded-chat div.awesomplete > ul:before,
#conversejs div.awesomplete > ul:before {
content: "";
position: absolute;
top: -.43em;
left: 1em;
width: 0;
height: 0;
background: white;
border: inherit;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
#converse-embedded-chat div.awesomplete > ul > li,
#conversejs div.awesomplete > ul > li {
text-overflow: ellipsis;
overflow-x: hidden;
position: relative;
cursor: pointer;
padding: 1em; }
#converse-embedded-chat div.awesomplete > ul[hidden],
#converse-embedded-chat div.awesomplete > ul:empty,
#conversejs div.awesomplete > ul[hidden],
#conversejs div.awesomplete > ul:empty {
display: none; }
@supports (transform: scale(0)) {
#converse-embedded-chat div.awesomplete > ul,
#conversejs div.awesomplete > ul {
transition: 0.3s cubic-bezier(0.4, 0.2, 0.5, 1.4);
transform-origin: 1.43em -.43em; }
#converse-embedded-chat div.awesomplete > ul[hidden], #converse-embedded-chat div.awesomplete > ul[hidden],
#converse-embedded-chat div.awesomplete > ul:empty, #converse-embedded-chat div.awesomplete > ul:empty,
#conversejs div.awesomplete > ul[hidden], #conversejs div.awesomplete > ul[hidden],
#conversejs div.awesomplete > ul:empty { #conversejs div.awesomplete > ul:empty {
display: none; } opacity: 0;
@supports (transform: scale(0)) { transform: scale(0);
#converse-embedded-chat div.awesomplete > ul, display: block;
#conversejs div.awesomplete > ul { transition-timing-function: ease; } }
transition: 0.3s cubic-bezier(0.4, 0.2, 0.5, 1.4); #converse-embedded-chat div.awesomplete > ul > li:hover,
transform-origin: 1.43em -.43em; } #conversejs div.awesomplete > ul > li:hover {
#converse-embedded-chat div.awesomplete > ul[hidden], background: #E77051;
#converse-embedded-chat div.awesomplete > ul:empty, color: white; }
#conversejs div.awesomplete > ul[hidden], #converse-embedded-chat div.awesomplete > ul > li[aria-selected="true"],
#conversejs div.awesomplete > ul:empty { #conversejs div.awesomplete > ul > li[aria-selected="true"] {
opacity: 0; background: #3d6d8f;
transform: scale(0); color: white; }
display: block; #converse-embedded-chat div.awesomplete li:hover mark,
transition-timing-function: ease; } } #conversejs div.awesomplete li:hover mark {
#converse-embedded-chat div.awesomplete > ul:before, background: #A53214;
#conversejs div.awesomplete > ul:before { color: white; }
content: ""; #converse-embedded-chat div.awesomplete li[aria-selected="true"] mark,
position: absolute; #conversejs div.awesomplete li[aria-selected="true"] mark {
top: -.43em; background: #3d6b00;
left: 1em; color: inherit; }
width: 0;
height: 0;
background: white;
border: inherit;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
#converse-embedded-chat div.awesomplete > ul > li,
#conversejs div.awesomplete > ul > li {
text-overflow: ellipsis;
overflow-x: hidden;
position: relative;
cursor: pointer; }
#converse-embedded-chat div.awesomplete > ul > li:hover,
#conversejs div.awesomplete > ul > li:hover {
background: #E77051;
color: white; }
#converse-embedded-chat div.awesomplete > ul > li[aria-selected="true"],
#conversejs div.awesomplete > ul > li[aria-selected="true"] {
background: #3d6d8f;
color: white; }
#converse-embedded-chat div.awesomplete mark,
#conversejs div.awesomplete mark {
background: #FFB9A7; }
#converse-embedded-chat div.awesomplete li:hover mark,
#conversejs div.awesomplete li:hover mark {
background: #A53214;
color: white; }
#converse-embedded-chat div.awesomplete li[aria-selected="true"] mark,
#conversejs div.awesomplete li[aria-selected="true"] mark {
background: #3d6b00;
color: inherit; }
/*# sourceMappingURL=converse.css.map */ /*# sourceMappingURL=converse.css.map */

View File

@ -8698,20 +8698,23 @@ body {
border: 1.2em solid #E7A151; border: 1.2em solid #E7A151;
border-top: 0.8em solid #E7A151; } border-top: 0.8em solid #E7A151; }
#converse-embedded-chat, #converse-embedded-chat [hidden],
#conversejs { #conversejs [hidden] {
/* Pointer */ } display: none; }
#converse-embedded-chat [hidden], #converse-embedded-chat .visually-hidden,
#conversejs [hidden] { #conversejs .visually-hidden {
display: none; } position: absolute;
#converse-embedded-chat .visually-hidden, clip: rect(0, 0, 0, 0); }
#conversejs .visually-hidden { #converse-embedded-chat .form-group .awesomplete,
position: absolute; #conversejs .form-group .awesomplete {
clip: rect(0, 0, 0, 0); } width: 100%; }
#converse-embedded-chat div.awesomplete, #converse-embedded-chat div.awesomplete,
#conversejs div.awesomplete { #conversejs div.awesomplete {
display: inline-block; display: inline-block;
position: relative; } position: relative; }
#converse-embedded-chat div.awesomplete mark,
#conversejs div.awesomplete mark {
background: #FFB9A7; }
#converse-embedded-chat div.awesomplete > input, #converse-embedded-chat div.awesomplete > input,
#conversejs div.awesomplete > input { #conversejs div.awesomplete > input {
display: block; } display: block; }
@ -8732,62 +8735,60 @@ body {
border: 1px solid rgba(0, 0, 0, 0.3); border: 1px solid rgba(0, 0, 0, 0.3);
box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2); box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2);
text-shadow: none; } text-shadow: none; }
#converse-embedded-chat div.awesomplete > ul:before,
#conversejs div.awesomplete > ul:before {
content: "";
position: absolute;
top: -.43em;
left: 1em;
width: 0;
height: 0;
background: white;
border: inherit;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
#converse-embedded-chat div.awesomplete > ul > li,
#conversejs div.awesomplete > ul > li {
text-overflow: ellipsis;
overflow-x: hidden;
position: relative;
cursor: pointer;
padding: 1em; }
#converse-embedded-chat div.awesomplete > ul[hidden],
#converse-embedded-chat div.awesomplete > ul:empty,
#conversejs div.awesomplete > ul[hidden],
#conversejs div.awesomplete > ul:empty {
display: none; }
@supports (transform: scale(0)) {
#converse-embedded-chat div.awesomplete > ul,
#conversejs div.awesomplete > ul {
transition: 0.3s cubic-bezier(0.4, 0.2, 0.5, 1.4);
transform-origin: 1.43em -.43em; }
#converse-embedded-chat div.awesomplete > ul[hidden], #converse-embedded-chat div.awesomplete > ul[hidden],
#converse-embedded-chat div.awesomplete > ul:empty, #converse-embedded-chat div.awesomplete > ul:empty,
#conversejs div.awesomplete > ul[hidden], #conversejs div.awesomplete > ul[hidden],
#conversejs div.awesomplete > ul:empty { #conversejs div.awesomplete > ul:empty {
display: none; } opacity: 0;
@supports (transform: scale(0)) { transform: scale(0);
#converse-embedded-chat div.awesomplete > ul, display: block;
#conversejs div.awesomplete > ul { transition-timing-function: ease; } }
transition: 0.3s cubic-bezier(0.4, 0.2, 0.5, 1.4); #converse-embedded-chat div.awesomplete > ul > li:hover,
transform-origin: 1.43em -.43em; } #conversejs div.awesomplete > ul > li:hover {
#converse-embedded-chat div.awesomplete > ul[hidden], background: #E77051;
#converse-embedded-chat div.awesomplete > ul:empty, color: white; }
#conversejs div.awesomplete > ul[hidden], #converse-embedded-chat div.awesomplete > ul > li[aria-selected="true"],
#conversejs div.awesomplete > ul:empty { #conversejs div.awesomplete > ul > li[aria-selected="true"] {
opacity: 0; background: #3d6d8f;
transform: scale(0); color: white; }
display: block; #converse-embedded-chat div.awesomplete li:hover mark,
transition-timing-function: ease; } } #conversejs div.awesomplete li:hover mark {
#converse-embedded-chat div.awesomplete > ul:before, background: #A53214;
#conversejs div.awesomplete > ul:before { color: white; }
content: ""; #converse-embedded-chat div.awesomplete li[aria-selected="true"] mark,
position: absolute; #conversejs div.awesomplete li[aria-selected="true"] mark {
top: -.43em; background: #3d6b00;
left: 1em; color: inherit; }
width: 0;
height: 0;
background: white;
border: inherit;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
#converse-embedded-chat div.awesomplete > ul > li,
#conversejs div.awesomplete > ul > li {
text-overflow: ellipsis;
overflow-x: hidden;
position: relative;
cursor: pointer; }
#converse-embedded-chat div.awesomplete > ul > li:hover,
#conversejs div.awesomplete > ul > li:hover {
background: #E77051;
color: white; }
#converse-embedded-chat div.awesomplete > ul > li[aria-selected="true"],
#conversejs div.awesomplete > ul > li[aria-selected="true"] {
background: #3d6d8f;
color: white; }
#converse-embedded-chat div.awesomplete mark,
#conversejs div.awesomplete mark {
background: #FFB9A7; }
#converse-embedded-chat div.awesomplete li:hover mark,
#conversejs div.awesomplete li:hover mark {
background: #A53214;
color: white; }
#converse-embedded-chat div.awesomplete li[aria-selected="true"] mark,
#conversejs div.awesomplete li[aria-selected="true"] mark {
background: #3d6b00;
color: inherit; }
/*# sourceMappingURL=inverse.css.map */ /*# sourceMappingURL=inverse.css.map */

View File

@ -1535,3 +1535,38 @@ Example:
whitelisted_plugins: ['myplugin'] whitelisted_plugins: ['myplugin']
}); });
}); });
xhr_user_search_url
-------------------
.. note::
XHR stands for XMLHTTPRequest, and is meant here in the AJAX sense (Asynchronous JavaScript and XML).
* Default: ``null``
There are two ways to add users.
* The user inputs a valid JID (Jabber ID, aka XMPP address), and the user is added as a pending contact.
* The user inputs some text (for example part of a first name or last name),
an XHR (Ajax Request) will be made to a remote server, and a list of matches are returned.
The user can then choose one of the matches to add as a contact.
By providing an XHR search URL, you're enabling the second mechanism.
*What is expected from the remote server?*
A default JSON encoded list of objects must be returned. Each object
corresponds to a matched user and needs the keys ``jid`` and ``fullname``.
.. code-block:: javascript
[{"jid": "marty@mcfly.net", "fullname": "Marty McFly"}, {"jid": "doc@brown.com", "fullname": "Doc Brown"}]
.. note::
Make sure your server script sets the header `Content-Type: application/json`.
This is the URL to which an XHR GET request will be made to fetch user data from your remote server.
The query string will be included in the request with ``q`` as its key.
The data returned must be a JSON encoded list of user JIDs.

View File

@ -7,31 +7,62 @@
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
} }
.form-group {
.awesomplete {
width: 100%;
}
}
div.awesomplete { div.awesomplete {
display: inline-block; display: inline-block;
position: relative; position: relative;
} mark {
background: $lightest-red;
}
div.awesomplete > input { > input {
display: block; display: block;
} }
div.awesomplete > ul { > ul {
position: absolute; &:before {
left: 0; content: "";
right: 0; position: absolute;
z-index: 1; top: -.43em;
min-width: 100%; left: 1em;
box-sizing: border-box; width: 0; height: 0;
list-style: none; background: white;
padding: 0; border: inherit;
border-radius: .3em; border-right: 0;
margin: .2em 0 0; border-bottom: 0;
background: hsla(0,0%,100%,.9); -webkit-transform: rotate(45deg);
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); transform: rotate(45deg);
border: 1px solid rgba(0,0,0,.3); }
box-shadow: .05em .2em .6em rgba(0,0,0,.2);
text-shadow: none; position: absolute;
left: 0;
right: 0;
z-index: 1;
min-width: 100%;
box-sizing: border-box;
list-style: none;
padding: 0;
border-radius: .3em;
margin: .2em 0 0;
background: hsla(0,0%,100%,.9);
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));
border: 1px solid rgba(0,0,0,.3);
box-shadow: .05em .2em .6em rgba(0,0,0,.2);
text-shadow: none;
> li {
text-overflow: ellipsis;
overflow-x: hidden;
position: relative;
cursor: pointer;
padding: 1em;
}
}
} }
div.awesomplete > ul[hidden], div.awesomplete > ul[hidden],
@ -53,28 +84,6 @@
transition-timing-function: ease; transition-timing-function: ease;
} }
} }
/* Pointer */
div.awesomplete > ul:before {
content: "";
position: absolute;
top: -.43em;
left: 1em;
width: 0; height: 0;
background: white;
border: inherit;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
div.awesomplete > ul > li {
text-overflow: ellipsis;
overflow-x: hidden;
position: relative;
cursor: pointer;
}
div.awesomplete > ul > li:hover { div.awesomplete > ul > li:hover {
background: $red; background: $red;
@ -86,10 +95,6 @@
color: white; color: white;
} }
div.awesomplete mark {
background: $lightest-red;
}
div.awesomplete li:hover mark { div.awesomplete li:hover mark {
background: $darkest-red; background: $darkest-red;
color: $inverse-link-color; color: $inverse-link-color;

View File

@ -1353,20 +1353,12 @@
$(view.el).find('.chat-area').remove(); $(view.el).find('.chat-area').remove();
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
return $(view.el).find('input.invited-contact').length; return $(view.el).find('input.invited-contact').length;
}, 300).then(function () { }, 300).then(function () {
var $input = $(view.el).find('input.invited-contact'); var $input = $(view.el).find('input.invited-contact');
expect($input.attr('placeholder')).toBe('Invite'); expect($input.attr('placeholder')).toBe('Invite');
$input.val("Felix"); $input.val("Felix");
var evt; var evt = new Event('input');
// check if Event() is a constructor function
// usage as per the spec, if true
if (typeof(Event) === 'function') {
evt = new Event('input');
} else { // the deprecated way for PhantomJS
evt = document.createEvent('CustomEvent');
evt.initCustomEvent('input', false, false, null);
}
$input[0].dispatchEvent(evt); $input[0].dispatchEvent(evt);
var sent_stanza; var sent_stanza;

View File

@ -33,6 +33,37 @@
describe("The \"Contacts\" section", function () { describe("The \"Contacts\" section", function () {
it("can be used to add contact and it checks for case-sensivity",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox();
// Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check)
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0]
});
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.') + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0]
});
test_utils.waitUntil(function () {
return $(_converse.rosterview.el).find('.roster-group li:visible').length;
}, 700).then(function () {
// Checking that only one entry is created because both JID is same (Case sensitive check)
expect($(_converse.rosterview.el).find('li:visible').length).toBe(1);
expect(_converse.rosterview.update).toHaveBeenCalled();
done();
});
}));
it("shows the number of unread mentions received", it("shows the number of unread mentions received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
@ -157,6 +188,8 @@
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox();
var panel = _converse.chatboxviews.get('controlbox').contactspanel; var panel = _converse.chatboxviews.get('controlbox').contactspanel;
var cbview = _converse.chatboxviews.get('controlbox'); var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
@ -165,37 +198,55 @@
return u.isVisible(modal.el); return u.isVisible(modal.el);
}, 1000).then(function () { }, 1000).then(function () {
expect(!_.isNull(modal.el.querySelector('form.add-xmpp-contact'))).toBeTruthy(); expect(!_.isNull(modal.el.querySelector('form.add-xmpp-contact'))).toBeTruthy();
var input_el = modal.el.querySelector('input[name="jid"]');
input_el.value = 'someone@';
var evt = new Event('input');
input_el.dispatchEvent(evt);
expect(modal.el.querySelector('.awesomplete li').textContent).toBe('someone@localhost');
done(); done();
}); });
})); }));
it("can be used to add contact and it checks for case-sensivity",
it("integrates with xhr_user_search_url to search for contacts",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'],
{ 'xhr_user_search': true,
'xhr_user_search_url': 'http://example.org/'
},
function (done, _converse) { function (done, _converse) {
spyOn(_converse, 'emit'); var xhr = {
spyOn(_converse.rosterview, 'update').and.callThrough(); 'open': _.noop,
test_utils.openControlBox(); 'send': function () {
// Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check) xhr.responseText = JSON.stringify([
_converse.roster.create({ {"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', {"jid": "doc@brown.com", "fullname": "Doc Brown"}
subscription: 'none', ]);
ask: 'subscribe', xhr.onload();
fullname: mock.pend_names[0] }
};
window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
XMLHttpRequest.and.callFake(function () {
return xhr;
}); });
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.') + '@localhost', var panel = _converse.chatboxviews.get('controlbox').contactspanel;
subscription: 'none', var cbview = _converse.chatboxviews.get('controlbox');
ask: 'subscribe', cbview.el.querySelector('.add-contact').click()
fullname: mock.pend_names[0] var modal = _converse.rosterview.add_contact_modal;
}); return test_utils.waitUntil(function () {
test_utils.waitUntil(function () { return u.isVisible(modal.el);
return $(_converse.rosterview.el).find('.roster-group li:visible').length; }, 1000).then(function () {
}, 700).then(function () { var input_el = modal.el.querySelector('input[name="jid"]');
// Checking that only one entry is created because both JID is same (Case sensitive check) input_el.value = 'marty@';
expect($(_converse.rosterview.el).find('li:visible').length).toBe(1); var evt = new Event('input');
expect(_converse.rosterview.update).toHaveBeenCalled(); input_el.dispatchEvent(evt);
return test_utils.waitUntil(function () {
return modal.el.querySelector('.awesomplete li');
});
}).then(function () {
expect(modal.el.querySelector('.awesomplete li').textContent).toBe('marty@mcfly.net');
done(); done();
}); });
})); }));

View File

@ -16,6 +16,7 @@
"tpl!roster_filter", "tpl!roster_filter",
"tpl!roster_item", "tpl!roster_item",
"tpl!search_contact", "tpl!search_contact",
"awesomplete",
"converse-chatboxes", "converse-chatboxes",
"converse-modal" "converse-modal"
], factory); ], factory);
@ -28,7 +29,8 @@
tpl_roster, tpl_roster,
tpl_roster_filter, tpl_roster_filter,
tpl_roster_item, tpl_roster_item,
tpl_search_contact tpl_search_contact,
Awesomplete
) { ) {
"use strict"; "use strict";
const { Backbone, Strophe, $iq, b64_sha1, sizzle, _ } = converse.env; const { Backbone, Strophe, $iq, b64_sha1, sizzle, _ } = converse.env;
@ -78,9 +80,10 @@
{ __ } = _converse; { __ } = _converse;
_converse.api.settings.update({ _converse.api.settings.update({
allow_chat_pending_contacts: true, 'allow_chat_pending_contacts': true,
allow_contact_removal: true, 'allow_contact_removal': true,
show_toolbar: true, 'show_toolbar': true,
'xhr_user_search_url': null
}); });
_converse.api.promises.add('rosterViewInitialized'); _converse.api.promises.add('rosterViewInitialized');
@ -147,6 +150,31 @@
})); }));
}, },
afterRender () {
const input_el = this.el.querySelector('input[name="jid"]');
if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
const awesomplete = new Awesomplete(input_el, {'list': [], 'minChars': 2});
const xhr = new window.XMLHttpRequest();
// `open` must be called after `onload` for
// mock/testing purposes.
xhr.onload = function () {
awesomplete.list = JSON.parse(xhr.responseText).map((i) => i.jid);
awesomplete.evaluate();
};
xhr.open("GET", _converse.xhr_user_search_url, true);
input_el.addEventListener('input', _.debounce(() => xhr.send()), 100, {'leading': true});
} else {
const list = _.uniq(_converse.roster.map((item) => Strophe.getDomainFromJid(item.get('jid'))));
new Awesomplete(input_el, {
'list': list,
'data': function (text, input) {
return input.slice(0, input.indexOf("@")) + "@" + text;
},
'filter': Awesomplete.FILTER_STARTSWITH
});
}
},
addContactFromForm (ev) { addContactFromForm (ev) {
ev.preventDefault(); ev.preventDefault();
const data = new FormData(ev.target), const data = new FormData(ev.target),

View File

@ -1,11 +0,0 @@
<form class="pure-form add-xmpp-contact">
{[ if (o.error_message) { ]}
<span class="pure-form-message error">{{{o.error_message}}}</span>
{[ } ]}
<input type="text"
name="identifier"
value="{{{o.value}}}"
class="username {[ if (o.error_message) { ]} error {[ } ]}"
placeholder="{{{o.label_contact_username}}}"/>
<button class="btn btn-primary" type="submit">{{{o.label_add}}}</button>
</form>

View File

@ -9,7 +9,7 @@
<form class="converse-form add-xmpp-contact"> <form class="converse-form add-xmpp-contact">
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<label for="jid">{{{o.label_xmpp_address}}}:</label> <label class="clearfix" for="jid">{{{o.label_xmpp_address}}}:</label>
<input type="text" name="jid" required="required" value="{{{o.jid}}}" <input type="text" name="jid" required="required" value="{{{o.jid}}}"
class="form-control {[ if (o.error_message) { ]} is-invalid {[ } ]}" class="form-control {[ if (o.error_message) { ]} is-invalid {[ } ]}"
placeholder="{{{o.contact_placeholder}}}"> placeholder="{{{o.contact_placeholder}}}">

View File

@ -1,6 +1,6 @@
<form class="room-invite"> <form class="room-invite">
{[ if (o.error_message) { ]} {[ if (o.error_message) { ]}
<span class="pure-form-message error">{{{o.error_message}}}</span> <span class="error">{{{o.error_message}}}</span>
{[ } ]} {[ } ]}
<input class="form-control invited-contact" placeholder="{{{o.label_invitation}}}" type="text"/> <input class="form-control invited-contact" placeholder="{{{o.label_invitation}}}" type="text"/>
</form> </form>