sharedialogview.js 12 KB

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