123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046 |
- // Converse.js
- // http://conversejs.org
- //
- // Copyright (c) 2013-2018, the Converse.js developers
- // Licensed under the Mozilla Public License (MPLv2)
- (function (root, factory) {
- define([
- "converse-core",
- "formdata-polyfill",
- "utils/muc",
- "xss",
- "templates/add_chatroom_modal.html",
- "templates/chatarea.html",
- "templates/chatroom.html",
- "templates/chatroom_details_modal.html",
- "templates/chatroom_disconnect.html",
- "templates/chatroom_features.html",
- "templates/chatroom_form.html",
- "templates/chatroom_head.html",
- "templates/chatroom_invite.html",
- "templates/chatroom_nickname_form.html",
- "templates/chatroom_password_form.html",
- "templates/chatroom_sidebar.html",
- "templates/info.html",
- "templates/list_chatrooms_modal.html",
- "templates/occupant.html",
- "templates/room_description.html",
- "templates/room_item.html",
- "templates/room_panel.html",
- "templates/rooms_results.html",
- "templates/spinner.html",
- "awesomplete",
- "converse-modal"
- ], factory);
- }(this, function (
- converse,
- _FormData,
- muc_utils,
- xss,
- tpl_add_chatroom_modal,
- tpl_chatarea,
- tpl_chatroom,
- tpl_chatroom_details_modal,
- tpl_chatroom_disconnect,
- tpl_chatroom_features,
- tpl_chatroom_form,
- tpl_chatroom_head,
- tpl_chatroom_invite,
- tpl_chatroom_nickname_form,
- tpl_chatroom_password_form,
- tpl_chatroom_sidebar,
- tpl_info,
- tpl_list_chatrooms_modal,
- tpl_occupant,
- tpl_room_description,
- tpl_room_item,
- tpl_room_panel,
- tpl_rooms_results,
- tpl_spinner,
- Awesomplete
- ) {
- "use strict";
- const { Backbone, Promise, Strophe, b64_sha1, moment, f, sizzle, _, $build, $iq, $msg, $pres } = converse.env;
- const u = converse.env.utils;
- const ROOM_FEATURES_MAP = {
- 'passwordprotected': 'unsecured',
- 'unsecured': 'passwordprotected',
- 'hidden': 'publicroom',
- 'publicroom': 'hidden',
- 'membersonly': 'open',
- 'open': 'membersonly',
- 'persistent': 'temporary',
- 'temporary': 'persistent',
- 'nonanonymous': 'semianonymous',
- 'semianonymous': 'nonanonymous',
- 'moderated': 'unmoderated',
- 'unmoderated': 'moderated'
- };
- converse.plugins.add('converse-muc-views', {
- /* Dependencies are other plugins which might be
- * overridden or relied upon, and therefore need to be loaded before
- * this plugin. They are "optional" because they might not be
- * available, in which case any overrides applicable to them will be
- * ignored.
- *
- * NB: These plugins need to have already been loaded via require.js.
- *
- * It's possible to make these dependencies "non-optional".
- * If the setting "strict_plugin_dependencies" is set to true,
- * an error will be raised if the plugin is not found.
- */
- dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"],
- overrides: {
- ControlBoxView: {
- renderRoomsPanel () {
- const { _converse } = this.__super__;
- this.roomspanel = new _converse.RoomsPanel({
- 'model': new (_converse.RoomsPanelModel.extend({
- 'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage
- 'browserStorage': new Backbone.BrowserStorage[_converse.config.get('storage')](
- b64_sha1(`converse.roomspanel${_converse.bare_jid}`))
- }))()
- });
- this.roomspanel.model.fetch();
- this.el.querySelector('.controlbox-pane').insertAdjacentElement(
- 'beforeEnd', this.roomspanel.render().el);
- if (!this.roomspanel.model.get('nick')) {
- this.roomspanel.model.save({
- nick: _converse.xmppstatus.vcard.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid)
- });
- }
- _converse.emit('roomsPanelRendered');
- },
- renderControlBoxPane () {
- const { _converse } = this.__super__;
- this.__super__.renderControlBoxPane.apply(this, arguments);
- if (_converse.allow_muc) {
- this.renderRoomsPanel();
- }
- },
- }
- },
- initialize () {
- const { _converse } = this,
- { __ } = _converse;
- _converse.api.promises.add(['roomsPanelRendered']);
- // Configuration values for this plugin
- // ====================================
- // Refer to docs/source/configuration.rst for explanations of these
- // configuration settings.
- _converse.api.settings.update({
- 'auto_list_rooms': false,
- 'hide_muc_server': false, // TODO: no longer implemented...
- 'muc_disable_moderator_commands': false,
- 'visible_toolbar_buttons': {
- 'toggle_occupants': true
- }
- });
- function ___ (str) {
- /* This is part of a hack to get gettext to scan strings to be
- * translated. Strings we cannot send to the function above because
- * they require variable interpolation and we don't yet have the
- * variables at scan time.
- *
- * See actionInfoMessages further below.
- */
- return str;
- }
- /* http://xmpp.org/extensions/xep-0045.html
- * ----------------------------------------
- * 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID
- * 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat
- * 102 message Configuration change Inform occupants that groupchat now shows unavailable members
- * 103 message Configuration change Inform occupants that groupchat now does not show unavailable members
- * 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred
- * 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants
- * 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled
- * 171 message Configuration change Inform occupants that groupchat logging is now disabled
- * 172 message Configuration change Inform occupants that the groupchat is now non-anonymous
- * 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous
- * 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous
- * 201 presence Entering a groupchat Inform user that a new groupchat has been created
- * 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick
- * 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat
- * 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname
- * 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat
- * 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change
- * 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member
- * 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown
- */
- _converse.muc = {
- info_messages: {
- 100: __('This groupchat is not anonymous'),
- 102: __('This groupchat now shows unavailable members'),
- 103: __('This groupchat does not show unavailable members'),
- 104: __('The groupchat configuration has changed'),
- 170: __('groupchat logging is now enabled'),
- 171: __('groupchat logging is now disabled'),
- 172: __('This groupchat is now no longer anonymous'),
- 173: __('This groupchat is now semi-anonymous'),
- 174: __('This groupchat is now fully-anonymous'),
- 201: __('A new groupchat has been created')
- },
- disconnect_messages: {
- 301: __('You have been banned from this groupchat'),
- 307: __('You have been kicked from this groupchat'),
- 321: __("You have been removed from this groupchat because of an affiliation change"),
- 322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"),
- 332: __("You have been removed from this groupchat because the service hosting it is being shut down")
- },
- action_info_messages: {
- /* XXX: Note the triple underscore function and not double
- * underscore.
- *
- * This is a hack. We can't pass the strings to __ because we
- * don't yet know what the variable to interpolate is.
- *
- * Triple underscore will just return the string again, but we
- * can then at least tell gettext to scan for it so that these
- * strings are picked up by the translation machinery.
- */
- 301: ___("%1$s has been banned"),
- 303: ___("%1$s's nickname has changed"),
- 307: ___("%1$s has been kicked out"),
- 321: ___("%1$s has been removed because of an affiliation change"),
- 322: ___("%1$s has been removed for not being a member")
- },
- new_nickname_messages: {
- 210: ___('Your nickname has been automatically set to %1$s'),
- 303: ___('Your nickname has been changed to %1$s')
- }
- };
- 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: http://xmpp.org/registrar/disco-features.html
- el.querySelector('span.spinner').remove();
- el.querySelector('a.room-info').classList.add('selected');
- el.insertAdjacentHTML(
- 'beforeEnd',
- tpl_room_description({
- 'jid': stanza.getAttribute('from'),
- 'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'),
- 'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'),
- 'hidden': sizzle('feature[var="muc_hidden"]', stanza).length,
- 'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length,
- 'moderated': sizzle('feature[var="muc_moderated"]', stanza).length,
- 'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length,
- 'open': sizzle('feature[var="muc_open"]', stanza).length,
- 'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length,
- 'persistent': sizzle('feature[var="muc_persistent"]', stanza).length,
- 'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length,
- 'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length,
- 'temporary': sizzle('feature[var="muc_temporary"]', stanza).length,
- 'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length,
- 'label_desc': __('Description:'),
- 'label_jid': __('Groupchat Address (JID):'),
- 'label_occ': __('Participants:'),
- 'label_features': __('Features:'),
- 'label_requires_auth': __('Requires authentication'),
- 'label_hidden': __('Hidden'),
- 'label_requires_invite': __('Requires an invitation'),
- 'label_moderated': __('Moderated'),
- 'label_non_anon': __('Non-anonymous'),
- 'label_open_room': __('Open'),
- 'label_permanent_room': __('Permanent'),
- 'label_public': __('Public'),
- 'label_semi_anon': __('Semi-anonymous'),
- 'label_temp_room': __('Temporary'),
- 'label_unmoderated': __('Unmoderated')
- }));
- }
- function toggleRoomInfo (ev) {
- /* Show/hide extra information about a groupchat in a listing. */
- const parent_el = u.ancestor(ev.target, '.room-item'),
- div_el = parent_el.querySelector('div.room-info');
- if (div_el) {
- u.slideIn(div_el).then(u.removeElement)
- parent_el.querySelector('a.room-info').classList.remove('selected');
- } else {
- parent_el.insertAdjacentHTML('beforeend', tpl_spinner());
- _converse.api.disco.info(ev.target.getAttribute('data-room-jid'), null)
- .then((stanza) => insertRoomInfo(parent_el, stanza))
- .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
- }
- }
- _converse.ListChatRoomsModal = _converse.BootstrapModal.extend({
- events: {
- 'submit form': 'showRooms',
- 'click a.room-info': 'toggleRoomInfo',
- 'change input[name=nick]': 'setNick',
- 'change input[name=server]': 'setDomain',
- 'click .open-room': 'openRoom'
- },
- initialize () {
- _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
- this.model.on('change:muc_domain', this.onDomainChange, this);
- },
- toHTML () {
- return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), {
- 'heading_list_chatrooms': __('Query for Groupchats'),
- 'label_server_address': __('Server address'),
- 'label_query': __('Show groupchats'),
- 'server_placeholder': __('conference.example.org')
- }));
- },
- afterRender () {
- this.el.addEventListener('shown.bs.modal', () => {
- this.el.querySelector('input[name="server"]').focus();
- }, false);
- },
- openRoom (ev) {
- ev.preventDefault();
- const jid = ev.target.getAttribute('data-room-jid');
- const name = ev.target.getAttribute('data-room-name');
- this.modal.hide();
- _converse.api.rooms.open(jid, {'name': name});
- },
- toggleRoomInfo (ev) {
- ev.preventDefault();
- toggleRoomInfo(ev);
- },
- onDomainChange (model) {
- if (_converse.auto_list_rooms) {
- this.updateRoomsList();
- }
- },
- roomStanzaItemToHTMLElement (groupchat) {
- const name = Strophe.unescapeNode(groupchat.getAttribute('name') || groupchat.getAttribute('jid'));
- const div = document.createElement('div');
- div.innerHTML = tpl_room_item({
- 'name': Strophe.xmlunescape(name),
- 'jid': groupchat.getAttribute('jid'),
- 'open_title': __('Click to open this groupchat'),
- 'info_title': __('Show more information on this groupchat')
- });
- return div.firstElementChild;
- },
- removeSpinner () {
- _.each(this.el.querySelectorAll('span.spinner'),
- (el) => el.parentNode.removeChild(el)
- );
- },
- informNoRoomsFound () {
- const chatrooms_el = this.el.querySelector('.available-chatrooms');
- chatrooms_el.innerHTML = tpl_rooms_results({
- 'feedback_text': __('No groupchats found')
- });
- const input_el = this.el.querySelector('input[name="server"]');
- input_el.classList.remove('hidden')
- this.removeSpinner();
- },
- onRoomsFound (iq) {
- /* Handle the IQ stanza returned from the server, containing
- * all its public groupchats.
- */
- const available_chatrooms = this.el.querySelector('.available-chatrooms');
- this.rooms = iq.querySelectorAll('query item');
- if (this.rooms.length) {
- // For translators: %1$s is a variable and will be
- // replaced with the XMPP server name
- available_chatrooms.innerHTML = tpl_rooms_results({
- 'feedback_text': __('Groupchats found:')
- });
- const fragment = document.createDocumentFragment();
- const children = _.reject(_.map(this.rooms, this.roomStanzaItemToHTMLElement), _.isNil)
- _.each(children, (child) => fragment.appendChild(child));
- available_chatrooms.appendChild(fragment);
- this.removeSpinner();
- } else {
- this.informNoRoomsFound();
- }
- return true;
- },
- updateRoomsList () {
- /* Send an IQ stanza to the server asking for all groupchats
- */
- _converse.connection.sendIQ(
- $iq({
- 'to': this.model.get('muc_domain'),
- 'from': _converse.connection.jid,
- 'type': "get"
- }).c("query", {xmlns: Strophe.NS.DISCO_ITEMS}),
- this.onRoomsFound.bind(this),
- this.informNoRoomsFound.bind(this),
- 5000
- );
- },
- showRooms (ev) {
- ev.preventDefault();
- const data = new FormData(ev.target);
- this.model.save('muc_domain', Strophe.getDomainFromJid(data.get('server')));
- this.updateRoomsList();
- },
- setDomain (ev) {
- this.model.save('muc_domain', Strophe.getDomainFromJid(ev.target.value));
- },
- setNick (ev) {
- this.model.save({nick: ev.target.value});
- }
- });
- _converse.AddChatRoomModal = _converse.BootstrapModal.extend({
- events: {
- 'submit form.add-chatroom': 'openChatRoom'
- },
- toHTML () {
- return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), {
- 'heading_new_chatroom': __('Enter a new Groupchat'),
- 'label_room_address': __('Groupchat address'),
- 'label_nickname': __('Optional nickname'),
- 'chatroom_placeholder': __('name@conference.example.org'),
- 'label_join': __('Join'),
- }));
- },
- afterRender () {
- this.el.addEventListener('shown.bs.modal', () => {
- this.el.querySelector('input[name="chatroom"]').focus();
- }, false);
- },
- parseRoomDataFromEvent (form) {
- const data = new FormData(form);
- const jid = data.get('chatroom');
- this.model.save('muc_domain', Strophe.getDomainFromJid(jid));
- return {
- 'jid': jid,
- 'nick': data.get('nickname')
- }
- },
- openChatRoom (ev) {
- ev.preventDefault();
- const data = this.parseRoomDataFromEvent(ev.target);
- if (data.nick === "") {
- // Make sure defaults apply if no nick is provided.
- data.nick = undefined;
- }
- _converse.api.rooms.open(data.jid, data);
- this.modal.hide();
- ev.target.reset();
- }
- });
- _converse.RoomDetailsModal = _converse.BootstrapModal.extend({
- initialize () {
- _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
- this.model.on('change', this.render, this);
- this.model.occupants.on('change', this.render, this);
- },
- toHTML () {
- return tpl_chatroom_details_modal(_.extend(
- this.model.toJSON(), {
- '_': _,
- '__': __,
- 'topic': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})),
- 'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()),
- 'num_occupants': this.model.occupants.length
- })
- );
- }
- });
- _converse.ChatRoomView = _converse.ChatBoxView.extend({
- /* Backbone.NativeView which renders a groupchat, based upon the view
- * for normal one-on-one chat boxes.
- */
- length: 300,
- tagName: 'div',
- className: 'chatbox chatroom hidden',
- is_chatroom: true,
- events: {
- 'change input.fileupload': 'onFileSelection',
- 'click .chat-msg__action-edit': 'onMessageEditButtonClicked',
- 'click .chatbox-navback': 'showControlBox',
- 'click .close-chatbox-button': 'close',
- 'click .configure-chatroom-button': 'getAndRenderConfigurationForm',
- 'click .show-room-details-modal': 'showRoomDetailsModal',
- 'click .hide-occupants': 'hideOccupants',
- 'click .new-msgs-indicator': 'viewUnreadMessages',
- 'click .occupant-nick': 'onOccupantClicked',
- 'click .send-button': 'onFormSubmitted',
- 'click .toggle-call': 'toggleCall',
- 'click .toggle-occupants': 'toggleOccupants',
- 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
- 'click .toggle-smiley': 'toggleEmojiMenu',
- 'click .upload-file': 'toggleFileUpload',
- 'keydown .chat-textarea': 'keyPressed',
- 'keyup .chat-textarea': 'keyUp',
- 'input .chat-textarea': 'inputChanged'
- },
- initialize () {
- this.initDebounced();
- this.model.messages.on('add', this.onMessageAdded, this);
- this.model.messages.on('rendered', this.scrollDown, this);
- this.model.on('change:affiliation', this.renderHeading, this);
- this.model.on('change:connection_status', this.afterConnected, this);
- this.model.on('change:name', this.renderHeading, this);
- this.model.on('change:subject', this.renderHeading, this);
- this.model.on('change:subject', this.setChatRoomSubject, this);
- this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this);
- this.model.on('destroy', this.hide, this);
- this.model.on('show', this.show, this);
- this.model.occupants.on('add', this.showJoinNotification, this);
- this.model.occupants.on('remove', this.showLeaveNotification, this);
- this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this);
- this.model.occupants.on('change:role', this.informOfOccupantsRoleChange, this);
- this.model.occupants.on('change:affiliation', this.informOfOccupantsAffiliationChange, this);
- this.createEmojiPicker();
- this.createOccupantsView();
- this.render().insertIntoDOM();
- this.registerHandlers();
- if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
- const handler = () => {
- if (!u.isPersistableModel(this.model)) {
- // Happens during tests, nothing to do if this
- // is a hanging chatbox (i.e. not in the collection anymore).
- return;
- }
- this.populateAndJoin();
- _converse.emit('chatRoomOpened', this);
- }
- this.model.getRoomFeatures().then(handler, handler);
- } else {
- this.fetchMessages();
- _converse.emit('chatRoomOpened', this);
- }
- },
- render () {
- this.el.setAttribute('id', this.model.get('box_id'));
- this.el.innerHTML = tpl_chatroom();
- this.renderHeading();
- this.renderChatArea();
- this.renderMessageForm();
- this.initAutoComplete();
- if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
- this.showSpinner();
- }
- return this;
- },
- renderHeading () {
- /* Render the heading UI of the groupchat. */
- this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML();
- },
- renderChatArea () {
- /* Render the UI container in which groupchat messages will appear.
- */
- if (_.isNull(this.el.querySelector('.chat-area'))) {
- const container_el = this.el.querySelector('.chatroom-body');
- container_el.insertAdjacentHTML('beforeend', tpl_chatarea({
- 'show_send_button': _converse.show_send_button
- }));
- container_el.insertAdjacentElement('beforeend', this.occupantsview.el);
- this.content = this.el.querySelector('.chat-content');
- this.toggleOccupants(null, true);
- }
- return this;
- },
- initAutoComplete () {
- this.auto_complete = new _converse.AutoComplete(this.el, {
- 'auto_first': true,
- 'auto_evaluate': false,
- 'min_chars': 1,
- 'match_current_word': true,
- 'match_on_tab': true,
- 'list': () => this.model.occupants.map(o => ({'label': o.getDisplayName(), 'value': `@${o.getDisplayName()}`})),
- 'filter': _converse.FILTER_STARTSWITH,
- 'trigger_on_at': true
- });
- this.auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
- },
- keyPressed (ev) {
- if (this.auto_complete.keyPressed(ev)) {
- return;
- }
- return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments);
- },
- keyUp (ev) {
- this.auto_complete.evaluate(ev);
- },
- showRoomDetailsModal (ev) {
- ev.preventDefault();
- if (_.isUndefined(this.model.room_details_modal)) {
- this.model.room_details_modal = new _converse.RoomDetailsModal({'model': this.model});
- }
- this.model.room_details_modal.show(ev);
- },
- showChatStateNotification (message) {
- if (message.get('sender') === 'me') {
- return;
- }
- return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments);
- },
- createOccupantsView () {
- /* Create the ChatRoomOccupantsView Backbone.NativeView
- */
- this.model.occupants.chatroomview = this;
- this.occupantsview = new _converse.ChatRoomOccupantsView({'model': this.model.occupants});
- return this;
- },
- informOfOccupantsAffiliationChange(occupant, changed) {
- const previous_affiliation = occupant._previousAttributes.affiliation,
- current_affiliation = occupant.get('affiliation');
- if (previous_affiliation === 'admin') {
- this.showChatEvent(__("%1$s is no longer an admin of this groupchat", occupant.get('nick')))
- } else if (previous_affiliation === 'owner') {
- this.showChatEvent(__("%1$s is no longer an owner of this groupchat", occupant.get('nick')))
- } else if (previous_affiliation === 'outcast') {
- this.showChatEvent(__("%1$s is no longer banned from this groupchat", occupant.get('nick')))
- }
- if (current_affiliation === 'none' && previous_affiliation === 'member') {
- this.showChatEvent(__("%1$s is no longer a permanent member of this groupchat", occupant.get('nick')))
- } if (current_affiliation === 'member') {
- this.showChatEvent(__("%1$s is now a permanent member of this groupchat", occupant.get('nick')))
- } else if (current_affiliation === 'outcast') {
- this.showChatEvent(__("%1$s has been banned from this groupchat", occupant.get('nick')))
- } else if (current_affiliation === 'admin' || current_affiliation == 'owner') {
- this.showChatEvent(__(`%1$s is now an ${current_affiliation} of this groupchat`, occupant.get('nick')))
- }
- },
- informOfOccupantsRoleChange (occupant, changed) {
- const previous_role = occupant._previousAttributes.role;
- if (previous_role === 'moderator') {
- this.showChatEvent(__("%1$s is no longer a moderator", occupant.get('nick')))
- }
- if (previous_role === 'visitor') {
- this.showChatEvent(__("%1$s has been given a voice again", occupant.get('nick')))
- }
- if (occupant.get('role') === 'visitor') {
- this.showChatEvent(__("%1$s has been muted", occupant.get('nick')))
- }
- if (occupant.get('role') === 'moderator') {
- this.showChatEvent(__("%1$s is now a moderator", occupant.get('nick')))
- }
- },
- generateHeadingHTML () {
- /* Returns the heading HTML to be rendered.
- */
- return tpl_chatroom_head(
- _.extend(this.model.toJSON(), {
- 'Strophe': Strophe,
- 'info_close': __('Close and leave this groupchat'),
- 'info_configure': __('Configure this groupchat'),
- 'info_details': __('Show more details about this groupchat'),
- 'description': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})),
- }));
- },
- afterShown () {
- /* Override from converse-chatview, specifically to avoid
- * the 'active' chat state from being sent out prematurely.
- *
- * This is instead done in `afterConnected` below.
- */
- if (u.isPersistableModel(this.model)) {
- this.model.clearUnreadMsgCounter();
- this.model.save();
- }
- this.occupantsview.setOccupantsHeight();
- this.scrollDown();
- this.renderEmojiPicker();
- },
- show () {
- if (u.isVisible(this.el)) {
- this.focus();
- return;
- }
- // Override from converse-chatview in order to not use
- // "fadeIn", which causes flashing.
- u.showElement(this.el);
- this.afterShown();
- },
- afterConnected () {
- if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
- this.hideSpinner();
- this.setChatState(_converse.ACTIVE);
- this.scrollDown();
- this.focus();
- }
- },
- getToolbarOptions () {
- return _.extend(
- _converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
- {
- 'label_hide_occupants': __('Hide the list of participants'),
- 'show_occupants_toggle': this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants
- }
- );
- },
- close (ev) {
- /* Close this chat box, which implies leaving the groupchat as
- * well.
- */
- this.hide();
- if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
- _converse.router.navigate('');
- }
- this.model.leave();
- _converse.ChatBoxView.prototype.close.apply(this, arguments);
- },
- setOccupantsVisibility () {
- const icon_el = this.el.querySelector('.toggle-occupants');
- if (this.model.get('hidden_occupants')) {
- u.removeClass('fa-angle-double-right', icon_el);
- u.addClass('fa-angle-double-left', icon_el);
- u.addClass('full', this.el.querySelector('.chat-area'));
- u.hideElement(this.el.querySelector('.occupants'));
- } else {
- u.addClass('fa-angle-double-right', icon_el);
- u.removeClass('fa-angle-double-left', icon_el);
- u.removeClass('full', this.el.querySelector('.chat-area'));
- u.removeClass('hidden', this.el.querySelector('.occupants'));
- }
- this.occupantsview.setOccupantsHeight();
- },
- hideOccupants (ev, preserve_state) {
- /* Show or hide the right sidebar containing the chat
- * occupants (and the invite widget).
- */
- if (ev) {
- ev.preventDefault();
- ev.stopPropagation();
- }
- this.model.save({'hidden_occupants': true});
- this.setOccupantsVisibility();
- this.scrollDown();
- },
- toggleOccupants (ev, preserve_state) {
- /* Show or hide the right sidebar containing the chat
- * occupants (and the invite widget).
- */
- if (ev) {
- ev.preventDefault();
- ev.stopPropagation();
- }
- if (!preserve_state) {
- this.model.set({'hidden_occupants': !this.model.get('hidden_occupants')});
- }
- this.setOccupantsVisibility();
- this.scrollDown();
- },
- onOccupantClicked (ev) {
- /* When an occupant is clicked, insert their nickname into
- * the chat textarea input.
- */
- this.insertIntoTextArea(ev.target.textContent);
- },
- handleChatStateNotification (message) {
- /* Override the method on the ChatBoxView base class to
- * ignore <gone/> notifications in groupchats.
- *
- * As laid out in the business rules in XEP-0085
- * http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat
- */
- if (message.get('fullname') === this.model.get('nick')) {
- // Don't know about other servers, but OpenFire sends
- // back to you your own chat state notifications.
- // We ignore them here...
- return;
- }
- if (message.get('chat_state') !== _converse.GONE) {
- _converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments);
- }
- },
- modifyRole (groupchat, nick, role, reason, onSuccess, onError) {
- const item = $build("item", {nick, role});
- const iq = $iq({to: groupchat, type: "set"}).c("query", {xmlns: Strophe.NS.MUC_ADMIN}).cnode(item.node);
- if (reason !== null) { iq.c("reason", reason); }
- return _converse.connection.sendIQ(iq, onSuccess, onError);
- },
- verifyRoles (roles) {
- const me = this.model.occupants.findWhere({'jid': _converse.bare_jid});
- if (!_.includes(roles, me.get('role'))) {
- this.showErrorMessage(__(`Forbidden: you do not have the necessary role in order to do that.`))
- return false;
- }
- return true;
- },
- verifyAffiliations (affiliations) {
- const me = this.model.occupants.findWhere({'jid': _converse.bare_jid});
- if (!_.includes(affiliations, me.get('affiliation'))) {
- this.showErrorMessage(__(`Forbidden: you do not have the necessary affiliation in order to do that.`))
- return false;
- }
- return true;
- },
- validateRoleChangeCommand (command, args) {
- /* Check that a command to change a groupchat user's role or
- * affiliation has anough arguments.
- */
- if (args.length < 1 || args.length > 2) {
- this.showErrorMessage(
- __('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.', command)
- );
- return false;
- }
- if (!this.model.occupants.findWhere({'nick': args[0]}) && !this.model.occupants.findWhere({'jid': args[0]})) {
- this.showErrorMessage(__('Error: couldn\'t find a groupchat participant "%1$s"', args[0]));
- return false;
- }
- return true;
- },
- onCommandError (err) {
- _converse.log(err, Strophe.LogLevel.FATAL);
- this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details."));
- },
- parseMessageForCommands (text) {
- if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) {
- return true;
- }
- if (_converse.muc_disable_moderator_commands) {
- return false;
- }
- const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''],
- args = match[2] && match[2].splitOnce(' ').filter(s => s) || [],
- command = match[1].toLowerCase();
- switch (command) {
- case 'admin':
- if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.model.setAffiliation('admin', [{
- 'jid': args[0],
- 'reason': args[1]
- }]).then(
- () => this.model.occupants.fetchMembers(),
- (err) => this.onCommandError(err)
- );
- break;
- case 'ban':
- if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.model.setAffiliation('outcast', [{
- 'jid': args[0],
- 'reason': args[1]
- }]).then(
- () => this.model.occupants.fetchMembers(),
- (err) => this.onCommandError(err)
- );
- break;
- case 'deop':
- if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.modifyRole(
- this.model.get('jid'), args[0], 'participant', args[1],
- undefined, this.onCommandError.bind(this));
- break;
- case 'help':
- this.showHelpMessages([
- `<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
- `<strong>/ban</strong>: ${__('Ban user from groupchat')}`,
- `<strong>/clear</strong>: ${__('Remove messages')}`,
- `<strong>/deop</strong>: ${__('Change user role to participant')}`,
- `<strong>/help</strong>: ${__('Show this menu')}`,
- `<strong>/kick</strong>: ${__('Kick user from groupchat')}`,
- `<strong>/me</strong>: ${__('Write in 3rd person')}`,
- `<strong>/member</strong>: ${__('Grant membership to a user')}`,
- `<strong>/mute</strong>: ${__("Remove user's ability to post messages")}`,
- `<strong>/nick</strong>: ${__('Change your nickname')}`,
- `<strong>/op</strong>: ${__('Grant moderator role to user')}`,
- `<strong>/owner</strong>: ${__('Grant ownership of this groupchat')}`,
- `<strong>/register</strong>: ${__("Register a nickname for this room")}`,
- `<strong>/revoke</strong>: ${__("Revoke user's membership")}`,
- `<strong>/subject</strong>: ${__('Set groupchat subject')}`,
- `<strong>/topic</strong>: ${__('Set groupchat subject (alias for /subject)')}`,
- `<strong>/voice</strong>: ${__('Allow muted user to post messages')}`
- ]);
- break;
- case 'kick':
- if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.modifyRole(
- this.model.get('jid'), args[0], 'none', args[1],
- undefined, this.onCommandError.bind(this));
- break;
- case 'mute':
- if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.modifyRole(
- this.model.get('jid'), args[0], 'visitor', args[1],
- undefined, this.onCommandError.bind(this));
- break;
- case 'member': {
- if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- const occupant = this.model.occupants.findWhere({'nick': args[0]}) ||
- this.model.occupants.findWhere({'jid': args[0]}),
- attrs = {
- 'jid': occupant.get('jid'),
- 'reason': args[1]
- };
- if (_converse.auto_register_muc_nickname) {
- attrs['nick'] = occupant.get('nick');
- }
- this.model.setAffiliation('member', [attrs])
- .then(() => this.model.occupants.fetchMembers())
- .catch(err => this.onCommandError(err));
- break;
- } case 'nick':
- if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) {
- break;
- }
- _converse.connection.send($pres({
- from: _converse.connection.jid,
- to: this.model.getRoomJIDAndNick(match[2]),
- id: _converse.connection.getUniqueId()
- }).tree());
- break;
- case 'owner':
- if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.model.setAffiliation('owner', [{
- 'jid': args[0],
- 'reason': args[1]
- }]).then(
- () => this.model.occupants.fetchMembers(),
- (err) => this.onCommandError(err)
- );
- break;
- case 'op':
- if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.modifyRole(
- this.model.get('jid'), args[0], 'moderator', args[1],
- undefined, this.onCommandError.bind(this));
- break;
- case 'register':
- if (args.length > 1) {
- this.showErrorMessage(__(`Error: invalid number of arguments`))
- } else {
- this.model.registerNickname().then(err_msg => {
- if (err_msg) this.showErrorMessage(err_msg)
- });
- }
- break;
- case 'revoke':
- if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.model.setAffiliation('none', [{
- 'jid': args[0],
- 'reason': args[1]
- }]).then(
- () => this.model.occupants.fetchMembers(),
- (err) => this.onCommandError(err)
- );
- break;
- case 'topic':
- case 'subject':
- // TODO: should be done via API call to _converse.api.rooms
- _converse.connection.send(
- $msg({
- to: this.model.get('jid'),
- from: _converse.connection.jid,
- type: "groupchat"
- }).c("subject", {xmlns: "jabber:client"}).t(match[2] || "").tree()
- );
- break;
- case 'voice':
- if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) {
- break;
- }
- this.modifyRole(
- this.model.get('jid'), args[0], 'participant', args[1],
- undefined, this.onCommandError.bind(this));
- break;
- default:
- return false;
- }
- return true;
- },
- registerHandlers () {
- /* Register presence and message handlers for this chat
- * groupchat
- */
- // XXX: Ideally this can be refactored out so that we don't
- // need to do stanza processing inside the views in this
- // module. See the comment in "onPresence" for more info.
- this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this));
- // XXX instead of having a method showStatusMessages, we could instead
- // create message models in converse-muc.js and then give them views in this module.
- this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this));
- },
- onPresence (pres) {
- /* Handles all MUC presence stanzas.
- *
- * Parameters:
- * (XMLElement) pres: The stanza
- */
- // XXX: Current thinking is that excessive stanza
- // processing inside a view is a "code smell".
- // Instead stanza processing should happen inside the
- // models/collections.
- if (pres.getAttribute('type') === 'error') {
- this.showErrorMessageFromPresence(pres);
- } else {
- // Instead of doing it this way, we could perhaps rather
- // create StatusMessage objects inside the messages
- // Collection and then simply render those. Then stanza
- // processing is done on the model and rendering in the
- // view(s).
- this.showStatusMessages(pres);
- }
- },
- populateAndJoin () {
- this.model.occupants.fetchMembers();
- this.join();
- this.fetchMessages();
- },
- 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')) {
- this.checkForReservedNick();
- return this;
- }
- this.model.join(nick, password);
- return this;
- },
- 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');
- _.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement);
- _.each(container_el.children, u.hideElement);
- container_el.insertAdjacentHTML('beforeend', tpl_chatroom_form());
- const form_el = container_el.querySelector('form.chatroom-form'),
- fieldset_el = form_el.querySelector('fieldset'),
- fields = stanza.querySelectorAll('field'),
- title = _.get(stanza.querySelector('title'), 'textContent'),
- instructions = _.get(stanza.querySelector('instructions'), 'textContent');
- u.removeElement(fieldset_el.querySelector('span.spinner'));
- fieldset_el.insertAdjacentHTML('beforeend', `<legend>${title}</legend>`);
- if (instructions && instructions !== title) {
- fieldset_el.insertAdjacentHTML('beforeend', `<p class="form-help">${instructions}</p>`);
- }
- _.each(fields, function (field) {
- fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza));
- });
- // Render save/cancel buttons
- const last_fieldset_el = document.createElement('fieldset');
- last_fieldset_el.insertAdjacentHTML(
- 'beforeend',
- `<input type="submit" class="btn btn-primary" value="${__('Save')}"/>`);
- last_fieldset_el.insertAdjacentHTML(
- 'beforeend',
- `<input type="button" class="btn btn-secondary" value="${__('Cancel')}"/>`);
- form_el.insertAdjacentElement('beforeend', last_fieldset_el);
- last_fieldset_el.querySelector('input[type=button]').addEventListener('click', (ev) => {
- ev.preventDefault();
- this.closeForm();
- });
- form_el.addEventListener('submit',
- (ev) => {
- ev.preventDefault();
- this.model.saveConfiguration(ev.target)
- .then(() => this.model.refreshRoomFeatures());
- this.closeForm();
- },
- false
- );
- },
- closeForm () {
- /* Remove the configuration form without submitting and
- * return to the chat view.
- */
- u.removeElement(this.el.querySelector('.chatroom-form-container'));
- this.renderAfterTransition();
- },
- getAndRenderConfigurationForm (ev) {
- /* Start the process of configuring a groupchat, either by
- * rendering a configuration form, or by auto-configuring
- * based on the "roomconfig" data stored on the
- * Backbone.Model.
- *
- * Stores the new configuration on the Backbone.Model once
- * completed.
- *
- * Paremeters:
- * (Event) ev: DOM event that might be passed in if this
- * method is called due to a user action. In this
- * case, auto-configure won't happen, regardless of
- * the settings.
- */
- this.showSpinner();
- this.model.fetchRoomConfiguration()
- .then(this.renderConfigurationForm.bind(this))
- .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
- },
- submitNickname (ev) {
- /* Get the nickname value from the form and then join the
- * groupchat with it.
- */
- ev.preventDefault();
- const nick_el = ev.target.nick;
- const nick = nick_el.value;
- if (!nick) {
- nick_el.classList.add('error');
- return;
- }
- else {
- nick_el.classList.remove('error');
- }
- this.el.querySelector('.chatroom-form-container').outerHTML = tpl_spinner();
- this.join(nick);
- },
- checkForReservedNick () {
- /* User 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.
- */
- this.showSpinner();
- this.model.checkForReservedNick()
- .then(this.onReservedNickFound.bind(this))
- .catch(this.onReservedNickNotFound.bind(this));
- },
- onReservedNickFound (iq) {
- if (this.model.get('nick')) {
- this.join();
- } else {
- this.onReservedNickNotFound();
- }
- },
- onReservedNickNotFound (message) {
- const nick = this.model.getDefaultNick();
- if (nick) {
- this.join(nick);
- } else {
- this.renderNicknameForm(message);
- }
- },
- onNicknameClash (presence) {
- /* When the nickname is already taken, we either render a
- * form for the user to choose a new nickname, or we
- * try to make the nickname unique by adding an integer to
- * it. So john will become john-2, and then john-3 and so on.
- *
- * Which option is take depends on the value of
- * muc_nickname_from_jid.
- */
- if (_converse.muc_nickname_from_jid) {
- const nick = presence.getAttribute('from').split('/')[1];
- if (nick === this.model.getDefaultNick()) {
- this.join(nick + '-2');
- } else {
- const del= nick.lastIndexOf("-");
- const num = nick.substring(del+1, nick.length);
- this.join(nick.substring(0, del+1) + String(Number(num)+1));
- }
- } else {
- this.renderNicknameForm(
- __("The nickname you chose is reserved or "+
- "currently in use, please choose a different one.")
- );
- }
- },
- hideChatRoomContents () {
- const container_el = this.el.querySelector('.chatroom-body');
- if (!_.isNull(container_el)) {
- _.each(container_el.children, (child) => { child.classList.add('hidden'); });
- }
- },
- renderNicknameForm (message) {
- /* Render a form which allows the user to choose their
- * nickname.
- */
- this.hideChatRoomContents();
- _.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement);
- if (!_.isString(message)) {
- message = '';
- }
- const container_el = this.el.querySelector('.chatroom-body');
- container_el.insertAdjacentHTML(
- 'beforeend',
- tpl_chatroom_nickname_form({
- heading: __('Please choose your nickname'),
- label_nickname: __('Nickname'),
- label_join: __('Enter groupchat'),
- validation_message: message
- }));
- this.model.save('connection_status', converse.ROOMSTATUS.NICKNAME_REQUIRED);
- const form_el = this.el.querySelector('.chatroom-form');
- form_el.addEventListener('submit', this.submitNickname.bind(this), false);
- },
- submitPassword (ev) {
- ev.preventDefault();
- const password = this.el.querySelector('.chatroom-form input[type=password]').value;
- this.showSpinner();
- this.join(this.model.get('nick'), password);
- },
- renderPasswordForm () {
- const container_el = this.el.querySelector('.chatroom-body');
- _.each(container_el.children, u.hideElement);
- _.each(this.el.querySelectorAll('.spinner'), u.removeElement);
- _.each(this.el.querySelectorAll('.chatroom-form-container'), u.removeElement);
- container_el.insertAdjacentHTML('beforeend',
- tpl_chatroom_password_form({
- 'heading': __('This groupchat requires a password'),
- 'label_password': __('Password: '),
- 'label_submit': __('Submit')
- }));
- this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
- this.el.querySelector('.chatroom-form')
- .addEventListener('submit', ev => this.submitPassword(ev), false);
- },
- showDisconnectMessages (msgs) {
- if (_.isString(msgs)) {
- msgs = [msgs];
- }
- u.hideElement(this.el.querySelector('.chat-area'));
- u.hideElement(this.el.querySelector('.occupants'));
- _.each(this.el.querySelectorAll('.spinner'), u.removeElement);
- const container = this.el.querySelector('.disconnect-container');
- container.innerHTML = tpl_chatroom_disconnect({
- '_': _,
- 'disconnect_messages': msgs
- })
- u.showElement(container);
- },
- 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');
- if (code === '110' || (code === '100' && !is_self)) { return; }
- if (code in _converse.muc.info_messages) {
- return _converse.muc.info_messages[code];
- }
- let nick;
- if (!is_self) {
- if (code in _converse.muc.action_info_messages) {
- nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
- return __(_converse.muc.action_info_messages[code], nick);
- }
- } else if (code in _converse.muc.new_nickname_messages) {
- if (is_self && code === "210") {
- nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
- } else if (is_self && code === "303") {
- nick = stanza.querySelector('x item').getAttribute('nick');
- }
- return __(_converse.muc.new_nickname_messages[code], nick);
- }
- return;
- },
- parseXUserElement (x, stanza, is_self) {
- /* Parse the passed-in <x xmlns='http://jabber.org/protocol/muc#user'>
- * element and construct a map containing relevant
- * information.
- */
- // 1. Get notification messages based on the <status> elements.
- const statuses = x.querySelectorAll('status');
- const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self);
- const notification = {};
- const messages = _.reject(_.map(statuses, mapper), _.isUndefined);
- if (messages.length) {
- notification.messages = messages;
- }
- // 2. Get disconnection messages based on the <status> elements
- const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code');
- const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages));
- const disconnected = is_self && disconnection_codes.length > 0;
- if (disconnected) {
- notification.disconnected = true;
- notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]];
- }
- // 3. Find the reason and actor from the <item> element
- const item = x.querySelector('item');
- // By using querySelector above, we assume here there is
- // one <item> per <x xmlns='http://jabber.org/protocol/muc#user'>
- // element. This appears to be a safe assumption, since
- // each <x/> element pertains to a single user.
- if (!_.isNull(item)) {
- const reason = item.querySelector('reason');
- if (reason) {
- notification.reason = reason ? reason.textContent : undefined;
- }
- const actor = item.querySelector('actor');
- if (actor) {
- notification.actor = actor ? actor.getAttribute('nick') : undefined;
- }
- }
- return notification;
- },
- showNotificationsforUser (notification) {
- /* Given the notification object generated by
- * parseXUserElement, display any relevant messages and
- * information to the user.
- */
- if (notification.disconnected) {
- const messages = [];
- messages.push(notification.disconnection_message);
- if (notification.actor) {
- messages.push(__('This action was done by %1$s.', notification.actor));
- }
- if (notification.reason) {
- messages.push(__('The reason given is: "%1$s".', notification.reason));
- }
- this.showDisconnectMessages(messages);
- this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
- return;
- }
- _.each(notification.messages, (message) => {
- this.content.insertAdjacentHTML(
- 'beforeend',
- tpl_info({
- 'data': '',
- 'isodate': moment().format(),
- 'extra_classes': 'chat-event',
- 'message': message
- }));
- });
- if (notification.reason) {
- this.showChatEvent(__('The reason given is: "%1$s".', notification.reason));
- }
- if (_.get(notification.messages, 'length')) {
- this.scrollDown();
- }
- },
- showJoinOrLeaveNotification (occupant) {
- if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) {
- return;
- }
- if (occupant.get('show') === 'offline') {
- this.showLeaveNotification(occupant);
- } else if (occupant.get('show') === 'online') {
- this.showJoinNotification(occupant);
- }
- },
- showJoinNotification (occupant) {
- if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
- return;
- }
- const nick = occupant.get('nick'),
- stat = occupant.get('status'),
- last_el = this.content.lastElementChild;
- if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
- _.get(last_el, 'dataset', {}).leave === `"${nick}"`) {
- last_el.outerHTML =
- tpl_info({
- 'data': `data-leavejoin="${nick}"`,
- 'isodate': moment().format(),
- 'extra_classes': 'chat-event',
- 'message': __('%1$s has left and re-entered the groupchat', nick)
- });
- const el = this.content.lastElementChild;
- setTimeout(() => u.addClass('fade-out', el), 5000);
- setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250);
- } else {
- let message;
- if (_.isNil(stat)) {
- message = __('%1$s has entered the groupchat', nick);
- } else {
- message = __('%1$s has entered the groupchat. "%2$s"', nick, stat);
- }
- const data = {
- 'data': `data-join="${nick}"`,
- 'isodate': moment().format(),
- 'extra_classes': 'chat-event',
- 'message': message
- };
- if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
- _.get(last_el, 'dataset', {}).joinleave === `"${nick}"`) {
- last_el.outerHTML = tpl_info(data);
- } else {
- const el = u.stringToElement(tpl_info(data));
- this.content.insertAdjacentElement('beforeend', el);
- this.insertDayIndicator(el);
- }
- }
- this.scrollDown();
- },
- showLeaveNotification (occupant) {
- if (_.includes(occupant.get('states'), '303') || _.includes(occupant.get('states'), '307')) {
- return;
- }
- const nick = occupant.get('nick'),
- stat = occupant.get('status'),
- last_el = this.content.lastElementChild;
- if (last_el &&
- _.includes(_.get(last_el, 'classList', []), 'chat-info') &&
- moment(last_el.getAttribute('data-isodate')).isSame(new Date(), "day") &&
- _.get(last_el, 'dataset', {}).join === `"${nick}"`) {
- let message;
- if (_.isNil(stat)) {
- message = __('%1$s has entered and left the groupchat', nick);
- } else {
- message = __('%1$s has entered and left the groupchat. "%2$s"', nick, stat);
- }
- last_el.outerHTML =
- tpl_info({
- 'data': `data-joinleave="${nick}"`,
- 'isodate': moment().format(),
- 'extra_classes': 'chat-event',
- 'message': message
- });
- const el = this.content.lastElementChild;
- setTimeout(() => u.addClass('fade-out', el), 5000);
- setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250);
- } else {
- let message;
- if (_.isNil(stat)) {
- message = __('%1$s has left the groupchat', nick);
- } else {
- message = __('%1$s has left the groupchat. "%2$s"', nick, stat);
- }
- const data = {
- 'message': message,
- 'isodate': moment().format(),
- 'extra_classes': 'chat-event',
- 'data': `data-leave="${nick}"`
- }
- if (last_el &&
- _.includes(_.get(last_el, 'classList', []), 'chat-info') &&
- _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
- last_el.outerHTML = tpl_info(data);
- } else {
- const el = u.stringToElement(tpl_info(data));
- this.content.insertAdjacentElement('beforeend', el);
- this.insertDayIndicator(el);
- }
- }
- this.scrollDown();
- },
- showStatusMessages (stanza) {
- /* Check for status codes and communicate their purpose to the user.
- * See: http://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 is_self = stanza.querySelectorAll("status[code='110']").length;
- const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
- const notifications = _.reject(_.map(elements, iteratee), _.isEmpty);
- _.each(notifications, this.showNotificationsforUser.bind(this));
- },
- showErrorMessageFromPresence (presence) {
- // We didn't enter the groupchat, so we must remove it from the MUC add-on
- const error = presence.querySelector('error');
- if (error.getAttribute('type') === 'auth') {
- if (!_.isNull(error.querySelector('not-authorized'))) {
- this.renderPasswordForm();
- } else if (!_.isNull(error.querySelector('registration-required'))) {
- this.showDisconnectMessages(__('You are not on the member list of this groupchat.'));
- } else if (!_.isNull(error.querySelector('forbidden'))) {
- this.showDisconnectMessages(__('You have been banned from this groupchat.'));
- }
- } else if (error.getAttribute('type') === 'modify') {
- if (!_.isNull(error.querySelector('jid-malformed'))) {
- this.showDisconnectMessages(__('No nickname was specified.'));
- }
- } else if (error.getAttribute('type') === 'cancel') {
- if (!_.isNull(error.querySelector('not-allowed'))) {
- this.showDisconnectMessages(__('You are not allowed to create new groupchats.'));
- } else if (!_.isNull(error.querySelector('not-acceptable'))) {
- this.showDisconnectMessages(__("Your nickname doesn't conform to this groupchat's policies."));
- } else if (!_.isNull(error.querySelector('conflict'))) {
- this.onNicknameClash(presence);
- } else if (!_.isNull(error.querySelector('item-not-found'))) {
- this.showDisconnectMessages(__("This groupchat does not (yet) exist."));
- } else if (!_.isNull(error.querySelector('service-unavailable'))) {
- this.showDisconnectMessages(__("This groupchat has reached its maximum number of participants."));
- } else if (!_.isNull(error.querySelector('remote-server-not-found'))) {
- const messages = [__("Remote server not found")];
- const reason = _.get(error.querySelector('text'), 'textContent');
- if (reason) {
- messages.push(__('The explanation given is: "%1$s".', reason));
- }
- this.showDisconnectMessages(messages);
- }
- }
- },
- renderAfterTransition () {
- /* Rerender the groupchat after some kind of transition. For
- * example after the spinner has been removed or after a
- * form has been submitted and removed.
- */
- if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) {
- this.renderNicknameForm();
- } else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
- this.renderPasswordForm();
- } else {
- this.el.querySelector('.chat-area').classList.remove('hidden');
- this.setOccupantsVisibility();
- this.scrollDown();
- }
- },
- showSpinner () {
- u.removeElement(this.el.querySelector('.spinner'));
- const container_el = this.el.querySelector('.chatroom-body');
- const children = Array.prototype.slice.call(container_el.children, 0);
- container_el.insertAdjacentHTML('afterbegin', tpl_spinner());
- _.each(children, u.hideElement);
- },
- hideSpinner () {
- /* Check if the spinner is being shown and if so, hide it.
- * Also make sure then that the chat area and occupants
- * list are both visible.
- */
- const spinner = this.el.querySelector('.spinner');
- if (!_.isNull(spinner)) {
- u.removeElement(spinner);
- this.renderAfterTransition();
- }
- return this;
- },
- setChatRoomSubject () {
- // For translators: the %1$s and %2$s parts will get
- // replaced by the user and topic text respectively
- // Example: Topic set by JC Brand to: Hello World!
- const subject = this.model.get('subject'),
- message = subject.text ? __('Topic set by %1$s', subject.author) :
- __('Topic cleared by %1$s', subject.author),
- date = moment().format();
- this.content.insertAdjacentHTML(
- 'beforeend',
- tpl_info({
- 'data': '',
- 'isodate': date,
- 'extra_classes': 'chat-event',
- 'message': message
- }));
- if (subject.text) {
- this.content.insertAdjacentHTML(
- 'beforeend',
- tpl_info({
- 'data': '',
- 'isodate': date,
- 'extra_classes': 'chat-topic',
- 'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})),
- 'render_message': true
- }));
- }
- this.scrollDown();
- }
- });
- _converse.RoomsPanel = Backbone.NativeView.extend({
- /* Backbone.NativeView which renders MUC section of the control box.
- */
- tagName: 'div',
- className: 'controlbox-section',
- id: 'chatrooms',
- events: {
- 'click a.chatbox-btn.show-add-muc-modal': 'showAddRoomModal',
- 'click a.chatbox-btn.show-list-muc-modal': 'showListRoomsModal'
- },
- render () {
- this.el.innerHTML = tpl_room_panel({
- 'heading_chatrooms': __('Groupchats'),
- 'title_new_room': __('Add a new groupchat'),
- 'title_list_rooms': __('Query for groupchats')
- });
- return this;
- },
- showAddRoomModal (ev) {
- if (_.isUndefined(this.add_room_modal)) {
- this.add_room_modal = new _converse.AddChatRoomModal({'model': this.model});
- }
- this.add_room_modal.show(ev);
- },
- showListRoomsModal(ev) {
- if (_.isUndefined(this.list_rooms_modal)) {
- this.list_rooms_modal = new _converse.ListChatRoomsModal({'model': this.model});
- }
- this.list_rooms_modal.show(ev);
- }
- });
- _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({
- tagName: 'li',
- initialize () {
- this.model.on('change', this.render, this);
- },
- toHTML () {
- const show = this.model.get('show');
- return tpl_occupant(
- _.extend(
- { '_': _, // XXX Normally this should already be included,
- // but with the current webpack build,
- // we only get a subset of the _ methods.
- 'jid': '',
- 'show': show,
- 'hint_show': _converse.PRETTY_CHAT_STATUS[show],
- 'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
- 'desc_moderator': __('This user is a moderator.'),
- 'desc_participant': __('This user can send messages in this groupchat.'),
- 'desc_visitor': __('This user can NOT send messages in this groupchat.'),
- 'label_moderator': __('Moderator'),
- 'label_visitor': __('Visitor'),
- 'label_owner': __('Owner'),
- 'label_member': __('Member'),
- 'label_admin': __('Admin')
- }, this.model.toJSON())
- );
- },
- destroy () {
- this.el.parentElement.removeChild(this.el);
- }
- });
- _converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({
- tagName: 'div',
- className: 'occupants col-md-3 col-4',
- listItems: 'model',
- sortEvent: 'change:role',
- listSelector: '.occupant-list',
- ItemView: _converse.ChatRoomOccupantView,
- initialize () {
- Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
- this.chatroomview = this.model.chatroomview;
- this.chatroomview.model.on('change:open', this.renderInviteWidget, this);
- this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this);
- this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:open', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this);
- this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this);
- this.render();
- this.model.fetch({
- 'add': true,
- 'silent': true,
- 'success': this.sortAndPositionAllItems.bind(this)
- });
- },
- render () {
- this.el.innerHTML = tpl_chatroom_sidebar(
- _.extend(this.chatroomview.model.toJSON(), {
- 'allow_muc_invitations': _converse.allow_muc_invitations,
- 'label_occupants': __('Participants')
- })
- );
- if (_converse.allow_muc_invitations) {
- _converse.api.waitUntil('rosterContactsFetched').then(
- this.renderInviteWidget.bind(this)
- );
- }
- return this.renderRoomFeatures();
- },
- renderInviteWidget () {
- const form = this.el.querySelector('form.room-invite');
- if (this.shouldInviteWidgetBeShown()) {
- if (_.isNull(form)) {
- const heading = this.el.querySelector('.occupants-heading');
- heading.insertAdjacentHTML(
- 'afterend',
- tpl_chatroom_invite({
- 'error_message': null,
- 'label_invitation': __('Invite'),
- })
- );
- this.initInviteWidget();
- }
- } else if (!_.isNull(form)) {
- form.remove();
- }
- return this;
- },
- renderRoomFeatures () {
- const picks = _.pick(this.chatroomview.model.attributes, converse.ROOM_FEATURES),
- iteratee = (a, v) => a || v,
- el = this.el.querySelector('.chatroom-features');
- el.innerHTML = tpl_chatroom_features(
- _.extend(this.chatroomview.model.toJSON(), {
- '__': __,
- 'has_features': _.reduce(_.values(picks), iteratee)
- }));
- this.setOccupantsHeight();
- return this;
- },
- onFeatureChanged (model) {
- /* When a feature has been changed, it's logical opposite
- * must be set to the opposite value.
- *
- * So for example, if "temporary" was set to "false", then
- * "persistent" will be set to "true" in this method.
- *
- * Additionally a debounced render method is called to make
- * sure the features widget gets updated.
- */
- if (_.isUndefined(this.debouncedRenderRoomFeatures)) {
- this.debouncedRenderRoomFeatures = _.debounce(
- this.renderRoomFeatures, 100, {'leading': false}
- );
- }
- const changed_features = {};
- _.each(_.keys(model.changed), function (k) {
- if (!_.isNil(ROOM_FEATURES_MAP[k])) {
- changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k];
- }
- });
- this.chatroomview.model.save(changed_features, {'silent': true});
- this.debouncedRenderRoomFeatures();
- },
- setOccupantsHeight () {
- const el = this.el.querySelector('.chatroom-features');
- this.el.querySelector('.occupant-list').style.cssText =
- `height: calc(100% - ${el.offsetHeight}px - 5em);`;
- },
- promptForInvite (suggestion) {
- const reason = prompt(
- __('You are about to invite %1$s to the groupchat "%2$s". '+
- 'You may optionally include a message, explaining the reason for the invitation.',
- suggestion.text.label, this.model.get('id'))
- );
- if (reason !== null) {
- this.chatroomview.model.directInvite(suggestion.text.value, reason);
- }
- const form = suggestion.target.form,
- error = form.querySelector('.pure-form-message.error');
- if (!_.isNull(error)) {
- error.parentNode.removeChild(error);
- }
- suggestion.target.value = '';
- },
- inviteFormSubmitted (evt) {
- evt.preventDefault();
- const el = evt.target.querySelector('input.invited-contact'),
- jid = el.value;
- if (!jid || _.compact(jid.split('@')).length < 2) {
- evt.target.outerHTML = tpl_chatroom_invite({
- 'error_message': __('Please enter a valid XMPP username'),
- 'label_invitation': __('Invite'),
- });
- this.initInviteWidget();
- return;
- }
- this.promptForInvite({
- 'target': el,
- 'text': {
- 'label': jid,
- 'value': jid
- }});
- },
- shouldInviteWidgetBeShown () {
- return _converse.allow_muc_invitations &&
- (this.chatroomview.model.get('open') ||
- this.chatroomview.model.get('affiliation') === "owner"
- );
- },
- initInviteWidget () {
- const form = this.el.querySelector('form.room-invite');
- if (_.isNull(form)) {
- return;
- }
- form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false);
- const el = this.el.querySelector('input.invited-contact');
- const list = _converse.roster.map(function (item) {
- const label = item.get('fullname') || item.get('jid');
- return {'label': label, 'value':item.get('jid')};
- });
- const awesomplete = new Awesomplete(el, {
- 'minChars': 1,
- 'list': list
- });
- el.addEventListener('awesomplete-selectcomplete',
- this.promptForInvite.bind(this));
- }
- });
- function setMUCDomain (domain, controlboxview) {
- _converse.muc_domain = domain;
- controlboxview.roomspanel.model.save('muc_domain', Strophe.getDomainFromJid(domain));
- }
- function setMUCDomainFromDisco (controlboxview) {
- /* Check whether service discovery for the user's domain
- * returned MUC information and use that to automatically
- * set the MUC domain in the "Add groupchat" modal.
- */
- function featureAdded (feature) {
- if (!feature) { return; }
- if (feature.get('var') === Strophe.NS.MUC) {
- feature.entity.getIdentity('conference', 'text').then(identity => {
- if (identity) {
- setMUCDomain(feature.get('from'), controlboxview);
- }
- });
- }
- }
- _converse.api.waitUntil('discoInitialized').then(() => {
- _converse.api.listen.on('serviceDiscovered', featureAdded);
- // Features could have been added before the controlbox was
- // initialized. We're only interested in MUC
- _converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({'var': Strophe.NS.MUC })));
- }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
- }
- function fetchAndSetMUCDomain (controlboxview) {
- if (controlboxview.model.get('connected')) {
- if (!controlboxview.roomspanel.model.get('muc_domain')) {
- if (_.isUndefined(_converse.muc_domain)) {
- setMUCDomainFromDisco(controlboxview);
- } else {
- setMUCDomain(_converse.muc_domain, controlboxview);
- }
- }
- }
- }
- /************************ BEGIN Event Handlers ************************/
- _converse.on('chatBoxViewsInitialized', () => {
- const that = _converse.chatboxviews;
- _converse.chatboxes.on('add', item => {
- if (!that.get(item.get('id')) && item.get('type') === _converse.CHATROOMS_TYPE) {
- return that.add(item.get('id'), new _converse.ChatRoomView({'model': item}));
- }
- });
- });
- _converse.on('controlboxInitialized', (view) => {
- if (!_converse.allow_muc) {
- return;
- }
- fetchAndSetMUCDomain(view);
- view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view));
- });
- function reconnectToChatRooms () {
- /* Upon a reconnection event from converse, join again
- * all the open groupchats.
- */
- _converse.chatboxviews.each(function (view) {
- if (view.model.get('type') === _converse.CHATROOMS_TYPE) {
- view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
- view.model.registerHandlers();
- view.populateAndJoin();
- }
- });
- }
- _converse.on('reconnected', reconnectToChatRooms);
- /************************ END Event Handlers ************************/
- /************************ BEGIN API ************************/
- _.extend(_converse.api, {
- /**
- * The "roomviews" namespace groups methods relevant to chatroom
- * (aka groupchats) views.
- *
- * @namespace _converse.api.roomviews
- * @memberOf _converse.api
- */
- 'roomviews': {
- /**
- * Lets you close open chatrooms.
- *
- * You can call this method without any arguments to close
- * all open chatrooms, or you can specify a single JID or
- * an array of JIDs.
- *
- * @method _converse.api.roomviews.close
- * @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s)
- */
- 'close' (jids) {
- if (_.isUndefined(jids)) {
- _converse.chatboxviews.each(function (view) {
- if (view.is_chatroom && view.model) {
- view.close();
- }
- });
- } else if (_.isString(jids)) {
- const view = _converse.chatboxviews.get(jids);
- if (view) { view.close(); }
- } else {
- _.each(jids, function (jid) {
- const view = _converse.chatboxviews.get(jid);
- if (view) { view.close(); }
- });
- }
- }
- }
- });
- }
- });
- }));
|