sharedialogview.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. var TEMPLATE_BASE =
  16. '<div class="resharerInfoView subView"></div>' +
  17. '{{#if isSharingAllowed}}' +
  18. '<label for="shareWith-{{cid}}" class="hidden-visually">{{shareLabel}}</label>' +
  19. '<div class="oneline">' +
  20. ' <input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{sharePlaceholder}}" />' +
  21. ' <span class="shareWithLoading icon-loading-small hidden"></span>'+
  22. '{{{remoteShareInfo}}}' +
  23. '</div>' +
  24. '{{/if}}' +
  25. '<div class="shareeListView subView"></div>' +
  26. '<div class="linkShareView subView"></div>' +
  27. '<div class="expirationView subView"></div>' +
  28. '<div class="mailView subView"></div>' +
  29. '<div class="loading hidden" style="height: 50px"></div>';
  30. var TEMPLATE_REMOTE_SHARE_INFO =
  31. '<a target="_blank" class="icon icon-info shareWithRemoteInfo hasTooltip" href="{{docLink}}" ' +
  32. 'title="{{tooltip}}"></a>';
  33. /**
  34. * @class OCA.Share.ShareDialogView
  35. * @member {OC.Share.ShareItemModel} model
  36. * @member {jQuery} $el
  37. * @memberof OCA.Sharing
  38. * @classdesc
  39. *
  40. * Represents the GUI of the share dialogue
  41. *
  42. */
  43. var ShareDialogView = OC.Backbone.View.extend({
  44. /** @type {Object} **/
  45. _templates: {},
  46. /** @type {boolean} **/
  47. _showLink: true,
  48. /** @type {string} **/
  49. tagName: 'div',
  50. /** @type {OC.Share.ShareConfigModel} **/
  51. configModel: undefined,
  52. /** @type {object} **/
  53. resharerInfoView: undefined,
  54. /** @type {object} **/
  55. linkShareView: undefined,
  56. /** @type {object} **/
  57. expirationView: undefined,
  58. /** @type {object} **/
  59. shareeListView: undefined,
  60. /** @type {object} **/
  61. mailView: undefined,
  62. events: {
  63. 'input .shareWithField': 'onShareWithFieldChanged'
  64. },
  65. initialize: function(options) {
  66. var view = this;
  67. this.model.on('fetchError', function() {
  68. OC.Notification.showTemporary(t('core', 'Share details could not be loaded for this item.'));
  69. });
  70. if(!_.isUndefined(options.configModel)) {
  71. this.configModel = options.configModel;
  72. } else {
  73. throw 'missing OC.Share.ShareConfigModel';
  74. }
  75. this.configModel.on('change:isRemoteShareAllowed', function() {
  76. view.render();
  77. });
  78. this.model.on('change:permissions', function() {
  79. view.render();
  80. });
  81. this.model.on('request', this._onRequest, this);
  82. this.model.on('sync', this._onEndRequest, this);
  83. var subViewOptions = {
  84. model: this.model,
  85. configModel: this.configModel
  86. };
  87. var subViews = {
  88. resharerInfoView: 'ShareDialogResharerInfoView',
  89. linkShareView: 'ShareDialogLinkShareView',
  90. expirationView: 'ShareDialogExpirationView',
  91. shareeListView: 'ShareDialogShareeListView',
  92. mailView: 'ShareDialogMailView'
  93. };
  94. for(var name in subViews) {
  95. var className = subViews[name];
  96. this[name] = _.isUndefined(options[name])
  97. ? new OC.Share[className](subViewOptions)
  98. : options[name];
  99. }
  100. _.bindAll(this,
  101. 'autocompleteHandler',
  102. '_onSelectRecipient',
  103. 'onShareWithFieldChanged'
  104. );
  105. },
  106. onShareWithFieldChanged: function() {
  107. var $el = this.$el.find('.shareWithField');
  108. if ($el.val().length < 2) {
  109. $el.removeClass('error').tooltip('hide');
  110. }
  111. },
  112. autocompleteHandler: function (search, response) {
  113. var view = this;
  114. var $loading = this.$el.find('.shareWithLoading');
  115. $loading.removeClass('hidden');
  116. $loading.addClass('inlineblock');
  117. $.get(
  118. OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
  119. {
  120. format: 'json',
  121. search: search.term.trim(),
  122. perPage: 200,
  123. itemType: view.model.get('itemType')
  124. },
  125. function (result) {
  126. $loading.addClass('hidden');
  127. $loading.removeClass('inlineblock');
  128. if (result.ocs.meta.statuscode === 100) {
  129. var users = result.ocs.data.exact.users.concat(result.ocs.data.users);
  130. var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups);
  131. var remotes = result.ocs.data.exact.remotes.concat(result.ocs.data.remotes);
  132. var emails = result.ocs.data.exact.emails.concat(result.ocs.data.emails);
  133. var usersLength;
  134. var groupsLength;
  135. var remotesLength;
  136. var emailsLength;
  137. var i, j;
  138. //Filter out the current user
  139. usersLength = users.length;
  140. for (i = 0 ; i < usersLength; i++) {
  141. if (users[i].value.shareWith === OC.currentUser) {
  142. users.splice(i, 1);
  143. break;
  144. }
  145. }
  146. // Filter out the owner of the share
  147. if (view.model.hasReshare()) {
  148. usersLength = users.length;
  149. for (i = 0 ; i < usersLength; i++) {
  150. if (users[i].value.shareWith === view.model.getReshareOwner()) {
  151. users.splice(i, 1);
  152. break;
  153. }
  154. }
  155. }
  156. var shares = view.model.get('shares');
  157. var sharesLength = shares.length;
  158. // Now filter out all sharees that are already shared with
  159. for (i = 0; i < sharesLength; i++) {
  160. var share = shares[i];
  161. if (share.share_type === OC.Share.SHARE_TYPE_USER) {
  162. usersLength = users.length;
  163. for (j = 0; j < usersLength; j++) {
  164. if (users[j].value.shareWith === share.share_with) {
  165. users.splice(j, 1);
  166. break;
  167. }
  168. }
  169. } else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) {
  170. groupsLength = groups.length;
  171. for (j = 0; j < groupsLength; j++) {
  172. if (groups[j].value.shareWith === share.share_with) {
  173. groups.splice(j, 1);
  174. break;
  175. }
  176. }
  177. } else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) {
  178. remotesLength = remotes.length;
  179. for (j = 0; j < remotesLength; j++) {
  180. if (remotes[j].value.shareWith === share.share_with) {
  181. remotes.splice(j, 1);
  182. break;
  183. }
  184. }
  185. } else if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
  186. emailsLength = emails.length;
  187. for (j = 0; j < emailsLength; j++) {
  188. if (emails[j].value.shareWith === share.share_with) {
  189. emails.splice(j, 1);
  190. break;
  191. }
  192. }
  193. }
  194. }
  195. var suggestions = users.concat(groups).concat(remotes).concat(emails);
  196. if (suggestions.length > 0) {
  197. $('.shareWithField').removeClass('error')
  198. .tooltip('hide')
  199. .autocomplete("option", "autoFocus", true);
  200. response(suggestions);
  201. } else {
  202. var title = t('core', 'No users or groups found for {search}', {search: $('.shareWithField').val()});
  203. if (!view.configModel.get('allowGroupSharing')) {
  204. title = t('core', 'No users found for {search}', {search: $('.shareWithField').val()});
  205. }
  206. $('.shareWithField').addClass('error')
  207. .attr('data-original-title', title)
  208. .tooltip('hide')
  209. .tooltip({
  210. placement: 'bottom',
  211. trigger: 'manual'
  212. })
  213. .tooltip('fixTitle')
  214. .tooltip('show');
  215. response();
  216. }
  217. } else {
  218. response();
  219. }
  220. }
  221. ).fail(function() {
  222. $loading.addClass('hidden');
  223. $loading.removeClass('inlineblock');
  224. OC.Notification.show(t('core', 'An error occurred. Please try again'));
  225. window.setTimeout(OC.Notification.hide, 5000);
  226. });
  227. },
  228. autocompleteRenderItem: function(ul, item) {
  229. var text = item.label;
  230. if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) {
  231. text = t('core', '{sharee} (group)', {
  232. sharee: text
  233. });
  234. } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) {
  235. if (item.value.server) {
  236. text = t('core', '{sharee} (at {server})', {
  237. sharee: text,
  238. server: item.value.server
  239. });
  240. } else {
  241. text = t('core', '{sharee} (remote)', {
  242. sharee: text
  243. });
  244. }
  245. } else if (item.value.shareType === OC.Share.SHARE_TYPE_EMAIL) {
  246. text = t('core', '{sharee} (email)', {
  247. sharee: text
  248. });
  249. }
  250. var insert = $("<div class='share-autocomplete-item'/>");
  251. var avatar = $("<div class='avatardiv'></div>").appendTo(insert);
  252. if (item.value.shareType === OC.Share.SHARE_TYPE_USER) {
  253. avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label);
  254. } else {
  255. avatar.imageplaceholder(text, undefined, 32);
  256. }
  257. $("<div class='autocomplete-item-text'></div>")
  258. .text(text)
  259. .appendTo(insert);
  260. insert.attr('title', item.value.shareWith);
  261. insert = $("<a>")
  262. .append(insert);
  263. return $("<li>")
  264. .addClass((item.value.shareType === OC.Share.SHARE_TYPE_GROUP) ? 'group' : 'user')
  265. .append(insert)
  266. .appendTo(ul);
  267. },
  268. _onSelectRecipient: function(e, s) {
  269. e.preventDefault();
  270. $(e.target).attr('disabled', true)
  271. .val(s.item.label);
  272. var $loading = this.$el.find('.shareWithLoading');
  273. $loading.removeClass('hidden')
  274. .addClass('inlineblock');
  275. this.model.addShare(s.item.value, {success: function() {
  276. $(e.target).val('')
  277. .attr('disabled', false);
  278. $loading.addClass('hidden')
  279. .removeClass('inlineblock');
  280. }, error: function(obj, msg) {
  281. OC.Notification.showTemporary(msg);
  282. $(e.target).attr('disabled', false)
  283. .autocomplete('search', $(e.target).val());
  284. $loading.addClass('hidden')
  285. .removeClass('inlineblock');
  286. }});
  287. },
  288. _toggleLoading: function(state) {
  289. this._loading = state;
  290. this.$el.find('.subView').toggleClass('hidden', state);
  291. this.$el.find('.loading').toggleClass('hidden', !state);
  292. },
  293. _onRequest: function() {
  294. // only show the loading spinner for the first request (for now)
  295. if (!this._loadingOnce) {
  296. this._toggleLoading(true);
  297. }
  298. },
  299. _onEndRequest: function() {
  300. var self = this;
  301. this._toggleLoading(false);
  302. if (!this._loadingOnce) {
  303. this._loadingOnce = true;
  304. // the first time, focus on the share field after the spinner disappeared
  305. _.defer(function() {
  306. self.$('.shareWithField').focus();
  307. });
  308. }
  309. },
  310. render: function() {
  311. var baseTemplate = this._getTemplate('base', TEMPLATE_BASE);
  312. this.$el.html(baseTemplate({
  313. cid: this.cid,
  314. shareLabel: t('core', 'Share'),
  315. sharePlaceholder: this._renderSharePlaceholderPart(),
  316. remoteShareInfo: this._renderRemoteShareInfoPart(),
  317. isSharingAllowed: this.model.sharePermissionPossible()
  318. }));
  319. var $shareField = this.$el.find('.shareWithField');
  320. if ($shareField.length) {
  321. $shareField.autocomplete({
  322. minLength: 1,
  323. delay: 750,
  324. focus: function(event) {
  325. event.preventDefault();
  326. },
  327. source: this.autocompleteHandler,
  328. select: this._onSelectRecipient
  329. }).data('ui-autocomplete')._renderItem = this.autocompleteRenderItem;
  330. }
  331. this.resharerInfoView.$el = this.$el.find('.resharerInfoView');
  332. this.resharerInfoView.render();
  333. this.linkShareView.$el = this.$el.find('.linkShareView');
  334. this.linkShareView.render();
  335. this.expirationView.$el = this.$el.find('.expirationView');
  336. this.expirationView.render();
  337. this.shareeListView.$el = this.$el.find('.shareeListView');
  338. this.shareeListView.render();
  339. this.mailView.$el = this.$el.find('.mailView');
  340. this.mailView.render();
  341. this.$el.find('.hasTooltip').tooltip();
  342. return this;
  343. },
  344. /**
  345. * sets whether share by link should be displayed or not. Default is
  346. * true.
  347. *
  348. * @param {bool} showLink
  349. */
  350. setShowLink: function(showLink) {
  351. this._showLink = (typeof showLink === 'boolean') ? showLink : true;
  352. this.linkShareView.showLink = this._showLink;
  353. },
  354. _renderRemoteShareInfoPart: function() {
  355. var remoteShareInfo = '';
  356. if(this.configModel.get('isRemoteShareAllowed')) {
  357. var infoTemplate = this._getRemoteShareInfoTemplate();
  358. remoteShareInfo = infoTemplate({
  359. docLink: this.configModel.getFederatedShareDocLink(),
  360. tooltip: t('core', 'Share with people on other servers using the syntax username@example.com/nextcloud')
  361. });
  362. }
  363. return remoteShareInfo;
  364. },
  365. _renderSharePlaceholderPart: function () {
  366. var sharePlaceholder = t('core', 'Share with users, or by mail...');
  367. if (this.configModel.get('allowGroupSharing')) {
  368. if (this.configModel.get('isRemoteShareAllowed')) {
  369. sharePlaceholder = t('core', 'Share with users, groups, remote users, or by mail…');
  370. } else {
  371. sharePlaceholder = t('core', 'Share with users, groups or by mail...');
  372. }
  373. } else if (this.configModel.get('isRemoteShareAllowed')) {
  374. sharePlaceholder = t('core', 'Share with users, remote users or by mail...');
  375. }
  376. return sharePlaceholder;
  377. },
  378. /**
  379. *
  380. * @param {string} key - an identifier for the template
  381. * @param {string} template - the HTML to be compiled by Handlebars
  382. * @returns {Function} from Handlebars
  383. * @private
  384. */
  385. _getTemplate: function (key, template) {
  386. if (!this._templates[key]) {
  387. this._templates[key] = Handlebars.compile(template);
  388. }
  389. return this._templates[key];
  390. },
  391. /**
  392. * returns the info template for remote sharing
  393. *
  394. * @returns {Function}
  395. * @private
  396. */
  397. _getRemoteShareInfoTemplate: function() {
  398. return this._getTemplate('remoteShareInfo', TEMPLATE_REMOTE_SHARE_INFO);
  399. }
  400. });
  401. OC.Share.ShareDialogView = ShareDialogView;
  402. })();