Convert older docstrings to JSDoc syntax
This commit is contained in:
parent
7ed99092f5
commit
a45bd8d14b
2
Makefile
2
Makefile
@ -261,4 +261,4 @@ html: dev docsdev apidoc
|
|||||||
|
|
||||||
PHONY: apidoc
|
PHONY: apidoc
|
||||||
apidoc:
|
apidoc:
|
||||||
$(JSDOC) --readme docs/source/jsdoc_intro.md -c docs/source/conf.json -d docs/html/api src/*.js src/utils/*.js src/headless/*.js src/headless/utils/*.js
|
$(JSDOC) --private --readme docs/source/jsdoc_intro.md -c docs/source/conf.json -d docs/html/api src/*.js src/utils/*.js src/headless/*.js src/headless/utils/*.js
|
||||||
|
1339
dist/converse.js
vendored
1339
dist/converse.js
vendored
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,5 @@ to fix a bug or to add new functionality.
|
|||||||
plugin_development
|
plugin_development
|
||||||
api/index
|
api/index
|
||||||
testing
|
testing
|
||||||
events
|
|
||||||
other_frameworks
|
other_frameworks
|
||||||
builds
|
builds
|
||||||
|
@ -3,29 +3,31 @@
|
|||||||
Welcome to the new Converse API documentation, generated with
|
Welcome to the new Converse API documentation, generated with
|
||||||
[JSDoc](http://usejsdoc.org/).
|
[JSDoc](http://usejsdoc.org/).
|
||||||
|
|
||||||
The old (increasingly out of date and incomplete) API documentation is
|
This documentation replaces the (increasingly out of date and incomplete) [old API documentation](/docs/html/developer_api.html)
|
||||||
currently still [available here](/docs/html/developer_api.html).
|
and [old "Events and Promises" documentation](/docs/html/events.html).
|
||||||
|
|
||||||
## The public and private API
|
## The public and private API
|
||||||
|
|
||||||
Converse has a public API and a private API.
|
Converse has a public API and a private API only available to plugins.
|
||||||
r
|
|
||||||
The reason we make this distinction between public and private is so that API
|
The reason we make this distinction between public and private is so that API
|
||||||
methods which might can be used to "impersonate" the user, for example by
|
methods which could be used to "impersonate" the user, for example by
|
||||||
sending messages on their behalf, are not available to random scripts running
|
sending messages on their behalf, are not available to random scripts running
|
||||||
in the websites.
|
in your website.
|
||||||
|
|
||||||
The public API is accessible via the `window.converse` global and is therefore
|
The public API is accessible via the [window.converse](/docs/html/api/converse.html)
|
||||||
available to all JavaScript running in the page.
|
global and is therefore available to any JavaScript running in the page.
|
||||||
|
|
||||||
Tehe private API is only accessible to plugins, which have been whitelisted and
|
The private API is only accessible to plugins, which have been whitelisted and
|
||||||
registered before `converse.initialize` (which is a public API method) has been
|
registered before [converse.initialize](/docs/html/api/converse.html#.initialize)
|
||||||
called. See the [plugin development](https://conversejs.org/docs/html/plugin_development.html)
|
(which is a public API method) has been called.
|
||||||
|
|
||||||
|
See the [plugin development](/docs/html/plugin_development.html)
|
||||||
section for more info on writing plugins.
|
section for more info on writing plugins.
|
||||||
|
|
||||||
Inside a plugin, you can get access to the `_converse.api` object. Note the
|
Inside a plugin, you can get access to the {@link _converse.api}
|
||||||
underscore in front of `_converse`, which indicates that this is a private,
|
object. Note the underscore in front of {@link _converse},
|
||||||
closured object.
|
which indicates that this is a private, closured object.
|
||||||
|
|
||||||
## API Namespaces
|
## API Namespaces
|
||||||
|
|
||||||
@ -35,9 +37,26 @@ group relevant methods.
|
|||||||
So, for example, all the XEP-0030 service discovery methods are under the
|
So, for example, all the XEP-0030 service discovery methods are under the
|
||||||
{@link \_converse.api.disco} namespace, in the [private API]{@link \_converse.api}.
|
{@link \_converse.api.disco} namespace, in the [private API]{@link \_converse.api}.
|
||||||
|
|
||||||
Which means that you access it via `_converse.api.disco`.
|
Which means that you access it via {@link _converse.api.disco}.
|
||||||
|
|
||||||
Namespaces can be nested. So the {@link \_converse.api.disco} namespace
|
### Nested Namespaces
|
||||||
namespace has {@link \_converse.api.disco.own} as a nested namespace.
|
|
||||||
|
Namespaces can be nested.
|
||||||
|
|
||||||
|
{@link _converse.api} is the top-level namespace, of which {@link \_converse.api.disco}
|
||||||
|
is a nested, child namespace and {@link \_converse.api.disco.own} is nested another
|
||||||
|
level deeper.
|
||||||
|
|
||||||
Not all methods are however within a namespace. For example {@link converse.initialize}.
|
Not all methods are however within a namespace. For example {@link converse.initialize}.
|
||||||
|
|
||||||
|
## Stable API versus unstable API
|
||||||
|
|
||||||
|
Converse uses [semantic versioning](https://semver.org/) for releases, which means that
|
||||||
|
we try to maintain a stable API for minor and patch releases and when we do change the
|
||||||
|
stable API we will make a major release.
|
||||||
|
|
||||||
|
In the JSDoc API documentation, all API methods that are **not** marked as *Private*
|
||||||
|
are considered to be part of the stable API, and you can therefore expect them to
|
||||||
|
not change between minor and patch releases. If a method is marked as *Private*,
|
||||||
|
then you could still use it, but we don't provide any guarantee that it won't change
|
||||||
|
between minor and patch releases.
|
||||||
|
@ -586,18 +586,18 @@ converse.plugins.add('converse-chatview', {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts an indicator into the chat area, showing the
|
||||||
|
* day as given by the passed in date.
|
||||||
|
* The indicator is only inserted if necessary.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#insertDayIndicator
|
||||||
|
* @param { HTMLElement } next_msg_el - The message element before
|
||||||
|
* which the day indicator element must be inserted.
|
||||||
|
* This element must have a "data-isodate" attribute
|
||||||
|
* which specifies its creation date.
|
||||||
|
*/
|
||||||
insertDayIndicator (next_msg_el) {
|
insertDayIndicator (next_msg_el) {
|
||||||
/* Inserts an indicator into the chat area, showing the
|
|
||||||
* day as given by the passed in date.
|
|
||||||
*
|
|
||||||
* The indicator is only inserted if necessary.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) next_msg_el - The message element before
|
|
||||||
* which the day indicator element must be inserted.
|
|
||||||
* This element must have a "data-isodate" attribute
|
|
||||||
* which specifies its creation date.
|
|
||||||
*/
|
|
||||||
const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"),
|
const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"),
|
||||||
prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'),
|
prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'),
|
||||||
next_msg_date = next_msg_el.getAttribute('data-isodate');
|
next_msg_date = next_msg_el.getAttribute('data-isodate');
|
||||||
@ -616,13 +616,14 @@ converse.plugins.add('converse-chatview', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ISO8601 format date of the latest message.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#getLastMessageDate
|
||||||
|
* @param { object } cutoff - Moment Date cutoff date. The last
|
||||||
|
* message received cutoff this date will be returned.
|
||||||
|
*/
|
||||||
getLastMessageDate (cutoff) {
|
getLastMessageDate (cutoff) {
|
||||||
/* Return the ISO8601 format date of the latest message.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) cutoff: Moment Date cutoff date. The last
|
|
||||||
* message received cutoff this date will be returned.
|
|
||||||
*/
|
|
||||||
const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'),
|
const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'),
|
||||||
oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null;
|
oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null;
|
||||||
if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) {
|
if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) {
|
||||||
@ -713,13 +714,14 @@ converse.plugins.add('converse-chatview', {
|
|||||||
return !u.isVisible(this.el);
|
return !u.isVisible(this.el);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a view representing a message, insert it into the
|
||||||
|
* content area of the chat box.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#insertMessage
|
||||||
|
* @param { Backbone.View } message - The message Backbone.View
|
||||||
|
*/
|
||||||
insertMessage (view) {
|
insertMessage (view) {
|
||||||
/* Given a view representing a message, insert it into the
|
|
||||||
* content area of the chat box.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Backbone.View) message: The message Backbone.View
|
|
||||||
*/
|
|
||||||
if (view.model.get('type') === 'error') {
|
if (view.model.get('type') === 'error') {
|
||||||
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
|
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
|
||||||
if (previous_msg_el) {
|
if (previous_msg_el) {
|
||||||
@ -746,20 +748,22 @@ converse.plugins.add('converse-chatview', {
|
|||||||
return this.trigger('messageInserted', view.el);
|
return this.trigger('messageInserted', view.el);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a message element, determine wether it should be
|
||||||
|
* marked as a followup message to the previous element.
|
||||||
|
*
|
||||||
|
* Also determine whether the element following it is a
|
||||||
|
* followup message or not.
|
||||||
|
*
|
||||||
|
* Followup messages are subsequent ones written by the same
|
||||||
|
* author with no other conversation elements inbetween and
|
||||||
|
* posted within 10 minutes of one another.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#markFollowups
|
||||||
|
* @param { HTMLElement } el - The message element
|
||||||
|
*/
|
||||||
markFollowups (el) {
|
markFollowups (el) {
|
||||||
/* Given a message element, determine wether it should be
|
|
||||||
* marked as a followup message to the previous element.
|
|
||||||
*
|
|
||||||
* Also determine whether the element following it is a
|
|
||||||
* followup message or not.
|
|
||||||
*
|
|
||||||
* Followup messages are subsequent ones written by the same
|
|
||||||
* author with no other conversation elements inbetween and
|
|
||||||
* posted within 10 minutes of one another.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) el - The message element.
|
|
||||||
*/
|
|
||||||
const from = el.getAttribute('data-from'),
|
const from = el.getAttribute('data-from'),
|
||||||
previous_el = el.previousElementSibling,
|
previous_el = el.previousElementSibling,
|
||||||
date = moment(el.getAttribute('data-isodate')),
|
date = moment(el.getAttribute('data-isodate')),
|
||||||
@ -783,15 +787,14 @@ converse.plugins.add('converse-chatview', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a chat message into the content area of the chat box.
|
||||||
|
* Will also insert a new day indicator if the message is on a different day.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#showMessage
|
||||||
|
* @param { _converse.Message } message - The message object
|
||||||
|
*/
|
||||||
async showMessage (message) {
|
async showMessage (message) {
|
||||||
/* Inserts a chat message into the content area of the chat box.
|
|
||||||
*
|
|
||||||
* Will also insert a new day indicator if the message is on a
|
|
||||||
* different day.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Backbone.Model) message: The message object
|
|
||||||
*/
|
|
||||||
if (!u.isNewMessage(message) && u.isEmptyMessage(message)) {
|
if (!u.isNewMessage(message) && u.isEmptyMessage(message)) {
|
||||||
// Handle archived or delayed messages without any message
|
// Handle archived or delayed messages without any message
|
||||||
// text to show.
|
// text to show.
|
||||||
@ -822,12 +825,13 @@ converse.plugins.add('converse-chatview', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler that gets called when a new message object is created.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#onMessageAdded
|
||||||
|
* @param { object } message - The message Backbone object that was added.
|
||||||
|
*/
|
||||||
onMessageAdded (message) {
|
onMessageAdded (message) {
|
||||||
/* Handler that gets called when a new message object is created.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) message - The message Backbone object that was added.
|
|
||||||
*/
|
|
||||||
this.showMessage(message);
|
this.showMessage(message);
|
||||||
if (message.get('correcting')) {
|
if (message.get('correcting')) {
|
||||||
this.insertIntoTextArea(message.get('message'), true, true);
|
this.insertIntoTextArea(message.get('message'), true, true);
|
||||||
@ -865,17 +869,18 @@ converse.plugins.add('converse-chatview', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutator for setting the chat state of this chat session.
|
||||||
|
* Handles clearing of any chat state notification timeouts and
|
||||||
|
* setting new ones if necessary.
|
||||||
|
* Timeouts are set when the state being set is COMPOSING or PAUSED.
|
||||||
|
* After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE.
|
||||||
|
* See XEP-0085 Chat State Notifications.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBoxView#setChatState
|
||||||
|
* @param { string } state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
|
||||||
|
*/
|
||||||
setChatState (state, options) {
|
setChatState (state, options) {
|
||||||
/* Mutator for setting the chat state of this chat session.
|
|
||||||
* Handles clearing of any chat state notification timeouts and
|
|
||||||
* setting new ones if necessary.
|
|
||||||
* Timeouts are set when the state being set is COMPOSING or PAUSED.
|
|
||||||
* After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE.
|
|
||||||
* See XEP-0085 Chat State Notifications.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
|
|
||||||
*/
|
|
||||||
if (!_.isUndefined(this.chat_state_timeout)) {
|
if (!_.isUndefined(this.chat_state_timeout)) {
|
||||||
window.clearTimeout(this.chat_state_timeout);
|
window.clearTimeout(this.chat_state_timeout);
|
||||||
delete this.chat_state_timeout;
|
delete this.chat_state_timeout;
|
||||||
|
@ -378,11 +378,6 @@ converse.plugins.add('converse-controlbox', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
showHelpMessages () {
|
showHelpMessages () {
|
||||||
/* Override showHelpMessages in ChatBoxView, for now do nothing.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Array) msgs: Array of messages
|
|
||||||
*/
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -203,15 +203,12 @@ converse.plugins.add('converse-muc-views', {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Insert groupchat info (based on returned #disco IQ stanza)
|
||||||
|
* @function insertRoomInfo
|
||||||
|
* @param { HTMLElement } el - The HTML DOM element that contains the info.
|
||||||
|
* @param { XMLElement } stanza - The IQ stanza containing the groupchat info.
|
||||||
|
*/
|
||||||
function insertRoomInfo (el, stanza) {
|
function insertRoomInfo (el, stanza) {
|
||||||
/* Insert groupchat info (based on returned #disco IQ stanza)
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) el: The HTML DOM element that should
|
|
||||||
* contain the info.
|
|
||||||
* (XMLElement) stanza: The IQ stanza containing the groupchat
|
|
||||||
* info.
|
|
||||||
*/
|
|
||||||
// All MUC features found here: https://xmpp.org/registrar/disco-features.html
|
// All MUC features found here: https://xmpp.org/registrar/disco-features.html
|
||||||
el.querySelector('span.spinner').remove();
|
el.querySelector('span.spinner').remove();
|
||||||
el.querySelector('a.room-info').classList.add('selected');
|
el.querySelector('a.room-info').classList.add('selected');
|
||||||
@ -1110,12 +1107,13 @@ converse.plugins.add('converse-muc-views', {
|
|||||||
this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this));
|
this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all MUC presence stanzas.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoomView#onPresence
|
||||||
|
* @param { XMLElement } pres - The stanza
|
||||||
|
*/
|
||||||
onPresence (pres) {
|
onPresence (pres) {
|
||||||
/* Handles all MUC presence stanzas.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The stanza
|
|
||||||
*/
|
|
||||||
// XXX: Current thinking is that excessive stanza
|
// XXX: Current thinking is that excessive stanza
|
||||||
// processing inside a view is a "code smell".
|
// processing inside a view is a "code smell".
|
||||||
// Instead stanza processing should happen inside the
|
// Instead stanza processing should happen inside the
|
||||||
@ -1138,14 +1136,14 @@ converse.plugins.add('converse-muc-views', {
|
|||||||
this.fetchMessages();
|
this.fetchMessages();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join the groupchat.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoomView#join
|
||||||
|
* @param { String } nick - The user's nickname
|
||||||
|
* @param { String } password - Optional password, if required by the groupchat
|
||||||
|
*/
|
||||||
join (nick, password) {
|
join (nick, password) {
|
||||||
/* Join the groupchat.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) nick: The user's nickname
|
|
||||||
* (String) password: Optional password, if required by
|
|
||||||
* the groupchat.
|
|
||||||
*/
|
|
||||||
if (!nick && !this.model.get('nick')) {
|
if (!nick && !this.model.get('nick')) {
|
||||||
this.checkForReservedNick();
|
this.checkForReservedNick();
|
||||||
return this;
|
return this;
|
||||||
@ -1154,17 +1152,16 @@ converse.plugins.add('converse-muc-views', {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a form given an IQ stanza containing the current
|
||||||
|
* groupchat configuration.
|
||||||
|
* Returns a promise which resolves once the user has
|
||||||
|
* either submitted the form, or canceled it.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoomView#renderConfigurationForm
|
||||||
|
* @param { XMLElement } stanza: The IQ stanza containing the groupchat config.
|
||||||
|
*/
|
||||||
renderConfigurationForm (stanza) {
|
renderConfigurationForm (stanza) {
|
||||||
/* Renders a form given an IQ stanza containing the current
|
|
||||||
* groupchat configuration.
|
|
||||||
*
|
|
||||||
* Returns a promise which resolves once the user has
|
|
||||||
* either submitted the form, or canceled it.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza: The IQ stanza containing the groupchat
|
|
||||||
* config.
|
|
||||||
*/
|
|
||||||
const container_el = this.el.querySelector('.chatroom-body');
|
const container_el = this.el.querySelector('.chatroom-body');
|
||||||
_.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement);
|
_.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement);
|
||||||
_.each(container_el.children, u.hideElement);
|
_.each(container_el.children, u.hideElement);
|
||||||
@ -1420,13 +1417,14 @@ converse.plugins.add('converse-muc-views', {
|
|||||||
u.showElement(container);
|
u.showElement(container);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoomView#getMessageFromStatus
|
||||||
|
* @param { XMLElement } stat: A <status> element
|
||||||
|
* @param { Boolean } is_self: Whether the element refers to the current user
|
||||||
|
* @param { XMLElement } stanza: The original stanza received
|
||||||
|
*/
|
||||||
getMessageFromStatus (stat, stanza, is_self) {
|
getMessageFromStatus (stat, stanza, is_self) {
|
||||||
/* Parameters:
|
|
||||||
* (XMLElement) stat: A <status> element.
|
|
||||||
* (Boolean) is_self: Whether the element refers to the
|
|
||||||
* current user.
|
|
||||||
* (XMLElement) stanza: The original stanza received.
|
|
||||||
*/
|
|
||||||
const code = stat.getAttribute('code');
|
const code = stat.getAttribute('code');
|
||||||
if (code === '110' || (code === '100' && !is_self)) { return; }
|
if (code === '110' || (code === '100' && !is_self)) { return; }
|
||||||
if (code in _converse.muc.info_messages) {
|
if (code in _converse.muc.info_messages) {
|
||||||
@ -1697,14 +1695,14 @@ converse.plugins.add('converse-muc-views', {
|
|||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for status codes and communicate their purpose to the user.
|
||||||
|
* See: https://xmpp.org/registrar/mucstatus.html
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoomView#showStatusMessages
|
||||||
|
* @param { XMLElement } stanza - The message or presence stanza containing the status codes
|
||||||
|
*/
|
||||||
showStatusMessages (stanza) {
|
showStatusMessages (stanza) {
|
||||||
/* Check for status codes and communicate their purpose to the user.
|
|
||||||
* See: https://xmpp.org/registrar/mucstatus.html
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza: The message or presence stanza
|
|
||||||
* containing the status codes.
|
|
||||||
*/
|
|
||||||
const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza);
|
const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza);
|
||||||
const is_self = stanza.querySelectorAll("status[code='110']").length;
|
const is_self = stanza.querySelectorAll("status[code='110']").length;
|
||||||
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
|
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
|
||||||
|
@ -159,6 +159,11 @@ converse.plugins.add('converse-register', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @namespace _converse.RegisterPanel
|
||||||
|
* @memberOf _converse
|
||||||
|
*/
|
||||||
_converse.RegisterPanel = Backbone.NativeView.extend({
|
_converse.RegisterPanel = Backbone.NativeView.extend({
|
||||||
tagName: 'div',
|
tagName: 'div',
|
||||||
id: "converse-register-panel",
|
id: "converse-register-panel",
|
||||||
@ -200,20 +205,21 @@ converse.plugins.add('converse-register', {
|
|||||||
if (!this._registering) {
|
if (!this._registering) {
|
||||||
connect_cb(req, callback, raw);
|
connect_cb(req, callback, raw);
|
||||||
} else {
|
} else {
|
||||||
if (this.getRegistrationFields(req, callback, raw)) {
|
if (this.getRegistrationFields(req, callback)) {
|
||||||
this._registering = false;
|
this._registering = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getRegistrationFields (req, _callback, raw) {
|
/**
|
||||||
/* Send an IQ stanza to the XMPP server asking for the
|
* Send an IQ stanza to the XMPP server asking for the registration fields.
|
||||||
* registration fields.
|
* @private
|
||||||
* Parameters:
|
* @method _converse.RegisterPanel#getRegistrationFields
|
||||||
* (Strophe.Request) req - The current request
|
* @param { Strophe.Request } req - The current request
|
||||||
* (Function) callback
|
* @param { Function } callback - The callback function
|
||||||
*/
|
*/
|
||||||
|
getRegistrationFields (req, _callback) {
|
||||||
const conn = _converse.connection;
|
const conn = _converse.connection;
|
||||||
conn.connected = true;
|
conn.connected = true;
|
||||||
|
|
||||||
@ -248,12 +254,13 @@ converse.plugins.add('converse-register', {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for {@link _converse.RegisterPanel#getRegistrationFields}
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#onRegistrationFields
|
||||||
|
* @param { XMLElement } stanza - The query stanza.
|
||||||
|
*/
|
||||||
onRegistrationFields (stanza) {
|
onRegistrationFields (stanza) {
|
||||||
/* Handler for Registration Fields Request.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) elem - The query stanza.
|
|
||||||
*/
|
|
||||||
if (stanza.getAttribute("type") === "error") {
|
if (stanza.getAttribute("type") === "error") {
|
||||||
_converse.connection._changeConnectStatus(
|
_converse.connection._changeConnectStatus(
|
||||||
Strophe.Status.REGIFAIL,
|
Strophe.Status.REGIFAIL,
|
||||||
@ -309,13 +316,13 @@ converse.plugins.add('converse-register', {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback method that gets called when the user has chosen an XMPP provider
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#onProviderChosen
|
||||||
|
* @param { HTMLElement } form - The form that was submitted
|
||||||
|
*/
|
||||||
onProviderChosen (form) {
|
onProviderChosen (form) {
|
||||||
/* Callback method that gets called when the user has chosen an
|
|
||||||
* XMPP provider.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) form - The form that was submitted
|
|
||||||
*/
|
|
||||||
const domain_input = form.querySelector('input[name=domain]'),
|
const domain_input = form.querySelector('input[name=domain]'),
|
||||||
domain = _.get(domain_input, 'value');
|
domain = _.get(domain_input, 'value');
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
@ -327,13 +334,13 @@ converse.plugins.add('converse-register', {
|
|||||||
this.fetchRegistrationForm(domain.trim());
|
this.fetchRegistrationForm(domain.trim());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a registration form from the requested domain
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#fetchRegistrationForm
|
||||||
|
* @param { String } domain_name - XMPP server domain
|
||||||
|
*/
|
||||||
fetchRegistrationForm (domain_name) {
|
fetchRegistrationForm (domain_name) {
|
||||||
/* This is called with a domain name based on which, it fetches a
|
|
||||||
* registration form from the requested domain.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) domain_name - XMPP server domain
|
|
||||||
*/
|
|
||||||
if (!this.model.get('registration_form_rendered')) {
|
if (!this.model.get('registration_form_rendered')) {
|
||||||
this.renderRegistrationRequest();
|
this.renderRegistrationRequest();
|
||||||
}
|
}
|
||||||
@ -386,16 +393,14 @@ converse.plugins.add('converse-register', {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function called by Strophe whenever the connection status changes.
|
||||||
|
* Passed to Strophe specifically during a registration attempt.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#onConnectStatusChanged
|
||||||
|
* @param { integer } status_code - The Strophe.Status status code
|
||||||
|
*/
|
||||||
onConnectStatusChanged(status_code) {
|
onConnectStatusChanged(status_code) {
|
||||||
/* Callback function called by Strophe whenever the
|
|
||||||
* connection status changes.
|
|
||||||
*
|
|
||||||
* Passed to Strophe specifically during a registration
|
|
||||||
* attempt.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Integer) status_code - The Stroph.Status status code
|
|
||||||
*/
|
|
||||||
_converse.log('converse-register: onConnectStatusChanged');
|
_converse.log('converse-register: onConnectStatusChanged');
|
||||||
if (_.includes([
|
if (_.includes([
|
||||||
Strophe.Status.DISCONNECTED,
|
Strophe.Status.DISCONNECTED,
|
||||||
@ -472,13 +477,14 @@ converse.plugins.add('converse-register', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the registration form based on the XForm fields
|
||||||
|
* received from the XMPP server.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#renderRegistrationForm
|
||||||
|
* @param { XMLElement } stanza - The IQ stanza received from the XMPP server.
|
||||||
|
*/
|
||||||
renderRegistrationForm (stanza) {
|
renderRegistrationForm (stanza) {
|
||||||
/* Renders the registration form based on the XForm fields
|
|
||||||
* received from the XMPP server.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - The IQ stanza received from the XMPP server.
|
|
||||||
*/
|
|
||||||
const form = this.el.querySelector('form');
|
const form = this.el.querySelector('form');
|
||||||
form.innerHTML = tpl_registration_form({
|
form.innerHTML = tpl_registration_form({
|
||||||
'__': _converse.__,
|
'__': _converse.__,
|
||||||
@ -528,14 +534,14 @@ converse.plugins.add('converse-register', {
|
|||||||
flash.classList.remove('hidden');
|
flash.classList.remove('hidden');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report back to the user any error messages received from the
|
||||||
|
* XMPP server after attempted registration.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#reportErrors
|
||||||
|
* @param { XMLElement } stanza - The IQ stanza received from the XMPP server
|
||||||
|
*/
|
||||||
reportErrors (stanza) {
|
reportErrors (stanza) {
|
||||||
/* Report back to the user any error messages received from the
|
|
||||||
* XMPP server after attempted registration.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - The IQ stanza received from the
|
|
||||||
* XMPP server.
|
|
||||||
*/
|
|
||||||
const errors = stanza.querySelectorAll('error');
|
const errors = stanza.querySelectorAll('error');
|
||||||
_.each(errors, (error) => {
|
_.each(errors, (error) => {
|
||||||
this.showValidationError(error.textContent);
|
this.showValidationError(error.textContent);
|
||||||
@ -568,14 +574,14 @@ converse.plugins.add('converse-register', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler, when the user submits the registration form.
|
||||||
|
* Provides form error feedback or starts the registration process.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#submitRegistrationForm
|
||||||
|
* @param { HTMLElement } form - The HTML form that was submitted
|
||||||
|
*/
|
||||||
submitRegistrationForm (form) {
|
submitRegistrationForm (form) {
|
||||||
/* Handler, when the user submits the registration form.
|
|
||||||
* Provides form error feedback or starts the registration
|
|
||||||
* process.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) form - The HTML form that was submitted
|
|
||||||
*/
|
|
||||||
const has_empty_inputs = _.reduce(
|
const has_empty_inputs = _.reduce(
|
||||||
this.el.querySelectorAll('input.required'),
|
this.el.querySelectorAll('input.required'),
|
||||||
function (result, input) {
|
function (result, input) {
|
||||||
@ -606,13 +612,12 @@ converse.plugins.add('converse-register', {
|
|||||||
this.setFields(iq.tree());
|
this.setFields(iq.tree());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Stores the values that will be sent to the XMPP server during attempted registration.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#setFields
|
||||||
|
* @param { XMLElement } stanza - the IQ stanza that will be sent to the XMPP server.
|
||||||
|
*/
|
||||||
setFields (stanza) {
|
setFields (stanza) {
|
||||||
/* Stores the values that will be sent to the XMPP server
|
|
||||||
* during attempted registration.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server.
|
|
||||||
*/
|
|
||||||
const query = stanza.querySelector('query');
|
const query = stanza.querySelector('query');
|
||||||
const xform = sizzle(`x[xmlns="${Strophe.NS.XFORM}"]`, query);
|
const xform = sizzle(`x[xmlns="${Strophe.NS.XFORM}"]`, query);
|
||||||
if (xform.length > 0) {
|
if (xform.length > 0) {
|
||||||
@ -653,14 +658,15 @@ converse.plugins.add('converse-register', {
|
|||||||
this.form_type = 'xform';
|
this.form_type = 'xform';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback method that gets called when a return IQ stanza
|
||||||
|
* is received from the XMPP server, after attempting to
|
||||||
|
* register a new user.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RegisterPanel#reportErrors
|
||||||
|
* @param { XMLElement } stanza - The IQ stanza.
|
||||||
|
*/
|
||||||
_onRegisterIQ (stanza) {
|
_onRegisterIQ (stanza) {
|
||||||
/* Callback method that gets called when a return IQ stanza
|
|
||||||
* is received from the XMPP server, after attempting to
|
|
||||||
* register a new user.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - The IQ stanza.
|
|
||||||
*/
|
|
||||||
if (stanza.getAttribute("type") === "error") {
|
if (stanza.getAttribute("type") === "error") {
|
||||||
_converse.log("Registration failed.", Strophe.LogLevel.ERROR);
|
_converse.log("Registration failed.", Strophe.LogLevel.ERROR);
|
||||||
this.reportErrors(stanza);
|
this.reportErrors(stanza);
|
||||||
|
@ -442,13 +442,13 @@ converse.plugins.add('converse-chatboxes', {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a {@link _converse.Message} return the XML stanza that represents it.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBox#createMessageStanza
|
||||||
|
* @param { _converse.Message } message - The message object
|
||||||
|
*/
|
||||||
createMessageStanza (message) {
|
createMessageStanza (message) {
|
||||||
/* Given a _converse.Message Backbone.Model, return the XML
|
|
||||||
* stanza that represents it.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) message - The Backbone.Model representing the message
|
|
||||||
*/
|
|
||||||
const stanza = $msg({
|
const stanza = $msg({
|
||||||
'from': _converse.connection.jid,
|
'from': _converse.connection.jid,
|
||||||
'to': this.get('jid'),
|
'to': this.get('jid'),
|
||||||
@ -630,13 +630,14 @@ converse.plugins.add('converse-chatboxes', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the XEP-0359 stanza IDs from the passed in stanza
|
||||||
|
* and return a map containing them.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBox#getStanzaIDs
|
||||||
|
* @param { XMLElement } stanza - The message stanza
|
||||||
|
*/
|
||||||
getStanzaIDs (stanza) {
|
getStanzaIDs (stanza) {
|
||||||
/* Extract the XEP-0359 stanza IDs from the passed in stanza
|
|
||||||
* and return a map containing them.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - The message stanza
|
|
||||||
*/
|
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
const stanza_ids = sizzle(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza);
|
const stanza_ids = sizzle(`stanza-id[xmlns="${Strophe.NS.SID}"]`, stanza);
|
||||||
if (stanza_ids.length) {
|
if (stanza_ids.length) {
|
||||||
@ -659,18 +660,17 @@ converse.plugins.add('converse-chatboxes', {
|
|||||||
return !_.isNil(sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop());
|
return !_.isNil(sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a passed in message stanza and returns an object
|
||||||
|
* of attributes.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBox#getMessageAttributesFromStanza
|
||||||
|
* @param { XMLElement } stanza - The message stanza
|
||||||
|
* @param { XMLElement } delay - The <delay> node from the stanza, if there was one.
|
||||||
|
* @param { XMLElement } original_stanza - The original stanza, that contains the
|
||||||
|
* message stanza, if it was contained, otherwise it's the message stanza itself.
|
||||||
|
*/
|
||||||
getMessageAttributesFromStanza (stanza, original_stanza) {
|
getMessageAttributesFromStanza (stanza, original_stanza) {
|
||||||
/* Parses a passed in message stanza and returns an object
|
|
||||||
* of attributes.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - The message stanza
|
|
||||||
* (XMLElement) delay - The <delay> node from the
|
|
||||||
* stanza, if there was one.
|
|
||||||
* (XMLElement) original_stanza - The original stanza,
|
|
||||||
* that contains the message stanza, if it was
|
|
||||||
* contained, otherwise it's the message stanza itself.
|
|
||||||
*/
|
|
||||||
const spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(),
|
const spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(),
|
||||||
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(),
|
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(),
|
||||||
text = _converse.chatboxes.getMessageBody(stanza) || undefined,
|
text = _converse.chatboxes.getMessageBody(stanza) || undefined,
|
||||||
@ -864,13 +864,13 @@ converse.plugins.add('converse-chatboxes', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler method for all incoming single-user chat "message" stanzas.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBox#onMessage
|
||||||
|
* @param { XMLElement } stanza - The incoming message stanza
|
||||||
|
*/
|
||||||
async onMessage (stanza) {
|
async onMessage (stanza) {
|
||||||
/* Handler method for all incoming single-user chat "message"
|
|
||||||
* stanzas.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza - The incoming message stanza
|
|
||||||
*/
|
|
||||||
let to_jid = stanza.getAttribute('to');
|
let to_jid = stanza.getAttribute('to');
|
||||||
const to_resource = Strophe.getResourceFromJid(to_jid);
|
const to_resource = Strophe.getResourceFromJid(to_jid);
|
||||||
|
|
||||||
@ -970,15 +970,16 @@ converse.plugins.add('converse-chatboxes', {
|
|||||||
_converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': chatbox});
|
_converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': chatbox});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a chat box or optionally return a newly
|
||||||
|
* created one if one doesn't exist.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatBox#getChatBox
|
||||||
|
* @param { string } jid - The JID of the user whose chat box we want
|
||||||
|
* @param { boolean } create - Should a new chat box be created if none exists?
|
||||||
|
* @param { object } attrs - Optional chat box atributes.
|
||||||
|
*/
|
||||||
getChatBox (jid, attrs={}, create) {
|
getChatBox (jid, attrs={}, create) {
|
||||||
/* Returns a chat box or optionally return a newly
|
|
||||||
* created one if one doesn't exist.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The JID of the user whose chat box we want
|
|
||||||
* (Boolean) create - Should a new chat box be created if none exists?
|
|
||||||
* (Object) attrs - Optional chat box atributes.
|
|
||||||
*/
|
|
||||||
if (_.isObject(jid)) {
|
if (_.isObject(jid)) {
|
||||||
create = attrs;
|
create = attrs;
|
||||||
attrs = jid;
|
attrs = jid;
|
||||||
|
@ -225,20 +225,19 @@ _converse.default_settings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs messages to the browser's developer console.
|
||||||
|
* Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
|
||||||
|
* 3 for 'error' and 4 for 'fatal'.
|
||||||
|
* When using the 'error' or 'warn' loglevels, a full stacktrace will be
|
||||||
|
* logged as well.
|
||||||
|
* @method log
|
||||||
|
* @private
|
||||||
|
* @memberOf _converse
|
||||||
|
* @param { string } message - The message to be logged
|
||||||
|
* @param { integer } level - The loglevel which allows for filtering of log messages
|
||||||
|
*/
|
||||||
_converse.log = function (message, level, style='') {
|
_converse.log = function (message, level, style='') {
|
||||||
/* Logs messages to the browser's developer console.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) message - The message to be logged.
|
|
||||||
* (Integer) level - The loglevel which allows for filtering of log
|
|
||||||
* messages.
|
|
||||||
*
|
|
||||||
* Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
|
|
||||||
* 3 for 'error' and 4 for 'fatal'.
|
|
||||||
*
|
|
||||||
* When using the 'error' or 'warn' loglevels, a full stacktrace will be
|
|
||||||
* logged as well.
|
|
||||||
*/
|
|
||||||
if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
|
if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
|
||||||
style = style || 'color: maroon';
|
style = style || 'color: maroon';
|
||||||
}
|
}
|
||||||
@ -275,12 +274,15 @@ Strophe.log = function (level, msg) { _converse.log(level+' '+msg, level); };
|
|||||||
Strophe.error = function (msg) { _converse.log(msg, Strophe.LogLevel.ERROR); };
|
Strophe.error = function (msg) { _converse.log(msg, Strophe.LogLevel.ERROR); };
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate the given string based on the current locale.
|
||||||
|
* Handles all MUC presence stanzas.
|
||||||
|
* @method __
|
||||||
|
* @private
|
||||||
|
* @memberOf _converse
|
||||||
|
* @param { String } str - The string to translate
|
||||||
|
*/
|
||||||
_converse.__ = function (str) {
|
_converse.__ = function (str) {
|
||||||
/* Translate the given string based on the current locale.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) str - The string to translate.
|
|
||||||
*/
|
|
||||||
if (_.isUndefined(i18n)) {
|
if (_.isUndefined(i18n)) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@ -565,12 +567,14 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
|
|
||||||
this.generateResource = () => `/converse.js-${Math.floor(Math.random()*139749528).toString()}`;
|
this.generateResource = () => `/converse.js-${Math.floor(Math.random()*139749528).toString()}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send out a Chat Status Notification (XEP-0352)
|
||||||
|
* @private
|
||||||
|
* @method sendCSI
|
||||||
|
* @memberOf _converse
|
||||||
|
* @param { String } stat - The user's chat status
|
||||||
|
*/
|
||||||
this.sendCSI = function (stat) {
|
this.sendCSI = function (stat) {
|
||||||
/* Send out a Chat Status Notification (XEP-0352)
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) stat: The user's chat status
|
|
||||||
*/
|
|
||||||
_converse.api.send($build(stat, {xmlns: Strophe.NS.CSI}));
|
_converse.api.send($build(stat, {xmlns: Strophe.NS.CSI}));
|
||||||
_converse.inactive = (stat === _converse.INACTIVE) ? true : false;
|
_converse.inactive = (stat === _converse.INACTIVE) ? true : false;
|
||||||
};
|
};
|
||||||
@ -661,14 +665,15 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject or cancel another user's subscription to our presence updates.
|
||||||
|
* @method rejectPresenceSubscription
|
||||||
|
* @private
|
||||||
|
* @memberOf _converse
|
||||||
|
* @param { String } jid - The Jabber ID of the user whose subscription is being canceled
|
||||||
|
* @param { String } message - An optional message to the user
|
||||||
|
*/
|
||||||
this.rejectPresenceSubscription = function (jid, message) {
|
this.rejectPresenceSubscription = function (jid, message) {
|
||||||
/* Reject or cancel another user's subscription to our presence updates.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The Jabber ID of the user whose subscription
|
|
||||||
* is being canceled.
|
|
||||||
* (String) message - An optional message to the user
|
|
||||||
*/
|
|
||||||
const pres = $pres({to: jid, type: "unsubscribed"});
|
const pres = $pres({to: jid, type: "unsubscribed"});
|
||||||
if (message && message !== "") { pres.c("status").t(message); }
|
if (message && message !== "") { pres.c("status").t(message); }
|
||||||
_converse.api.send(pres);
|
_converse.api.send(pres);
|
||||||
|
@ -23,6 +23,11 @@ converse.plugins.add('converse-disco', {
|
|||||||
_converse.api.promises.add('discoInitialized');
|
_converse.api.promises.add('discoInitialized');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @namespace _converse.DiscoEntity
|
||||||
|
* @memberOf _converse
|
||||||
|
*/
|
||||||
_converse.DiscoEntity = Backbone.Model.extend({
|
_converse.DiscoEntity = Backbone.Model.extend({
|
||||||
/* A Disco Entity is a JID addressable entity that can be queried
|
/* A Disco Entity is a JID addressable entity that can be queried
|
||||||
* for features.
|
* for features.
|
||||||
@ -64,14 +69,15 @@ converse.plugins.add('converse-disco', {
|
|||||||
this.items.fetch();
|
this.items.fetch();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise which resolves with a map indicating
|
||||||
|
* whether a given identity is provided by this entity.
|
||||||
|
* @private
|
||||||
|
* @method _converse.DiscoEntity#getIdentity
|
||||||
|
* @param { String } category - The identity category
|
||||||
|
* @param { String } type - The identity type
|
||||||
|
*/
|
||||||
async getIdentity (category, type) {
|
async getIdentity (category, type) {
|
||||||
/* Returns a Promise which resolves with a map indicating
|
|
||||||
* whether a given identity is provided by this entity.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) category - The identity category
|
|
||||||
* (String) type - The identity type
|
|
||||||
*/
|
|
||||||
await this.waitUntilFeaturesDiscovered;
|
await this.waitUntilFeaturesDiscovered;
|
||||||
return this.identities.findWhere({
|
return this.identities.findWhere({
|
||||||
'category': category,
|
'category': category,
|
||||||
@ -79,13 +85,14 @@ converse.plugins.add('converse-disco', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise which resolves with a map indicating
|
||||||
|
* whether a given feature is supported.
|
||||||
|
* @private
|
||||||
|
* @method _converse.DiscoEntity#hasFeature
|
||||||
|
* @param { String } feature - The feature that might be supported.
|
||||||
|
*/
|
||||||
async hasFeature (feature) {
|
async hasFeature (feature) {
|
||||||
/* Returns a Promise which resolves with a map indicating
|
|
||||||
* whether a given feature is supported.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) feature - The feature that might be supported.
|
|
||||||
*/
|
|
||||||
await this.waitUntilFeaturesDiscovered
|
await this.waitUntilFeaturesDiscovered
|
||||||
if (this.features.findWhere({'var': feature})) {
|
if (this.features.findWhere({'var': feature})) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -291,14 +291,14 @@ converse.plugins.add('converse-muc', {
|
|||||||
return this.get('name') || this.get('jid');
|
return this.get('name') || this.get('jid');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join the groupchat.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#join
|
||||||
|
* @param { String } nick - The user's nickname
|
||||||
|
* @param { String } password - Optional password, if required by the groupchat.
|
||||||
|
*/
|
||||||
join (nick, password) {
|
join (nick, password) {
|
||||||
/* Join the groupchat.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) nick: The user's nickname
|
|
||||||
* (String) password: Optional password, if required by
|
|
||||||
* the groupchat.
|
|
||||||
*/
|
|
||||||
nick = nick ? nick : this.get('nick');
|
nick = nick ? nick : this.get('nick');
|
||||||
if (!nick) {
|
if (!nick) {
|
||||||
throw new TypeError('join: You need to provide a valid nickname');
|
throw new TypeError('join: You need to provide a valid nickname');
|
||||||
@ -322,13 +322,12 @@ converse.plugins.add('converse-muc', {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Leave the groupchat.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#leave
|
||||||
|
* @param { string } exit_msg - Optional message to indicate your reason for leaving
|
||||||
|
*/
|
||||||
leave (exit_msg) {
|
leave (exit_msg) {
|
||||||
/* Leave the groupchat.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) exit_msg: Optional message to indicate your
|
|
||||||
* reason for leaving.
|
|
||||||
*/
|
|
||||||
this.features.destroy();
|
this.features.destroy();
|
||||||
this.occupants.browserStorage._clear();
|
this.occupants.browserStorage._clear();
|
||||||
this.occupants.reset();
|
this.occupants.reset();
|
||||||
@ -479,12 +478,14 @@ converse.plugins.add('converse-muc', {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a direct invitation as per XEP-0249
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#directInvite
|
||||||
|
* @param { String } recipient - JID of the person being invited
|
||||||
|
* @param { String } reason - Optional reason for the invitation
|
||||||
|
*/
|
||||||
directInvite (recipient, reason) {
|
directInvite (recipient, reason) {
|
||||||
/* Send a direct invitation as per XEP-0249
|
|
||||||
* Parameters:
|
|
||||||
* (String) recipient - JID of the person being invited
|
|
||||||
* (String) reason - Optional reason for the invitation
|
|
||||||
*/
|
|
||||||
if (this.features.get('membersonly')) {
|
if (this.features.get('membersonly')) {
|
||||||
// When inviting to a members-only groupchat, we first add
|
// When inviting to a members-only groupchat, we first add
|
||||||
// the person to the member list by giving them an
|
// the person to the member list by giving them an
|
||||||
@ -565,20 +566,17 @@ converse.plugins.add('converse-muc', {
|
|||||||
this.features.save(attrs);
|
this.features.save(attrs);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Send an IQ stanza to the server, asking it for the
|
||||||
|
* member-list of this groupchat.
|
||||||
|
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#requestMemberList
|
||||||
|
* @param { string } affiliation - The specific member list to
|
||||||
|
* fetch. 'admin', 'owner' or 'member'.
|
||||||
|
* @returns:
|
||||||
|
* A promise which resolves once the list has been retrieved.
|
||||||
|
*/
|
||||||
requestMemberList (affiliation) {
|
requestMemberList (affiliation) {
|
||||||
/* Send an IQ stanza to the server, asking it for the
|
|
||||||
* member-list of this groupchat.
|
|
||||||
*
|
|
||||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) affiliation: The specific member list to
|
|
||||||
* fetch. 'admin', 'owner' or 'member'.
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* A promise which resolves once the list has been
|
|
||||||
* retrieved.
|
|
||||||
*/
|
|
||||||
affiliation = affiliation || 'member';
|
affiliation = affiliation || 'member';
|
||||||
const iq = $iq({to: this.get('jid'), type: "get"})
|
const iq = $iq({to: this.get('jid'), type: "get"})
|
||||||
.c("query", {xmlns: Strophe.NS.MUC_ADMIN})
|
.c("query", {xmlns: Strophe.NS.MUC_ADMIN})
|
||||||
@ -586,28 +584,26 @@ converse.plugins.add('converse-muc', {
|
|||||||
return _converse.api.sendIQ(iq);
|
return _converse.api.sendIQ(iq);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send IQ stanzas to the server to set an affiliation for
|
||||||
|
* the provided JIDs.
|
||||||
|
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||||
|
*
|
||||||
|
* Prosody doesn't accept multiple JIDs' affiliations
|
||||||
|
* being set in one IQ stanza, so as a workaround we send
|
||||||
|
* a separate stanza for each JID.
|
||||||
|
* Related ticket: https://issues.prosody.im/345
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#setAffiliation
|
||||||
|
* @param { string } affiliation - The affiliation
|
||||||
|
* @param { object } members - A map of jids, affiliations and
|
||||||
|
* optionally reasons. Only those entries with the
|
||||||
|
* same affiliation as being currently set will be considered.
|
||||||
|
* @returns
|
||||||
|
* A promise which resolves and fails depending on the XMPP server response.
|
||||||
|
*/
|
||||||
setAffiliation (affiliation, members) {
|
setAffiliation (affiliation, members) {
|
||||||
/* Send IQ stanzas to the server to set an affiliation for
|
|
||||||
* the provided JIDs.
|
|
||||||
*
|
|
||||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
|
||||||
*
|
|
||||||
* XXX: Prosody doesn't accept multiple JIDs' affiliations
|
|
||||||
* being set in one IQ stanza, so as a workaround we send
|
|
||||||
* a separate stanza for each JID.
|
|
||||||
* Related ticket: https://issues.prosody.im/345
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) affiliation: The affiliation
|
|
||||||
* (Object) members: A map of jids, affiliations and
|
|
||||||
* optionally reasons. Only those entries with the
|
|
||||||
* same affiliation as being currently set will be
|
|
||||||
* considered.
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* A promise which resolves and fails depending on the
|
|
||||||
* XMPP server response.
|
|
||||||
*/
|
|
||||||
members = _.filter(members, (member) =>
|
members = _.filter(members, (member) =>
|
||||||
// We only want those members who have the right
|
// We only want those members who have the right
|
||||||
// affiliation (or none, which implies the provided one).
|
// affiliation (or none, which implies the provided one).
|
||||||
@ -618,18 +614,19 @@ converse.plugins.add('converse-muc', {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the groupchat configuration form by sending an IQ
|
||||||
|
* stanza to the server.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#saveConfiguration
|
||||||
|
* @param { HTMLElement } form - The configuration form DOM element.
|
||||||
|
* If no form is provided, the default configuration
|
||||||
|
* values will be used.
|
||||||
|
* @returns { promise }
|
||||||
|
* Returns a promise which resolves once the XMPP server
|
||||||
|
* has return a response IQ.
|
||||||
|
*/
|
||||||
saveConfiguration (form) {
|
saveConfiguration (form) {
|
||||||
/* Submit the groupchat configuration form by sending an IQ
|
|
||||||
* stanza to the server.
|
|
||||||
*
|
|
||||||
* Returns a promise which resolves once the XMPP server
|
|
||||||
* has return a response IQ.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) form: The configuration form DOM element.
|
|
||||||
* If no form is provided, the default configuration
|
|
||||||
* values will be used.
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
|
const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
|
||||||
configArray = _.map(inputs, u.webForm2xForm);
|
configArray = _.map(inputs, u.webForm2xForm);
|
||||||
@ -689,20 +686,21 @@ converse.plugins.add('converse-muc', {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an IQ stanza with the groupchat configuration.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#sendConfiguration
|
||||||
|
* @param { Array } config - The groupchat configuration
|
||||||
|
* @param { Function } callback - Callback upon succesful IQ response
|
||||||
|
* The first parameter passed in is IQ containing the
|
||||||
|
* groupchat configuration.
|
||||||
|
* The second is the response IQ from the server.
|
||||||
|
* @param { Function } errback - Callback upon error IQ response
|
||||||
|
* The first parameter passed in is IQ containing the
|
||||||
|
* groupchat configuration.
|
||||||
|
* The second is the response IQ from the server.
|
||||||
|
*/
|
||||||
sendConfiguration (config, callback, errback) {
|
sendConfiguration (config, callback, errback) {
|
||||||
/* Send an IQ stanza with the groupchat configuration.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Array) config: The groupchat configuration
|
|
||||||
* (Function) callback: Callback upon succesful IQ response
|
|
||||||
* The first parameter passed in is IQ containing the
|
|
||||||
* groupchat configuration.
|
|
||||||
* The second is the response IQ from the server.
|
|
||||||
* (Function) errback: Callback upon error IQ response
|
|
||||||
* The first parameter passed in is IQ containing the
|
|
||||||
* groupchat configuration.
|
|
||||||
* The second is the response IQ from the server.
|
|
||||||
*/
|
|
||||||
const iq = $iq({to: this.get('jid'), type: "set"})
|
const iq = $iq({to: this.get('jid'), type: "set"})
|
||||||
.c("query", {xmlns: Strophe.NS.MUC_OWNER})
|
.c("query", {xmlns: Strophe.NS.MUC_OWNER})
|
||||||
.c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
|
.c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
|
||||||
@ -712,13 +710,13 @@ converse.plugins.add('converse-muc', {
|
|||||||
return _converse.api.sendIQ(iq).then(callback).catch(errback);
|
return _converse.api.sendIQ(iq).then(callback).catch(errback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the presence stanza for the current user's affiliation.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#saveAffiliationAndRole
|
||||||
|
* @param { XMLElement } pres - A <presence> stanza.
|
||||||
|
*/
|
||||||
saveAffiliationAndRole (pres) {
|
saveAffiliationAndRole (pres) {
|
||||||
/* Parse the presence stanza for the current user's
|
|
||||||
* affiliation.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: A <presence> stanza.
|
|
||||||
*/
|
|
||||||
const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop();
|
const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop();
|
||||||
const is_self = pres.querySelector("status[code='110']");
|
const is_self = pres.querySelector("status[code='110']");
|
||||||
if (is_self && !_.isNil(item)) {
|
if (is_self && !_.isNil(item)) {
|
||||||
@ -733,15 +731,16 @@ converse.plugins.add('converse-muc', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an IQ stanza specifying an affiliation change.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#
|
||||||
|
* @param { String } affiliation: affiliation
|
||||||
|
* (could also be stored on the member object).
|
||||||
|
* @param { Object } member: Map containing the member's jid and
|
||||||
|
* optionally a reason and affiliation.
|
||||||
|
*/
|
||||||
sendAffiliationIQ (affiliation, member) {
|
sendAffiliationIQ (affiliation, member) {
|
||||||
/* Send an IQ stanza specifying an affiliation change.
|
|
||||||
*
|
|
||||||
* Paremeters:
|
|
||||||
* (String) affiliation: affiliation (could also be stored
|
|
||||||
* on the member object).
|
|
||||||
* (Object) member: Map containing the member's jid and
|
|
||||||
* optionally a reason and affiliation.
|
|
||||||
*/
|
|
||||||
const iq = $iq({to: this.get('jid'), type: "set"})
|
const iq = $iq({to: this.get('jid'), type: "set"})
|
||||||
.c("query", {xmlns: Strophe.NS.MUC_ADMIN})
|
.c("query", {xmlns: Strophe.NS.MUC_ADMIN})
|
||||||
.c("item", {
|
.c("item", {
|
||||||
@ -755,17 +754,17 @@ converse.plugins.add('converse-muc', {
|
|||||||
return _converse.api.sendIQ(iq);
|
return _converse.api.sendIQ(iq);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send IQ stanzas to the server to modify the
|
||||||
|
* affiliations in this groupchat.
|
||||||
|
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#setAffiliations
|
||||||
|
* @param { object } members - A map of jids, affiliations and optionally reasons
|
||||||
|
* @param { function } onSuccess - callback for a succesful response
|
||||||
|
* @param { function } onError - callback for an error response
|
||||||
|
*/
|
||||||
setAffiliations (members) {
|
setAffiliations (members) {
|
||||||
/* Send IQ stanzas to the server to modify the
|
|
||||||
* affiliations in this groupchat.
|
|
||||||
*
|
|
||||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) members: A map of jids, affiliations and optionally reasons
|
|
||||||
* (Function) onSuccess: callback for a succesful response
|
|
||||||
* (Function) onError: callback for an error response
|
|
||||||
*/
|
|
||||||
const affiliations = _.uniq(_.map(members, 'affiliation'));
|
const affiliations = _.uniq(_.map(members, 'affiliation'));
|
||||||
return Promise.all(_.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members)));
|
return Promise.all(_.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members)));
|
||||||
},
|
},
|
||||||
@ -787,39 +786,39 @@ converse.plugins.add('converse-muc', {
|
|||||||
return [].concat.apply([], result).filter(p => p);
|
return [].concat.apply([], result).filter(p => p);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the lists of users with the given affiliations.
|
||||||
|
* Then compute the delta between those users and
|
||||||
|
* the passed in members, and if it exists, send the delta
|
||||||
|
* to the XMPP server to update the member list.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#updateMemberLists
|
||||||
|
* @param { object } members - Map of member jids and affiliations.
|
||||||
|
* @param { string|array } affiliation - An array of affiliations or
|
||||||
|
* a string if only one affiliation.
|
||||||
|
* @param { function } deltaFunc - The function to compute the delta
|
||||||
|
* between old and new member lists.
|
||||||
|
* @returns { promise }
|
||||||
|
* A promise which is resolved once the list has been
|
||||||
|
* updated or once it's been established there's no need
|
||||||
|
* to update the list.
|
||||||
|
*/
|
||||||
updateMemberLists (members, affiliations, deltaFunc) {
|
updateMemberLists (members, affiliations, deltaFunc) {
|
||||||
/* Fetch the lists of users with the given affiliations.
|
|
||||||
* Then compute the delta between those users and
|
|
||||||
* the passed in members, and if it exists, send the delta
|
|
||||||
* to the XMPP server to update the member list.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) members: Map of member jids and affiliations.
|
|
||||||
* (String|Array) affiliation: An array of affiliations or
|
|
||||||
* a string if only one affiliation.
|
|
||||||
* (Function) deltaFunc: The function to compute the delta
|
|
||||||
* between old and new member lists.
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* A promise which is resolved once the list has been
|
|
||||||
* updated or once it's been established there's no need
|
|
||||||
* to update the list.
|
|
||||||
*/
|
|
||||||
this.getJidsWithAffiliations(affiliations)
|
this.getJidsWithAffiliations(affiliations)
|
||||||
.then(old_members => this.setAffiliations(deltaFunc(members, old_members)))
|
.then(old_members => this.setAffiliations(deltaFunc(members, old_members)))
|
||||||
.then(() => this.occupants.fetchMembers())
|
.then(() => this.occupants.fetchMembers())
|
||||||
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use service-discovery to ask the XMPP server whether
|
||||||
|
* this user has a reserved nickname for this groupchat.
|
||||||
|
* If so, we'll use that, otherwise we render the nickname form.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#checkForReservedNick
|
||||||
|
* @returns { promise } A promise which resolves with the response IQ
|
||||||
|
*/
|
||||||
async checkForReservedNick () {
|
async checkForReservedNick () {
|
||||||
/* Use service-discovery to ask the XMPP server whether
|
|
||||||
* this user has a reserved nickname for this groupchat.
|
|
||||||
* If so, we'll use that, otherwise we render the nickname form.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Function) callback: Callback upon succesful IQ response
|
|
||||||
* (Function) errback: Callback upon error IQ response
|
|
||||||
*/
|
|
||||||
const iq = await _converse.api.sendIQ(
|
const iq = await _converse.api.sendIQ(
|
||||||
$iq({
|
$iq({
|
||||||
'to': this.get('jid'),
|
'to': this.get('jid'),
|
||||||
@ -887,13 +886,14 @@ converse.plugins.add('converse-muc', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a presence stanza, update the occupant model
|
||||||
|
* based on its contents.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#updateOccupantsOnPresence
|
||||||
|
* @param { XMLElement } pres - The presence stanza
|
||||||
|
*/
|
||||||
updateOccupantsOnPresence (pres) {
|
updateOccupantsOnPresence (pres) {
|
||||||
/* Given a presence stanza, update the occupant model
|
|
||||||
* based on its contents.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The presence stanza
|
|
||||||
*/
|
|
||||||
const data = this.parsePresence(pres);
|
const data = this.parsePresence(pres);
|
||||||
if (data.type === 'error' || (!data.jid && !data.nick)) {
|
if (data.type === 'error' || (!data.jid && !data.nick)) {
|
||||||
return true;
|
return true;
|
||||||
@ -989,12 +989,13 @@ converse.plugins.add('converse-muc', {
|
|||||||
acknowledged[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length > 0;
|
acknowledged[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a subject change and return `true` if so.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#subjectChangeHandled
|
||||||
|
* @param { object } attrs - The message attributes
|
||||||
|
*/
|
||||||
subjectChangeHandled (attrs) {
|
subjectChangeHandled (attrs) {
|
||||||
/* Handle a subject change and return `true` if so.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) attrs: The message attributes
|
|
||||||
*/
|
|
||||||
if (attrs.subject && !attrs.thread && !attrs.message) {
|
if (attrs.subject && !attrs.thread && !attrs.message) {
|
||||||
// https://xmpp.org/extensions/xep-0045.html#subject-mod
|
// https://xmpp.org/extensions/xep-0045.html#subject-mod
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
@ -1007,13 +1008,14 @@ converse.plugins.add('converse-muc', {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a chat state notification that can be ignored,
|
||||||
|
* because it's old or because it's from us.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#ignorableCSN
|
||||||
|
* @param { Object } attrs - The message attributes
|
||||||
|
*/
|
||||||
ignorableCSN (attrs) {
|
ignorableCSN (attrs) {
|
||||||
/* Is this a chat state notification that can be ignored,
|
|
||||||
* because it's old or because it's from us.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Object) attrs: The message attributes
|
|
||||||
*/
|
|
||||||
const is_csn = u.isOnlyChatStateNotification(attrs),
|
const is_csn = u.isOnlyChatStateNotification(attrs),
|
||||||
own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick');
|
own_message = Strophe.getResourceFromJid(attrs.from) == this.get('nick');
|
||||||
return is_csn && (attrs.is_delayed || own_message);
|
return is_csn && (attrs.is_delayed || own_message);
|
||||||
@ -1038,12 +1040,13 @@ converse.plugins.add('converse-muc', {
|
|||||||
return attrs;
|
return attrs;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for all MUC messages sent to this groupchat.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#onMessage
|
||||||
|
* @param { XMLElement } stanza - The message stanza.
|
||||||
|
*/
|
||||||
async onMessage (stanza) {
|
async onMessage (stanza) {
|
||||||
/* Handler for all MUC messages sent to this groupchat.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza: The message stanza.
|
|
||||||
*/
|
|
||||||
this.fetchFeaturesIfConfigurationChanged(stanza);
|
this.fetchFeaturesIfConfigurationChanged(stanza);
|
||||||
|
|
||||||
const original_stanza = stanza,
|
const original_stanza = stanza,
|
||||||
@ -1076,12 +1079,13 @@ converse.plugins.add('converse-muc', {
|
|||||||
_converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': this});
|
_converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': this});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all MUC presence stanzas.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#onPresence
|
||||||
|
* @param { XMLElement } pres - The stanza
|
||||||
|
*/
|
||||||
onPresence (pres) {
|
onPresence (pres) {
|
||||||
/* Handles all MUC presence stanzas.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The stanza
|
|
||||||
*/
|
|
||||||
if (pres.getAttribute('type') === 'error') {
|
if (pres.getAttribute('type') === 'error') {
|
||||||
this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
||||||
return;
|
return;
|
||||||
@ -1096,22 +1100,22 @@ converse.plugins.add('converse-muc', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a received presence relating to the current user.
|
||||||
|
*
|
||||||
|
* For locked groupchats (which are by definition "new"), the
|
||||||
|
* groupchat will either be auto-configured or created instantly
|
||||||
|
* (with default config) or a configuration groupchat will be
|
||||||
|
* rendered.
|
||||||
|
*
|
||||||
|
* If the groupchat is not locked, then the groupchat will be
|
||||||
|
* auto-configured only if applicable and if the current
|
||||||
|
* user is the groupchat's owner.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#onOwnPresence
|
||||||
|
* @param { XMLElement } pres - The stanza
|
||||||
|
*/
|
||||||
onOwnPresence (pres) {
|
onOwnPresence (pres) {
|
||||||
/* Handles a received presence relating to the current
|
|
||||||
* user.
|
|
||||||
*
|
|
||||||
* For locked groupchats (which are by definition "new"), the
|
|
||||||
* groupchat will either be auto-configured or created instantly
|
|
||||||
* (with default config) or a configuration groupchat will be
|
|
||||||
* rendered.
|
|
||||||
*
|
|
||||||
* If the groupchat is not locked, then the groupchat will be
|
|
||||||
* auto-configured only if applicable and if the current
|
|
||||||
* user is the groupchat's owner.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The stanza
|
|
||||||
*/
|
|
||||||
this.saveAffiliationAndRole(pres);
|
this.saveAffiliationAndRole(pres);
|
||||||
|
|
||||||
const locked_room = pres.querySelector("status[code='201']");
|
const locked_room = pres.querySelector("status[code='201']");
|
||||||
@ -1148,13 +1152,14 @@ converse.plugins.add('converse-muc', {
|
|||||||
this.save('connection_status', converse.ROOMSTATUS.ENTERED);
|
this.save('connection_status', converse.ROOMSTATUS.ENTERED);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean to indicate whether the current user
|
||||||
|
* was mentioned in a message.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#isUserMentioned
|
||||||
|
* @param { String } - The text message
|
||||||
|
*/
|
||||||
isUserMentioned (message) {
|
isUserMentioned (message) {
|
||||||
/* Returns a boolean to indicate whether the current user
|
|
||||||
* was mentioned in a message.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String): The text message
|
|
||||||
*/
|
|
||||||
const nick = this.get('nick');
|
const nick = this.get('nick');
|
||||||
if (message.get('references').length) {
|
if (message.get('references').length) {
|
||||||
const mentions = message.get('references').filter(ref => (ref.type === 'mention')).map(ref => ref.value);
|
const mentions = message.get('references').filter(ref => (ref.type === 'mention')).map(ref => ref.value);
|
||||||
@ -1164,13 +1169,12 @@ converse.plugins.add('converse-muc', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Given a newly received message, update the unread counter if necessary.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#incrementUnreadMsgCounter
|
||||||
|
* @param { XMLElement } - The <messsage> stanza
|
||||||
|
*/
|
||||||
incrementUnreadMsgCounter (message) {
|
incrementUnreadMsgCounter (message) {
|
||||||
/* Given a newly received message, update the unread counter if
|
|
||||||
* necessary.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement): The <messsage> stanza
|
|
||||||
*/
|
|
||||||
if (!message) { return; }
|
if (!message) { return; }
|
||||||
const body = message.get('message');
|
const body = message.get('message');
|
||||||
if (_.isNil(body)) { return; }
|
if (_.isNil(body)) { return; }
|
||||||
@ -1311,14 +1315,14 @@ converse.plugins.add('converse-muc', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A direct MUC invitation to join a groupchat has been received
|
||||||
|
* See XEP-0249: Direct MUC invitations.
|
||||||
|
* @private
|
||||||
|
* @method _converse.ChatRoom#onDirectMUCInvitation
|
||||||
|
* @param { XMLElement } message - The message stanza containing the invitation.
|
||||||
|
*/
|
||||||
_converse.onDirectMUCInvitation = function (message) {
|
_converse.onDirectMUCInvitation = function (message) {
|
||||||
/* A direct MUC invitation to join a groupchat has been received
|
|
||||||
* See XEP-0249: Direct MUC invitations.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) message: The message stanza containing the
|
|
||||||
* invitation.
|
|
||||||
*/
|
|
||||||
const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
|
const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
|
||||||
from = Strophe.getBareJidFromJid(message.getAttribute('from')),
|
from = Strophe.getBareJidFromJid(message.getAttribute('from')),
|
||||||
room_jid = x_el.getAttribute('jid'),
|
room_jid = x_el.getAttribute('jid'),
|
||||||
|
@ -45,10 +45,13 @@ converse.plugins.add('converse-roster', {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Bakcbone collections that represent the contats
|
||||||
|
* roster and the roster groups.
|
||||||
|
* @private
|
||||||
|
* @method _converse.initRoster
|
||||||
|
*/
|
||||||
_converse.initRoster = function () {
|
_converse.initRoster = function () {
|
||||||
/* Initialize the Bakcbone collections that represent the contats
|
|
||||||
* roster and the roster groups.
|
|
||||||
*/
|
|
||||||
const storage = _converse.config.get('storage');
|
const storage = _converse.config.get('storage');
|
||||||
_converse.roster = new _converse.RosterContacts();
|
_converse.roster = new _converse.RosterContacts();
|
||||||
_converse.roster.browserStorage = new Backbone.BrowserStorage[storage](
|
_converse.roster.browserStorage = new Backbone.BrowserStorage[storage](
|
||||||
@ -75,15 +78,16 @@ converse.plugins.add('converse-roster', {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all the roster groups, and then the roster contacts.
|
||||||
|
* Emit an event after fetching is done in each case.
|
||||||
|
* @private
|
||||||
|
* @method _converse.populateRoster
|
||||||
|
* @param { Bool } ignore_cache - If set to to true, the local cache
|
||||||
|
* will be ignored it's guaranteed that the XMPP server
|
||||||
|
* will be queried for the roster.
|
||||||
|
*/
|
||||||
_converse.populateRoster = async function (ignore_cache=false) {
|
_converse.populateRoster = async function (ignore_cache=false) {
|
||||||
/* Fetch all the roster groups, and then the roster contacts.
|
|
||||||
* Emit an event after fetching is done in each case.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Bool) ignore_cache - If set to to true, the local cache
|
|
||||||
* will be ignored it's guaranteed that the XMPP server
|
|
||||||
* will be queried for the roster.
|
|
||||||
*/
|
|
||||||
if (ignore_cache) {
|
if (ignore_cache) {
|
||||||
_converse.send_initial_presence = true;
|
_converse.send_initial_presence = true;
|
||||||
try {
|
try {
|
||||||
@ -272,13 +276,14 @@ converse.plugins.add('converse-roster', {
|
|||||||
return this.vcard.get('fullname');
|
return this.vcard.get('fullname');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a presence subscription request to this roster contact
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#subscribe
|
||||||
|
* @param { String } message - An optional message to explain the
|
||||||
|
* reason for the subscription request.
|
||||||
|
*/
|
||||||
subscribe (message) {
|
subscribe (message) {
|
||||||
/* Send a presence subscription request to this roster contact
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) message - An optional message to explain the
|
|
||||||
* reason for the subscription request.
|
|
||||||
*/
|
|
||||||
const pres = $pres({to: this.get('jid'), type: "subscribe"});
|
const pres = $pres({to: this.get('jid'), type: "subscribe"});
|
||||||
if (message && message !== "") {
|
if (message && message !== "") {
|
||||||
pres.c("status").t(message).up();
|
pres.c("status").t(message).up();
|
||||||
@ -292,46 +297,55 @@ converse.plugins.add('converse-roster', {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upon receiving the presence stanza of type "subscribed",
|
||||||
|
* the user SHOULD acknowledge receipt of that subscription
|
||||||
|
* state notification by sending a presence stanza of type
|
||||||
|
* "subscribe" to the contact
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#ackSubscribe
|
||||||
|
*/
|
||||||
ackSubscribe () {
|
ackSubscribe () {
|
||||||
/* Upon receiving the presence stanza of type "subscribed",
|
|
||||||
* the user SHOULD acknowledge receipt of that subscription
|
|
||||||
* state notification by sending a presence stanza of type
|
|
||||||
* "subscribe" to the contact
|
|
||||||
*/
|
|
||||||
_converse.api.send($pres({
|
_converse.api.send($pres({
|
||||||
'type': 'subscribe',
|
'type': 'subscribe',
|
||||||
'to': this.get('jid')
|
'to': this.get('jid')
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upon receiving the presence stanza of type "unsubscribed",
|
||||||
|
* the user SHOULD acknowledge receipt of that subscription state
|
||||||
|
* notification by sending a presence stanza of type "unsubscribe"
|
||||||
|
* this step lets the user's server know that it MUST no longer
|
||||||
|
* send notification of the subscription state change to the user.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#ackUnsubscribe
|
||||||
|
* @param { String } jid - The Jabber ID of the user who is unsubscribing
|
||||||
|
*/
|
||||||
ackUnsubscribe () {
|
ackUnsubscribe () {
|
||||||
/* Upon receiving the presence stanza of type "unsubscribed",
|
|
||||||
* the user SHOULD acknowledge receipt of that subscription state
|
|
||||||
* notification by sending a presence stanza of type "unsubscribe"
|
|
||||||
* this step lets the user's server know that it MUST no longer
|
|
||||||
* send notification of the subscription state change to the user.
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The Jabber ID of the user who is unsubscribing
|
|
||||||
*/
|
|
||||||
_converse.api.send($pres({'type': 'unsubscribe', 'to': this.get('jid')}));
|
_converse.api.send($pres({'type': 'unsubscribe', 'to': this.get('jid')}));
|
||||||
this.removeFromRoster();
|
this.removeFromRoster();
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unauthorize this contact's presence subscription
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#unauthorize
|
||||||
|
* @param { String } message - Optional message to send to the person being unauthorized
|
||||||
|
*/
|
||||||
unauthorize (message) {
|
unauthorize (message) {
|
||||||
/* Unauthorize this contact's presence subscription
|
|
||||||
* Parameters:
|
|
||||||
* (String) message - Optional message to send to the person being unauthorized
|
|
||||||
*/
|
|
||||||
_converse.rejectPresenceSubscription(this.get('jid'), message);
|
_converse.rejectPresenceSubscription(this.get('jid'), message);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize presence subscription
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#authorize
|
||||||
|
* @param { String } message - Optional message to send to the person being authorized
|
||||||
|
*/
|
||||||
authorize (message) {
|
authorize (message) {
|
||||||
/* Authorize presence subscription
|
|
||||||
* Parameters:
|
|
||||||
* (String) message - Optional message to send to the person being authorized
|
|
||||||
*/
|
|
||||||
const pres = $pres({'to': this.get('jid'), 'type': "subscribed"});
|
const pres = $pres({'to': this.get('jid'), 'type': "subscribed"});
|
||||||
if (message && message !== "") {
|
if (message && message !== "") {
|
||||||
pres.c("status").t(message);
|
pres.c("status").t(message);
|
||||||
@ -340,11 +354,13 @@ converse.plugins.add('converse-roster', {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instruct the XMPP server to remove this contact from our roster
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#
|
||||||
|
* @returns { Promise }
|
||||||
|
*/
|
||||||
removeFromRoster () {
|
removeFromRoster () {
|
||||||
/* Instruct the XMPP server to remove this contact from our roster
|
|
||||||
* Parameters:
|
|
||||||
* (Function) callback
|
|
||||||
*/
|
|
||||||
const iq = $iq({type: 'set'})
|
const iq = $iq({type: 'set'})
|
||||||
.c('query', {xmlns: Strophe.NS.ROSTER})
|
.c('query', {xmlns: Strophe.NS.ROSTER})
|
||||||
.c('item', {jid: this.get('jid'), subscription: "remove"});
|
.c('item', {jid: this.get('jid'), subscription: "remove"});
|
||||||
@ -352,7 +368,11 @@ converse.plugins.add('converse-roster', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @namespace _converse.RosterContacts
|
||||||
|
* @memberOf _converse
|
||||||
|
*/
|
||||||
_converse.RosterContacts = Backbone.Collection.extend({
|
_converse.RosterContacts = Backbone.Collection.extend({
|
||||||
model: _converse.RosterContact,
|
model: _converse.RosterContact,
|
||||||
|
|
||||||
@ -460,17 +480,18 @@ converse.plugins.add('converse-roster', {
|
|||||||
return u.isSameBareJID(jid, _converse.connection.jid);
|
return u.isSameBareJID(jid, _converse.connection.jid);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a roster contact and then once we have confirmation from
|
||||||
|
* the XMPP server we subscribe to that contact's presence updates.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#addAndSubscribe
|
||||||
|
* @param { String } jid - The Jabber ID of the user being added and subscribed to.
|
||||||
|
* @param { String } name - The name of that user
|
||||||
|
* @param { Array.String } groups - Any roster groups the user might belong to
|
||||||
|
* @param { String } message - An optional message to explain the reason for the subscription request.
|
||||||
|
* @param { Object } attributes - Any additional attributes to be stored on the user's model.
|
||||||
|
*/
|
||||||
addAndSubscribe (jid, name, groups, message, attributes) {
|
addAndSubscribe (jid, name, groups, message, attributes) {
|
||||||
/* Add a roster contact and then once we have confirmation from
|
|
||||||
* the XMPP server we subscribe to that contact's presence updates.
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The Jabber ID of the user being added and subscribed to.
|
|
||||||
* (String) name - The name of that user
|
|
||||||
* (Array of Strings) groups - Any roster groups the user might belong to
|
|
||||||
* (String) message - An optional message to explain the
|
|
||||||
* reason for the subscription request.
|
|
||||||
* (Object) attributes - Any additional attributes to be stored on the user's model.
|
|
||||||
*/
|
|
||||||
const handler = (contact) => {
|
const handler = (contact) => {
|
||||||
if (contact instanceof _converse.RosterContact) {
|
if (contact instanceof _converse.RosterContact) {
|
||||||
contact.subscribe(message);
|
contact.subscribe(message);
|
||||||
@ -479,16 +500,17 @@ converse.plugins.add('converse-roster', {
|
|||||||
this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
|
this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an IQ stanza to the XMPP server to add a new roster contact.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#sendContactAddIQ
|
||||||
|
* @param { String } jid - The Jabber ID of the user being added
|
||||||
|
* @param { String } name - The name of that user
|
||||||
|
* @param { Array.String } groups - Any roster groups the user might belong to
|
||||||
|
* @param { Function } callback - A function to call once the IQ is returned
|
||||||
|
* @param { Function } errback - A function to call if an error occurred
|
||||||
|
*/
|
||||||
sendContactAddIQ (jid, name, groups) {
|
sendContactAddIQ (jid, name, groups) {
|
||||||
/* Send an IQ stanza to the XMPP server to add a new roster contact.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The Jabber ID of the user being added
|
|
||||||
* (String) name - The name of that user
|
|
||||||
* (Array of Strings) groups - Any roster groups the user might belong to
|
|
||||||
* (Function) callback - A function to call once the IQ is returned
|
|
||||||
* (Function) errback - A function to call if an error occurred
|
|
||||||
*/
|
|
||||||
name = _.isEmpty(name) ? null : name;
|
name = _.isEmpty(name) ? null : name;
|
||||||
const iq = $iq({'type': 'set'})
|
const iq = $iq({'type': 'set'})
|
||||||
.c('query', {'xmlns': Strophe.NS.ROSTER})
|
.c('query', {'xmlns': Strophe.NS.ROSTER})
|
||||||
@ -497,18 +519,18 @@ converse.plugins.add('converse-roster', {
|
|||||||
return _converse.api.sendIQ(iq);
|
return _converse.api.sendIQ(iq);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a RosterContact instance to _converse.roster and
|
||||||
|
* registers the contact on the XMPP server.
|
||||||
|
* Returns a promise which is resolved once the XMPP server has responded.
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#addContactToRoster
|
||||||
|
* @param { String } jid - The Jabber ID of the user being added and subscribed to.
|
||||||
|
* @param { String } name - The name of that user
|
||||||
|
* @param { Array.String } groups - Any roster groups the user might belong to
|
||||||
|
* @param { Object } attributes - Any additional attributes to be stored on the user's model.
|
||||||
|
*/
|
||||||
async addContactToRoster (jid, name, groups, attributes) {
|
async addContactToRoster (jid, name, groups, attributes) {
|
||||||
/* Adds a RosterContact instance to _converse.roster and
|
|
||||||
* registers the contact on the XMPP server.
|
|
||||||
* Returns a promise which is resolved once the XMPP server has
|
|
||||||
* responded.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The Jabber ID of the user being added and subscribed to.
|
|
||||||
* (String) name - The name of that user
|
|
||||||
* (Array of Strings) groups - Any roster groups the user might belong to
|
|
||||||
* (Object) attributes - Any additional attributes to be stored on the user's model.
|
|
||||||
*/
|
|
||||||
groups = groups || [];
|
groups = groups || [];
|
||||||
try {
|
try {
|
||||||
await this.sendContactAddIQ(jid, name, groups);
|
await this.sendContactAddIQ(jid, name, groups);
|
||||||
@ -551,13 +573,14 @@ converse.plugins.add('converse-roster', {
|
|||||||
return _.sum(this.models.filter((model) => !_.includes(ignored, model.presence.get('show'))));
|
return _.sum(this.models.filter((model) => !_.includes(ignored, model.presence.get('show'))));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle roster updates from the XMPP server.
|
||||||
|
* See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
|
||||||
|
* @private
|
||||||
|
* @method _converse.RosterContacts#onRosterPush
|
||||||
|
* @param { XMLElement } IQ - The IQ stanza received from the XMPP server.
|
||||||
|
*/
|
||||||
onRosterPush (iq) {
|
onRosterPush (iq) {
|
||||||
/* Handle roster updates from the XMPP server.
|
|
||||||
* See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) IQ - The IQ stanza received from the XMPP server.
|
|
||||||
*/
|
|
||||||
const id = iq.getAttribute('id');
|
const id = iq.getAttribute('id');
|
||||||
const from = iq.getAttribute('from');
|
const from = iq.getAttribute('from');
|
||||||
if (from && from !== _converse.bare_jid) {
|
if (from && from !== _converse.bare_jid) {
|
||||||
|
@ -100,12 +100,6 @@ converse.plugins.add('converse-vcard', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getVCard (_converse, jid) {
|
async function getVCard (_converse, jid) {
|
||||||
/* Request the VCard of another user. Returns a promise.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) jid - The Jabber ID of the user whose VCard
|
|
||||||
* is being requested.
|
|
||||||
*/
|
|
||||||
const to = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
|
const to = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
|
||||||
let iq;
|
let iq;
|
||||||
try {
|
try {
|
||||||
|
941
src/headless/dist/converse-headless.js
vendored
941
src/headless/dist/converse-headless.js
vendored
File diff suppressed because it is too large
Load Diff
@ -44,10 +44,8 @@ import moment from "moment";
|
|||||||
function detectLocale (library_check) {
|
function detectLocale (library_check) {
|
||||||
/* Determine which locale is supported by the user's system as well
|
/* Determine which locale is supported by the user's system as well
|
||||||
* as by the relevant library (e.g. converse.js or moment.js).
|
* as by the relevant library (e.g. converse.js or moment.js).
|
||||||
*
|
* @param { Function } library_check - Returns a boolean indicating whether
|
||||||
* Parameters:
|
* the locale is supported.
|
||||||
* (Function) library_check - Returns a boolean indicating whether
|
|
||||||
* the locale is supported.
|
|
||||||
*/
|
*/
|
||||||
var locale, i;
|
var locale, i;
|
||||||
if (window.navigator.userLanguage) {
|
if (window.navigator.userLanguage) {
|
||||||
@ -87,13 +85,11 @@ function getLocale (preferred_locale, isSupportedByLibrary) {
|
|||||||
return detectLocale(isSupportedByLibrary) || 'en';
|
return detectLocale(isSupportedByLibrary) || 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check whether the locale or sub locale (e.g. en-US, en) is supported.
|
||||||
|
* @param { String } locale - The locale to check for
|
||||||
|
* @param { Function } available - Returns a boolean indicating whether the locale is supported
|
||||||
|
*/
|
||||||
function isLocaleAvailable (locale, available) {
|
function isLocaleAvailable (locale, available) {
|
||||||
/* Check whether the locale or sub locale (e.g. en-US, en) is supported.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) locale - The locale to check for
|
|
||||||
* (Function) available - returns a boolean indicating whether the locale is supported
|
|
||||||
*/
|
|
||||||
if (available(locale)) {
|
if (available(locale)) {
|
||||||
return locale;
|
return locale;
|
||||||
} else {
|
} else {
|
||||||
@ -106,6 +102,9 @@ function isLocaleAvailable (locale, available) {
|
|||||||
|
|
||||||
let jed_instance;
|
let jed_instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace i18n
|
||||||
|
*/
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
setLocales (preferred_locale, _converse) {
|
setLocales (preferred_locale, _converse) {
|
||||||
@ -120,23 +119,23 @@ export default {
|
|||||||
if (_.isNil(jed_instance)) {
|
if (_.isNil(jed_instance)) {
|
||||||
return Jed.sprintf.apply(Jed, arguments);
|
return Jed.sprintf.apply(Jed, arguments);
|
||||||
}
|
}
|
||||||
var t = jed_instance.translate(str);
|
const t = jed_instance.translate(str);
|
||||||
if (arguments.length>1) {
|
if (arguments.length > 1) {
|
||||||
return t.fetch.apply(t, [].slice.call(arguments, 1));
|
return t.fetch.apply(t, [].slice.call(arguments, 1));
|
||||||
} else {
|
} else {
|
||||||
return t.fetch();
|
return t.fetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the translations for the given local at the given URL.
|
||||||
|
* @private
|
||||||
|
* @method i18n#fetchTranslations
|
||||||
|
* @param { String } locale -The given i18n locale
|
||||||
|
* @param { Array } supported_locales - List of locales supported
|
||||||
|
* @param { String } locale_url - The URL from which the translations should be fetched
|
||||||
|
*/
|
||||||
fetchTranslations (locale, supported_locales, locale_url) {
|
fetchTranslations (locale, supported_locales, locale_url) {
|
||||||
/* Fetch the translations for the given local at the given URL.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) locale: The given i18n locale
|
|
||||||
* (Array) supported_locales: List of locales supported
|
|
||||||
* (String) locale_url: The URL from which the translations
|
|
||||||
* should be fetched.
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!isConverseLocale(locale, supported_locales) || locale === 'en') {
|
if (!isConverseLocale(locale, supported_locales) || locale === 'en') {
|
||||||
return resolve();
|
return resolve();
|
||||||
|
@ -14,6 +14,11 @@ import { Strophe } from "strophe.js";
|
|||||||
import _ from "../lodash.noconflict";
|
import _ from "../lodash.noconflict";
|
||||||
import sizzle from "sizzle";
|
import sizzle from "sizzle";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The utils object
|
||||||
|
* @namespace u
|
||||||
|
*/
|
||||||
const u = {};
|
const u = {};
|
||||||
|
|
||||||
u.toStanza = function (string) {
|
u.toStanza = function (string) {
|
||||||
@ -147,14 +152,15 @@ u.applyUserSettings = function applyUserSettings (context, settings, user_settin
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an HTML string into a DOM Node.
|
||||||
|
* Expects that the HTML string has only one top-level element,
|
||||||
|
* i.e. not multiple ones.
|
||||||
|
* @private
|
||||||
|
* @method u#stringToNode
|
||||||
|
* @param { String } s - The HTML string
|
||||||
|
*/
|
||||||
u.stringToNode = function (s) {
|
u.stringToNode = function (s) {
|
||||||
/* Converts an HTML string into a DOM Node.
|
|
||||||
* Expects that the HTML string has only one top-level element,
|
|
||||||
* i.e. not multiple ones.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) s - The HTML string
|
|
||||||
*/
|
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = s;
|
div.innerHTML = s;
|
||||||
return div.firstElementChild;
|
return div.firstElementChild;
|
||||||
@ -170,26 +176,28 @@ u.getOuterWidth = function (el, include_margin=false) {
|
|||||||
return width;
|
return width;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an HTML string into a DOM element.
|
||||||
|
* Expects that the HTML string has only one top-level element,
|
||||||
|
* i.e. not multiple ones.
|
||||||
|
* @private
|
||||||
|
* @method u#stringToElement
|
||||||
|
* @param { String } s - The HTML string
|
||||||
|
*/
|
||||||
u.stringToElement = function (s) {
|
u.stringToElement = function (s) {
|
||||||
/* Converts an HTML string into a DOM element.
|
|
||||||
* Expects that the HTML string has only one top-level element,
|
|
||||||
* i.e. not multiple ones.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) s - The HTML string
|
|
||||||
*/
|
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = s;
|
div.innerHTML = s;
|
||||||
return div.firstElementChild;
|
return div.firstElementChild;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the DOM element matches the given selector.
|
||||||
|
* @private
|
||||||
|
* @method u#matchesSelector
|
||||||
|
* @param { DOMElement } el - The DOM element
|
||||||
|
* @param { String } selector - The selector
|
||||||
|
*/
|
||||||
u.matchesSelector = function (el, selector) {
|
u.matchesSelector = function (el, selector) {
|
||||||
/* Checks whether the DOM element matches the given selector.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (DOMElement) el - The DOM element
|
|
||||||
* (String) selector - The selector
|
|
||||||
*/
|
|
||||||
const match = (
|
const match = (
|
||||||
el.matches ||
|
el.matches ||
|
||||||
el.matchesSelector ||
|
el.matchesSelector ||
|
||||||
@ -201,15 +209,14 @@ u.matchesSelector = function (el, selector) {
|
|||||||
return match ? match.call(el, selector) : false;
|
return match ? match.call(el, selector) : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of children of the DOM element that match the selector.
|
||||||
|
* @private
|
||||||
|
* @method u#queryChildren
|
||||||
|
* @param { DOMElement } el - the DOM element
|
||||||
|
* @param { String } selector - the selector they should be matched against
|
||||||
|
*/
|
||||||
u.queryChildren = function (el, selector) {
|
u.queryChildren = function (el, selector) {
|
||||||
/* Returns a list of children of the DOM element that match the
|
|
||||||
* selector.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (DOMElement) el - the DOM element
|
|
||||||
* (String) selector - the selector they should be matched
|
|
||||||
* against.
|
|
||||||
*/
|
|
||||||
return _.filter(el.childNodes, _.partial(u.matchesSelector, _, selector));
|
return _.filter(el.childNodes, _.partial(u.matchesSelector, _, selector));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -296,16 +303,17 @@ u.interpolate = function (string, o) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the callback once all the events have been triggered
|
||||||
|
* @private
|
||||||
|
* @method u#onMultipleEvents
|
||||||
|
* @param { Array } events: An array of objects, with keys `object` and
|
||||||
|
* `event`, representing the event name and the object it's
|
||||||
|
* triggered upon.
|
||||||
|
* @param { Function } callback: The function to call once all events have
|
||||||
|
* been triggered.
|
||||||
|
*/
|
||||||
u.onMultipleEvents = function (events=[], callback) {
|
u.onMultipleEvents = function (events=[], callback) {
|
||||||
/* Call the callback once all the events have been triggered
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Array) events: An array of objects, with keys `object` and
|
|
||||||
* `event`, representing the event name and the object it's
|
|
||||||
* triggered upon.
|
|
||||||
* (Function) callback: The function to call once all events have
|
|
||||||
* been triggered.
|
|
||||||
*/
|
|
||||||
let triggered = [];
|
let triggered = [];
|
||||||
|
|
||||||
function handler (result) {
|
function handler (result) {
|
||||||
|
@ -10,12 +10,13 @@ import _ from "../lodash.noconflict";
|
|||||||
import tpl_field from "../templates/field.html";
|
import tpl_field from "../templates/field.html";
|
||||||
import u from "./core";
|
import u from "./core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an HTML DOM and turns it into an XForm field.
|
||||||
|
* @private
|
||||||
|
* @method u#webForm2xForm
|
||||||
|
* @param { DOMElement } field - the field to convert
|
||||||
|
*/
|
||||||
u.webForm2xForm = function (field) {
|
u.webForm2xForm = function (field) {
|
||||||
/* Takes an HTML DOM and turns it into an XForm field.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (DOMElement) field - the field to convert
|
|
||||||
*/
|
|
||||||
let value;
|
let value;
|
||||||
if (field.getAttribute('type') === 'checkbox') {
|
if (field.getAttribute('type') === 'checkbox') {
|
||||||
value = field.checked && 1 || 0;
|
value = field.checked && 1 || 0;
|
||||||
|
@ -15,33 +15,34 @@ import u from "./core";
|
|||||||
const { Strophe, sizzle, _ } = converse.env;
|
const { Strophe, sizzle, _ } = converse.env;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two lists of objects with 'jid', 'affiliation' and
|
||||||
|
* 'reason' properties, return a new list containing
|
||||||
|
* those objects that are new, changed or removed
|
||||||
|
* (depending on the 'remove_absentees' boolean).
|
||||||
|
*
|
||||||
|
* The affiliations for new and changed members stay the
|
||||||
|
* same, for removed members, the affiliation is set to 'none'.
|
||||||
|
*
|
||||||
|
* The 'reason' property is not taken into account when
|
||||||
|
* comparing whether affiliations have been changed.
|
||||||
|
* @private
|
||||||
|
* @method u#computeAffiliationsDelta
|
||||||
|
* @param { boolean } exclude_existing - Indicates whether JIDs from
|
||||||
|
* the new list which are also in the old list
|
||||||
|
* (regardless of affiliation) should be excluded
|
||||||
|
* from the delta. One reason to do this
|
||||||
|
* would be when you want to add a JID only if it
|
||||||
|
* doesn't have *any* existing affiliation at all.
|
||||||
|
* @param { boolean } remove_absentees - Indicates whether JIDs
|
||||||
|
* from the old list which are not in the new list
|
||||||
|
* should be considered removed and therefore be
|
||||||
|
* included in the delta with affiliation set
|
||||||
|
* to 'none'.
|
||||||
|
* @param { array } new_list - Array containing the new affiliations
|
||||||
|
* @param { array } old_list - Array containing the old affiliations
|
||||||
|
*/
|
||||||
u.computeAffiliationsDelta = function computeAffiliationsDelta (exclude_existing, remove_absentees, new_list, old_list) {
|
u.computeAffiliationsDelta = function computeAffiliationsDelta (exclude_existing, remove_absentees, new_list, old_list) {
|
||||||
/* Given two lists of objects with 'jid', 'affiliation' and
|
|
||||||
* 'reason' properties, return a new list containing
|
|
||||||
* those objects that are new, changed or removed
|
|
||||||
* (depending on the 'remove_absentees' boolean).
|
|
||||||
*
|
|
||||||
* The affiliations for new and changed members stay the
|
|
||||||
* same, for removed members, the affiliation is set to 'none'.
|
|
||||||
*
|
|
||||||
* The 'reason' property is not taken into account when
|
|
||||||
* comparing whether affiliations have been changed.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Boolean) exclude_existing: Indicates whether JIDs from
|
|
||||||
* the new list which are also in the old list
|
|
||||||
* (regardless of affiliation) should be excluded
|
|
||||||
* from the delta. One reason to do this
|
|
||||||
* would be when you want to add a JID only if it
|
|
||||||
* doesn't have *any* existing affiliation at all.
|
|
||||||
* (Boolean) remove_absentees: Indicates whether JIDs
|
|
||||||
* from the old list which are not in the new list
|
|
||||||
* should be considered removed and therefore be
|
|
||||||
* included in the delta with affiliation set
|
|
||||||
* to 'none'.
|
|
||||||
* (Array) new_list: Array containing the new affiliations
|
|
||||||
* (Array) old_list: Array containing the old affiliations
|
|
||||||
*/
|
|
||||||
const new_jids = _.map(new_list, 'jid');
|
const new_jids = _.map(new_list, 'jid');
|
||||||
const old_jids = _.map(old_list, 'jid');
|
const old_jids = _.map(old_list, 'jid');
|
||||||
|
|
||||||
|
@ -304,13 +304,14 @@ u.nextUntil = function (el, selector, include_self=false) {
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that replace HTML-escaped symbols with equivalent characters
|
||||||
|
* (e.g. transform occurrences of '&' to '&')
|
||||||
|
* @private
|
||||||
|
* @method u#unescapeHTML
|
||||||
|
* @param { String } string - a String containing the HTML-escaped symbols.
|
||||||
|
*/
|
||||||
u.unescapeHTML = function (string) {
|
u.unescapeHTML = function (string) {
|
||||||
/* Helper method that replace HTML-escaped symbols with equivalent characters
|
|
||||||
* (e.g. transform occurrences of '&' to '&')
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) string: a String containing the HTML-escaped symbols.
|
|
||||||
*/
|
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = string;
|
div.innerHTML = string;
|
||||||
return div.innerText;
|
return div.innerText;
|
||||||
@ -381,13 +382,14 @@ u.slideToggleElement = function (el, duration) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/expands an element by sliding it out of itself
|
||||||
|
* @private
|
||||||
|
* @method u#slideOut
|
||||||
|
* @param { HTMLElement } el - The HTML string
|
||||||
|
* @param { Number } duration - The duration amount in milliseconds
|
||||||
|
*/
|
||||||
u.slideOut = function (el, duration=200) {
|
u.slideOut = function (el, duration=200) {
|
||||||
/* Shows/expands an element by sliding it out of itself
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) el - The HTML string
|
|
||||||
* (Number) duration - The duration amount in milliseconds
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (_.isNil(el)) {
|
if (_.isNil(el)) {
|
||||||
const err = "Undefined or null element passed into slideOut"
|
const err = "Undefined or null element passed into slideOut"
|
||||||
@ -528,16 +530,15 @@ u.fadeIn = function (el, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
||||||
|
* and turns it into an HTML field.
|
||||||
|
* Returns either text or a DOM element (which is not ideal, but fine for now).
|
||||||
|
* @private
|
||||||
|
* @method u#xForm2webForm
|
||||||
|
* @param { XMLElement } field - the field to convert
|
||||||
|
*/
|
||||||
u.xForm2webForm = function (field, stanza, domain) {
|
u.xForm2webForm = function (field, stanza, domain) {
|
||||||
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
|
||||||
* and turns it into an HTML field.
|
|
||||||
*
|
|
||||||
* Returns either text or a DOM element (which is not ideal, but fine
|
|
||||||
* for now).
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) field - the field to convert
|
|
||||||
*/
|
|
||||||
if (field.getAttribute('type') === 'list-single' ||
|
if (field.getAttribute('type') === 'list-single' ||
|
||||||
field.getAttribute('type') === 'list-multi') {
|
field.getAttribute('type') === 'list-multi') {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user