sharedialogshareelistview.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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. } else if (shareType === OC.Share.SHARE_TYPE_CIRCLE) {
  145. }
  146. if (shareType === OC.Share.SHARE_TYPE_GROUP) {
  147. shareWithTitle = shareWith + " (" + t('core', 'group') + ')';
  148. } else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
  149. shareWithTitle = shareWith + " (" + t('core', 'remote') + ')';
  150. } else if (shareType === OC.Share.SHARE_TYPE_EMAIL) {
  151. shareWithTitle = shareWith + " (" + t('core', 'email') + ')';
  152. } else if (shareType === OC.Share.SHARE_TYPE_CIRCLE) {
  153. shareWithTitle = shareWith;
  154. }
  155. return _.extend(hasPermissionOverride, {
  156. cid: this.cid,
  157. hasSharePermission: this.model.hasSharePermission(shareIndex),
  158. hasEditPermission: this.model.hasEditPermission(shareIndex),
  159. hasCreatePermission: this.model.hasCreatePermission(shareIndex),
  160. hasUpdatePermission: this.model.hasUpdatePermission(shareIndex),
  161. hasDeletePermission: this.model.hasDeletePermission(shareIndex),
  162. shareWith: shareWith,
  163. shareWithDisplayName: shareWithDisplayName,
  164. shareWithTitle: shareWithTitle,
  165. shareType: shareType,
  166. shareId: this.model.get('shares')[shareIndex].id,
  167. modSeed: shareType !== OC.Share.SHARE_TYPE_USER,
  168. isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE,
  169. isMailShare: shareType === OC.Share.SHARE_TYPE_EMAIL,
  170. isCircleShare: shareType === OC.Share.SHARE_TYPE_CIRCLE,
  171. isFileSharedByMail: shareType === OC.Share.SHARE_TYPE_EMAIL && !this.model.isFolder()
  172. });
  173. },
  174. getShareProperties: function() {
  175. return {
  176. unshareLabel: t('core', 'Unshare'),
  177. canShareLabel: t('core', 'can reshare'),
  178. canEditLabel: t('core', 'can edit'),
  179. createPermissionLabel: t('core', 'can create'),
  180. updatePermissionLabel: t('core', 'can change'),
  181. deletePermissionLabel: t('core', 'can delete'),
  182. crudsLabel: t('core', 'access control'),
  183. triangleSImage: OC.imagePath('core', 'actions/triangle-s'),
  184. isResharingAllowed: this.configModel.get('isResharingAllowed'),
  185. sharePermissionPossible: this.model.sharePermissionPossible(),
  186. editPermissionPossible: this.model.editPermissionPossible(),
  187. createPermissionPossible: this.model.createPermissionPossible(),
  188. updatePermissionPossible: this.model.updatePermissionPossible(),
  189. deletePermissionPossible: this.model.deletePermissionPossible(),
  190. sharePermission: OC.PERMISSION_SHARE,
  191. createPermission: OC.PERMISSION_CREATE,
  192. updatePermission: OC.PERMISSION_UPDATE,
  193. deletePermission: OC.PERMISSION_DELETE,
  194. isFolder: this.model.isFolder()
  195. };
  196. },
  197. /**
  198. * get an array of sharees' share properties
  199. *
  200. * @returns {Array}
  201. */
  202. getShareeList: function() {
  203. var universal = this.getShareProperties();
  204. if(!this.model.hasUserShares()) {
  205. return [];
  206. }
  207. var shares = this.model.get('shares');
  208. var list = [];
  209. for(var index = 0; index < shares.length; index++) {
  210. var share = this.getShareeObject(index);
  211. if (share.shareType === OC.Share.SHARE_TYPE_LINK) {
  212. continue;
  213. }
  214. // first empty {} is necessary, otherwise we get in trouble
  215. // with references
  216. list.push(_.extend({}, universal, share));
  217. }
  218. return list;
  219. },
  220. getLinkReshares: function() {
  221. var universal = {
  222. unshareLabel: t('core', 'Unshare'),
  223. };
  224. if(!this.model.hasUserShares()) {
  225. return [];
  226. }
  227. var shares = this.model.get('shares');
  228. var list = [];
  229. for(var index = 0; index < shares.length; index++) {
  230. var share = this.getShareeObject(index);
  231. if (share.shareType !== OC.Share.SHARE_TYPE_LINK) {
  232. continue;
  233. }
  234. // first empty {} is necessary, otherwise we get in trouble
  235. // with references
  236. list.push(_.extend({}, universal, share, {
  237. shareInitiator: shares[index].uid_owner,
  238. shareInitiatorDisplayName: shares[index].displayname_owner
  239. }));
  240. }
  241. return list;
  242. },
  243. render: function() {
  244. if(!this._renderPermissionChange) {
  245. this.$el.html(this.template({
  246. cid: this.cid,
  247. sharees: this.getShareeList(),
  248. linkReshares: this.getLinkReshares()
  249. }));
  250. this.$('.avatar').each(function () {
  251. var $this = $(this);
  252. if ($this.hasClass('imageplaceholderseed')) {
  253. $this.css({width: 32, height: 32});
  254. $this.imageplaceholder($this.data('seed'));
  255. } else {
  256. // user, size, ie8fix, hidedefault, callback, displayname
  257. $this.avatar($this.data('username'), 32, undefined, undefined, undefined, $this.data('displayname'));
  258. }
  259. });
  260. this.$('.has-tooltip').tooltip({
  261. placement: 'bottom'
  262. });
  263. } else {
  264. var permissionChangeShareId = parseInt(this._renderPermissionChange, 10);
  265. var shareWithIndex = this.model.findShareWithIndex(permissionChangeShareId);
  266. var sharee = this.getShareeObject(shareWithIndex);
  267. $.extend(sharee, this.getShareProperties());
  268. var $li = this.$('li[data-share-id=' + permissionChangeShareId + ']');
  269. $li.find('.popovermenu').replaceWith(this.popoverMenuTemplate(sharee));
  270. var checkBoxId = 'canEdit-' + this.cid + '-' + sharee.shareWith;
  271. checkBoxId = '#' + checkBoxId.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1");
  272. var $edit = $li.parent().find(checkBoxId);
  273. if($edit.length === 1) {
  274. $edit.prop('checked', sharee.hasEditPermission);
  275. }
  276. }
  277. var _this = this;
  278. this.$('.popovermenu').on('afterHide', function() {
  279. _this._menuOpen = false;
  280. });
  281. if (this._menuOpen != false) {
  282. // Open menu again if it was opened before
  283. var shareId = parseInt(this._menuOpen, 10);
  284. if(!_.isNaN(shareId)) {
  285. var liSelector = 'li[data-share-id=' + shareId + ']';
  286. OC.showMenu(null, this.$(liSelector + ' .popovermenu'));
  287. }
  288. }
  289. this._renderPermissionChange = false;
  290. this.delegateEvents();
  291. return this;
  292. },
  293. /**
  294. * @returns {Function} from Handlebars
  295. * @private
  296. */
  297. template: function (data) {
  298. if (!this._template) {
  299. this._template = Handlebars.compile(TEMPLATE);
  300. }
  301. var sharees = data.sharees;
  302. if(_.isArray(sharees)) {
  303. for (var i = 0; i < sharees.length; i++) {
  304. data.sharees[i].popoverMenu = this.popoverMenuTemplate(sharees[i]);
  305. }
  306. }
  307. return this._template(data);
  308. },
  309. /**
  310. * renders the popover template and returns the resulting HTML
  311. *
  312. * @param {Object} data
  313. * @returns {string}
  314. */
  315. popoverMenuTemplate: function(data) {
  316. if(!this._popoverMenuTemplate) {
  317. this._popoverMenuTemplate = Handlebars.compile(TEMPLATE_POPOVER_MENU);
  318. }
  319. return this._popoverMenuTemplate(data);
  320. },
  321. onUnshare: function(event) {
  322. event.preventDefault();
  323. event.stopPropagation();
  324. var self = this;
  325. var $element = $(event.target);
  326. if (!$element.is('a')) {
  327. $element = $element.closest('a');
  328. }
  329. var $loading = $element.find('.icon-loading-small').eq(0);
  330. if(!$loading.hasClass('hidden')) {
  331. // in process
  332. return false;
  333. }
  334. $loading.removeClass('hidden');
  335. var $li = $element.closest('li[data-share-id]');
  336. var shareId = $li.data('share-id');
  337. self.model.removeShare(shareId)
  338. .done(function() {
  339. $li.remove();
  340. })
  341. .fail(function() {
  342. $loading.addClass('hidden');
  343. OC.Notification.showTemporary(t('core', 'Could not unshare'));
  344. });
  345. return false;
  346. },
  347. onToggleMenu: function(event) {
  348. event.preventDefault();
  349. event.stopPropagation();
  350. var $element = $(event.target);
  351. var $li = $element.closest('li[data-share-id]');
  352. var $menu = $li.find('.popovermenu');
  353. OC.showMenu(null, $menu);
  354. this._menuOpen = $li.data('share-id');
  355. },
  356. onPermissionChange: function(event) {
  357. event.preventDefault();
  358. event.stopPropagation();
  359. var $element = $(event.target);
  360. var $li = $element.closest('li[data-share-id]');
  361. var shareId = $li.data('share-id');
  362. var permissions = OC.PERMISSION_READ;
  363. if (this.model.isFolder()) {
  364. // adjust checkbox states
  365. var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]');
  366. var checked;
  367. if ($element.attr('name') === 'edit') {
  368. checked = $element.is(':checked');
  369. // Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck
  370. $($checkboxes).prop('checked', checked);
  371. if (checked) {
  372. permissions |= OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_DELETE;
  373. }
  374. } else {
  375. var numberChecked = $checkboxes.filter(':checked').length;
  376. checked = numberChecked > 0;
  377. $('input[name="edit"]', $li).prop('checked', checked);
  378. }
  379. } else {
  380. if ($element.attr('name') === 'edit' && $element.is(':checked')) {
  381. permissions |= OC.PERMISSION_UPDATE;
  382. }
  383. }
  384. $('.permissions', $li).not('input[name="edit"]').filter(':checked').each(function(index, checkbox) {
  385. permissions |= $(checkbox).data('permissions');
  386. });
  387. /** disable checkboxes during save operation to avoid race conditions **/
  388. $li.find('input[type=checkbox]').prop('disabled', true);
  389. var enableCb = function() {
  390. $li.find('input[type=checkbox]').prop('disabled', false);
  391. };
  392. var errorCb = function(elem, msg) {
  393. OC.dialogs.alert(msg, t('core', 'Error while sharing'));
  394. enableCb();
  395. };
  396. this.model.updateShare(shareId, {permissions: permissions}, {error: errorCb, success: enableCb});
  397. this._renderPermissionChange = shareId;
  398. },
  399. });
  400. OC.Share.ShareDialogShareeListView = ShareDialogShareeListView;
  401. })();