You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sharedialogview.js 14KB

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