Add new config setting autocomplete_add_contact
Determines whether search suggestions are shown in the "Add Contact" modal.
This commit is contained in:
parent
42128e051d
commit
cc865de0f0
|
@ -1,6 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
## 4.1.3 (Unreleased)
|
||||
## 4.2.0 (Unreleased)
|
||||
|
||||
- Updated translation: lt
|
||||
- Upgrade to Backbone 1.4.0, Strophe 1.3.2 and Jasmine 2.99.2
|
||||
|
@ -9,13 +9,14 @@
|
|||
- Fix handling of CAPTCHAs offered by ejabberd
|
||||
- Don't send out receipts or markers for MAM messages
|
||||
- Allow setting of debug mode via URL with `/#converse?debug=true`
|
||||
- Render inline images served over HTTP if Converse itself was loaded on an unsecured (HTTP) page.
|
||||
- Make sure `nickname` passed in via `_converse.initialize` has first preference as MUC nickname
|
||||
- Make sure required registration fields have "required" attribute
|
||||
- New config setting [autocomplete_add_contact](https://conversejs.org/docs/html/configuration.html#autocomplete-add-contact)
|
||||
- New config setting [locked_muc_domain](https://conversejs.org/docs/html/configuration.html#locked-muc-domain)
|
||||
- New config setting [locked_muc_nickname](https://conversejs.org/docs/html/configuration.html#locked-muc-nickname)
|
||||
- New config setting [show_client_info](https://conversejs.org/docs/html/configuration.html#show-client-info)
|
||||
- Render inline images served over HTTP if Converse itself was loaded on an unsecured (HTTP) page.
|
||||
- Document new API method [sendMessage](https://conversejs.org/docs/html/api/-_converse.ChatBox.html#sendMessage)
|
||||
- Make sure `nickname` passed in via `_converse.initialize` has first preference as MUC nickname
|
||||
- Make sure required registration fields have "required" attribute
|
||||
- #1149: With `xhr_user_search_url`, contact requests are not being sent out
|
||||
- #1213: Switch roster filter input and icons
|
||||
- #1327: fix False mentions positives in URLs and Email addresses
|
||||
|
|
91
dist/converse.js
vendored
91
dist/converse.js
vendored
|
@ -58987,6 +58987,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
|
|||
__ = _converse.__;
|
||||
|
||||
_converse.api.settings.update({
|
||||
'autocomplete_add_contact': true,
|
||||
'allow_chat_pending_contacts': true,
|
||||
'allow_contact_removal': true,
|
||||
'hide_offline_users': false,
|
||||
|
@ -59075,10 +59076,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
|
|||
afterRender() {
|
||||
if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
|
||||
this.initXHRAutoComplete();
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
} else {
|
||||
this.initJIDAutoComplete();
|
||||
}
|
||||
|
@ -59088,6 +59085,10 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
|
|||
},
|
||||
|
||||
initJIDAutoComplete() {
|
||||
if (!_converse.autocomplete_add_contact) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = this.el.querySelector('.suggestion-box__jid').parentElement;
|
||||
this.jid_auto_complete = new _converse.AutoComplete(el, {
|
||||
'data': (text, input) => `${input.slice(0, input.indexOf("@"))}@${text}`,
|
||||
|
@ -59097,6 +59098,10 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
|
|||
},
|
||||
|
||||
initXHRAutoComplete() {
|
||||
if (!_converse.autocomplete_add_contact) {
|
||||
return this.initXHRFetch();
|
||||
}
|
||||
|
||||
const el = this.el.querySelector('.suggestion-box__name').parentElement;
|
||||
this.name_auto_complete = new _converse.AutoComplete(el, {
|
||||
'auto_evaluate': false,
|
||||
|
@ -59122,28 +59127,76 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
|
|||
xhr.open("GET", `${_converse.xhr_user_search_url}q=${input_el.value}`, true);
|
||||
xhr.send();
|
||||
}, 300));
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
},
|
||||
|
||||
addContactFromForm(ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target),
|
||||
jid = data.get('jid'),
|
||||
name = data.get('name');
|
||||
initXHRFetch() {
|
||||
this.xhr = new window.XMLHttpRequest();
|
||||
|
||||
this.xhr.onload = () => {
|
||||
if (this.xhr.responseText) {
|
||||
const r = this.xhr.responseText;
|
||||
const list = JSON.parse(r).map(i => ({
|
||||
'label': i.fullname || i.jid,
|
||||
'value': i.jid
|
||||
}));
|
||||
|
||||
if (list.length !== 1) {
|
||||
const el = this.el.querySelector('.suggestion-box__name .invalid-feedback');
|
||||
el.textContent = __('Sorry, could not find a contact with that name');
|
||||
u.addClass('d-block', el);
|
||||
return;
|
||||
}
|
||||
|
||||
const jid = list[0].value;
|
||||
|
||||
if (this.validateSubmission(jid)) {
|
||||
const form = this.el.querySelector('form');
|
||||
const name = list[0].label;
|
||||
this.afterSubmission(form, jid, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
validateSubmission(jid) {
|
||||
if (!jid || _.compact(jid.split('@')).length < 2) {
|
||||
// XXX: we used to have to do this manually, instead of via
|
||||
// toHTML because Awesomplete messes things up and
|
||||
// confuses Snabbdom
|
||||
// We now use _converse.AutoComplete, can this be removed?
|
||||
u.addClass('is-invalid', this.el.querySelector('input[name="jid"]'));
|
||||
u.addClass('d-block', this.el.querySelector('.invalid-feedback'));
|
||||
} else {
|
||||
ev.target.reset();
|
||||
u.addClass('d-block', this.el.querySelector('.suggestion-box__jid .invalid-feedback'));
|
||||
return false;
|
||||
}
|
||||
|
||||
_converse.roster.addAndSubscribe(jid, name);
|
||||
return true;
|
||||
},
|
||||
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
afterSubmission(form, jid, name) {
|
||||
_converse.roster.addAndSubscribe(jid, name);
|
||||
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
},
|
||||
|
||||
addContactFromForm(ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target),
|
||||
jid = data.get('jid');
|
||||
|
||||
if (!jid && _converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
this.xhr.open("GET", `${_converse.xhr_user_search_url}q=${input_el.value}`, true);
|
||||
this.xhr.send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.validateSubmission(jid)) {
|
||||
this.afterSubmission(ev.target, jid, data.get('name'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68597,7 +68650,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
|
|||
* (Function) callback - A function to call once the IQ is returned
|
||||
* (Function) errback - A function to call if an error occurred
|
||||
*/
|
||||
name = _.isEmpty(name) ? jid : name;
|
||||
name = _.isEmpty(name) ? null : name;
|
||||
const iq = $iq({
|
||||
'type': 'set'
|
||||
}).c('query', {
|
||||
|
@ -92115,7 +92168,11 @@ __p += ' hidden ';
|
|||
} ;
|
||||
__p += '">\n <label class="clearfix" for="jid">' +
|
||||
__e(o.label_xmpp_address) +
|
||||
':</label>\n <div class="suggestion-box suggestion-box__jid">\n <ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>\n <input type="text" name="jid" required="required" value="' +
|
||||
':</label>\n <div class="suggestion-box suggestion-box__jid">\n <ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>\n <input type="text" name="jid"\n ';
|
||||
if (!o._converse.xhr_user_search_url) { ;
|
||||
__p += ' required="required" ';
|
||||
} ;
|
||||
__p += '\n value="' +
|
||||
__e(o.jid) +
|
||||
'"\n class="form-control suggestion-box__input"\n placeholder="' +
|
||||
__e(o.contact_placeholder) +
|
||||
|
|
|
@ -250,6 +250,13 @@ available) and the amount returned will be no more than the page size.
|
|||
You will be able to query for even older messages by scrolling upwards in the chatbox or room
|
||||
(the so-called infinite scrolling pattern).
|
||||
|
||||
autocomplete_add_contact
|
||||
------------------------
|
||||
|
||||
* Default: ``true``
|
||||
|
||||
Determines whether search suggestions are shown in the "Add Contact" modal.
|
||||
|
||||
auto_list_rooms
|
||||
---------------
|
||||
|
||||
|
|
|
@ -218,13 +218,44 @@
|
|||
done();
|
||||
}));
|
||||
|
||||
it("can be configured to not provide search suggestions",
|
||||
mock.initConverse(
|
||||
null, ['rosterGroupsFetched'], {'autocomplete_add_contact': false},
|
||||
async function (done, _converse) {
|
||||
|
||||
test_utils.openControlBox();
|
||||
const panel = _converse.chatboxviews.get('controlbox').contactspanel;
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.el.querySelector('.add-contact').click()
|
||||
const modal = _converse.rosterview.add_contact_modal;
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete).toBe(undefined);
|
||||
|
||||
await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
const sendIQ = _converse.connection.sendIQ;
|
||||
let sent_stanza;
|
||||
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
|
||||
sent_stanza = iq;
|
||||
sendIQ.bind(this)(iq, callback, errback);
|
||||
});
|
||||
expect(!_.isNull(modal.el.querySelector('form.add-xmpp-contact'))).toBeTruthy();
|
||||
const input_jid = modal.el.querySelector('input[name="jid"]');
|
||||
const input_name = modal.el.querySelector('input[name="name"]');
|
||||
input_jid.value = 'someone@localhost';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
await test_utils.waitUntil(() => sent_stanza.nodeTree.matches('iq'));
|
||||
expect(sent_stanza.toLocaleString()).toEqual(
|
||||
`<iq id="${sent_stanza.nodeTree.getAttribute('id')}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="someone@localhost"/></query>`+
|
||||
`</iq>`);
|
||||
done();
|
||||
}));
|
||||
|
||||
|
||||
it("integrates with xhr_user_search_url to search for contacts",
|
||||
mock.initConverse(
|
||||
null, ['rosterGroupsFetched'],
|
||||
{ 'xhr_user_search': true,
|
||||
'xhr_user_search_url': 'http://example.org/?'
|
||||
},
|
||||
{ 'xhr_user_search_url': 'http://example.org/?' },
|
||||
async function (done, _converse) {
|
||||
|
||||
const xhr = {
|
||||
|
@ -278,5 +309,74 @@
|
|||
window.XMLHttpRequest = XMLHttpRequestBackup;
|
||||
done();
|
||||
}));
|
||||
|
||||
it("can be configured to not provide search suggestions for XHR search results",
|
||||
mock.initConverse(
|
||||
null, ['rosterGroupsFetched'],
|
||||
{ 'autocomplete_add_contact': false,
|
||||
'xhr_user_search_url': 'http://example.org/?' },
|
||||
async function (done, _converse) {
|
||||
|
||||
var modal;
|
||||
const xhr = {
|
||||
'open': _.noop,
|
||||
'send': function () {
|
||||
const value = modal.el.querySelector('input[name="name"]').value;
|
||||
if (value === 'ambiguous') {
|
||||
xhr.responseText = JSON.stringify([
|
||||
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
|
||||
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
|
||||
]);
|
||||
} else if (value === 'insufficient') {
|
||||
xhr.responseText = JSON.stringify([]);
|
||||
} else {
|
||||
xhr.responseText = JSON.stringify([{"jid": "marty@mcfly.net", "fullname": "Marty McFly"}]);
|
||||
}
|
||||
xhr.onload();
|
||||
}
|
||||
};
|
||||
const XMLHttpRequestBackup = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
|
||||
XMLHttpRequest.and.callFake(() => xhr);
|
||||
|
||||
const panel = _converse.chatboxviews.get('controlbox').contactspanel;
|
||||
const cbview = _converse.chatboxviews.get('controlbox');
|
||||
cbview.el.querySelector('.add-contact').click()
|
||||
modal = _converse.rosterview.add_contact_modal;
|
||||
await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
|
||||
|
||||
expect(modal.jid_auto_complete).toBe(undefined);
|
||||
expect(modal.name_auto_complete).toBe(undefined);
|
||||
|
||||
const sendIQ = _converse.connection.sendIQ;
|
||||
let sent_stanza, IQ_id;
|
||||
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
|
||||
sent_stanza = iq;
|
||||
IQ_id = sendIQ.bind(this)(iq, callback, errback);
|
||||
});
|
||||
|
||||
const input_el = modal.el.querySelector('input[name="name"]');
|
||||
input_el.value = 'ambiguous';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
|
||||
let feedback_el = modal.el.querySelector('.suggestion-box__name .invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
|
||||
feedback_el.textContent = '';
|
||||
|
||||
input_el.value = 'insufficient';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
|
||||
feedback_el = modal.el.querySelector('.suggestion-box__name .invalid-feedback');
|
||||
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
|
||||
|
||||
input_el.value = 'Marty McFly';
|
||||
modal.el.querySelector('button[type="submit"]').click();
|
||||
expect(sent_stanza.toLocaleString()).toEqual(
|
||||
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
|
||||
`</iq>`);
|
||||
window.XMLHttpRequest = XMLHttpRequestBackup;
|
||||
done();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
expect(sent_stanza.toLocaleString()).toBe(
|
||||
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
|
||||
`<query xmlns="jabber:iq:roster">`+
|
||||
`<item jid="contact@example.org" name="contact@example.org"/>`+
|
||||
`<item jid="contact@example.org"/>`+
|
||||
`</query>`+
|
||||
`</iq>`
|
||||
);
|
||||
|
|
|
@ -55,13 +55,14 @@ converse.plugins.add('converse-rosterview', {
|
|||
{ __ } = _converse;
|
||||
|
||||
_converse.api.settings.update({
|
||||
'autocomplete_add_contact': true,
|
||||
'allow_chat_pending_contacts': true,
|
||||
'allow_contact_removal': true,
|
||||
'hide_offline_users': false,
|
||||
'roster_groups': true,
|
||||
'show_only_online_users': false,
|
||||
'show_toolbar': true,
|
||||
'xhr_user_search_url': null
|
||||
'xhr_user_search_url': null,
|
||||
});
|
||||
_converse.api.promises.add('rosterViewInitialized');
|
||||
|
||||
|
@ -132,10 +133,6 @@ converse.plugins.add('converse-rosterview', {
|
|||
afterRender () {
|
||||
if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
|
||||
this.initXHRAutoComplete();
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
} else {
|
||||
this.initJIDAutoComplete();
|
||||
}
|
||||
|
@ -144,6 +141,9 @@ converse.plugins.add('converse-rosterview', {
|
|||
},
|
||||
|
||||
initJIDAutoComplete () {
|
||||
if (!_converse.autocomplete_add_contact) {
|
||||
return;
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__jid').parentElement;
|
||||
this.jid_auto_complete = new _converse.AutoComplete(el, {
|
||||
'data': (text, input) => `${input.slice(0, input.indexOf("@"))}@${text}`,
|
||||
|
@ -153,6 +153,9 @@ converse.plugins.add('converse-rosterview', {
|
|||
},
|
||||
|
||||
initXHRAutoComplete () {
|
||||
if (!_converse.autocomplete_add_contact) {
|
||||
return this.initXHRFetch();
|
||||
}
|
||||
const el = this.el.querySelector('.suggestion-box__name').parentElement;
|
||||
this.name_auto_complete = new _converse.AutoComplete(el, {
|
||||
'auto_evaluate': false,
|
||||
|
@ -174,25 +177,66 @@ converse.plugins.add('converse-rosterview', {
|
|||
xhr.open("GET", `${_converse.xhr_user_search_url}q=${input_el.value}`, true);
|
||||
xhr.send()
|
||||
} , 300));
|
||||
this.name_auto_complete.on('suggestion-box-selectcomplete', ev => {
|
||||
this.el.querySelector('input[name="name"]').value = ev.text.label;
|
||||
this.el.querySelector('input[name="jid"]').value = ev.text.value;
|
||||
});
|
||||
},
|
||||
|
||||
addContactFromForm (ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target),
|
||||
jid = data.get('jid'),
|
||||
name = data.get('name');
|
||||
initXHRFetch () {
|
||||
this.xhr = new window.XMLHttpRequest();
|
||||
this.xhr.onload = () => {
|
||||
if (this.xhr.responseText) {
|
||||
const r = this.xhr.responseText;
|
||||
const list = JSON.parse(r).map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
||||
if (list.length !== 1) {
|
||||
const el = this.el.querySelector('.suggestion-box__name .invalid-feedback');
|
||||
el.textContent = __('Sorry, could not find a contact with that name')
|
||||
u.addClass('d-block', el);
|
||||
return;
|
||||
}
|
||||
const jid = list[0].value;
|
||||
if (this.validateSubmission(jid)) {
|
||||
const form = this.el.querySelector('form');
|
||||
const name = list[0].label;
|
||||
this.afterSubmission(form, jid, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
validateSubmission (jid) {
|
||||
if (!jid || _.compact(jid.split('@')).length < 2) {
|
||||
// XXX: we used to have to do this manually, instead of via
|
||||
// toHTML because Awesomplete messes things up and
|
||||
// confuses Snabbdom
|
||||
// We now use _converse.AutoComplete, can this be removed?
|
||||
u.addClass('is-invalid', this.el.querySelector('input[name="jid"]'));
|
||||
u.addClass('d-block', this.el.querySelector('.invalid-feedback'));
|
||||
} else {
|
||||
ev.target.reset();
|
||||
_converse.roster.addAndSubscribe(jid, name);
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
u.addClass('d-block', this.el.querySelector('.suggestion-box__jid .invalid-feedback'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
afterSubmission (form, jid, name) {
|
||||
_converse.roster.addAndSubscribe(jid, name);
|
||||
this.model.clear();
|
||||
this.modal.hide();
|
||||
},
|
||||
|
||||
addContactFromForm (ev) {
|
||||
ev.preventDefault();
|
||||
const data = new FormData(ev.target),
|
||||
jid = data.get('jid');
|
||||
|
||||
if (!jid && _converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
|
||||
const input_el = this.el.querySelector('input[name="name"]');
|
||||
this.xhr.open("GET", `${_converse.xhr_user_search_url}q=${input_el.value}`, true);
|
||||
this.xhr.send()
|
||||
return;
|
||||
}
|
||||
if (this.validateSubmission(jid)) {
|
||||
this.afterSubmission(ev.target, jid, data.get('name'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -453,7 +453,7 @@ converse.plugins.add('converse-roster', {
|
|||
* (Function) callback - A function to call once the IQ is returned
|
||||
* (Function) errback - A function to call if an error occurred
|
||||
*/
|
||||
name = _.isEmpty(name)? jid: name;
|
||||
name = _.isEmpty(name) ? null : name;
|
||||
const iq = $iq({'type': 'set'})
|
||||
.c('query', {'xmlns': Strophe.NS.ROSTER})
|
||||
.c('item', { jid, name });
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
<label class="clearfix" for="jid">{{{o.label_xmpp_address}}}:</label>
|
||||
<div class="suggestion-box suggestion-box__jid">
|
||||
<ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>
|
||||
<input type="text" name="jid" required="required" value="{{{o.jid}}}"
|
||||
<input type="text" name="jid"
|
||||
{[ if (!o._converse.xhr_user_search_url) { ]} required="required" {[ } ]}
|
||||
value="{{{o.jid}}}"
|
||||
class="form-control suggestion-box__input"
|
||||
placeholder="{{{o.contact_placeholder}}}"/>
|
||||
<div class="invalid-feedback">{{{o.error_message}}}</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user