Source: jsxc.lib.muc.js

  1. /**
  2. * Implements Multi-User Chat (XEP-0045).
  3. *
  4. * @namespace jsxc.muc
  5. */
  6. jsxc.muc = {
  7. /** strophe connection */
  8. conn: null,
  9. /** some constants */
  10. CONST: {
  11. AFFILIATION: {
  12. ADMIN: 'admin',
  13. MEMBER: 'member',
  14. OUTCAST: 'outcast',
  15. OWNER: 'owner',
  16. NONE: 'none'
  17. },
  18. ROLE: {
  19. MODERATOR: 'moderator',
  20. PARTICIPANT: 'participant',
  21. VISITOR: 'visitor',
  22. NONE: 'none'
  23. },
  24. ROOMSTATE: {
  25. INIT: 0,
  26. ENTERED: 1,
  27. EXITED: 2,
  28. AWAIT_DESTRUCTION: 3,
  29. DESTROYED: 4
  30. },
  31. ROOMCONFIG: {
  32. INSTANT: 'instant'
  33. }
  34. },
  35. /**
  36. * Initialize muc plugin.
  37. *
  38. * @private
  39. * @memberof jsxc.muc
  40. * @param {object} o Options
  41. */
  42. init: function(o) {
  43. var self = jsxc.muc;
  44. self.conn = jsxc.xmpp.conn;
  45. var options = o || jsxc.options.get('muc');
  46. if (!options || typeof options.server !== 'string') {
  47. jsxc.debug('Discover muc service');
  48. // prosody does not respond, if we send query before initial presence was sent
  49. setTimeout(function() {
  50. self.conn.disco.items(Strophe.getDomainFromJid(self.conn.jid), null, function(items) {
  51. $(items).find('item').each(function() {
  52. var jid = $(this).attr('jid');
  53. var discovered = false;
  54. self.conn.disco.info(jid, null, function(info) {
  55. var mucFeature = $(info).find('feature[var="' + Strophe.NS.MUC + '"]');
  56. var mucIdentity = $(info).find('identity[category="conference"][type="text"]');
  57. if (mucFeature.length > 0 && mucIdentity.length > 0) {
  58. jsxc.debug('muc service found', jid);
  59. jsxc.options.set('muc', {
  60. server: jid,
  61. name: $(info).find('identity').attr('name')
  62. });
  63. discovered = true;
  64. self.init();
  65. }
  66. });
  67. return !discovered;
  68. });
  69. });
  70. }, 1000);
  71. return;
  72. }
  73. if (jsxc.gui.roster.ready) {
  74. self.initMenu();
  75. } else {
  76. $(document).one('ready.roster.jsxc', jsxc.muc.initMenu);
  77. }
  78. // remove maybe previously attached handlers
  79. $(document).off('presence.jsxc', jsxc.muc.onPresence);
  80. $(document).off('error.presence.jsxc', jsxc.muc.onPresenceError);
  81. $(document).on('presence.jsxc', jsxc.muc.onPresence);
  82. $(document).on('error.presence.jsxc', jsxc.muc.onPresenceError);
  83. self.conn.addHandler(self.onGroupchatMessage, null, 'message', 'groupchat');
  84. self.conn.muc.roomNames = jsxc.storage.getUserItem('roomNames') || [];
  85. },
  86. /**
  87. * Add entry to menu.
  88. *
  89. * @memberOf jsxc.muc
  90. */
  91. initMenu: function() {
  92. var li = $('<li>').attr('class', 'jsxc_joinChat jsxc_groupcontacticon').text($.t('Join_chat'));
  93. li.click(jsxc.muc.showJoinChat);
  94. if ($('#jsxc_menu .jsxc_joinChat').length === 0) {
  95. $('#jsxc_menu ul .jsxc_about').before(li);
  96. }
  97. },
  98. /**
  99. * Open join dialog.
  100. *
  101. * @memberOf jsxc.muc
  102. * @param {string} [r] - room jid
  103. * @param {string} [p] - room password
  104. */
  105. showJoinChat: function(r, p) {
  106. var self = jsxc.muc;
  107. var dialog = jsxc.gui.dialog.open(jsxc.gui.template.get('joinChat'));
  108. // @TODO split this monster function apart
  109. // hide second step button
  110. dialog.find('.jsxc_join').hide();
  111. // prepopulate room jid
  112. if (typeof r === 'string') {
  113. dialog.find('#jsxc_room').val(r);
  114. }
  115. // prepopulate room password
  116. if (typeof p === 'string') {
  117. dialog.find('#jsxc_password').val(p);
  118. }
  119. // display conference server
  120. var serverInputTimeout;
  121. dialog.find('#jsxc_server').val(jsxc.options.get('muc').server);
  122. dialog.find('#jsxc_server').on('input', function() {
  123. var self = $(this);
  124. if (serverInputTimeout) {
  125. clearTimeout(serverInputTimeout);
  126. dialog.find('.jsxc_inputinfo.jsxc_room').hide();
  127. }
  128. dialog.find('.jsxc_inputinfo.jsxc_server').hide().text('');
  129. dialog.find('#jsxc_server').removeClass('jsxc_invalid');
  130. if (self.val() && self.val().match(/^[.-0-9a-zA-Z]+$/i)) {
  131. dialog.find('.jsxc_inputinfo.jsxc_room').show().addClass('jsxc_waiting');
  132. serverInputTimeout = setTimeout(function() {
  133. loadRoomList(self.val());
  134. }, 1800);
  135. }
  136. }).trigger('input');
  137. // handle error response
  138. var error_handler = function(event, condition, room) {
  139. var msg;
  140. switch (condition) {
  141. case 'not-authorized':
  142. // password-protected room
  143. msg = $.t('A_password_is_required');
  144. break;
  145. case 'registration-required':
  146. // members-only room
  147. msg = $.t('You_are_not_on_the_member_list');
  148. break;
  149. case 'forbidden':
  150. // banned users
  151. msg = $.t('You_are_banned_from_this_room');
  152. break;
  153. case 'conflict':
  154. // nickname conflict
  155. msg = $.t('Your_desired_nickname_');
  156. break;
  157. case 'service-unavailable':
  158. // max users
  159. msg = $.t('The_maximum_number_');
  160. break;
  161. case 'item-not-found':
  162. // locked or non-existing room
  163. msg = $.t('This_room_is_locked_');
  164. break;
  165. case 'not-allowed':
  166. // room creation is restricted
  167. msg = $.t('You_are_not_allowed_to_create_');
  168. break;
  169. default:
  170. jsxc.warn('Unknown muc error condition: ' + condition);
  171. msg = $.t('Error') + ': ' + condition;
  172. }
  173. // clean up strophe.muc rooms
  174. var roomIndex = self.conn.muc.roomNames.indexOf(room);
  175. if (roomIndex > -1) {
  176. self.conn.muc.roomNames.splice(roomIndex, 1);
  177. delete self.conn.muc.rooms[room];
  178. }
  179. $('<p>').addClass('jsxc_warning').text(msg).appendTo(dialog.find('.jsxc_msg'));
  180. };
  181. $(document).on('error.muc.jsxc', error_handler);
  182. $(document).on('close.dialog.jsxc', function() {
  183. $(document).off('error.muc.jsxc', error_handler);
  184. });
  185. dialog.find('#jsxc_nickname').attr('placeholder', Strophe.getNodeFromJid(self.conn.jid));
  186. dialog.find('#jsxc_bookmark').change(function() {
  187. if ($(this).prop('checked')) {
  188. $('#jsxc_autojoin').prop('disabled', false);
  189. $('#jsxc_autojoin').parent('.checkbox').removeClass('disabled');
  190. } else {
  191. $('#jsxc_autojoin').prop('disabled', true).prop('checked', false);
  192. $('#jsxc_autojoin').parent('.checkbox').addClass('disabled');
  193. }
  194. });
  195. dialog.find('.jsxc_continue').click(function(ev) {
  196. ev.preventDefault();
  197. var room = ($('#jsxc_room').val()) ? jsxc.jidToBid($('#jsxc_room').val()) : null;
  198. var nickname = $('#jsxc_nickname').val() || Strophe.getNodeFromJid(self.conn.jid);
  199. var server = dialog.find('#jsxc_server').val();
  200. if (!room || !room.match(/^[^"&\'\/:<>@\s]+$/i)) {
  201. $('#jsxc_room').addClass('jsxc_invalid').keyup(function() {
  202. if ($(this).val()) {
  203. $(this).removeClass('jsxc_invalid');
  204. }
  205. });
  206. return false;
  207. }
  208. if (dialog.find('#jsxc_server').hasClass('jsxc_invalid')) {
  209. return false;
  210. }
  211. if (!room.match(/@(.*)$/)) {
  212. room += '@' + server;
  213. }
  214. if (jsxc.xmpp.conn.muc.roomNames.indexOf(room) < 0) {
  215. // not already joined
  216. var discoReceived = function(roomName, subject) {
  217. // we received the room information
  218. jsxc.gui.dialog.resize();
  219. dialog.find('.jsxc_continue').hide();
  220. dialog.find('.jsxc_join').show().effect('highlight', {
  221. color: 'green'
  222. }, 4000);
  223. dialog.find('.jsxc_join').click(function(ev) {
  224. ev.preventDefault();
  225. var bookmark = $("#jsxc_bookmark").prop("checked");
  226. var autojoin = $('#jsxc_autojoin').prop('checked');
  227. var password = $('#jsxc_password').val() || null;
  228. // clean up
  229. jsxc.gui.window.clear(room);
  230. jsxc.storage.setUserItem('member', room, {});
  231. self.join(room, nickname, password, roomName, subject, bookmark, autojoin);
  232. return false;
  233. });
  234. };
  235. dialog.find('.jsxc_msg').append($('<p>').text($.t('Loading_room_information')).addClass('jsxc_waiting'));
  236. jsxc.gui.dialog.resize();
  237. self.conn.disco.info(room, null, function(stanza) {
  238. dialog.find('.jsxc_msg').html('<p>' + $.t('This_room_is') + '</p>');
  239. var table = $('<table>');
  240. $(stanza).find('feature').each(function() {
  241. var feature = $(this).attr('var');
  242. if (feature !== '' && i18next.exists(feature)) {
  243. var tr = $('<tr>');
  244. $('<td>').text($.t(feature + '.keyword')).appendTo(tr);
  245. $('<td>').text($.t(feature + '.description')).appendTo(tr);
  246. tr.appendTo(table);
  247. }
  248. if (feature === 'muc_passwordprotected') {
  249. dialog.find('#jsxc_password').parents('.form-group').removeClass('jsxc_hidden');
  250. dialog.find('#jsxc_password').attr('required', 'required');
  251. dialog.find('#jsxc_password').addClass('jsxc_invalid');
  252. }
  253. });
  254. dialog.find('.jsxc_msg').append(table);
  255. var roomName = $(stanza).find('identity').attr('name');
  256. var subject = $(stanza).find('field[var="muc#roominfo_subject"]').attr('label');
  257. //@TODO display subject, number of occupants, etc.
  258. discoReceived(roomName, subject);
  259. }, function() {
  260. dialog.find('.jsxc_msg').empty();
  261. $('<p>').text($.t('Room_not_found_')).appendTo(dialog.find('.jsxc_msg'));
  262. discoReceived();
  263. });
  264. } else {
  265. $('<p>').addClass('jsxc_warning').text($.t('You_already_joined_this_room')).appendTo(dialog.find('.jsxc_msg'));
  266. }
  267. return false;
  268. });
  269. dialog.find('input').keydown(function(ev) {
  270. if (ev.which !== 13) {
  271. // reset messages and room information
  272. dialog.find('.jsxc_warning').remove();
  273. if (dialog.find('.jsxc_continue').is(":hidden") && $(this).attr('id') !== 'jsxc_password') {
  274. dialog.find('.jsxc_continue').show();
  275. dialog.find('.jsxc_join').hide().off('click');
  276. dialog.find('.jsxc_msg').empty();
  277. dialog.find('#jsxc_password').parents('.form-group').addClass('jsxc_hidden');
  278. dialog.find('#jsxc_password').attr('required', '');
  279. dialog.find('#jsxc_password').removeClass('jsxc_invalid');
  280. jsxc.gui.dialog.resize();
  281. }
  282. return;
  283. }
  284. if (!dialog.find('.jsxc_continue').is(":hidden")) {
  285. dialog.find('.jsxc_continue').click();
  286. } else {
  287. dialog.find('.jsxc_join').click();
  288. }
  289. });
  290. function loadRoomList(server) {
  291. if (!server) {
  292. dialog.find('.jsxc_inputinfo').hide();
  293. return;
  294. }
  295. // load room list
  296. self.conn.muc.listRooms(server, function(stanza) {
  297. // workaround: chrome does not display dropdown arrow for dynamically filled datalists
  298. $('#jsxc_roomlist option:last').remove();
  299. $(stanza).find('item').each(function() {
  300. var r = $('<option>');
  301. var rjid = $(this).attr('jid').toLowerCase();
  302. var rnode = Strophe.getNodeFromJid(rjid);
  303. var rname = $(this).attr('name') || rnode;
  304. r.text(rname);
  305. r.attr('data-jid', rjid);
  306. r.attr('value', rnode);
  307. $('#jsxc_roomlist select').append(r);
  308. });
  309. var set = $(stanza).find('set[xmlns="http://jabber.org/protocol/rsm"]');
  310. if (set.length > 0) {
  311. var count = set.find('count').text() || '?';
  312. dialog.find('.jsxc_inputinfo').show().removeClass('jsxc_waiting').text($.t('Could_load_only', {
  313. count: count
  314. }));
  315. } else {
  316. dialog.find('.jsxc_inputinfo').hide();
  317. }
  318. }, function(stanza) {
  319. var errTextMsg = $(stanza).find('error text').text() || null;
  320. jsxc.warn('Could not load rooms', errTextMsg);
  321. if (errTextMsg) {
  322. dialog.find('.jsxc_inputinfo.jsxc_server').show().text(errTextMsg);
  323. }
  324. if ($(stanza).find('error remote-server-not-found')) {
  325. dialog.find('#jsxc_server').addClass('jsxc_invalid');
  326. }
  327. dialog.find('.jsxc_inputinfo.jsxc_room').hide();
  328. });
  329. }
  330. },
  331. /**
  332. * Request and show room configuration.
  333. *
  334. * @memberOf jsxc.muc
  335. * @param {string} room - room jid
  336. */
  337. showRoomConfiguration: function(room) {
  338. var self = jsxc.muc;
  339. self.conn.muc.configure(room, function(stanza) {
  340. var form = Strophe.x.Form.fromXML(stanza);
  341. window.f = form;
  342. self._showRoomConfiguration(room, form);
  343. }, function() {
  344. jsxc.debug('Could not load room configuration');
  345. //@TODO show error
  346. });
  347. },
  348. /**
  349. * Show room configuration.
  350. *
  351. * @private
  352. * @memberOf jsxc.muc
  353. * @param {string} room - room jid
  354. * @param {Strophe.x.Form} config - current room config as Form object
  355. */
  356. _showRoomConfiguration: function(room, config) {
  357. var self = jsxc.muc;
  358. var dialog = jsxc.gui.dialog.open(jsxc.muc.helper.formToHTML(config));
  359. var form = dialog.find('form');
  360. // work around Strophe.x behaviour
  361. form.find('[type="checkbox"]').change(function() {
  362. $(this).val(this.checked ? 1 : 0);
  363. });
  364. var submit = $('<button>');
  365. submit.addClass('btn btn-primary');
  366. submit.attr('type', 'submit');
  367. submit.text($.t('Save'));
  368. var cancel = $('<button>');
  369. cancel.addClass('btn btn-default');
  370. cancel.attr('type', 'button');
  371. cancel.text($.t('Cancel'));
  372. var formGroup = $('<div>');
  373. formGroup.addClass('form-group');
  374. $('<div>').addClass('col-sm-offset-6 col-sm-6').appendTo(formGroup);
  375. formGroup.find('>div').append(cancel);
  376. formGroup.find('>div').append(submit);
  377. form.append(formGroup);
  378. form.submit(function(ev) {
  379. ev.preventDefault();
  380. var config = Strophe.x.Form.fromHTML(form.get(0));
  381. self.conn.muc.saveConfiguration(room, config, function() {
  382. jsxc.storage.updateUserItem('buddy', room, 'config', config);
  383. jsxc.debug('Room configuration saved.');
  384. }, function() {
  385. jsxc.warn('Could not save room configuration.');
  386. //@TODO display error
  387. });
  388. jsxc.gui.dialog.close();
  389. return false;
  390. });
  391. cancel.click(function() {
  392. self.conn.muc.cancelConfigure(room);
  393. jsxc.gui.dialog.close();
  394. });
  395. },
  396. /**
  397. * Join the given room.
  398. *
  399. * @memberOf jsxc.muc
  400. * @param {string} room Room jid
  401. * @param {string} nickname Desired nickname
  402. * @param {string} [password] Password
  403. * @param {string} [roomName] Room alias
  404. * @param {string} [subject] Current subject
  405. */
  406. join: function(room, nickname, password, roomName, subject, bookmark, autojoin) {
  407. var self = jsxc.muc;
  408. jsxc.storage.setUserItem('buddy', room, {
  409. jid: room,
  410. name: roomName || room,
  411. sub: 'both',
  412. type: 'groupchat',
  413. state: self.CONST.ROOMSTATE.INIT,
  414. subject: subject,
  415. bookmarked: bookmark || false,
  416. autojoin: autojoin || false,
  417. nickname: nickname,
  418. config: null
  419. });
  420. jsxc.xmpp.conn.muc.join(room, nickname, null, null, null, password);
  421. if (bookmark) {
  422. jsxc.xmpp.bookmarks.add(room, roomName, nickname, autojoin);
  423. }
  424. },
  425. /**
  426. * Leave given room.
  427. *
  428. * @memberOf jsxc.muc
  429. * @param {string} room Room jid
  430. */
  431. leave: function(room) {
  432. if (!jsxc.master) {
  433. jsxc.tab.execMaster('muc.leave', room);
  434. return;
  435. }
  436. var self = jsxc.muc;
  437. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  438. var data = jsxc.storage.getUserItem('buddy', room) || {};
  439. if (data.state === self.CONST.ROOMSTATE.ENTERED) {
  440. self.conn.muc.leave(room, own[room], function() {
  441. self.onExited(room);
  442. });
  443. } else {
  444. self.onExited(room);
  445. }
  446. },
  447. /**
  448. * Clean up after we exited a room.
  449. *
  450. * @private
  451. * @memberOf jsxc.muc
  452. * @param {string} room Room jid
  453. */
  454. onExited: function(room) {
  455. var self = jsxc.muc;
  456. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  457. var roomdata = jsxc.storage.getUserItem('buddy', room) || {};
  458. jsxc.storage.setUserItem('roomNames', self.conn.muc.roomNames);
  459. delete own[room];
  460. jsxc.storage.setUserItem('ownNicknames', own);
  461. jsxc.storage.removeUserItem('member', room);
  462. jsxc.storage.removeUserItem('chat', room);
  463. jsxc.gui.window.close(room);
  464. jsxc.storage.updateUserItem('buddy', room, 'state', self.CONST.ROOMSTATE.EXITED);
  465. if (!roomdata.bookmarked) {
  466. jsxc.gui.roster.purge(room);
  467. }
  468. },
  469. /**
  470. * Destroy the given room.
  471. *
  472. * @memberOf jsxc.muc
  473. * @param {string} room Room jid
  474. * @param {function} handler_cb Function to handle the successful destruction
  475. * @param {function} error_cb Function to handle an error
  476. */
  477. destroy: function(room, handler_cb, error_cb) {
  478. if (!jsxc.master) {
  479. jsxc.tab.execMaster('muc.destroy', room);
  480. return;
  481. }
  482. var self = jsxc.muc;
  483. var roomdata = jsxc.storage.getUserItem('buddy', room);
  484. jsxc.storage.updateUserItem('buddy', room, 'state', self.CONST.ROOMSTATE.AWAIT_DESTRUCTION);
  485. jsxc.gui.window.postMessage({
  486. bid: room,
  487. direction: jsxc.Message.SYS,
  488. msg: $.t('This_room_will_be_closed')
  489. });
  490. var iq = $iq({
  491. to: room,
  492. type: "set"
  493. }).c("query", {
  494. xmlns: Strophe.NS.MUC_OWNER
  495. }).c("destroy");
  496. jsxc.muc.conn.sendIQ(iq.tree(), handler_cb, error_cb);
  497. if (roomdata.bookmarked) {
  498. jsxc.xmpp.bookmarks.delete(room);
  499. }
  500. },
  501. /**
  502. * Close the given room.
  503. *
  504. * @memberOf jsxc.muc
  505. * @param room Room jid
  506. */
  507. close: function(room) {
  508. var self = jsxc.muc;
  509. var roomdata = jsxc.storage.getUserItem('buddy', room) || {};
  510. self.emptyMembers(room);
  511. var roomIndex = self.conn.muc.roomNames.indexOf(room);
  512. if (roomIndex > -1) {
  513. self.conn.muc.roomNames.splice(roomIndex, 1);
  514. delete self.conn.muc.rooms[room];
  515. }
  516. jsxc.storage.setUserItem('roomNames', self.conn.muc.roomNames);
  517. if (roomdata.state === self.CONST.ROOMSTATE.AWAIT_DESTRUCTION) {
  518. self.onExited(room);
  519. }
  520. if (jsxc.storage.getUserItem('budy', room)) {
  521. roomdata.state = self.CONST.ROOMSTATE.DESTROYED;
  522. jsxc.storage.setUserItem('buddy', room, roomdata);
  523. }
  524. },
  525. /**
  526. * Init group chat window.
  527. *
  528. * @private
  529. * @memberOf jsxc.muc
  530. * @param event Event
  531. * @param {jQuery} win Window object
  532. */
  533. initWindow: function(event, win) {
  534. var self = jsxc.muc;
  535. if (!jsxc.xmpp.conn && jsxc.master) {
  536. $(document).one('attached.jsxc', function() {
  537. self.initWindow(null, win);
  538. });
  539. return;
  540. }
  541. var data = win.data();
  542. var bid = jsxc.jidToBid(data.jid);
  543. var roomdata = jsxc.storage.getUserItem('buddy', bid);
  544. if (roomdata.type !== 'groupchat') {
  545. return;
  546. }
  547. win.addClass('jsxc_groupchat');
  548. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  549. var ownNickname = own[bid];
  550. var mlIcon = $('<div class="jsxc_members"></div>');
  551. win.find('.jsxc_tools > .jsxc_settings').after(mlIcon);
  552. var ml = $('<div class="jsxc_memberlist"><ul></ul></div>');
  553. win.find('.jsxc_fade').prepend(ml);
  554. ml.on('wheel', function(ev) {
  555. jsxc.muc.scrollMemberListBy(bid, (ev.originalEvent.wheelDelta > 0) ? 50 : -50);
  556. });
  557. // toggle member list
  558. var toggleMl = function(ev) {
  559. if (ev) {
  560. ev.preventDefault();
  561. }
  562. var slimOptions = {};
  563. var ul = ml.find('ul:first');
  564. var slimHeight = null;
  565. ml.toggleClass('jsxc_expand');
  566. if (ml.hasClass('jsxc_expand')) {
  567. $('body').click();
  568. $('body').one('click', toggleMl);
  569. ul.mouseleave(function() {
  570. ul.data('timer', window.setTimeout(toggleMl, 2000));
  571. }).mouseenter(function() {
  572. window.clearTimeout(ul.data('timer'));
  573. }).css('left', '0px');
  574. var maxHeight = win.find(".jsxc_textarea").height() * 0.8;
  575. var innerHeight = ml.find('ul').height() + 3;
  576. slimHeight = (innerHeight > maxHeight) ? maxHeight : innerHeight;
  577. slimOptions = {
  578. distance: '3px',
  579. height: slimHeight + 'px',
  580. width: '100%',
  581. color: '#fff',
  582. opacity: '0.5'
  583. };
  584. ml.css('height', slimHeight + 'px');
  585. } else {
  586. slimOptions = {
  587. destroy: true
  588. };
  589. ul.attr('style', '');
  590. ml.css('height', '');
  591. window.clearTimeout(ul.data('timer'));
  592. $('body').off('click', null, toggleMl);
  593. ul.off('mouseleave mouseenter');
  594. }
  595. ul.slimscroll(slimOptions);
  596. return false;
  597. };
  598. mlIcon.click(toggleMl);
  599. win.on('resize', function() {
  600. // update member list position
  601. jsxc.muc.scrollMemberListBy(bid, 0);
  602. });
  603. var destroy = $('<a>');
  604. destroy.attr('href', '#');
  605. destroy.text($.t('Destroy'));
  606. destroy.addClass('jsxc_destroy');
  607. destroy.hide();
  608. destroy.click(function() {
  609. self.destroy(bid);
  610. });
  611. win.find('.jsxc_settings ul').append($('<li>').append(destroy));
  612. var configure = $('<a>');
  613. configure.attr('href', '#');
  614. configure.text($.t('Configure'));
  615. configure.addClass('jsxc_configure');
  616. configure.hide();
  617. configure.click(function() {
  618. self.showRoomConfiguration(bid);
  619. });
  620. if (self.conn) {
  621. win.find('.jsxc_settings ul').append($('<li>').append(configure));
  622. }
  623. if (roomdata.state > self.CONST.ROOMSTATE.INIT) {
  624. var member = jsxc.storage.getUserItem('member', bid) || {};
  625. $.each(member, function(nickname, val) {
  626. self.insertMember(bid, nickname, val);
  627. if (nickname === ownNickname && val.affiliation === self.CONST.AFFILIATION.OWNER) {
  628. destroy.show();
  629. }
  630. if (nickname === ownNickname && (val.affiliation === self.CONST.AFFILIATION.OWNER || val.affiliation === self.CONST.AFFILIATION.OWNER)) {
  631. configure.show();
  632. }
  633. });
  634. }
  635. var leave = $('<a>');
  636. leave.attr('href', '#');
  637. leave.text($.t('Leave'));
  638. leave.addClass('jsxc_leave');
  639. leave.click(function() {
  640. self.leave(bid);
  641. });
  642. win.find('.jsxc_settings ul').append($('<li>').append(leave));
  643. },
  644. /**
  645. * Triggered on incoming presence stanzas.
  646. *
  647. * @private
  648. * @memberOf jsxc.muc
  649. * @param event
  650. * @param {string} from Jid
  651. * @param {integer} status Online status between 0 and 5
  652. * @param {string} presence Presence stanza
  653. */
  654. onPresence: function(event, from, status, presence) {
  655. var self = jsxc.muc;
  656. var room = jsxc.jidToBid(from);
  657. var roomdata = jsxc.storage.getUserItem('buddy', room);
  658. var xdata = $(presence).find('x[xmlns^="' + Strophe.NS.MUC + '"]');
  659. if (self.conn.muc.roomNames.indexOf(room) < 0 || xdata.length === 0) {
  660. return true;
  661. }
  662. var res = Strophe.getResourceFromJid(from) || '';
  663. var nickname = Strophe.unescapeNode(res);
  664. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  665. var member = jsxc.storage.getUserItem('member', room) || {};
  666. var codes = [];
  667. xdata.find('status').each(function() {
  668. var code = $(this).attr('code');
  669. jsxc.debug('[muc][code]', code);
  670. codes.push(code);
  671. });
  672. if (roomdata.state === self.CONST.ROOMSTATE.INIT) {
  673. // successfully joined
  674. roomdata.status = jsxc.CONST.STATUS.indexOf('online');
  675. jsxc.storage.setUserItem('buddy', room, roomdata);
  676. jsxc.storage.setUserItem('roomNames', jsxc.xmpp.conn.muc.roomNames);
  677. if (jsxc.gui.roster.getItem(room).length === 0) {
  678. var bl = jsxc.storage.getUserItem('buddylist');
  679. bl.push(room);
  680. jsxc.storage.setUserItem('buddylist', bl);
  681. jsxc.gui.roster.add(room);
  682. }
  683. if ($('#jsxc_dialog').length > 0) {
  684. // User joined the room manually
  685. jsxc.gui.dialog.close();
  686. jsxc.gui.window.open(room);
  687. }
  688. }
  689. var jid = xdata.find('item').attr('jid') || null;
  690. if (status === 0) {
  691. if (xdata.find('destroy').length > 0) {
  692. // room has been destroyed
  693. member = {};
  694. jsxc.gui.window.postMessage({
  695. bid: room,
  696. direction: jsxc.Message.SYS,
  697. msg: $.t('This_room_has_been_closed')
  698. });
  699. self.close(room);
  700. } else {
  701. delete member[nickname];
  702. self.removeMember(room, nickname);
  703. var newNickname = xdata.find('item').attr('nick');
  704. if (codes.indexOf('303') > -1 && newNickname) {
  705. // user changed his nickname
  706. newNickname = Strophe.unescapeNode(newNickname);
  707. // prevent to display enter message
  708. member[newNickname] = {};
  709. jsxc.gui.window.postMessage({
  710. bid: room,
  711. direction: jsxc.Message.SYS,
  712. msg: $.t('is_now_known_as', {
  713. oldNickname: nickname,
  714. newNickname: newNickname,
  715. escapeInterpolation: true
  716. })
  717. });
  718. } else if (codes.length === 0 || (codes.length === 1 && codes.indexOf('110') > -1)) {
  719. // normal user exit
  720. jsxc.gui.window.postMessage({
  721. bid: room,
  722. direction: jsxc.Message.SYS,
  723. msg: $.t('left_the_building', {
  724. nickname: nickname,
  725. escapeInterpolation: true
  726. })
  727. });
  728. }
  729. }
  730. } else {
  731. // new member joined
  732. if (!member[nickname] && own[room]) {
  733. jsxc.gui.window.postMessage({
  734. bid: room,
  735. direction: jsxc.Message.SYS,
  736. msg: $.t('entered_the_room', {
  737. nickname: nickname,
  738. escapeInterpolation: true
  739. })
  740. });
  741. }
  742. member[nickname] = {
  743. jid: jid,
  744. status: status,
  745. roomJid: from,
  746. affiliation: xdata.find('item').attr('affiliation'),
  747. role: xdata.find('item').attr('role')
  748. };
  749. self.insertMember(room, nickname, member[nickname]);
  750. }
  751. jsxc.storage.setUserItem('member', room, member);
  752. $.each(codes, function(index, code) {
  753. // call code functions and trigger event
  754. if (typeof self.onStatus[code] === 'function') {
  755. self.onStatus[code].call(this, room, nickname, member[nickname] || {}, xdata);
  756. }
  757. $(document).trigger('status.muc.jsxc', [code, room, nickname, member[nickname] || {}, presence]);
  758. });
  759. return true;
  760. },
  761. /**
  762. * Handle group chat presence errors.
  763. *
  764. * @memberOf jsxc.muc
  765. * @param event
  766. * @param {string} from Jid
  767. * @param {string} presence Presence stanza
  768. * @returns {Boolean} Returns true on success
  769. */
  770. onPresenceError: function(event, from, presence) {
  771. var self = jsxc.muc;
  772. var xdata = $(presence).find('x[xmlns="' + Strophe.NS.MUC + '"]');
  773. var room = jsxc.jidToBid(from);
  774. if (xdata.length === 0 || self.conn.muc.roomNames.indexOf(room) < 0) {
  775. return true;
  776. }
  777. var error = $(presence).find('error');
  778. var condition = error.children()[0].tagName;
  779. jsxc.debug('[muc][error]', condition);
  780. $(document).trigger('error.muc.jsxc', [condition, room]);
  781. return true;
  782. },
  783. /**
  784. * Handle status codes. Every function gets room jid, nickname, member data and xdata.
  785. *
  786. * @memberOf jsxc.muc
  787. */
  788. onStatus: {
  789. /** Inform user that presence refers to itself */
  790. 110: function(room, nickname, data) {
  791. var self = jsxc.muc;
  792. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  793. own[room] = nickname;
  794. jsxc.storage.setUserItem('ownNicknames', own);
  795. if (data.affiliation === self.CONST.AFFILIATION.OWNER) {
  796. jsxc.gui.window.get(room).find('.jsxc_destroy').show();
  797. }
  798. var roomdata = jsxc.storage.getUserItem('buddy', room);
  799. if (roomdata.state === self.CONST.ROOMSTATE.INIT) {
  800. roomdata.state = self.CONST.ROOMSTATE.ENTERED;
  801. jsxc.storage.setUserItem('buddy', room, roomdata);
  802. }
  803. },
  804. /** Inform occupants that room logging is now enabled */
  805. 170: function(room) {
  806. jsxc.gui.window.postMessage({
  807. bid: room,
  808. direction: jsxc.Message.SYS,
  809. msg: $.t('Room_logging_is_enabled')
  810. });
  811. },
  812. /** Inform occupants that room logging is now disabled */
  813. 171: function(room) {
  814. jsxc.gui.window.postMessage({
  815. bid: room,
  816. direction: jsxc.Message.SYS,
  817. msg: $.t('Room_logging_is_disabled')
  818. });
  819. },
  820. /** Inform occupants that the room is now non-anonymous */
  821. 172: function(room) {
  822. jsxc.gui.window.postMessage({
  823. bid: room,
  824. direction: jsxc.Message.SYS,
  825. msg: $.t('Room_is_now_non-anoymous')
  826. });
  827. },
  828. /** Inform occupants that the room is now semi-anonymous */
  829. 173: function(room) {
  830. jsxc.gui.window.postMessage({
  831. bid: room,
  832. direction: jsxc.Message.SYS,
  833. msg: $.t('Room_is_now_semi-anonymous')
  834. });
  835. },
  836. /** Inform user that a new room has been created */
  837. 201: function(room) {
  838. var self = jsxc.muc;
  839. var roomdata = jsxc.storage.getUserItem('buddy', room) || {};
  840. if (roomdata.autojoin && roomdata.config === self.CONST.ROOMCONFIG.INSTANT) {
  841. self.conn.muc.createInstantRoom(room);
  842. } else if (roomdata.autojoin && typeof roomdata.config !== 'undefined' && roomdata.config !== null) {
  843. self.conn.muc.saveConfiguration(room, roomdata.config, function() {
  844. jsxc.debug('Cached room configuration saved.');
  845. }, function() {
  846. jsxc.warn('Could not save cached room configuration.');
  847. //@TODO display error
  848. });
  849. } else {
  850. jsxc.gui.showSelectionDialog({
  851. header: $.t('Room_creation'),
  852. msg: $.t('Do_you_want_to_change_the_default_room_configuration'),
  853. primary: {
  854. label: $.t('Default'),
  855. cb: function() {
  856. jsxc.gui.dialog.close();
  857. self.conn.muc.createInstantRoom(room);
  858. jsxc.storage.updateUserItem('buddy', room, 'config', self.CONST.ROOMCONFIG.INSTANT);
  859. }
  860. },
  861. option: {
  862. label: $.t('Change'),
  863. cb: function() {
  864. self.showRoomConfiguration(room);
  865. }
  866. }
  867. });
  868. }
  869. },
  870. /** Inform user that he or she has been banned */
  871. 301: function(room, nickname, data, xdata) {
  872. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  873. if (own[room] === nickname) {
  874. jsxc.muc.close(room);
  875. jsxc.gui.window.postMessage({
  876. bid: room,
  877. direction: jsxc.Message.SYS,
  878. msg: $.t('muc_removed_banned')
  879. });
  880. jsxc.muc.postReason(room, xdata);
  881. } else {
  882. jsxc.gui.window.postMessage({
  883. bid: room,
  884. direction: jsxc.Message.SYS,
  885. msg: $.t('muc_removed_info_banned', {
  886. nickname: nickname,
  887. escapeInterpolation: true
  888. })
  889. });
  890. }
  891. },
  892. /** Inform user that he or she has been kicked */
  893. 307: function(room, nickname, data, xdata) {
  894. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  895. if (own[room] === nickname) {
  896. jsxc.muc.close(room);
  897. jsxc.gui.window.postMessage({
  898. bid: room,
  899. direction: jsxc.Message.SYS,
  900. msg: $.t('muc_removed_kicked')
  901. });
  902. jsxc.muc.postReason(room, xdata);
  903. } else {
  904. jsxc.gui.window.postMessage({
  905. bid: room,
  906. direction: jsxc.Message.SYS,
  907. msg: $.t('muc_removed_info_kicked', {
  908. nickname: nickname,
  909. escapeInterpolation: true
  910. })
  911. });
  912. }
  913. },
  914. /** Inform user that he or she is beeing removed from the room because of an affiliation change */
  915. 321: function(room, nickname) {
  916. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  917. if (own[room] === nickname) {
  918. jsxc.muc.close(room);
  919. jsxc.gui.window.postMessage({
  920. bid: room,
  921. direction: jsxc.Message.SYS,
  922. msg: $.t('muc_removed_affiliation')
  923. });
  924. } else {
  925. jsxc.gui.window.postMessage({
  926. bid: room,
  927. direction: jsxc.Message.SYS,
  928. msg: $.t('muc_removed_info_affiliation', {
  929. nickname: nickname,
  930. escapeInterpolation: true
  931. })
  932. });
  933. }
  934. },
  935. /**
  936. * Inform user that he or she is beeing removed from the room because the room has been
  937. * changed to members-only and the user is not a member
  938. */
  939. 322: function(room, nickname) {
  940. var own = jsxc.storage.getUserItem('ownNicknames') || {};
  941. if (own[room] === nickname) {
  942. jsxc.muc.close(room);
  943. jsxc.gui.window.postMessage({
  944. bid: room,
  945. direction: jsxc.Message.SYS,
  946. msg: $.t('muc_removed_membersonly')
  947. });
  948. } else {
  949. jsxc.gui.window.postMessage({
  950. bid: room,
  951. direction: jsxc.Message.SYS,
  952. msg: $.t('muc_removed_info_membersonly', {
  953. nickname: nickname,
  954. escapeInterpolation: true
  955. })
  956. });
  957. }
  958. },
  959. /**
  960. * Inform user that he or she is beeing removed from the room because the MUC service
  961. * is being shut down
  962. */
  963. 332: function(room) {
  964. jsxc.muc.close(room);
  965. jsxc.gui.window.postMessage({
  966. bid: room,
  967. direction: jsxc.Message.SYS,
  968. msg: $.t('muc_removed_shutdown')
  969. });
  970. }
  971. },
  972. /**
  973. * Extract reason from xdata and if available post it to room.
  974. *
  975. * @memberOf jsxc.muc
  976. * @param {string} room Room jid
  977. * @param {jQuery} xdata Xdata
  978. */
  979. postReason: function(room, xdata) {
  980. var actor = {
  981. name: xdata.find('actor').attr('nick'),
  982. jid: xdata.find('actor').attr('jid')
  983. };
  984. var reason = xdata.find('reason').text();
  985. if (reason !== '') {
  986. reason = $.t('Reason') + ': ' + reason;
  987. if (typeof actor.name === 'string' || typeof actor.jid === 'string') {
  988. jsxc.gui.window.postMessage({
  989. bid: room,
  990. direction: jsxc.Message.IN,
  991. msg: reason,
  992. sender: actor
  993. });
  994. } else {
  995. jsxc.gui.window.postMessage({
  996. bid: room,
  997. direction: jsxc.Message.SYS,
  998. msg: reason
  999. });
  1000. }
  1001. }
  1002. },
  1003. /**
  1004. * Insert member to room member list.
  1005. *
  1006. * @memberOf jsxc.muc
  1007. * @param {string} room Room jid
  1008. * @param {string} nickname Nickname
  1009. * @param {string} memberdata Member data
  1010. */
  1011. insertMember: function(room, nickname, memberdata) {
  1012. var win = jsxc.gui.window.get(room);
  1013. var jid = memberdata.jid;
  1014. var ownBid = jsxc.jidToBid(jsxc.storage.getItem('jid'));
  1015. var m = win.find('.jsxc_memberlist li[data-nickname="' + nickname + '"]');
  1016. if (m.length === 0) {
  1017. var title = jsxc.escapeHTML(nickname);
  1018. m = $('<li><div class="jsxc_avatar"></div><div class="jsxc_name"/></li>');
  1019. m.attr('data-nickname', nickname);
  1020. win.find('.jsxc_memberlist ul').append(m);
  1021. if (typeof jid === 'string') {
  1022. m.find('.jsxc_name').text(jsxc.jidToBid(jid));
  1023. title = title + '\n' + jsxc.jidToBid(jid);
  1024. var data = jsxc.storage.getUserItem('buddy', jsxc.jidToBid(jid));
  1025. if (data !== null && typeof data === 'object') {
  1026. jsxc.gui.avatar.update(m, jsxc.jidToBid(jid), data.avatar);
  1027. } else if (jsxc.jidToBid(jid) === ownBid) {
  1028. jsxc.gui.avatar.update(m, jsxc.jidToBid(jid), 'own');
  1029. }
  1030. } else {
  1031. m.find('.jsxc_name').text(nickname);
  1032. jsxc.gui.avatarPlaceholder(m.find('.jsxc_avatar'), nickname);
  1033. }
  1034. m.attr('title', title);
  1035. }
  1036. },
  1037. /**
  1038. * Remove member from room member list.
  1039. *
  1040. * @memberOf jsxc.muc
  1041. * @param {string} room Room jid
  1042. * @param {string} nickname Nickname
  1043. */
  1044. removeMember: function(room, nickname) {
  1045. var win = jsxc.gui.window.get(room);
  1046. var m = win.find('.jsxc_memberlist li[data-nickname="' + nickname + '"]');
  1047. if (m.length > 0) {
  1048. m.remove();
  1049. }
  1050. },
  1051. /**
  1052. * Scroll or update member list position.
  1053. *
  1054. * @memberOf jsxc.muc
  1055. * @param {string} room Room jid
  1056. * @param {integer} offset =0: update position; >0: Scroll to left; <0: Scroll to right
  1057. */
  1058. scrollMemberListBy: function(room, offset) {
  1059. var win = jsxc.gui.window.get(room);
  1060. if (win.find('.jsxc_memberlist').hasClass('jsxc_expand')) {
  1061. return;
  1062. }
  1063. var el = win.find('.jsxc_memberlist ul:first');
  1064. var scrollWidth = el.width();
  1065. var width = win.find('.jsxc_memberlist').width();
  1066. var left = parseInt(el.css('left'));
  1067. left = (isNaN(left)) ? 0 - offset : left - offset;
  1068. if (scrollWidth < width || left > 0) {
  1069. left = 0;
  1070. } else if (left < width - scrollWidth) {
  1071. left = width - scrollWidth;
  1072. }
  1073. el.css('left', left + 'px');
  1074. },
  1075. /**
  1076. * Empty member list.
  1077. *
  1078. * @memberOf jsxc.muc
  1079. * @param {string} room Room jid
  1080. */
  1081. emptyMembers: function(room) {
  1082. var win = jsxc.gui.window.get(room);
  1083. win.find('.jsxc_memberlist').empty();
  1084. jsxc.storage.setUserItem('member', room, {});
  1085. },
  1086. /**
  1087. * Handle incoming group chat message.
  1088. *
  1089. * @private
  1090. * @memberOf jsxc.muc
  1091. * @param {string} message Message stanza
  1092. * @returns {boolean} True on success
  1093. */
  1094. onGroupchatMessage: function(message) {
  1095. var id = $(message).attr('id');
  1096. if (id && jsxc.el_exists(jsxc.Message.getDOM(id))) {
  1097. // ignore own incoming messages
  1098. return true;
  1099. }
  1100. var from = $(message).attr('from');
  1101. var body = $(message).find('body:first').text();
  1102. var room = jsxc.jidToBid(from);
  1103. var nickname = Strophe.unescapeNode(Strophe.getResourceFromJid(from));
  1104. if (body !== '') {
  1105. var delay = $(message).find('delay[xmlns="urn:xmpp:delay"]');
  1106. var stamp = (delay.length > 0) ? new Date(delay.attr('stamp')) : new Date();
  1107. stamp = stamp.getTime();
  1108. var member = jsxc.storage.getUserItem('member', room) || {};
  1109. var sender = {};
  1110. sender.name = nickname;
  1111. if (member[nickname] && typeof member[nickname].jid === 'string') {
  1112. sender.jid = member[nickname].jid;
  1113. }
  1114. jsxc.gui.window.init(room);
  1115. jsxc.gui.window.postMessage({
  1116. bid: room,
  1117. direction: jsxc.Message.IN,
  1118. msg: body,
  1119. stamp: stamp,
  1120. sender: sender
  1121. });
  1122. }
  1123. var subject = $(message).find('subject');
  1124. if (subject.length > 0) {
  1125. var roomdata = jsxc.storage.getUserItem('buddy', room);
  1126. roomdata.subject = subject.text();
  1127. jsxc.storage.setUserItem('buddy', room, roomdata);
  1128. jsxc.gui.window.postMessage({
  1129. bid: room,
  1130. direction: jsxc.Message.SYS,
  1131. msg: $.t('changed_subject_to', {
  1132. nickname: nickname,
  1133. subject: subject.text()
  1134. })
  1135. });
  1136. }
  1137. return true;
  1138. },
  1139. /**
  1140. * Prepare group chat roster item.
  1141. *
  1142. * @private
  1143. * @memberOf jsxc.muc
  1144. * @param event
  1145. * @param {string} room Room jid
  1146. * @param {object} data Room data
  1147. * @param {jQuery} bud Roster item
  1148. */
  1149. onAddRoster: function(event, room, data, bud) {
  1150. var self = jsxc.muc;
  1151. if (data.type !== 'groupchat') {
  1152. return;
  1153. }
  1154. var bo = $('<a>');
  1155. $('<span>').addClass('jsxc_icon jsxc_bookmarkicon').appendTo(bo);
  1156. $('<span>').text($.t('Bookmark')).appendTo(bo);
  1157. bo.addClass('jsxc_bookmarkOptions');
  1158. bo.click(function(ev) {
  1159. ev.preventDefault();
  1160. jsxc.xmpp.bookmarks.showDialog(room);
  1161. return false;
  1162. });
  1163. bud.find('.jsxc_menu ul').append($('<li>').append(bo));
  1164. if (data.bookmarked) {
  1165. bud.addClass('jsxc_bookmarked');
  1166. }
  1167. bud.off('click').click(function() {
  1168. var data = jsxc.storage.getUserItem('buddy', room);
  1169. if (data.state === self.CONST.ROOMSTATE.INIT || data.state === self.CONST.ROOMSTATE.EXITED) {
  1170. self.showJoinChat();
  1171. $('#jsxc_room').val(Strophe.getNodeFromJid(data.jid));
  1172. $('#jsxc_nickname').val(data.nickname);
  1173. $('#jsxc_bookmark').prop('checked', data.bookmarked);
  1174. $('#jsxc_autojoin').prop('checked', data.autojoin);
  1175. $('#jsxc_dialog .jsxc_bookmark').hide();
  1176. } else {
  1177. jsxc.gui.window.open(room);
  1178. }
  1179. });
  1180. bud.find('.jsxc_delete').click(function() {
  1181. if (data.bookmarked) {
  1182. jsxc.xmpp.bookmarks.delete(room);
  1183. }
  1184. self.leave(room);
  1185. return false;
  1186. });
  1187. },
  1188. /**
  1189. * Some helper functions.
  1190. *
  1191. * @type {Object}
  1192. */
  1193. helper: {
  1194. /**
  1195. * Convert x:data form to html.
  1196. *
  1197. * @param {Strophe.x.Form} form - x:data form
  1198. * @return {jQuery} jQuery representation of x:data field
  1199. */
  1200. formToHTML: function(form) {
  1201. if (!(form instanceof Strophe.x.Form)) {
  1202. return;
  1203. }
  1204. var html = $('<form>');
  1205. html.attr('data-type', form.type);
  1206. html.addClass('form-horizontal');
  1207. if (form.title) {
  1208. html.append("<h3>" + form.title + "</h3>");
  1209. }
  1210. if (form.instructions) {
  1211. html.append("<p>" + form.instructions + "</p>");
  1212. }
  1213. if (form.fields.length > 0) {
  1214. var i;
  1215. for (i = 0; i < form.fields.length; i++) {
  1216. html.append(jsxc.muc.helper.fieldToHtml(form.fields[i]));
  1217. }
  1218. }
  1219. return $('<div>').append(html).html();
  1220. },
  1221. /**
  1222. * Convert x:data field to html.
  1223. *
  1224. * @param {Strophe.x.Field} field - x:data field
  1225. * @return {html} html representation of x:data field
  1226. */
  1227. fieldToHtml: function(field) {
  1228. var self = field || this;
  1229. field = null;
  1230. var el, val, opt, i, o, j, k, txt, line, _ref2;
  1231. var id = "Strophe.x.Field-" + self['type'] + "-" + self['var'];
  1232. var html = $('<div>');
  1233. html.addClass('form-group');
  1234. if (self.label) {
  1235. var label = $('<label>');
  1236. label.attr('for', id);
  1237. label.addClass('col-sm-6 control-label');
  1238. label.text(self.label);
  1239. label.appendTo(html);
  1240. }
  1241. switch (self.type.toLowerCase()) {
  1242. case 'list-single':
  1243. case 'list-multi':
  1244. el = $('<select>');
  1245. if (self.type === 'list-multi') {
  1246. el.attr('multiple', 'multiple');
  1247. }
  1248. for (i = 0; i < self.options.length; i++) {
  1249. opt = self.options[i];
  1250. if (!opt) {
  1251. continue;
  1252. }
  1253. o = $(opt.toHTML());
  1254. for (j = 0; j < self.values.length; j++) {
  1255. k = self.values[j];
  1256. if (k.toString() === opt.value.toString()) {
  1257. o.attr('selected', 'selected');
  1258. }
  1259. }
  1260. o.appendTo(el);
  1261. }
  1262. break;
  1263. case 'text-multi':
  1264. case 'jid-multi':
  1265. el = $("<textarea>");
  1266. txt = ((function() {
  1267. var i, _results;
  1268. _results = [];
  1269. for (i = 0; i < self.values.length; i++) {
  1270. line = self.values[i];
  1271. _results.push(line);
  1272. }
  1273. return _results;
  1274. }).call(this)).join('\n');
  1275. if (txt) {
  1276. el.text(txt);
  1277. }
  1278. break;
  1279. case 'text-single':
  1280. case 'boolean':
  1281. case 'text-private':
  1282. case 'hidden':
  1283. case 'fixed':
  1284. case 'jid-single':
  1285. el = $("<input>");
  1286. if (self.values) {
  1287. el.attr('value', self.values[0]);
  1288. }
  1289. switch (self.type.toLowerCase()) {
  1290. case 'text-single':
  1291. el.attr('type', 'text');
  1292. el.attr('placeholder', self.desc);
  1293. el.addClass('form-control');
  1294. break;
  1295. case 'boolean':
  1296. el.attr('type', 'checkbox');
  1297. val = (_ref2 = self.values[0]) != null ? typeof _ref2.toString === "function" ? _ref2.toString() : void 0 : void 0;
  1298. if (val && (val === "true" || val === "1")) {
  1299. el.attr('checked', 'checked');
  1300. }
  1301. break;
  1302. case 'text-private':
  1303. el.attr('type', 'password');
  1304. el.addClass('form-control');
  1305. break;
  1306. case 'hidden':
  1307. el.attr('type', 'hidden');
  1308. break;
  1309. case 'fixed':
  1310. el.attr('type', 'text').attr('readonly', 'readonly');
  1311. el.addClass('form-control');
  1312. break;
  1313. case 'jid-single':
  1314. el.attr('type', 'email');
  1315. el.addClass('form-control');
  1316. }
  1317. break;
  1318. default:
  1319. el = $("<input type='text'>");
  1320. }
  1321. el.attr('id', id);
  1322. el.attr('name', self["var"]);
  1323. if (self.required) {
  1324. el.attr('required', self.required);
  1325. }
  1326. var inner = el;
  1327. el = $('<div>');
  1328. el.addClass('col-sm-6');
  1329. el.append(inner);
  1330. html.append(el);
  1331. return html.get(0);
  1332. }
  1333. },
  1334. isGroupchat: function(jid) {
  1335. var bid = jsxc.jidToBid(jid);
  1336. var userData = jsxc.storage.setUserItem('buddy', bid) || {};
  1337. return userData.type === 'groupchat';
  1338. }
  1339. };
  1340. $(document).on('init.window.jsxc', jsxc.muc.initWindow);
  1341. $(document).on('add.roster.jsxc', jsxc.muc.onAddRoster);
  1342. $(document).on('attached.jsxc', function() {
  1343. jsxc.muc.init();
  1344. });
  1345. $(document).one('connected.jsxc', function() {
  1346. jsxc.storage.removeUserItem('roomNames');
  1347. jsxc.storage.removeUserItem('ownNicknames');
  1348. });