sharedialogview.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. /*
  2. * Copyright (c) 2015
  3. *
  4. * This file is licensed under the Affero General Public License version 3
  5. * or later.
  6. *
  7. * See the COPYING-README file.
  8. *
  9. */
  10. /* globals Handlebars */
  11. (function() {
  12. if(!OC.Share) {
  13. OC.Share = {};
  14. }
  15. /**
  16. * @class OCA.Share.ShareDialogView
  17. * @member {OC.Share.ShareItemModel} model
  18. * @member {jQuery} $el
  19. * @memberof OCA.Sharing
  20. * @classdesc
  21. *
  22. * Represents the GUI of the share dialogue
  23. *
  24. */
  25. var ShareDialogView = OC.Backbone.View.extend({
  26. /** @type {Object} **/
  27. _templates: {},
  28. /** @type {boolean} **/
  29. _showLink: true,
  30. /** @type {string} **/
  31. tagName: 'div',
  32. /** @type {OC.Share.ShareConfigModel} **/
  33. configModel: undefined,
  34. /** @type {object} **/
  35. resharerInfoView: undefined,
  36. /** @type {object} **/
  37. linkShareView: undefined,
  38. /** @type {object} **/
  39. shareeListView: undefined,
  40. /** @type {object} **/
  41. _lastSuggestions: undefined,
  42. /** @type {int} **/
  43. _pendingOperationsCount: 0,
  44. events: {
  45. 'focus .shareWithField': 'onShareWithFieldFocus',
  46. 'input .shareWithField': 'onShareWithFieldChanged',
  47. 'click .shareWithConfirm': '_confirmShare'
  48. },
  49. initialize: function(options) {
  50. var view = this;
  51. this.model.on('fetchError', function() {
  52. OC.Notification.showTemporary(t('core', 'Share details could not be loaded for this item.'));
  53. });
  54. if(!_.isUndefined(options.configModel)) {
  55. this.configModel = options.configModel;
  56. } else {
  57. throw 'missing OC.Share.ShareConfigModel';
  58. }
  59. this.configModel.on('change:isRemoteShareAllowed', function() {
  60. view.render();
  61. });
  62. this.configModel.on('change:isRemoteGroupShareAllowed', function() {
  63. view.render();
  64. });
  65. this.model.on('change:permissions', function() {
  66. view.render();
  67. });
  68. this.model.on('request', this._onRequest, this);
  69. this.model.on('sync', this._onEndRequest, this);
  70. var subViewOptions = {
  71. model: this.model,
  72. configModel: this.configModel
  73. };
  74. var subViews = {
  75. resharerInfoView: 'ShareDialogResharerInfoView',
  76. linkShareView: 'ShareDialogLinkShareView',
  77. shareeListView: 'ShareDialogShareeListView'
  78. };
  79. for(var name in subViews) {
  80. var className = subViews[name];
  81. this[name] = _.isUndefined(options[name])
  82. ? new OC.Share[className](subViewOptions)
  83. : options[name];
  84. }
  85. _.bindAll(this,
  86. 'autocompleteHandler',
  87. '_onSelectRecipient',
  88. 'onShareWithFieldChanged',
  89. 'onShareWithFieldFocus'
  90. );
  91. OC.Plugins.attach('OC.Share.ShareDialogView', this);
  92. },
  93. onShareWithFieldChanged: function() {
  94. var $el = this.$el.find('.shareWithField');
  95. if ($el.val().length < 2) {
  96. $el.removeClass('error').tooltip('hide');
  97. }
  98. },
  99. /* trigger search after the field was re-selected */
  100. onShareWithFieldFocus: function() {
  101. this.$el.find('.shareWithField').autocomplete("search");
  102. },
  103. _getSuggestions: function(searchTerm, perPage, model) {
  104. if (this._lastSuggestions &&
  105. this._lastSuggestions.searchTerm === searchTerm &&
  106. this._lastSuggestions.perPage === perPage &&
  107. this._lastSuggestions.model === model) {
  108. return this._lastSuggestions.promise;
  109. }
  110. var deferred = $.Deferred();
  111. $.get(
  112. OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
  113. {
  114. format: 'json',
  115. search: searchTerm,
  116. perPage: perPage,
  117. itemType: model.get('itemType')
  118. },
  119. function (result) {
  120. if (result.ocs.meta.statuscode === 100) {
  121. var filter = function(users, groups, remotes, remote_groups, emails, circles, rooms) {
  122. if (typeof(emails) === 'undefined') {
  123. emails = [];
  124. }
  125. if (typeof(circles) === 'undefined') {
  126. circles = [];
  127. }
  128. if (typeof(rooms) === 'undefined') {
  129. rooms = [];
  130. }
  131. var usersLength;
  132. var groupsLength;
  133. var remotesLength;
  134. var remoteGroupsLength;
  135. var emailsLength;
  136. var circlesLength;
  137. var roomsLength;
  138. var i, j;
  139. //Filter out the current user
  140. usersLength = users.length;
  141. for (i = 0; i < usersLength; i++) {
  142. if (users[i].value.shareWith === OC.currentUser) {
  143. users.splice(i, 1);
  144. break;
  145. }
  146. }
  147. // Filter out the owner of the share
  148. if (model.hasReshare()) {
  149. usersLength = users.length;
  150. for (i = 0 ; i < usersLength; i++) {
  151. if (users[i].value.shareWith === model.getReshareOwner()) {
  152. users.splice(i, 1);
  153. break;
  154. }
  155. }
  156. }
  157. var shares = model.get('shares');
  158. var sharesLength = shares.length;
  159. // Now filter out all sharees that are already shared with
  160. for (i = 0; i < sharesLength; i++) {
  161. var share = shares[i];
  162. if (share.share_type === OC.Share.SHARE_TYPE_USER) {
  163. usersLength = users.length;
  164. for (j = 0; j < usersLength; j++) {
  165. if (users[j].value.shareWith === share.share_with) {
  166. users.splice(j, 1);
  167. break;
  168. }
  169. }
  170. } else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) {
  171. groupsLength = groups.length;
  172. for (j = 0; j < groupsLength; j++) {
  173. if (groups[j].value.shareWith === share.share_with) {
  174. groups.splice(j, 1);
  175. break;
  176. }
  177. }
  178. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) {
  179. remotesLength = remotes.length;
  180. for (j = 0; j < remotesLength; j++) {
  181. if (remotes[j].value.shareWith === share.share_with) {
  182. remotes.splice(j, 1);
  183. break;
  184. }
  185. }
  186. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
  187. remoteGroupsLength = remote_groups.length;
  188. for (j = 0; j < remoteGroupsLength; j++) {
  189. if (remote_groups[j].value.shareWith === share.share_with) {
  190. remote_groups.splice(j, 1);
  191. break;
  192. }
  193. }
  194. } else if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
  195. emailsLength = emails.length;
  196. for (j = 0; j < emailsLength; j++) {
  197. if (emails[j].value.shareWith === share.share_with) {
  198. emails.splice(j, 1);
  199. break;
  200. }
  201. }
  202. } else if (share.share_type === OC.Share.SHARE_TYPE_CIRCLE) {
  203. circlesLength = circles.length;
  204. for (j = 0; j < circlesLength; j++) {
  205. if (circles[j].value.shareWith === share.share_with) {
  206. circles.splice(j, 1);
  207. break;
  208. }
  209. }
  210. } else if (share.share_type === OC.Share.SHARE_TYPE_ROOM) {
  211. roomsLength = rooms.length;
  212. for (j = 0; j < roomsLength; j++) {
  213. if (rooms[j].value.shareWith === share.share_with) {
  214. rooms.splice(j, 1);
  215. break;
  216. }
  217. }
  218. }
  219. }
  220. };
  221. filter(
  222. result.ocs.data.exact.users,
  223. result.ocs.data.exact.groups,
  224. result.ocs.data.exact.remotes,
  225. result.ocs.data.exact.remote_groups,
  226. result.ocs.data.exact.emails,
  227. result.ocs.data.exact.circles,
  228. result.ocs.data.exact.rooms
  229. );
  230. var exactUsers = result.ocs.data.exact.users;
  231. var exactGroups = result.ocs.data.exact.groups;
  232. var exactRemotes = result.ocs.data.exact.remotes;
  233. var exactRemoteGroups = result.ocs.data.exact.remote_groups;
  234. var exactEmails = [];
  235. if (typeof(result.ocs.data.emails) !== 'undefined') {
  236. exactEmails = result.ocs.data.exact.emails;
  237. }
  238. var exactCircles = [];
  239. if (typeof(result.ocs.data.circles) !== 'undefined') {
  240. exactCircles = result.ocs.data.exact.circles;
  241. }
  242. var exactRooms = [];
  243. if (typeof(result.ocs.data.rooms) !== 'undefined') {
  244. exactRooms = result.ocs.data.exact.rooms;
  245. }
  246. var exactMatches = exactUsers.concat(exactGroups).concat(exactRemotes).concat(exactRemoteGroups).concat(exactEmails).concat(exactCircles).concat(exactRooms);
  247. filter(
  248. result.ocs.data.users,
  249. result.ocs.data.groups,
  250. result.ocs.data.remotes,
  251. result.ocs.data.remote_groups,
  252. result.ocs.data.emails,
  253. result.ocs.data.circles,
  254. result.ocs.data.rooms
  255. );
  256. var users = result.ocs.data.users;
  257. var groups = result.ocs.data.groups;
  258. var remotes = result.ocs.data.remotes;
  259. var remoteGroups = result.ocs.data.remote_groups;
  260. var lookup = result.ocs.data.lookup;
  261. var emails = [];
  262. if (typeof(result.ocs.data.emails) !== 'undefined') {
  263. emails = result.ocs.data.emails;
  264. }
  265. var circles = [];
  266. if (typeof(result.ocs.data.circles) !== 'undefined') {
  267. circles = result.ocs.data.circles;
  268. }
  269. var rooms = [];
  270. if (typeof(result.ocs.data.rooms) !== 'undefined') {
  271. rooms = result.ocs.data.rooms;
  272. }
  273. var suggestions = exactMatches.concat(users).concat(groups).concat(remotes).concat(remoteGroups).concat(emails).concat(circles).concat(rooms).concat(lookup);
  274. function dynamicSort(property) {
  275. return function (a,b) {
  276. var aProperty = '';
  277. var bProperty = '';
  278. if (typeof a[property] !== 'undefined') {
  279. aProperty = a[property];
  280. }
  281. if (typeof b[property] !== 'undefined') {
  282. bProperty = b[property];
  283. }
  284. return (aProperty < bProperty) ? -1 : (aProperty > bProperty) ? 1 : 0;
  285. }
  286. }
  287. /**
  288. * Sort share entries by uuid to properly group them
  289. */
  290. var grouped = suggestions.sort(dynamicSort('uuid'));
  291. var previousUuid = null;
  292. var groupedLength = grouped.length;
  293. var result = [];
  294. /**
  295. * build the result array that only contains all contact entries from
  296. * merged contacts, if the search term matches its contact name
  297. */
  298. for (var i = 0; i < groupedLength; i++) {
  299. if (typeof grouped[i].uuid !== 'undefined' && grouped[i].uuid === previousUuid) {
  300. grouped[i].merged = true;
  301. }
  302. if (searchTerm === grouped[i].name || typeof grouped[i].merged === 'undefined') {
  303. result.push(grouped[i]);
  304. }
  305. previousUuid = grouped[i].uuid;
  306. }
  307. var moreResultsAvailable =
  308. (
  309. oc_config['sharing.maxAutocompleteResults'] > 0
  310. && Math.min(perPage, oc_config['sharing.maxAutocompleteResults'])
  311. <= Math.max(
  312. users.length + exactUsers.length,
  313. groups.length + exactGroups.length,
  314. remoteGroups.length + exactRemoteGroups.length,
  315. remotes.length + exactRemotes.length,
  316. emails.length + exactEmails.length,
  317. circles.length + exactCircles.length,
  318. rooms.length + exactRooms.length,
  319. lookup.length
  320. )
  321. );
  322. deferred.resolve(result, exactMatches, moreResultsAvailable);
  323. } else {
  324. deferred.reject(result.ocs.meta.message);
  325. }
  326. }
  327. ).fail(function() {
  328. deferred.reject();
  329. });
  330. this._lastSuggestions = {
  331. searchTerm: searchTerm,
  332. perPage: perPage,
  333. model: model,
  334. promise: deferred.promise()
  335. };
  336. return this._lastSuggestions.promise;
  337. },
  338. autocompleteHandler: function (search, response) {
  339. var $shareWithField = $('.shareWithField'),
  340. view = this,
  341. $loading = this.$el.find('.shareWithLoading'),
  342. $confirm = this.$el.find('.shareWithConfirm');
  343. var count = oc_config['sharing.minSearchStringLength'];
  344. if (search.term.trim().length < count) {
  345. var title = n('core',
  346. 'At least {count} character is needed for autocompletion',
  347. 'At least {count} characters are needed for autocompletion',
  348. count,
  349. { count: count }
  350. );
  351. $shareWithField.addClass('error')
  352. .attr('data-original-title', title)
  353. .tooltip('hide')
  354. .tooltip({
  355. placement: 'bottom',
  356. trigger: 'manual'
  357. })
  358. .tooltip('fixTitle')
  359. .tooltip('show');
  360. response();
  361. return;
  362. }
  363. $loading.removeClass('hidden');
  364. $loading.addClass('inlineblock');
  365. $confirm.addClass('hidden');
  366. this._pendingOperationsCount++;
  367. $shareWithField.removeClass('error')
  368. .tooltip('hide');
  369. var perPage = parseInt(oc_config['sharing.maxAutocompleteResults'], 10) || 200;
  370. this._getSuggestions(
  371. search.term.trim(),
  372. perPage,
  373. view.model
  374. ).done(function(suggestions, exactMatches, moreResultsAvailable) {
  375. view._pendingOperationsCount--;
  376. if (view._pendingOperationsCount === 0) {
  377. $loading.addClass('hidden');
  378. $loading.removeClass('inlineblock');
  379. $confirm.removeClass('hidden');
  380. }
  381. if (suggestions.length > 0) {
  382. $shareWithField
  383. .autocomplete("option", "autoFocus", true);
  384. response(suggestions);
  385. // show a notice that the list is truncated
  386. // this is the case if one of the search results is at least as long as the max result config option
  387. if(moreResultsAvailable) {
  388. var message = t('core', 'This list is maybe truncated - please refine your search term to see more results.');
  389. $('.ui-autocomplete').append('<li class="autocomplete-note">' + message + '</li>');
  390. }
  391. } else {
  392. var title = t('core', 'No users or groups found for {search}', {search: $shareWithField.val()});
  393. if (!view.configModel.get('allowGroupSharing')) {
  394. title = t('core', 'No users found for {search}', {search: $('.shareWithField').val()});
  395. }
  396. $shareWithField.addClass('error')
  397. .attr('data-original-title', title)
  398. .tooltip('hide')
  399. .tooltip({
  400. placement: 'bottom',
  401. trigger: 'manual'
  402. })
  403. .tooltip('fixTitle')
  404. .tooltip('show');
  405. response();
  406. }
  407. }).fail(function(message) {
  408. view._pendingOperationsCount--;
  409. if (view._pendingOperationsCount === 0) {
  410. $loading.addClass('hidden');
  411. $loading.removeClass('inlineblock');
  412. $confirm.removeClass('hidden');
  413. }
  414. if (message) {
  415. OC.Notification.showTemporary(t('core', 'An error occurred ("{message}"). Please try again', { message: message }));
  416. } else {
  417. OC.Notification.showTemporary(t('core', 'An error occurred. Please try again'));
  418. }
  419. });
  420. },
  421. autocompleteRenderItem: function(ul, item) {
  422. var icon = 'icon-user';
  423. var text = escapeHTML(item.label);
  424. var description = '';
  425. var type = '';
  426. var getTranslatedType = function(type) {
  427. switch (type) {
  428. case 'HOME':
  429. return t('core', 'Home');
  430. case 'WORK':
  431. return t('core', 'Work');
  432. case 'OTHER':
  433. return t('core', 'Other');
  434. default:
  435. return '' + type;
  436. }
  437. };
  438. if (typeof item.type !== 'undefined' && item.type !== null) {
  439. type = getTranslatedType(item.type) + ' ';
  440. }
  441. if (typeof item.name !== 'undefined') {
  442. text = escapeHTML(item.name);
  443. }
  444. if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) {
  445. icon = 'icon-contacts-dark';
  446. } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) {
  447. icon = 'icon-shared';
  448. description += item.value.shareWith;
  449. } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
  450. text = t('core', '{sharee} (remote group)', { sharee: text }, undefined, { escape: false });
  451. icon = 'icon-shared';
  452. description += item.value.shareWith;
  453. } else if (item.value.shareType === OC.Share.SHARE_TYPE_EMAIL) {
  454. icon = 'icon-mail';
  455. description += item.value.shareWith;
  456. } else if (item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
  457. text = t('core', '{sharee} ({type}, {owner})', {sharee: text, type: item.value.circleInfo, owner: item.value.circleOwner}, undefined, {escape: false});
  458. icon = 'icon-circle';
  459. } else if (item.value.shareType === OC.Share.SHARE_TYPE_ROOM) {
  460. icon = 'icon-talk';
  461. }
  462. var insert = $("<div class='share-autocomplete-item'/>");
  463. if (item.merged) {
  464. insert.addClass('merged');
  465. text = item.value.shareWith;
  466. description = type;
  467. } else {
  468. var avatar = $("<div class='avatardiv'></div>").appendTo(insert);
  469. if (item.value.shareType === OC.Share.SHARE_TYPE_USER || item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) {
  470. avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label);
  471. } else {
  472. if (typeof item.uuid === 'undefined') {
  473. item.uuid = text;
  474. }
  475. avatar.imageplaceholder(item.uuid, text, 32);
  476. }
  477. description = type + description;
  478. }
  479. if (description !== '') {
  480. insert.addClass('with-description');
  481. }
  482. $("<div class='autocomplete-item-text'></div>")
  483. .html(
  484. text.replace(
  485. new RegExp(this.term, "gi"),
  486. "<span class='ui-state-highlight'>$&</span>")
  487. + '<span class="autocomplete-item-details">' + description + '</span>'
  488. )
  489. .appendTo(insert);
  490. insert.attr('title', item.value.shareWith);
  491. insert.append('<span class="icon '+icon+'" title="' + text + '"></span>');
  492. insert = $("<a>")
  493. .append(insert);
  494. return $("<li>")
  495. .addClass((item.value.shareType === OC.Share.SHARE_TYPE_GROUP) ? 'group' : 'user')
  496. .append(insert)
  497. .appendTo(ul);
  498. },
  499. _onSelectRecipient: function(e, s) {
  500. var self = this;
  501. if (e.keyCode == 9) {
  502. e.preventDefault();
  503. if (typeof s.item.name !== 'undefined') {
  504. e.target.value = s.item.name;
  505. } else {
  506. e.target.value = s.item.label;
  507. }
  508. setTimeout(function() {
  509. $(e.target).attr('disabled', false)
  510. .autocomplete('search', $(e.target).val());
  511. }, 0);
  512. return false;
  513. }
  514. e.preventDefault();
  515. // Ensure that the keydown handler for the input field is not
  516. // called; otherwise it would try to add the recipient again, which
  517. // would fail.
  518. e.stopImmediatePropagation();
  519. $(e.target).attr('disabled', true)
  520. .val(s.item.label);
  521. var $loading = this.$el.find('.shareWithLoading');
  522. var $confirm = this.$el.find('.shareWithConfirm');
  523. $loading.removeClass('hidden');
  524. $loading.addClass('inlineblock');
  525. $confirm.addClass('hidden');
  526. this._pendingOperationsCount++;
  527. this.model.addShare(s.item.value, {success: function() {
  528. // Adding a share changes the suggestions.
  529. self._lastSuggestions = undefined;
  530. $(e.target).val('')
  531. .attr('disabled', false);
  532. self._pendingOperationsCount--;
  533. if (self._pendingOperationsCount === 0) {
  534. $loading.addClass('hidden');
  535. $loading.removeClass('inlineblock');
  536. $confirm.removeClass('hidden');
  537. }
  538. }, error: function(obj, msg) {
  539. OC.Notification.showTemporary(msg);
  540. $(e.target).attr('disabled', false)
  541. .autocomplete('search', $(e.target).val());
  542. self._pendingOperationsCount--;
  543. if (self._pendingOperationsCount === 0) {
  544. $loading.addClass('hidden');
  545. $loading.removeClass('inlineblock');
  546. $confirm.removeClass('hidden');
  547. }
  548. }});
  549. },
  550. _confirmShare: function() {
  551. var self = this;
  552. var $shareWithField = $('.shareWithField');
  553. var $loading = this.$el.find('.shareWithLoading');
  554. var $confirm = this.$el.find('.shareWithConfirm');
  555. $loading.removeClass('hidden');
  556. $loading.addClass('inlineblock');
  557. $confirm.addClass('hidden');
  558. this._pendingOperationsCount++;
  559. $shareWithField.prop('disabled', true);
  560. // Disabling the autocompletion does not clear its search timeout;
  561. // removing the focus from the input field does, but only if the
  562. // autocompletion is not disabled when the field loses the focus.
  563. // Thus, the field has to be disabled before disabling the
  564. // autocompletion to prevent an old pending search result from
  565. // appearing once the field is enabled again.
  566. $shareWithField.autocomplete('close');
  567. $shareWithField.autocomplete('disable');
  568. var restoreUI = function() {
  569. self._pendingOperationsCount--;
  570. if (self._pendingOperationsCount === 0) {
  571. $loading.addClass('hidden');
  572. $loading.removeClass('inlineblock');
  573. $confirm.removeClass('hidden');
  574. }
  575. $shareWithField.prop('disabled', false);
  576. $shareWithField.focus();
  577. };
  578. var perPage = parseInt(oc_config['sharing.maxAutocompleteResults'], 10) || 200;
  579. var onlyExactMatches = true;
  580. this._getSuggestions(
  581. $shareWithField.val(),
  582. perPage,
  583. this.model,
  584. onlyExactMatches
  585. ).done(function(suggestions, exactMatches) {
  586. if (suggestions.length === 0) {
  587. restoreUI();
  588. $shareWithField.autocomplete('enable');
  589. // There is no need to show an error message here; it will
  590. // be automatically shown when the autocomplete is activated
  591. // again (due to the focus on the field) and it finds no
  592. // matches.
  593. return;
  594. }
  595. if (exactMatches.length !== 1) {
  596. restoreUI();
  597. $shareWithField.autocomplete('enable');
  598. return;
  599. }
  600. var actionSuccess = function() {
  601. // Adding a share changes the suggestions.
  602. self._lastSuggestions = undefined;
  603. $shareWithField.val('');
  604. restoreUI();
  605. $shareWithField.autocomplete('enable');
  606. };
  607. var actionError = function(obj, msg) {
  608. restoreUI();
  609. $shareWithField.autocomplete('enable');
  610. OC.Notification.showTemporary(msg);
  611. };
  612. self.model.addShare(exactMatches[0].value, {
  613. success: actionSuccess,
  614. error: actionError
  615. });
  616. }).fail(function(message) {
  617. restoreUI();
  618. $shareWithField.autocomplete('enable');
  619. // There is no need to show an error message here; it will be
  620. // automatically shown when the autocomplete is activated again
  621. // (due to the focus on the field) and getting the suggestions
  622. // fail.
  623. });
  624. },
  625. _toggleLoading: function(state) {
  626. this._loading = state;
  627. this.$el.find('.subView').toggleClass('hidden', state);
  628. this.$el.find('.loading').toggleClass('hidden', !state);
  629. },
  630. _onRequest: function() {
  631. // only show the loading spinner for the first request (for now)
  632. if (!this._loadingOnce) {
  633. this._toggleLoading(true);
  634. }
  635. },
  636. _onEndRequest: function() {
  637. var self = this;
  638. this._toggleLoading(false);
  639. if (!this._loadingOnce) {
  640. this._loadingOnce = true;
  641. // the first time, focus on the share field after the spinner disappeared
  642. if (!OC.Util.isIE()) {
  643. _.defer(function () {
  644. self.$('.shareWithField').focus();
  645. });
  646. }
  647. }
  648. },
  649. render: function() {
  650. var self = this;
  651. var baseTemplate = OC.Share.Templates['sharedialogview'];
  652. this.$el.html(baseTemplate({
  653. cid: this.cid,
  654. shareLabel: t('core', 'Share'),
  655. sharePlaceholder: this._renderSharePlaceholderPart(),
  656. isSharingAllowed: this.model.sharePermissionPossible()
  657. }));
  658. var $shareField = this.$el.find('.shareWithField');
  659. if ($shareField.length) {
  660. var shareFieldKeydownHandler = function(event) {
  661. if (event.keyCode !== 13) {
  662. return true;
  663. }
  664. self._confirmShare();
  665. return false;
  666. };
  667. $shareField.autocomplete({
  668. minLength: 1,
  669. delay: 750,
  670. focus: function(event) {
  671. event.preventDefault();
  672. },
  673. source: this.autocompleteHandler,
  674. select: this._onSelectRecipient,
  675. open: function() {
  676. var autocomplete = $(this).autocomplete('widget');
  677. var numberOfItems = autocomplete.find('li').size();
  678. autocomplete.removeClass('item-count-1');
  679. autocomplete.removeClass('item-count-2');
  680. if (numberOfItems <= 2) {
  681. autocomplete.addClass('item-count-' + numberOfItems);
  682. }
  683. }
  684. }).data('ui-autocomplete')._renderItem = this.autocompleteRenderItem;
  685. $shareField.on('keydown', null, shareFieldKeydownHandler);
  686. }
  687. this.resharerInfoView.$el = this.$el.find('.resharerInfoView');
  688. this.resharerInfoView.render();
  689. this.linkShareView.$el = this.$el.find('.linkShareView');
  690. this.linkShareView.render();
  691. this.shareeListView.$el = this.$el.find('.shareeListView');
  692. this.shareeListView.render();
  693. this.$el.find('.hasTooltip').tooltip();
  694. return this;
  695. },
  696. /**
  697. * sets whether share by link should be displayed or not. Default is
  698. * true.
  699. *
  700. * @param {bool} showLink
  701. */
  702. setShowLink: function(showLink) {
  703. this._showLink = (typeof showLink === 'boolean') ? showLink : true;
  704. this.linkShareView.showLink = this._showLink;
  705. },
  706. _renderSharePlaceholderPart: function () {
  707. var allowRemoteSharing = this.configModel.get('isRemoteShareAllowed');
  708. var allowMailSharing = this.configModel.get('isMailShareAllowed');
  709. if (!allowRemoteSharing && allowMailSharing) {
  710. return t('core', 'Name or email address...');
  711. }
  712. if (allowRemoteSharing && !allowMailSharing) {
  713. return t('core', 'Name or federated cloud ID...');
  714. }
  715. if (allowRemoteSharing && allowMailSharing) {
  716. return t('core', 'Name, federated cloud ID or email address...');
  717. }
  718. return t('core', 'Name...');
  719. },
  720. });
  721. OC.Share.ShareDialogView = ShareDialogView;
  722. })();