sharedialogview.js 12 KB

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