2 var Marionette = require('backbone.marionette');
3 var ModalView = require('../modals/modal');
4 var Backbone = require('backbone');
5 var clientEnv = require('gitter-client-env');
6 var avatars = require('gitter-web-avatars');
7 var context = require('gitter-web-client-context');
8 var apiClient = require('../../components/api-client');
9 var Typeahead = require('../controls/typeahead');
10 var userSearchModels = require('../../collections/user-search');
11 var template = require('./tmpl/addPeople.hbs');
12 var userSearchItemTemplate = require('./tmpl/userSearchItem.hbs');
13 var itemTemplate = require('./tmpl/addPeopleItemView.hbs');
14 require('../behaviors/widgets');
16 var DEFAULT_AVATAR_UNTIL_AVATARS_SERVICE_ARRIVES = avatars.getDefault();
18 * Ridiculously sloppy regexp based email validator, let the server
19 * do the real validation
21 function isEmailAddress(string) {
22 return /^[^@]+@[^@]+\.[^@]+$/.test(string);
25 var RowView = Marionette.ItemView.extend({
27 'submit form': 'invite'
36 email: 'input[type=email]'
39 className: 'gtrPeopleRosterItem',
40 template: itemTemplate,
43 var model = this.model;
44 var email = this.ui.email.val().trim();
49 .post('/invites', { githubUsername: this.model.get('username'), email: email })
59 var message = e.friendlyMessage || 'Unable to invite user to Gitter';
60 self.trigger('invite:error', message);
65 var View = Marionette.CompositeView.extend({
66 childViewContainer: '.gtrPeopleAddRoster',
71 share: '.js-add-people-share',
72 loading: '.js-add-roster-loading',
73 validation: '#modal-failure',
74 success: '#modal-success'
77 initialize: function() {
78 if (!this.collection) {
79 var ResultsCollection = Backbone.Collection.extend({
80 comparator: function(a, b) {
81 return b.get('timeAdded') - a.get('timeAdded');
85 this.collection = new ResultsCollection();
88 this.listenTo(this, 'menuItemClicked', this.menuItemClicked);
91 onChildviewInviteError: function(childView, message) {
93 this.ui.loading.toggleClass('hide', true);
94 this.showValidationMessage(message);
97 selected: function(m) {
98 this.addUserToRoom(m);
99 this.typeahead.dropdown.hide();
102 menuItemClicked: function(button) {
106 window.location.hash = '#share';
116 * showMessage() slides the given element down then up
118 * el DOM Element - element to be animated
120 showMessage: function(el) {
121 el.slideDown('fast');
122 setTimeout(function() {
128 showValidationMessage: function(message) {
129 this.ui.validation.text(message);
130 this.showMessage(this.ui.validation);
133 showSuccessMessage: function(message) {
134 this.ui.success.text(message);
135 this.showMessage(this.ui.success);
138 handleError: function(/*res, status, message */) {
139 // TODO: what should go here?
143 * addUserToRoom() sends request and handles response of adding an user to a room
145 * m BackboneModel - the user to be added to the room
147 addUserToRoom: function(model) {
150 self.ui.loading.toggleClass('hide');
151 var username = model.get('username');
152 var email = model.get('email');
155 body = { githubUsername: username };
157 body = { email: email.trim() };
160 return apiClient.room
161 .post('/invites', body)
162 .then(function(invite) {
163 self.ui.loading.toggleClass('hide');
165 added: invite.status === 'added',
166 invited: invite.status === 'invited',
168 timeAdded: Date.now(),
171 username: (invite.user && invite.user.username) || username
174 self.collection.add(model);
175 self.typeahead.clear();
178 self.ui.loading.toggleClass('hide');
179 var message = e.friendlyMessage || 'Error';
181 // XXX: why not use the payment required status code for this?
182 if (message.match(/has reached its limit/)) {
183 self.dialog.showPremium();
186 self.typeahead.clear();
189 message = `Inviting a user by email is limited to ${
190 clientEnv['inviteEmailAbuseThresholdPerDay']
191 } per day, see #2153`;
194 message = model.get('username') + ' has already been invited';
201 timeAdded: Date.now(),
205 self.collection.add(model);
209 self.showValidationMessage(message);
213 onRender: function() {
216 setTimeout(function() {
217 self.ui.input.focus();
220 this.typeahead = new Typeahead({
221 collection: new userSearchModels.Collection(),
222 itemTemplate: userSearchItemTemplate,
223 el: this.ui.input[0],
224 autoSelector: function(input) {
226 var displayName = m.get('displayName');
227 var username = m.get('username');
230 (displayName && displayName.indexOf(input) >= 0) ||
231 (username && username.indexOf(input) >= 0)
235 fetch: function(input, collection, fetchSuccess) {
236 if (input.indexOf('@') >= 0) {
237 if (isEmailAddress(input)) {
238 this.collection.reset([
242 avatarUrlSmall: DEFAULT_AVATAR_UNTIL_AVATARS_SERVICE_ARRIVES,
243 avatarUrlMedium: DEFAULT_AVATAR_UNTIL_AVATARS_SERVICE_ARRIVES
247 this.collection.reset([]);
250 return fetchSuccess();
253 this.collection.fetch(
254 { data: { q: input } },
255 { add: true, remove: true, merge: true, success: fetchSuccess }
260 this.listenTo(this.typeahead, 'selected', this.selected);
263 onDestroy: function() {
264 if (this.typeahead) {
265 this.typeahead.destroy();
270 var modalButtons = [];
272 if (context.troupe().get('security') !== 'PRIVATE') {
276 text: 'Share this room',
277 className: 'modal--default__footer__link'
281 module.exports = ModalView.extend({
282 disableAutoFocus: true,
283 initialize: function(options) {
284 options = options || {};
285 options.title = options.title || 'Add people to this room';
287 ModalView.prototype.initialize.call(this, options);
288 this.view = new View(options);
290 menuItems: modalButtons