sharedialogshareelistview.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /* global OC, Handlebars */
  2. /*
  3. * Copyright (c) 2015
  4. *
  5. * This file is licensed under the Affero General Public License version 3
  6. * or later.
  7. *
  8. * See the COPYING-README file.
  9. *
  10. */
  11. /* globals Handlebars */
  12. (function() {
  13. if (!OC.Share) {
  14. OC.Share = {};
  15. }
  16. var TEMPLATE =
  17. '<ul id="shareWithList" class="shareWithList">' +
  18. '{{#each sharees}}' +
  19. '<li data-share-id="{{shareId}}" data-share-type="{{shareType}}" data-share-with="{{shareWith}}">' +
  20. '<div class="avatar {{#if modSeed}}imageplaceholderseed{{/if}}" data-username="{{shareWith}}" data-displayname="{{shareWithDisplayName}}" {{#if modSeed}}data-seed="{{shareWith}} {{shareType}}"{{/if}}></div>' +
  21. '<span class="has-tooltip username" title="{{shareWithTitle}}">{{shareWithDisplayName}}</span>' +
  22. '<span class="sharingOptionsGroup">' +
  23. '{{#if editPermissionPossible}}' +
  24. '{{#unless isFileSharedByMail}}' +
  25. '<span class="shareOption">' +
  26. '<input id="canEdit-{{cid}}-{{shareWith}}" type="checkbox" name="edit" class="permissions checkbox" {{#if hasEditPermission}}checked="checked"{{/if}} />' +
  27. '<label for="canEdit-{{cid}}-{{shareWith}}">{{canEditLabel}}</label>' +
  28. '</span>' +
  29. '{{/unless}}' +
  30. '{{/if}}' +
  31. '<a href="#"><span class="icon icon-more"></span></a>' +
  32. '{{{popoverMenu}}}' +
  33. '</span>' +
  34. '</li>' +
  35. '{{/each}}' +
  36. '{{#each linkReshares}}' +
  37. '<li data-share-id="{{shareId}}" data-share-type="{{shareType}}">' +
  38. '<div class="avatar" data-username="{{shareInitiator}}"></div>' +
  39. '<span class="has-tooltip username" title="{{shareInitiator}}">' + t('core', '{{shareInitiatorDisplayName}} shared via link') + '</span>' +
  40. '<span class="sharingOptionsGroup">' +
  41. '<a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span class="hidden-visually">{{unshareLabel}}</span></a>' +
  42. '</span>' +
  43. '</li>' +
  44. '{{/each}}' +
  45. '</ul>'
  46. ;
  47. var TEMPLATE_POPOVER_MENU =
  48. '<div class="popovermenu bubble hidden menu">' +
  49. '<ul>' +
  50. '{{#if isResharingAllowed}} {{#if sharePermissionPossible}} {{#unless isMailShare}}' +
  51. '<li>' +
  52. '<span class="shareOption menuitem">' +
  53. '<input id="canShare-{{cid}}-{{shareWith}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" />' +
  54. '<label for="canShare-{{cid}}-{{shareWith}}">{{canShareLabel}}</label>' +
  55. '</span>' +
  56. '</li>' +
  57. '{{/unless}} {{/if}} {{/if}}' +
  58. '{{#if isFolder}}' +
  59. '{{#if createPermissionPossible}}{{#unless isMailShare}}' +
  60. '<li>' +
  61. '<span class="shareOption menuitem">' +
  62. '<input id="canCreate-{{cid}}-{{shareWith}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/>' +
  63. '<label for="canCreate-{{cid}}-{{shareWith}}">{{createPermissionLabel}}</label>' +
  64. '</span>' +
  65. '</li>' +
  66. '{{/unless}}{{/if}}' +
  67. '{{#if updatePermissionPossible}}{{#unless isMailShare}}' +
  68. '<li>' +
  69. '<span class="shareOption menuitem">' +
  70. '<input id="canUpdate-{{cid}}-{{shareWith}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/>' +
  71. '<label for="canUpdate-{{cid}}-{{shareWith}}">{{updatePermissionLabel}}</label>' +
  72. '</span>' +
  73. '</li>' +
  74. '{{/unless}}{{/if}}' +
  75. '{{#if deletePermissionPossible}}{{#unless isMailShare}}' +
  76. '<li>' +
  77. '<span class="shareOption menuitem">' +
  78. '<input id="canDelete-{{cid}}-{{shareWith}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/>' +
  79. '<label for="canDelete-{{cid}}-{{shareWith}}">{{deletePermissionLabel}}</label>' +
  80. '</span>' +
  81. '</li>' +
  82. '{{/unless}}{{/if}}' +
  83. '{{/if}}' +
  84. '<li>' +
  85. '<a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span>{{unshareLabel}}</span></a>' +
  86. '</li>' +
  87. '</ul>' +
  88. '</div>';
  89. /**
  90. * @class OCA.Share.ShareDialogShareeListView
  91. * @member {OC.Share.ShareItemModel} model
  92. * @member {jQuery} $el
  93. * @memberof OCA.Sharing
  94. * @classdesc
  95. *
  96. * Represents the sharee list part in the GUI of the share dialogue
  97. *
  98. */
  99. var ShareDialogShareeListView = OC.Backbone.View.extend({
  100. /** @type {string} **/
  101. id: 'shareDialogLinkShare',
  102. /** @type {OC.Share.ShareConfigModel} **/
  103. configModel: undefined,
  104. /** @type {Function} **/
  105. _template: undefined,
  106. /** @type {Function} **/
  107. _popoverMenuTemplate: undefined,
  108. _menuOpen: false,
  109. /** @type {boolean|number} **/
  110. _renderPermissionChange: false,
  111. events: {
  112. 'click .unshare': 'onUnshare',
  113. 'click .icon-more': 'onToggleMenu',
  114. 'click .permissions': 'onPermissionChange',
  115. },
  116. initialize: function(options) {
  117. if(!_.isUndefined(options.configModel)) {
  118. this.configModel = options.configModel;
  119. } else {
  120. throw 'missing OC.Share.ShareConfigModel';
  121. }
  122. var view = this;
  123. this.model.on('change:shares', function() {
  124. view.render();
  125. });
  126. },
  127. /**
  128. *
  129. * @param {OC.Share.Types.ShareInfo} shareInfo
  130. * @returns {object}
  131. */
  132. getShareeObject: function(shareIndex) {
  133. var shareWith = this.model.getShareWith(shareIndex);
  134. var shareWithDisplayName = this.model.getShareWithDisplayName(shareIndex);
  135. var shareWithTitle = '';
  136. var shareType = this.model.getShareType(shareIndex);
  137. var hasPermissionOverride = {};
  138. if (shareType === OC.Share.SHARE_TYPE_GROUP) {
  139. shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')';
  140. } else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
  141. shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'remote') + ')';
  142. } else if (shareType === OC.Share.SHARE_TYPE_EMAIL) {
  143. shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'email') + ')';
  144. }
  145. if (shareType === OC.Share.SHARE_TYPE_GROUP) {
  146. shareWithTitle = shareWith + " (" + t('core', 'group') + ')';
  147. } else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
  148. shareWithTitle = shareWith + " (" + t('core', 'remote') + ')';
  149. } else if (shareType === OC.Share.SHARE_TYPE_EMAIL) {
  150. shareWithTitle = shareWith + " (" + t('core', 'email') + ')';
  151. }
  152. return _.extend(hasPermissionOverride, {
  153. cid: this.cid,
  154. hasSharePermission: this.model.hasSharePermission(shareIndex),
  155. hasEditPermission: this.model.hasEditPermission(shareIndex),
  156. hasCreatePermission: this.model.hasCreatePermission(shareIndex),
  157. hasUpdatePermission: this.model.hasUpdatePermission(shareIndex),
  158. hasDeletePermission: this.model.hasDeletePermission(shareIndex),
  159. shareWith: shareWith,
  160. shareWithDisplayName: shareWithDisplayName,
  161. shareWithTitle: shareWithTitle,
  162. shareType: shareType,
  163. shareId: this.model.get('shares')[shareIndex].id,
  164. modSeed: shareType !== OC.Share.SHARE_TYPE_USER,
  165. isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE,
  166. isMailShare: shareType === OC.Share.SHARE_TYPE_EMAIL,
  167. isFileSharedByMail: shareType === OC.Share.SHARE_TYPE_EMAIL && !this.model.isFolder()
  168. });
  169. },
  170. getShareProperties: function() {
  171. return {
  172. unshareLabel: t('core', 'Unshare'),
  173. canShareLabel: t('core', 'can reshare'),
  174. canEditLabel: t('core', 'can edit'),
  175. createPermissionLabel: t('core', 'can create'),
  176. updatePermissionLabel: t('core', 'can change'),
  177. deletePermissionLabel: t('core', 'can delete'),
  178. crudsLabel: t('core', 'access control'),
  179. triangleSImage: OC.imagePath('core', 'actions/triangle-s'),
  180. isResharingAllowed: this.configModel.get('isResharingAllowed'),
  181. sharePermissionPossible: this.model.sharePermissionPossible(),
  182. editPermissionPossible: this.model.editPermissionPossible(),
  183. createPermissionPossible: this.model.createPermissionPossible(),
  184. updatePermissionPossible: this.model.updatePermissionPossible(),
  185. deletePermissionPossible: this.model.deletePermissionPossible(),
  186. sharePermission: OC.PERMISSION_SHARE,
  187. createPermission: OC.PERMISSION_CREATE,
  188. updatePermission: OC.PERMISSION_UPDATE,
  189. deletePermission: OC.PERMISSION_DELETE,
  190. isFolder: this.model.isFolder()
  191. };
  192. },
  193. /**
  194. * get an array of sharees' share properties
  195. *
  196. * @returns {Array}
  197. */
  198. getShareeList: function() {
  199. var universal = this.getShareProperties();
  200. if(!this.model.hasUserShares()) {
  201. return [];
  202. }
  203. var shares = this.model.get('shares');
  204. var list = [];
  205. for(var index = 0; index < shares.length; index++) {
  206. var share = this.getShareeObject(index);
  207. if (share.shareType === OC.Share.SHARE_TYPE_LINK) {
  208. continue;
  209. }
  210. // first empty {} is necessary, otherwise we get in trouble
  211. // with references
  212. list.push(_.extend({}, universal, share));
  213. }
  214. return list;
  215. },
  216. getLinkReshares: function() {
  217. var universal = {
  218. unshareLabel: t('core', 'Unshare'),
  219. };
  220. if(!this.model.hasUserShares()) {
  221. return [];
  222. }
  223. var shares = this.model.get('shares');
  224. var list = [];
  225. for(var index = 0; index < shares.length; index++) {
  226. var share = this.getShareeObject(index);
  227. if (share.shareType !== OC.Share.SHARE_TYPE_LINK) {
  228. continue;
  229. }
  230. // first empty {} is necessary, otherwise we get in trouble
  231. // with references
  232. list.push(_.extend({}, universal, share, {
  233. shareInitiator: shares[index].uid_owner,
  234. shareInitiatorDisplayName: shares[index].displayname_owner
  235. }));
  236. }
  237. return list;
  238. },
  239. render: function() {
  240. if(!this._renderPermissionChange) {
  241. this.$el.html(this.template({
  242. cid: this.cid,
  243. sharees: this.getShareeList(),
  244. linkReshares: this.getLinkReshares()
  245. }));
  246. this.$('.avatar').each(function () {
  247. var $this = $(this);
  248. if ($this.hasClass('imageplaceholderseed')) {
  249. $this.css({width: 32, height: 32});
  250. $this.imageplaceholder($this.data('seed'));
  251. } else {
  252. // user, size, ie8fix, hidedefault, callback, displayname
  253. $this.avatar($this.data('username'), 32, undefined, undefined, undefined, $this.data('displayname'));
  254. }
  255. });
  256. this.$('.has-tooltip').tooltip({
  257. placement: 'bottom'
  258. });
  259. } else {
  260. var permissionChangeShareId = parseInt(this._renderPermissionChange, 10);
  261. var shareWithIndex = this.model.findShareWithIndex(permissionChangeShareId);
  262. var sharee = this.getShareeObject(shareWithIndex);
  263. $.extend(sharee, this.getShareProperties());
  264. var $li = this.$('li[data-share-id=' + permissionChangeShareId + ']');
  265. $li.find('.popovermenu').replaceWith(this.popoverMenuTemplate(sharee));
  266. var checkBoxId = 'canEdit-' + this.cid + '-' + sharee.shareWith;
  267. checkBoxId = '#' + checkBoxId.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1");
  268. var $edit = $li.parent().find(checkBoxId);
  269. if($edit.length === 1) {
  270. $edit.prop('checked', sharee.hasEditPermission);
  271. }
  272. }
  273. var _this = this;
  274. this.$('.popovermenu').on('afterHide', function() {
  275. _this._menuOpen = false;
  276. });
  277. if (this._menuOpen != false) {
  278. // Open menu again if it was opened before
  279. var shareId = parseInt(this._menuOpen, 10);
  280. if(!_.isNaN(shareId)) {
  281. var liSelector = 'li[data-share-id=' + shareId + ']';
  282. OC.showMenu(null, this.$(liSelector + ' .popovermenu'));
  283. }
  284. }
  285. this._renderPermissionChange = false;
  286. this.delegateEvents();
  287. return this;
  288. },
  289. /**
  290. * @returns {Function} from Handlebars
  291. * @private
  292. */
  293. template: function (data) {
  294. if (!this._template) {
  295. this._template = Handlebars.compile(TEMPLATE);
  296. }
  297. var sharees = data.sharees;
  298. if(_.isArray(sharees)) {
  299. for (var i = 0; i < sharees.length; i++) {
  300. data.sharees[i].popoverMenu = this.popoverMenuTemplate(sharees[i]);
  301. }
  302. }
  303. return this._template(data);
  304. },
  305. /**
  306. * renders the popover template and returns the resulting HTML
  307. *
  308. * @param {Object} data
  309. * @returns {string}
  310. */
  311. popoverMenuTemplate: function(data) {
  312. if(!this._popoverMenuTemplate) {
  313. this._popoverMenuTemplate = Handlebars.compile(TEMPLATE_POPOVER_MENU);
  314. }
  315. return this._popoverMenuTemplate(data);
  316. },
  317. onUnshare: function(event) {
  318. event.preventDefault();
  319. event.stopPropagation();
  320. var self = this;
  321. var $element = $(event.target);
  322. if (!$element.is('a')) {
  323. $element = $element.closest('a');
  324. }
  325. var $loading = $element.find('.icon-loading-small').eq(0);
  326. if(!$loading.hasClass('hidden')) {
  327. // in process
  328. return false;
  329. }
  330. $loading.removeClass('hidden');
  331. var $li = $element.closest('li[data-share-id]');
  332. var shareId = $li.data('share-id');
  333. self.model.removeShare(shareId)
  334. .done(function() {
  335. $li.remove();
  336. })
  337. .fail(function() {
  338. $loading.addClass('hidden');
  339. OC.Notification.showTemporary(t('core', 'Could not unshare'));
  340. });
  341. return false;
  342. },
  343. onToggleMenu: function(event) {
  344. event.preventDefault();
  345. event.stopPropagation();
  346. var $element = $(event.target);
  347. var $li = $element.closest('li[data-share-id]');
  348. var $menu = $li.find('.popovermenu');
  349. OC.showMenu(null, $menu);
  350. this._menuOpen = $li.data('share-id');
  351. },
  352. onPermissionChange: function(event) {
  353. event.preventDefault();
  354. event.stopPropagation();
  355. var $element = $(event.target);
  356. var $li = $element.closest('li[data-share-id]');
  357. var shareId = $li.data('share-id');
  358. var permissions = OC.PERMISSION_READ;
  359. if (this.model.isFolder()) {
  360. // adjust checkbox states
  361. var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]');
  362. var checked;
  363. if ($element.attr('name') === 'edit') {
  364. checked = $element.is(':checked');
  365. // Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck
  366. $($checkboxes).prop('checked', checked);
  367. if (checked) {
  368. permissions |= OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_DELETE;
  369. }
  370. } else {
  371. var numberChecked = $checkboxes.filter(':checked').length;
  372. checked = numberChecked > 0;
  373. $('input[name="edit"]', $li).prop('checked', checked);
  374. }
  375. } else {
  376. if ($element.attr('name') === 'edit' && $element.is(':checked')) {
  377. permissions |= OC.PERMISSION_UPDATE;
  378. }
  379. }
  380. $('.permissions', $li).not('input[name="edit"]').filter(':checked').each(function(index, checkbox) {
  381. permissions |= $(checkbox).data('permissions');
  382. });
  383. /** disable checkboxes during save operation to avoid race conditions **/
  384. $li.find('input[type=checkbox]').prop('disabled', true);
  385. var enableCb = function() {
  386. $li.find('input[type=checkbox]').prop('disabled', false);
  387. };
  388. var errorCb = function(elem, msg) {
  389. OC.dialogs.alert(msg, t('core', 'Error while sharing'));
  390. enableCb();
  391. };
  392. this.model.updateShare(shareId, {permissions: permissions}, {error: errorCb, success: enableCb});
  393. this._renderPermissionChange = shareId;
  394. },
  395. });
  396. OC.Share.ShareDialogShareeListView = ShareDialogShareeListView;
  397. })();