diff options
-rw-r--r-- | apps/files_sharing/js/app.js | 4 | ||||
-rw-r--r-- | apps/files_sharing/js/share.js | 239 | ||||
-rw-r--r-- | apps/files_sharing/js/sharedfilelist.js | 43 | ||||
-rw-r--r-- | apps/files_sharing/tests/js/appSpec.js | 4 | ||||
-rw-r--r-- | apps/files_sharing/tests/js/shareSpec.js | 450 | ||||
-rw-r--r-- | apps/files_sharing/tests/js/sharedfilelistSpec.js | 10 | ||||
-rw-r--r-- | core/img/filetypes/folder-shared.png | bin | 1155 -> 1229 bytes | |||
-rw-r--r-- | core/img/filetypes/folder-shared.svg | 44 | ||||
-rw-r--r-- | core/js/share.js | 135 | ||||
-rw-r--r-- | core/js/tests/specs/shareSpec.js | 138 | ||||
-rw-r--r-- | tests/karma.config.js | 3 |
11 files changed, 901 insertions, 169 deletions
diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 3764328a5d0..800873cd638 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -8,7 +8,9 @@ * */ -OCA.Sharing = {}; +if (!OCA.Sharing) { + OCA.Sharing = {}; +} OCA.Sharing.App = { _inFileList: null, diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 5a42604c866..d63a590fb8e 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -8,103 +8,166 @@ * */ -$(document).ready(function() { - if (!_.isUndefined(OC.Share) && !_.isUndefined(OCA.Files)) { - // TODO: make a separate class for this or a hook or jQuery event ? - if (OCA.Files.FileList) { - var oldCreateRow = OCA.Files.FileList.prototype._createRow; - OCA.Files.FileList.prototype._createRow = function(fileData) { - var tr = oldCreateRow.apply(this, arguments); - if (fileData.shareOwner) { - tr.attr('data-share-owner', fileData.shareOwner); - // user should always be able to rename a mount point - if (fileData.isShareMountPoint) { - tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE); - tr.attr('data-reshare-permissions', fileData.permissions); +(function() { + if (!OCA.Sharing) { + OCA.Sharing = {}; + } + OCA.Sharing.Util = { + initialize: function(fileActions) { + if (OCA.Files.FileList) { + var oldCreateRow = OCA.Files.FileList.prototype._createRow; + OCA.Files.FileList.prototype._createRow = function(fileData) { + var tr = oldCreateRow.apply(this, arguments); + if (fileData.shareOwner) { + tr.attr('data-share-owner', fileData.shareOwner); + // user should always be able to rename a mount point + if (fileData.isShareMountPoint) { + tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE); + tr.attr('data-reshare-permissions', fileData.permissions); + } } - } - return tr; - }; - } + if (fileData.recipientsDisplayName) { + tr.attr('data-share-recipients', fileData.recipientsDisplayName); + } + return tr; + }; - // use delegate to catch the case with multiple file lists - $('#content').delegate('#fileList', 'fileActionsReady',function(ev){ - // if no share action exists because the admin disabled sharing for this user - // we create a share notification action to inform the user about files - // shared with him otherwise we just update the existing share action. - var fileList = ev.fileList; - var $fileList = $(this); - $fileList.find('[data-share-owner]').each(function() { - var $tr = $(this); - var $action; - var owner; - var message; - var permissions = $tr.data('permissions'); - if(permissions & OC.PERMISSION_SHARE) { - $action = $tr.find('[data-Action="Share"]'); - $action.addClass('permanent'); - owner = $tr.closest('tr').attr('data-share-owner'); - message = ' ' + t('files_sharing', 'Shared by {owner}', {owner: owner}); - $action.find('span').text(message); - } else { - var shareNotification = '<a class="action action-share-notification permanent"' + - ' data-action="Share-Notification" href="#" original-title="">' + - ' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>'; - $tr.find('.fileactions').append(function() { - var owner = $(this).closest('tr').attr('data-share-owner'); - var shareBy = t('files_sharing', 'Shared by {owner}', {owner: owner}); - var $result = $(shareNotification + '<span> ' + shareBy + '</span></span>'); - $result.on('click', function() { - return false; - }); - return $result; - }); + var oldRenderRow = OCA.Files.FileList.prototype._renderRow; + OCA.Files.FileList.prototype._renderRow = function(fileData) { + var $tr = oldRenderRow.apply(this, arguments); + // if the statuses are loaded already, use them for the icon + // (needed when scrolling to the next page) + var shareStatus = OC.Share.statuses[fileData.id]; + if (fileData.shareOwner || fileData.recipientsDisplayName || shareStatus) { + var permissions = $tr.data('permissions'); + var hasLink = !!(shareStatus && shareStatus.link); + if (permissions & OC.PERMISSION_SHARE) { + OC.Share.markFileAsShared($tr, true, hasLink); + } else { + // if no share action exists because the admin disabled sharing for this user + // we create a share notification action to inform the user about files + // shared with him otherwise we just update the existing share action. + // TODO: make this work like/with OC.Share.markFileAsShared() + var shareNotification = '<a class="action action-share-notification permanent"' + + ' data-action="Share-Notification" href="#" original-title="">' + + ' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>'; + $tr.find('.fileactions').append(function() { + var shareBy = t('files_sharing', 'Shared by {owner}', {owner: fileData.shareOwner}); + var $result = $(shareNotification + '<span> ' + shareBy + '</span></span>'); + $result.on('click', function() { + return false; + }); + return $result; + }); + } + } + return $tr; + }; + } + + // use delegate to catch the case with multiple file lists + $('#content').delegate('#fileList', 'fileActionsReady',function(ev){ + var fileList = ev.fileList; + if (!OCA.Sharing.sharesLoaded){ + OC.Share.loadIcons('file', fileList); + // assume that we got all shares, so switching directories + // will not invalidate that list + OCA.Sharing.sharesLoaded = true; + } + else{ + // this will update the icons for all the currently visible elements + // additionally added elements when scrolling down will be + // updated in the _renderRow override + OC.Share.updateIcons('file', fileList); } }); - if (!OCA.Sharing.sharesLoaded){ - OC.Share.loadIcons('file', fileList); - // assume that we got all shares, so switching directories - // will not invalidate that list - OCA.Sharing.sharesLoaded = true; - } - else{ - OC.Share.updateIcons('file', fileList); - } - }); - - OCA.Files.fileActions.register( - 'all', - 'Share', - OC.PERMISSION_SHARE, - OC.imagePath('core', 'actions/share'), - function(filename, context) { + fileActions.register( + 'all', + 'Share', + OC.PERMISSION_SHARE, + OC.imagePath('core', 'actions/share'), + function(filename, context) { - var $tr = context.$file; - var itemType = 'file'; - if ($tr.data('type') === 'dir') { - itemType = 'folder'; - } - var possiblePermissions = $tr.data('reshare-permissions'); - if (_.isUndefined(possiblePermissions)) { - possiblePermissions = $tr.data('permissions'); - } + var $tr = context.$file; + var itemType = 'file'; + if ($tr.data('type') === 'dir') { + itemType = 'folder'; + } + var possiblePermissions = $tr.data('reshare-permissions'); + if (_.isUndefined(possiblePermissions)) { + possiblePermissions = $tr.data('permissions'); + } - var appendTo = $tr.find('td.filename'); - // Check if drop down is already visible for a different file - if (OC.Share.droppedDown) { - if ($tr.data('id') !== $('#dropdown').attr('data-item-source')) { - OC.Share.hideDropDown(function () { - $tr.addClass('mouseOver'); - OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); - }); + var appendTo = $tr.find('td.filename'); + // Check if drop down is already visible for a different file + if (OC.Share.droppedDown) { + if ($tr.data('id') !== $('#dropdown').attr('data-item-source')) { + OC.Share.hideDropDown(function () { + $tr.addClass('mouseOver'); + OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); + }); + } else { + OC.Share.hideDropDown(); + } } else { - OC.Share.hideDropDown(); + $tr.addClass('mouseOver'); + OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); } - } else { - $tr.addClass('mouseOver'); - OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); + $('#dropdown').on('sharesChanged', function(ev) { + // files app current cannot show recipients on load, so we don't update the + // icon when changed for consistency + if (context.fileList.$el.closest('#app-content-files').length) { + return; + } + var recipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_USER], 'share_with_displayname'); + var groupRecipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_GROUP], 'share_with_displayname'); + recipients = recipients.concat(groupRecipients); + // note: we only update the data attribute because updateIcon() + // is called automatically after this event + if (recipients.length) { + $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients)); + } + else { + $tr.removeAttr('data-share-recipients'); + } + }); + }); + }, + + /** + * Formats a recipients array to be displayed. + * The first four recipients will be shown and the + * other ones will be shown as "+x" where "x" is the number of + * remaining recipients. + * + * @param recipients recipients array + * @param count optional total recipients count (in case the array was shortened) + * @return formatted recipients display text + */ + formatRecipients: function(recipients, count) { + var maxRecipients = 4; + var text; + if (!_.isNumber(count)) { + count = recipients.length; + } + // TODO: use natural sort + recipients = _.first(recipients, maxRecipients).sort(); + text = recipients.join(', '); + if (count > maxRecipients) { + text += ', +' + (count - maxRecipients); } - }); + return text; + } + }; +})(); + +$(document).ready(function() { + // FIXME: HACK: do not init when running unit tests, need a better way + if (!window.TESTING) { + if (!_.isUndefined(OC.Share) && !_.isUndefined(OCA.Files)) { + OCA.Sharing.Util.initialize(OCA.Files.fileActions); + } } }); + diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index ef1034ecfdc..97fabf87131 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -38,6 +38,13 @@ } }, + _renderRow: function() { + // HACK: needed to call the overridden _renderRow + // this is because at the time this class is created + // the overriding hasn't been done yet... + return OCA.Files.FileList.prototype._renderRow.apply(this, arguments); + }, + _createRow: function(fileData) { // TODO: hook earlier and render the whole row here var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments); @@ -46,7 +53,7 @@ $tr.find('td.filename input:checkbox').remove(); $tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(',')); if (this._sharedWithUser) { - $tr.attr('data-share-owner', fileData.shares[0].ownerDisplayName); + $tr.attr('data-share-owner', fileData.shareOwner); } return $tr; }, @@ -159,7 +166,7 @@ stime: share.stime * 1000, }; if (self._sharedWithUser) { - file.share.ownerDisplayName = share.displayname_owner; + file.shareOwner = share.displayname_owner; file.name = OC.basename(share.file_target); file.path = OC.dirname(share.file_target); file.permissions = share.permissions; @@ -179,15 +186,15 @@ // inside the same file object (by file id). .reduce(function(memo, file) { var data = memo[file.id]; - var counterPart = file.share.ownerDisplayName || file.share.targetDisplayName; + var recipient = file.share.targetDisplayName; if (!data) { data = memo[file.id] = file; data.shares = [file.share]; // using a hash to make them unique, // this is only a list to be displayed - data.counterParts = {}; + data.recipients = {}; // counter is cheaper than calling _.keys().length - data.counterPartsCount = 0; + data.recipientsCount = 0; data.mtime = file.share.stime; } else { @@ -198,12 +205,14 @@ data.shares.push(file.share); } - if (file.share.type === OC.Share.SHARE_TYPE_LINK) { - data.hasLinkShare = true; - } else if (counterPart && data.counterPartsCount < 10) { + if (recipient) { // limit counterparts for output - data.counterParts[counterPart] = true; - data.counterPartsCount++; + if (data.recipientsCount < 4) { + // only store the first ones, they will be the only ones + // displayed + data.recipients[recipient] = true; + } + data.recipientsCount++; } delete file.share; @@ -213,14 +222,14 @@ .values() // Clean up .each(function(data) { - // convert the counterParts map to a flat + // convert the recipients map to a flat // array of sorted names - data.counterParts = _.chain(data.counterParts).keys().sort().value(); - if (data.hasLinkShare) { - data.counterParts.unshift(t('files_sharing', 'link')); - delete data.hasLinkShare; - } - delete data.counterPartsCount; + data.recipients = _.keys(data.recipients); + data.recipientsDisplayName = OCA.Sharing.Util.formatRecipients( + data.recipients, + data.recipientsCount + ); + delete data.recipientsCount; }) // Sort by expected sort comparator .sortBy(this._sortComparator) diff --git a/apps/files_sharing/tests/js/appSpec.js b/apps/files_sharing/tests/js/appSpec.js index 3a1b495a16b..9c46b7caf1b 100644 --- a/apps/files_sharing/tests/js/appSpec.js +++ b/apps/files_sharing/tests/js/appSpec.js @@ -122,9 +122,7 @@ describe('OCA.Sharing.App tests', function() { type: 'dir', path: '/somewhere/inside/subdir', counterParts: ['user2'], - shares: [{ - ownerDisplayName: 'user2' - }] + shareOwner: 'user2' }]); fileListIn.findFileEl('testdir').find('td a.name').click(); diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js new file mode 100644 index 00000000000..455addaccc0 --- /dev/null +++ b/apps/files_sharing/tests/js/shareSpec.js @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +describe('OCA.Sharing.Util tests', function() { + var oldFileListPrototype; + var fileList; + var testFiles; + + function getImageUrl($el) { + // might be slightly different cross-browser + var url = $el.css('background-image'); + var r = url.match(/url\(['"]?([^'")]*)['"]?\)/); + if (!r) { + return url; + } + return r[1]; + } + + beforeEach(function() { + // back up prototype, as it will be extended by + // the sharing code + oldFileListPrototype = _.extend({}, OCA.Files.FileList.prototype); + + var $content = $('<div id="content"></div>'); + $('#testArea').append($content); + // dummy file list + var $div = $( + '<div>' + + '<table id="filestable">' + + '<thead></thead>' + + '<tbody id="fileList"></tbody>' + + '</table>' + + '</div>'); + $('#content').append($div); + + var fileActions = new OCA.Files.FileActions(); + OCA.Sharing.Util.initialize(fileActions); + fileList = new OCA.Files.FileList( + $div, { + fileActions : fileActions + } + ); + + testFiles = [{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc', + shareOwner: 'User One', + isShareMountPoint: false + }]; + + OCA.Sharing.sharesLoaded = true; + OC.Share.statuses = { + 1: {link: false, path: '/subdir'} + }; + }); + afterEach(function() { + OCA.Files.FileList.prototype = oldFileListPrototype; + delete OCA.Sharing.sharesLoaded; + delete OC.Share.droppedDown; + OC.Share.statuses = {}; + OC.Share.currentShares = {}; + }); + + describe('Sharing data in table row', function() { + // TODO: test data-permissions, data-share-owner, etc + }); + describe('Share action icon', function() { + it('do not shows share text when not shared', function() { + var $action, $tr; + OC.Share.statuses = {}; + fileList.setFiles([{ + id: 1, + type: 'dir', + name: 'One', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc' + }]); + $tr = fileList.$el.find('tbody tr:first'); + $action = $tr.find('.action-share'); + expect($action.hasClass('permanent')).toEqual(false); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder.svg'); + expect($action.find('img').length).toEqual(1); + }); + it('shows simple share text with share icon', function() { + var $action, $tr; + fileList.setFiles([{ + id: 1, + type: 'dir', + name: 'One', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc' + }]); + $tr = fileList.$el.find('tbody tr:first'); + $action = $tr.find('.action-share'); + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg'); + expect($action.find('img').length).toEqual(1); + }); + it('shows simple share text with public icon when shared with link', function() { + var $action, $tr; + OC.Share.statuses = {1: {link: true, path: '/subdir'}}; + fileList.setFiles([{ + id: 1, + type: 'dir', + name: 'One', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc' + }]); + $tr = fileList.$el.find('tbody tr:first'); + $action = $tr.find('.action-share'); + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('public.svg'); + expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-public.svg'); + expect($action.find('img').length).toEqual(1); + }); + it('shows owner name when owner is available', function() { + var $action, $tr; + fileList.setFiles([{ + id: 1, + type: 'dir', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + shareOwner: 'User One', + etag: 'abc' + }]); + $tr = fileList.$el.find('tbody tr:first'); + $action = $tr.find('.action-share'); + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared by User One'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg'); + }); + it('shows recipients when recipients are available', function() { + var $action, $tr; + fileList.setFiles([{ + id: 1, + type: 'dir', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + recipientsDisplayName: 'User One, User Two', + etag: 'abc' + }]); + $tr = fileList.$el.find('tbody tr:first'); + $action = $tr.find('.action-share'); + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared with User One, User Two'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg'); + expect($action.find('img').length).toEqual(1); + }); + it('shows static share text when file shared with user that has no share permission', function() { + var $action, $tr; + fileList.setFiles([{ + id: 1, + type: 'dir', + name: 'One', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_CREATE, + etag: 'abc', + shareOwner: 'User One' + }]); + $tr = fileList.$el.find('tbody tr:first'); + expect($tr.find('.action-share').length).toEqual(0); + $action = $tr.find('.action-share-notification'); + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text().trim()).toEqual('Shared by User One'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg'); + expect($action.find('img').length).toEqual(1); + }); + }); + describe('Share action', function() { + var showDropDownStub; + + function makeDummyShareItem(displayName) { + return { + share_with_displayname: displayName + }; + } + + beforeEach(function() { + showDropDownStub = sinon.stub(OC.Share, 'showDropDown', function() { + $('#testArea').append($('<div id="dropdown"></div>')); + }); + }); + afterEach(function() { + showDropDownStub.restore(); + }); + it('adds share icon after sharing a non-shared file', function() { + var $action, $tr; + OC.Share.statuses = {}; + fileList.setFiles([{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc' + }]); + $action = fileList.$el.find('tbody tr:first .action-share'); + $tr = fileList.$el.find('tr:first'); + + expect($action.hasClass('permanent')).toEqual(false); + + $tr.find('.action-share').click(); + + expect(showDropDownStub.calledOnce).toEqual(true); + + // simulate what the dropdown does + var shares = {}; + OC.Share.itemShares[OC.Share.SHARE_TYPE_USER] = ['user1', 'user2']; + OC.Share.itemShares[OC.Share.SHARE_TYPE_GROUP] = ['group1', 'group2']; + shares[OC.Share.SHARE_TYPE_USER] = _.map(['User One', 'User Two'], makeDummyShareItem); + shares[OC.Share.SHARE_TYPE_GROUP] = _.map(['Group One', 'Group Two'], makeDummyShareItem); + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: shares})); + + expect($tr.attr('data-share-recipients')).toEqual('Group One, Group Two, User One, User Two'); + + OC.Share.updateIcon('file', 1); + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared with Group One, Group Two, User One, User Two'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + }); + it('updates share icon after updating shares of a file', function() { + var $action, $tr; + OC.Share.statuses = {1: {link: false, path: '/subdir'}}; + fileList.setFiles([{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc' + }]); + $action = fileList.$el.find('tbody tr:first .action-share'); + $tr = fileList.$el.find('tr:first'); + + expect($action.hasClass('permanent')).toEqual(true); + + $tr.find('.action-share').click(); + + expect(showDropDownStub.calledOnce).toEqual(true); + + // simulate what the dropdown does + var shares = {}; + OC.Share.itemShares[OC.Share.SHARE_TYPE_USER] = ['user1', 'user2', 'user3']; + shares[OC.Share.SHARE_TYPE_USER] = _.map(['User One', 'User Two', 'User Three'], makeDummyShareItem); + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: shares})); + + expect($tr.attr('data-share-recipients')).toEqual('User One, User Three, User Two'); + + OC.Share.updateIcon('file', 1); + + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared with User One, User Three, User Two'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + }); + it('removes share icon after removing all shares from a file', function() { + var $action, $tr; + OC.Share.statuses = {1: {link: false, path: '/subdir'}}; + fileList.setFiles([{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc', + recipients: 'User One, User Two' + }]); + $action = fileList.$el.find('tbody tr:first .action-share'); + $tr = fileList.$el.find('tr:first'); + + expect($action.hasClass('permanent')).toEqual(true); + + $tr.find('.action-share').click(); + + expect(showDropDownStub.calledOnce).toEqual(true); + + // simulate what the dropdown does + OC.Share.itemShares = {}; + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: {}})); + + expect($tr.attr('data-share-recipients')).not.toBeDefined(); + + OC.Share.updateIcon('file', 1); + expect($action.hasClass('permanent')).toEqual(false); + }); + it('keep share text after updating reshare', function() { + var $action, $tr; + OC.Share.statuses = {1: {link: false, path: '/subdir'}}; + fileList.setFiles([{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc', + shareOwner: 'User One' + }]); + $action = fileList.$el.find('tbody tr:first .action-share'); + $tr = fileList.$el.find('tr:first'); + + expect($action.hasClass('permanent')).toEqual(true); + + $tr.find('.action-share').click(); + + expect(showDropDownStub.calledOnce).toEqual(true); + + // simulate what the dropdown does + var shares = {}; + OC.Share.itemShares[OC.Share.SHARE_TYPE_USER] = ['user2']; + shares[OC.Share.SHARE_TYPE_USER] = _.map(['User Two'], makeDummyShareItem); + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: shares})); + + expect($tr.attr('data-share-recipients')).toEqual('User Two'); + + OC.Share.updateIcon('file', 1); + + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared by User One'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + }); + it('keep share text after unsharing reshare', function() { + var $action, $tr; + OC.Share.statuses = {1: {link: false, path: '/subdir'}}; + fileList.setFiles([{ + id: 1, + type: 'file', + name: 'One.txt', + path: '/subdir', + mimetype: 'text/plain', + size: 12, + permissions: OC.PERMISSION_ALL, + etag: 'abc', + shareOwner: 'User One', + recipients: 'User Two' + }]); + $action = fileList.$el.find('tbody tr:first .action-share'); + $tr = fileList.$el.find('tr:first'); + + expect($action.hasClass('permanent')).toEqual(true); + + $tr.find('.action-share').click(); + + expect(showDropDownStub.calledOnce).toEqual(true); + + // simulate what the dropdown does + OC.Share.itemShares = {}; + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: {}})); + + expect($tr.attr('data-share-recipients')).not.toBeDefined(); + + OC.Share.updateIcon('file', 1); + + expect($action.hasClass('permanent')).toEqual(true); + expect($action.find('>span').text()).toEqual('Shared by User One'); + expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); + }); + }); + describe('formatRecipients', function() { + it('returns a single recipient when one passed', function() { + expect(OCA.Sharing.Util.formatRecipients(['User one'])) + .toEqual('User one'); + }); + it('returns two recipients when two passed', function() { + expect(OCA.Sharing.Util.formatRecipients(['User one', 'User two'])) + .toEqual('User one, User two'); + }); + it('returns four recipients with plus when five passed', function() { + var recipients = [ + 'User one', + 'User two', + 'User three', + 'User four', + 'User five' + ]; + expect(OCA.Sharing.Util.formatRecipients(recipients)) + .toEqual('User four, User one, User three, User two, +1'); + }); + it('returns four recipients with plus when ten passed', function() { + var recipients = [ + 'User one', + 'User two', + 'User three', + 'User four', + 'User five', + 'User six', + 'User seven', + 'User eight', + 'User nine', + 'User ten' + ]; + expect(OCA.Sharing.Util.formatRecipients(recipients)) + .toEqual('User four, User one, User three, User two, +6'); + }); + it('returns four recipients with plus when four passed with counter', function() { + var recipients = [ + 'User one', + 'User two', + 'User three', + 'User four' + ]; + expect(OCA.Sharing.Util.formatRecipients(recipients, 10)) + .toEqual('User four, User one, User three, User two, +6'); + }); + }); + +}); diff --git a/apps/files_sharing/tests/js/sharedfilelistSpec.js b/apps/files_sharing/tests/js/sharedfilelistSpec.js index 7aec8322a44..f53e79e277f 100644 --- a/apps/files_sharing/tests/js/sharedfilelistSpec.js +++ b/apps/files_sharing/tests/js/sharedfilelistSpec.js @@ -9,7 +9,8 @@ */ describe('OCA.Sharing.FileList tests', function() { - var testFiles, alertStub, notificationStub, fileList; + var testFiles, alertStub, notificationStub, fileList, fileActions; + var oldFileListPrototype; beforeEach(function() { alertStub = sinon.stub(OC.dialogs, 'alert'); @@ -45,10 +46,17 @@ describe('OCA.Sharing.FileList tests', function() { '<div id="emptycontent">Empty content message</div>' + '</div>' ); + // back up prototype, as it will be extended by + // the sharing code + oldFileListPrototype = _.extend({}, OCA.Files.FileList.prototype); + fileActions = new OCA.Files.FileActions(); + OCA.Sharing.Util.initialize(fileActions); }); afterEach(function() { + OCA.Files.FileList.prototype = oldFileListPrototype; testFiles = undefined; fileList = undefined; + fileActions = undefined; notificationStub.restore(); alertStub.restore(); diff --git a/core/img/filetypes/folder-shared.png b/core/img/filetypes/folder-shared.png Binary files differindex e3c0ee9815a..b4f02ac7399 100644 --- a/core/img/filetypes/folder-shared.png +++ b/core/img/filetypes/folder-shared.png diff --git a/core/img/filetypes/folder-shared.svg b/core/img/filetypes/folder-shared.svg index a389e535439..aba8cadbe28 100644 --- a/core/img/filetypes/folder-shared.svg +++ b/core/img/filetypes/folder-shared.svg @@ -1,60 +1,64 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="32px" width="32px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> <defs> - <linearGradient id="p" x1="27.557" gradientUnits="userSpaceOnUse" y1="7.1627" gradientTransform="matrix(.89186 0 0 1.0539 3.1208 5.4125)" x2="27.557" y2="21.387"> + <linearGradient id="c" y2="21.387" gradientUnits="userSpaceOnUse" x2="27.557" gradientTransform="matrix(.89186 0 0 1.0539 3.1208 5.4125)" y1="7.1627" x1="27.557"> <stop stop-color="#fff" offset="0"/> <stop stop-color="#fff" stop-opacity=".23529" offset=".0097359"/> <stop stop-color="#fff" stop-opacity=".15686" offset=".99001"/> <stop stop-color="#fff" stop-opacity=".39216" offset="1"/> </linearGradient> - <linearGradient id="o" x1="22.935" gradientUnits="userSpaceOnUse" y1="49.629" gradientTransform="matrix(.74675 0 0 .65549 -1.9219 1.1676)" x2="22.809" y2="36.658"> + <linearGradient id="d" y2="36.658" gradientUnits="userSpaceOnUse" x2="22.809" gradientTransform="matrix(.74675 0 0 .65549 -1.9219 1.1676)" y1="49.629" x1="22.935"> <stop stop-color="#0a0a0a" stop-opacity=".498" offset="0"/> <stop stop-color="#0a0a0a" stop-opacity="0" offset="1"/> </linearGradient> - <linearGradient id="n" x1="35.793" gradientUnits="userSpaceOnUse" y1="17.118" gradientTransform="matrix(.64444 0 0 .64286 .53352 .89286)" x2="35.793" y2="43.761"> + <linearGradient id="e" y2="43.761" gradientUnits="userSpaceOnUse" x2="35.793" gradientTransform="matrix(.64444 0 0 .64286 .53352 .89286)" y1="17.118" x1="35.793"> <stop stop-color="#b4cee1" offset="0"/> <stop stop-color="#5d9fcd" offset="1"/> </linearGradient> - <linearGradient id="m" x1="302.86" gradientUnits="userSpaceOnUse" y1="366.65" gradientTransform="matrix(.051143 0 0 .015916 -2.49 22.299)" x2="302.86" y2="609.51"> + <linearGradient id="f" y2="609.51" gradientUnits="userSpaceOnUse" x2="302.86" gradientTransform="matrix(.051143 0 0 .015916 -2.49 22.299)" y1="366.65" x1="302.86"> <stop stop-opacity="0" offset="0"/> <stop offset=".5"/> <stop stop-opacity="0" offset="1"/> </linearGradient> - <radialGradient id="q" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(.019836 0 0 .015916 16.388 22.299)" r="117.14"> + <radialGradient id="b" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(.019836 0 0 .015916 16.388 22.299)" r="117.14"> <stop offset="0"/> <stop stop-opacity="0" offset="1"/> </radialGradient> - <radialGradient id="r" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-.019836 0 0 .015916 15.601 22.299)" r="117.14"> + <radialGradient id="a" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-.019836 0 0 .015916 15.601 22.299)" r="117.14"> <stop offset="0"/> <stop stop-opacity="0" offset="1"/> </radialGradient> - <linearGradient id="l" x1="21.37" gradientUnits="userSpaceOnUse" y1="4.7324" gradientTransform="matrix(.54384 0 0 .61466 3.2689 5.0911)" x2="21.37" y2="34.143"> + <linearGradient id="g" y2="34.143" gradientUnits="userSpaceOnUse" x2="21.37" gradientTransform="matrix(.54384 0 0 .61466 3.2689 5.0911)" y1="4.7324" x1="21.37"> <stop stop-color="#fff" offset="0"/> <stop stop-color="#fff" stop-opacity=".23529" offset=".11063"/> <stop stop-color="#fff" stop-opacity=".15686" offset=".99001"/> <stop stop-color="#fff" stop-opacity=".39216" offset="1"/> </linearGradient> - <linearGradient id="k" x1="62.989" gradientUnits="userSpaceOnUse" y1="13" gradientTransform="matrix(.61905 0 0 .61905 -30.392 1.4286)" x2="62.989" y2="16"> + <linearGradient id="h" y2="16" gradientUnits="userSpaceOnUse" x2="62.989" gradientTransform="matrix(.61905 0 0 .61905 -30.392 1.4286)" y1="13" x1="62.989"> <stop stop-color="#f9f9f9" offset="0"/> <stop stop-color="#d8d8d8" offset="1"/> </linearGradient> - <linearGradient id="j" x1="-51.786" gradientUnits="userSpaceOnUse" x2="-51.786" gradientTransform="matrix(.50703 0 0 .503 68.029 1.3298)" y1="53.514" y2="3.6337"> + <linearGradient id="i" y2="3.6337" gradientUnits="userSpaceOnUse" y1="53.514" gradientTransform="matrix(.50703 0 0 .503 68.029 1.3298)" x2="-51.786" x1="-51.786"> <stop stop-opacity=".32174" offset="0"/> <stop stop-opacity=".27826" offset="1"/> </linearGradient> </defs> - <path opacity=".8" style="color:#000000" d="m4.0002 6.5001c-0.43342 0.005-0.5 0.21723-0.5 0.6349v1.365c-1.2457 0-1-0.002-1 0.54389 0.0216 6.5331 0 6.9014 0 7.4561 0.90135 0 27-2.349 27-3.36v-4.0961c0-0.41767-0.34799-0.54876-0.78141-0.54389h-14.219v-1.365c0-0.41767-0.26424-0.63977-0.69767-0.6349h-9.8023z" stroke="url(#j)" fill="none"/> - <path style="color:#000000" d="m4.0002 7v2h-1v4h26v-4h-15v-2h-10z" fill="url(#k)"/> - <path style="color:#000000" stroke-linecap="round" d="m4.5002 7.5v2h-1v4h25v-4h-15v-2h-9z" stroke="url(#l)" fill="none"/> + <path opacity=".8" style="color:#000000" d="m4.0002 6.5001c-0.43342 0.005-0.5 0.21723-0.5 0.6349v1.365c-1.2457 0-1-0.002-1 0.54389 0.0216 6.5331 0 6.9014 0 7.4561 0.90135 0 27-2.349 27-3.36v-4.0961c0-0.41767-0.34799-0.54876-0.78141-0.54389h-14.219v-1.365c0-0.41767-0.26424-0.63977-0.69767-0.6349h-9.8023z" stroke="url(#i)" fill="none"/> + <path style="color:#000000" d="m4.0002 7v2h-1v4h26v-4h-15v-2h-10z" fill="url(#h)"/> + <path style="color:#000000" d="m4.5002 7.5v2h-1v4h25v-4h-15v-2h-9z" stroke="url(#g)" stroke-linecap="round" fill="none"/> <g transform="translate(.00017936 -1)"> - <rect opacity=".3" height="3.8653" width="24.695" y="28.135" x="3.6472" fill="url(#m)"/> - <path opacity=".3" d="m28.342 28.135v3.865c1.0215 0.0073 2.4695-0.86596 2.4695-1.9328s-1.1399-1.9323-2.4695-1.9323z" fill="url(#q)"/> - <path opacity=".3" d="m3.6472 28.135v3.865c-1.0215 0.0073-2.4695-0.86596-2.4695-1.9328s1.1399-1.9323 2.4695-1.9323z" fill="url(#r)"/> + <rect opacity=".3" height="3.8653" width="24.695" y="28.135" x="3.6472" fill="url(#f)"/> + <path opacity=".3" d="m28.342 28.135v3.865c1.0215 0.0073 2.4695-0.86596 2.4695-1.9328s-1.1399-1.9323-2.4695-1.9323z" fill="url(#b)"/> + <path opacity=".3" d="m3.6472 28.135v3.865c-1.0215 0.0073-2.4695-0.86596-2.4695-1.9328s1.1399-1.9323 2.4695-1.9323z" fill="url(#a)"/> </g> - <path style="color:#000000" d="m1.927 11.5c-0.69105 0.0796-0.32196 0.90258-0.37705 1.3654 0.0802 0.29906 0.59771 15.718 0.59771 16.247 0 0.46018 0.22667 0.38222 0.80101 0.38222h26.397c0.61872 0.0143 0.48796 0.007 0.48796-0.38947 0.0452-0.20269 0.63993-16.978 0.66282-17.243 0-0.279 0.0581-0.3621-0.30493-0.3621h-28.265z" fill="url(#n)"/> - <path opacity=".4" d="m1.682 11 28.636 0.00027c0.4137 0 0.68181 0.29209 0.68181 0.65523l-0.6735 17.712c0.01 0.45948-0.1364 0.64166-0.61707 0.63203l-27.256-0.0115c-0.4137 0-0.83086-0.27118-0.83086-0.63432l-0.62244-17.698c0-0.36314 0.26812-0.65549 0.68182-0.65549z" fill="url(#o)"/> - <path opacity=".5" style="color:#000000" d="m2.5002 12.5 0.62498 16h25.749l0.62498-16z" stroke="url(#p)" stroke-linecap="round" fill="none"/> + <path style="color:#000000" d="m1.927 11.5c-0.69105 0.0796-0.32196 0.90258-0.37705 1.3654 0.0802 0.29906 0.59771 15.718 0.59771 16.247 0 0.46018 0.22667 0.38222 0.80101 0.38222h26.397c0.61872 0.0143 0.48796 0.007 0.48796-0.38947 0.0452-0.20269 0.63993-16.978 0.66282-17.243 0-0.279 0.0581-0.3621-0.30493-0.3621h-28.265z" fill="url(#e)"/> + <path opacity=".4" d="m1.682 11 28.636 0.00027c0.4137 0 0.68181 0.29209 0.68181 0.65523l-0.6735 17.712c0.01 0.45948-0.1364 0.64166-0.61707 0.63203l-27.256-0.0115c-0.4137 0-0.83086-0.27118-0.83086-0.63432l-0.62244-17.698c0-0.36314 0.26812-0.65549 0.68182-0.65549z" fill="url(#d)"/> + <path opacity=".5" style="color:#000000" d="m2.5002 12.5 0.62498 16h25.749l0.62498-16z" stroke="url(#c)" stroke-linecap="round" fill="none"/> <path opacity=".3" stroke-linejoin="round" style="color:#000000" d="m1.927 11.5c-0.69105 0.0796-0.32196 0.90258-0.37705 1.3654 0.0802 0.29906 0.59771 15.718 0.59771 16.247 0 0.46018 0.22667 0.38222 0.80101 0.38222h26.397c0.61872 0.0143 0.48796 0.007 0.48796-0.38947 0.0452-0.20269 0.63993-16.978 0.66282-17.243 0-0.279 0.0581-0.3621-0.30493-0.3621h-28.265z" stroke="#000" stroke-linecap="round" fill="none"/> - <path opacity=".3" style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m12.388 16.483c-0.96482 0-1.7833 0.70559-1.7833 1.6162 0.0069 0.28781 0.03259 0.64272 0.20434 1.3933v0.01858l0.01857 0.01857c0.05513 0.15793 0.13537 0.24827 0.24149 0.37154 0.10612 0.12326 0.23263 0.26834 0.35294 0.39011 0.01415 0.01433 0.02323 0.0232 0.03715 0.03716 0.02386 0.10383 0.05276 0.21557 0.0743 0.3158 0.05732 0.26668 0.05144 0.45553 0.03716 0.52015-0.4146 0.1454-0.9304 0.3187-1.3932 0.5199-0.2598 0.113-0.4949 0.2139-0.6873 0.3344-0.1923 0.1206-0.3836 0.2116-0.4458 0.483-0.000797 0.01237-0.000797 0.02479 0 0.03716-0.06076 0.55788-0.15266 1.3783-0.22291 1.932-0.015166 0.11656 0.046264 0.23943 0.14861 0.29723 0.84033 0.45393 2.1312 0.63663 3.418 0.63161 1.2868-0.005 2.5674-0.19845 3.3808-0.63161 0.10234-0.0578 0.16378-0.18067 0.14861-0.29723-0.0224-0.173-0.05-0.5633-0.0743-0.9474-0.0243-0.384-0.0454-0.7617-0.0743-0.9845-0.0101-0.0552-0.0362-0.1074-0.0743-0.1486-0.2584-0.3086-0.6445-0.4973-1.096-0.6874-0.4122-0.1735-0.8954-0.3538-1.3746-0.5573-0.02682-0.05975-0.05346-0.23358 0-0.50157 0.01436-0.07196 0.03684-0.14903 0.05573-0.22292 0.04503-0.05044 0.08013-0.09166 0.13003-0.14861 0.1064-0.1215 0.2207-0.2489 0.3157-0.3715 0.0951-0.1226 0.1728-0.2279 0.223-0.3715l0.01857-0.01858c0.1941-0.7837 0.1942-1.1107 0.2043-1.3933v-0.01857c0-0.91058-0.81848-1.6162-1.7833-1.6162zm5.101-1.4831c-1.4067 0-2.6 1.0287-2.6 2.3562 0.01 0.4196 0.04751 0.93701 0.29791 2.0312v0.02708l0.02708 0.02708c0.08038 0.23025 0.19736 0.36196 0.35208 0.54166s0.33917 0.39121 0.51458 0.56874c0.02064 0.02089 0.03386 0.03383 0.05416 0.05418 0.03479 0.15137 0.07693 0.31428 0.10833 0.46041 0.08357 0.38879 0.07499 0.66411 0.05417 0.75832-0.6045 0.2122-1.3565 0.465-2.0312 0.7583-0.3789 0.1647-0.7217 0.3118-1.0021 0.4875-0.28044 0.17574-0.55934 0.30851-0.64999 0.70416-0.0012 0.01804-0.0012 0.03613 0 0.05418-0.08858 0.81334-0.22257 2.0094-0.325 2.8166-0.02211 0.16993 0.06745 0.34906 0.21666 0.43333 1.2252 0.66179 3.1072 0.92814 4.9833 0.92082 1.8761-0.0073 3.7431-0.28932 4.9291-0.92082 0.14921-0.08427 0.23878-0.2634 0.21666-0.43333-0.0327-0.25234-0.07287-0.82136-0.10833-1.3812-0.03546-0.55988-0.06625-1.1106-0.10833-1.4354-0.01468-0.0805-0.05274-0.15661-0.10833-0.21666-0.377-0.4498-0.94-0.7248-1.598-1.002-0.601-0.253-1.306-0.5158-2.004-0.8125-0.0391-0.08711-0.07795-0.34054 0-0.73124 0.02093-0.10491 0.05371-0.21727 0.08125-0.325 0.06566-0.07354 0.11683-0.13363 0.18958-0.21666 0.15516-0.17709 0.32189-0.36287 0.46041-0.54166s0.25186-0.33217 0.325-0.54166l0.02708-0.02708c0.28309-1.1425 0.28324-1.6193 0.29792-2.0312v-0.02708c0-1.3275-1.1933-2.3562-2.6-2.3562z" fill="#FFF"/> - <path opacity=".7" style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m12.388 15.483c-0.96482 0-1.7833 0.70559-1.7833 1.6162 0.0069 0.28781 0.03259 0.64272 0.20434 1.3933v0.01858l0.01857 0.01857c0.05513 0.15793 0.13537 0.24827 0.24149 0.37154 0.10612 0.12326 0.23263 0.26834 0.35294 0.39011 0.01415 0.01433 0.02323 0.0232 0.03715 0.03716 0.02386 0.10383 0.05276 0.21557 0.0743 0.3158 0.05732 0.26668 0.05144 0.45553 0.03716 0.52015-0.4146 0.1454-0.9304 0.3187-1.3932 0.5199-0.2598 0.113-0.4949 0.2139-0.6873 0.3344-0.1923 0.1206-0.3836 0.2116-0.4458 0.483-0.000797 0.01237-0.000797 0.02479 0 0.03716-0.06076 0.55788-0.15266 1.3783-0.22291 1.932-0.015166 0.11656 0.046264 0.23943 0.14861 0.29723 0.84033 0.45393 2.1312 0.63663 3.418 0.63161 1.2868-0.005 2.5674-0.19845 3.3808-0.63161 0.10234-0.0578 0.16378-0.18067 0.14861-0.29723-0.0224-0.173-0.05-0.5633-0.0743-0.9474-0.0243-0.384-0.0454-0.7617-0.0743-0.9845-0.0101-0.0552-0.0362-0.1074-0.0743-0.1486-0.2584-0.3086-0.6445-0.4973-1.096-0.6874-0.4122-0.1735-0.8954-0.3538-1.3746-0.5573-0.02682-0.05975-0.05346-0.23358 0-0.50157 0.01436-0.07196 0.03684-0.14903 0.05573-0.22292 0.04503-0.05044 0.08013-0.09166 0.13003-0.14861 0.1064-0.1215 0.2207-0.2489 0.3157-0.3715 0.0951-0.1226 0.1728-0.2279 0.223-0.3715l0.01857-0.01858c0.1941-0.7837 0.1942-1.1107 0.2043-1.3933v-0.01857c0-0.91058-0.81848-1.6162-1.7833-1.6162zm5.101-1.4831c-1.4067 0-2.6 1.0287-2.6 2.3562 0.01 0.4196 0.04751 0.93701 0.29791 2.0312v0.02708l0.02708 0.02708c0.08038 0.23025 0.19736 0.36196 0.35208 0.54166s0.33917 0.39121 0.51458 0.56874c0.02064 0.02089 0.03386 0.03383 0.05416 0.05418 0.03479 0.15137 0.07693 0.31428 0.10833 0.46041 0.08357 0.38879 0.07499 0.66411 0.05417 0.75832-0.6045 0.2122-1.3565 0.465-2.0312 0.7583-0.3789 0.1647-0.7217 0.3118-1.0021 0.4875-0.28044 0.17574-0.55934 0.30851-0.64999 0.70416-0.0012 0.01804-0.0012 0.03613 0 0.05418-0.08858 0.81334-0.22257 2.0094-0.325 2.8166-0.02211 0.16993 0.06745 0.34906 0.21666 0.43333 1.2252 0.66179 3.1072 0.92814 4.9833 0.92082 1.8761-0.0073 3.7431-0.28932 4.9291-0.92082 0.14921-0.08427 0.23878-0.2634 0.21666-0.43333-0.0327-0.25234-0.07287-0.82136-0.10833-1.3812-0.03546-0.55988-0.06625-1.1106-0.10833-1.4354-0.01468-0.0805-0.05274-0.15661-0.10833-0.21666-0.377-0.4498-0.94-0.7248-1.598-1.002-0.601-0.253-1.306-0.5158-2.004-0.8125-0.0391-0.08711-0.07795-0.34054 0-0.73124 0.02093-0.10491 0.05371-0.21727 0.08125-0.325 0.06566-0.07354 0.11683-0.13363 0.18958-0.21666 0.15516-0.17709 0.32189-0.36287 0.46041-0.54166s0.25186-0.33217 0.325-0.54166l0.02708-0.02708c0.28309-1.1425 0.28324-1.6193 0.29792-2.0312v-0.02708c0-1.3275-1.1933-2.3562-2.6-2.3562z"/> + <g opacity=".3" transform="translate(6.9998,-1023.4)" fill="#fff"> + <path fill="#fff" d="m12.228 1037.4c-1.3565 0-2.4592 1.0977-2.4592 2.4542 0 0.075 0.0084 0.1504 0.0149 0.2236l-4.7346 2.4145c-0.4291-0.3667-0.98611-0.5863-1.5947-0.5863-1.3565 0-2.4542 1.0977-2.4542 2.4543 0 1.3565 1.0977 2.4542 2.4542 2.4542 0.54607 0 1.0528-0.1755 1.4606-0.477l4.8637 2.4741c-0.0024 0.044-0.0099 0.089-0.0099 0.1342 0 1.3565 1.1027 2.4542 2.4592 2.4542s2.4542-1.0977 2.4542-2.4542-1.0977-2.4592-2.4542-2.4592c-0.63653 0-1.218 0.2437-1.6544 0.6409l-4.6953-2.4c0.01892-0.1228 0.03478-0.2494 0.03478-0.3775 0-0.072-0.0089-0.1437-0.0149-0.2137l4.7395-2.4145c0.42802 0.3627 0.98488 0.5813 1.5898 0.5813 1.3565 0 2.4542-1.1027 2.4542-2.4592s-1.0977-2.4542-2.4542-2.4542z"/> + </g> + <g opacity=".7" transform="translate(6.9998,-1024.4)"> + <path d="m12.228 1037.4c-1.3565 0-2.4592 1.0977-2.4592 2.4542 0 0.075 0.0084 0.1504 0.0149 0.2236l-4.7346 2.4145c-0.4291-0.3667-0.98611-0.5863-1.5947-0.5863-1.3565 0-2.4542 1.0977-2.4542 2.4543 0 1.3565 1.0977 2.4542 2.4542 2.4542 0.54607 0 1.0528-0.1755 1.4606-0.477l4.8637 2.4741c-0.0024 0.044-0.0099 0.089-0.0099 0.1342 0 1.3565 1.1027 2.4542 2.4592 2.4542s2.4542-1.0977 2.4542-2.4542-1.0977-2.4592-2.4542-2.4592c-0.63653 0-1.218 0.2437-1.6544 0.6409l-4.6953-2.4c0.01892-0.1228 0.03478-0.2494 0.03478-0.3775 0-0.072-0.0089-0.1437-0.0149-0.2137l4.7395-2.4145c0.42802 0.3627 0.98488 0.5813 1.5898 0.5813 1.3565 0 2.4542-1.1027 2.4542-2.4592s-1.0977-2.4542-2.4542-2.4542z"/> + </g> </svg> diff --git a/core/js/share.js b/core/js/share.js index bcc04225b9f..7e3b0d8c65d 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -3,8 +3,25 @@ OC.Share={ SHARE_TYPE_GROUP:1, SHARE_TYPE_LINK:3, SHARE_TYPE_EMAIL:4, + /** + * @deprecated use OC.Share.currentShares instead + */ itemShares:[], + /** + * Full list of all share statuses + */ statuses:{}, + /** + * Shares for the currently selected file. + * (for which the dropdown is open) + * + * Key is item type and value is an array or + * shares of the given item type. + */ + currentShares: {}, + /** + * Whether the share dropdown is opened. + */ droppedDown:false, /** * Loads ALL share statuses from server, stores them in @@ -54,8 +71,9 @@ OC.Share={ $fileList = fileList.$fileList; currentDir = fileList.getCurrentDirectory(); } + // TODO: iterating over the files might be more efficient for (item in OC.Share.statuses){ - var image = OC.imagePath('core', 'actions/shared'); + var image = OC.imagePath('core', 'actions/share'); var data = OC.Share.statuses[item]; var hasLink = data.link; // Links override shared in terms of icon display @@ -63,20 +81,14 @@ OC.Share={ image = OC.imagePath('core', 'actions/public'); } if (itemType !== 'file' && itemType !== 'folder') { - $fileList.find('a.share[data-item="'+item+'"]').css('background', 'url('+image+') no-repeat center'); + $('a.share[data-item="'+item+'"]').css('background', 'url('+image+') no-repeat center'); } else { + // TODO: ultimately this part should be moved to files_sharing app var file = $fileList.find('tr[data-id="'+item+'"]'); var shareFolder = OC.imagePath('core', 'filetypes/folder-shared'); var img; if (file.length > 0) { - var type = file.data('type'); - if (type === 'dir') { - file.children('.filename').css('background-image', 'url('+shareFolder+')'); - } - var action = $(file).find('.fileactions .action[data-action="Share"]'); - img = action.find('img').attr('src', image); - action.addClass('permanent'); - action.html(' <span>'+t('core', 'Shared')+'</span>').prepend(img); + this.markFileAsShared(file, true, hasLink); } else { var dir = currentDir; if (dir.length > 1) { @@ -89,6 +101,7 @@ OC.Share={ var files = $fileList.find('.filename'); var i; for (i = 0; i < actions.length; i++) { + // TODO: use this.markFileAsShared() img = $(actions[i]).find('img'); if (img.attr('src') !== OC.imagePath('core', 'actions/public')) { img.attr('src', image); @@ -125,35 +138,19 @@ OC.Share={ } } else if (OC.Share.itemShares[index].length > 0) { shares = true; - image = OC.imagePath('core', 'actions/shared'); + image = OC.imagePath('core', 'actions/share'); } } }); if (itemType != 'file' && itemType != 'folder') { $('a.share[data-item="'+itemSource+'"]').css('background', 'url('+image+') no-repeat center'); } else { - var file = $('tr').filterAttr('data-id', String(itemSource)); - if (file.length > 0) { - var type = file.data('type'); - var shareFolder = OC.imagePath('core', 'filetypes/folder'); - if (type === 'dir' && shares) { - shareFolder = OC.imagePath('core', 'filetypes/folder-shared'); - file.children('.filename').css('background-image', 'url('+shareFolder+')'); - } else if (type === 'dir') { - file.children('.filename').css('background-image', 'url('+shareFolder+')'); - } - var action = $(file).find('.fileactions .action').filterAttr('data-action', 'Share'); - // in case of multiple lists/rows, there might be more than one visible - action.each(function() { - var action = $(this); - var img = action.find('img').attr('src', image); - if (shares) { - action.addClass('permanent'); - action.html(' <span>'+ escapeHTML(t('core', 'Shared'))+'</span>').prepend(img); - } else { - action.removeClass('permanent'); - action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img); - } + var $tr = $('tr').filterAttr('data-id', String(itemSource)); + if ($tr.length > 0) { + // it might happen that multiple lists exist in the DOM + // with the same id + $tr.each(function() { + OC.Share.markFileAsShared($(this), shares, link); }); } } @@ -164,6 +161,60 @@ OC.Share={ delete OC.Share.statuses[itemSource]; } }, + /** + * Marks/unmarks a given file as shared by changing its action icon + * and folder icon. + * + * @param $tr file element to mark as shared + * @param hasShares whether shares are available + * @param hasLink whether link share is available + */ + markFileAsShared: function($tr, hasShares, hasLink) { + var action = $tr.find('.fileactions .action[data-action="Share"]'); + var type = $tr.data('type'); + var img = action.find('img'); + var message; + var recipients; + var owner = $tr.attr('data-share-owner'); + var shareFolderIcon; + var image = OC.imagePath('core', 'actions/share'); + // update folder icon + if (type === 'dir' && (hasShares || hasLink)) { + if (hasLink) { + shareFolderIcon = OC.imagePath('core', 'filetypes/folder-public'); + } + else { + shareFolderIcon = OC.imagePath('core', 'filetypes/folder-shared'); + } + $tr.children('.filename').css('background-image', 'url(' + shareFolderIcon + ')'); + } else if (type === 'dir') { + shareFolderIcon = OC.imagePath('core', 'filetypes/folder'); + $tr.children('.filename').css('background-image', 'url(' + shareFolderIcon + ')'); + } + // update share action text / icon + if (hasShares || owner) { + recipients = $tr.attr('data-share-recipients'); + + action.addClass('permanent'); + message = t('core', 'Shared'); + // even if reshared, only show "Shared by" + if (owner) { + message = t('files_sharing', 'Shared by {owner}', {owner: owner}); + } + else if (recipients) { + message = t('core', 'Shared with {recipients}', {recipients: recipients}); + } + action.html(' <span>'+ message + '</span>').prepend(img); + } + else { + action.removeClass('permanent'); + action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img); + } + if (hasLink) { + image = OC.imagePath('core', 'actions/public'); + } + img.attr('src', image); + }, loadItem:function(itemType, itemSource) { var data = ''; var checkReshare = true; @@ -319,6 +370,7 @@ OC.Share={ dropDownEl = dropDownEl.appendTo(appendTo); // Reset item shares OC.Share.itemShares = []; + OC.Share.currentShares = {}; if (data.shares) { $.each(data.shares, function(index, share) { if (share.share_type == OC.Share.SHARE_TYPE_LINK) { @@ -392,6 +444,7 @@ OC.Share={ OC.Share.share(itemType, itemSource, shareType, shareWith, permissions, itemSourceName, expirationDate, function() { OC.Share.addShareWith(shareType, shareWith, selected.item.label, permissions, possiblePermissions); $('#shareWith').val(''); + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); OC.Share.updateIcon(itemType, itemSource); }); return false; @@ -448,6 +501,7 @@ OC.Share={ $('#shareWith').focus(); }, hideDropDown:function(callback) { + OC.Share.currentShares = null; $('#dropdown').hide('blind', function() { OC.Share.droppedDown = false; $('#dropdown').remove(); @@ -460,6 +514,12 @@ OC.Share={ }); }, addShareWith:function(shareType, shareWith, shareWithDisplayName, permissions, possiblePermissions, mailSend, collection) { + var shareItem = { + share_type: shareType, + share_with: shareWith, + share_with_displayname: shareWithDisplayName, + permissions: permissions + }; if (shareType === 1) { shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')'; } @@ -538,6 +598,10 @@ OC.Share={ html.find('.cruds').before(showCrudsButton); } $('#expiration').show(); + if (!OC.Share.currentShares[shareType]) { + OC.Share.currentShares[shareType] = []; + } + OC.Share.currentShares[shareType].push(shareItem); } }, showLink:function(token, password, itemSource) { @@ -673,6 +737,9 @@ $(document).ready(function() { $li.remove(); var index = OC.Share.itemShares[shareType].indexOf(shareWith); OC.Share.itemShares[shareType].splice(index, 1); + // updated list of shares + OC.Share.currentShares[shareType].splice(index, 1); + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); OC.Share.updateIcon(itemType, itemSource); if (typeof OC.Share.statuses[itemSource] === 'undefined') { $('#expiration').hide('blind'); @@ -731,6 +798,7 @@ $(document).ready(function() { if (oc_appconfig.core.enforcePasswordForPublicLink === false) { OC.Share.share(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, '', OC.PERMISSION_READ, itemSourceName, expirationDate, function(data) { OC.Share.showLink(data.token, null, itemSource); + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); OC.Share.updateIcon(itemType, itemSource); }); } else { @@ -743,6 +811,7 @@ $(document).ready(function() { if ($('#linkText').val() !== '') { OC.Share.unshare(itemType, itemSource, OC.Share.SHARE_TYPE_LINK, '', function() { OC.Share.itemShares[OC.Share.SHARE_TYPE_LINK] = false; + $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); OC.Share.updateIcon(itemType, itemSource); if (typeof OC.Share.statuses[itemSource] === 'undefined') { $('#expiration').hide('blind'); diff --git a/core/js/tests/specs/shareSpec.js b/core/js/tests/specs/shareSpec.js index a487b71fdbb..458bc41b6a1 100644 --- a/core/js/tests/specs/shareSpec.js +++ b/core/js/tests/specs/shareSpec.js @@ -26,13 +26,17 @@ describe('OC.Share tests', function() { var oldAppConfig; var loadItemStub; var autocompleteStub; + beforeEach(function() { $('#testArea').append($('<div id="shareContainer"></div>')); + // horrible parameters + $('#testArea').append('<input id="allowShareWithLink" type="hidden" value="yes">'); $container = $('#shareContainer'); /* jshint camelcase:false */ - oldAppConfig = oc_appconfig.core; - loadItemStub = sinon.stub(OC.Share, 'loadItem'); + oldAppConfig = _.extend({}, oc_appconfig.core); + oc_appconfig.core.enforcePasswordForPublicLink = false; + loadItemStub = sinon.stub(OC.Share, 'loadItem'); loadItemStub.returns({ reshare: [], shares: [] @@ -89,9 +93,133 @@ describe('OC.Share tests', function() { oc_appconfig.core.defaultExpireDate = ''; // TODO: expect that default date was NOT set }); - // TODO: test password field visibility (whenever enforced or not) - // TODO: check link share field visibility based on whether it is allowed - // TODO: check public upload visibility based on config + describe('Share with link', function() { + // TODO: test ajax calls + // TODO: test password field visibility (whenever enforced or not) + // TODO: check public upload visibility based on config + it('shows share with link checkbox when allowed', function() { + $('#allowShareWithLink').val('yes'); + OC.Share.showDropDown( + 'file', + 123, + $container, + 'http://localhost/dummylink', + 31, + 'shared_file_name.txt' + ); + expect($('#dropdown #linkCheckbox').length).toEqual(1); + }); + it('does not show share with link checkbox when not allowed', function() { + $('#allowShareWithLink').val('no'); + OC.Share.showDropDown( + 'file', + 123, + $container, + 'http://localhost/dummylink', + 31, + 'shared_file_name.txt' + ); + expect($('#dropdown #linkCheckbox').length).toEqual(0); + }); + }); + describe('"sharesChanged" event', function() { + var autocompleteOptions; + var handler; + beforeEach(function() { + handler = sinon.stub(); + loadItemStub.returns({ + reshare: [], + shares: [{ + id: 100, + item_source: 123, + permissions: 31, + share_type: OC.Share.SHARE_TYPE_USER, + share_with: 'user1', + share_with_displayname: 'User One' + }] + }); + OC.Share.showDropDown( + 'file', + 123, + $container, + 'http://localhost/dummylink', + 31, + 'shared_file_name.txt' + ); + $('#dropdown').on('sharesChanged', handler); + autocompleteOptions = autocompleteStub.getCall(0).args[0]; + }); + afterEach(function() { + autocompleteOptions = null; + handler = null; + }); + it('triggers "sharesChanged" event when adding shares', function() { + // simulate autocomplete selection + autocompleteOptions.select(new $.Event('select'), { + item: { + label: 'User Two', + value: { + shareType: OC.Share.SHARE_TYPE_USER, + shareWith: 'user2' + } + } + }); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(handler.calledOnce).toEqual(true); + var shares = handler.getCall(0).args[0].shares; + expect(shares).toBeDefined(); + expect(shares[OC.Share.SHARE_TYPE_USER][0].share_with_displayname).toEqual('User One'); + expect(shares[OC.Share.SHARE_TYPE_USER][1].share_with_displayname).toEqual('User Two'); + expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); + }); + it('triggers "sharesChanged" event when deleting shares', function() { + $('#dropdown .unshare:eq(0)').click(); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(handler.calledOnce).toEqual(true); + var shares = handler.getCall(0).args[0].shares; + expect(shares).toBeDefined(); + expect(shares[OC.Share.SHARE_TYPE_USER]).toEqual([]); + expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); + }); + it('triggers "sharesChanged" event when toggling link share', function() { + // simulate autocomplete selection + $('#dropdown #linkCheckbox').click(); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success', data: { token: 'abc' }}) + ); + expect(handler.calledOnce).toEqual(true); + var shares = handler.getCall(0).args[0].shares; + expect(shares).toBeDefined(); + expect(shares[OC.Share.SHARE_TYPE_USER][0].share_with_displayname).toEqual('User One'); + expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); + + handler.reset(); + + // uncheck checkbox + $('#dropdown #linkCheckbox').click(); + fakeServer.requests[1].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + + expect(handler.calledOnce).toEqual(true); + shares = handler.getCall(0).args[0].shares; + expect(shares).toBeDefined(); + expect(shares[OC.Share.SHARE_TYPE_USER][0].share_with_displayname).toEqual('User One'); + expect(shares[OC.Share.SHARE_TYPE_GROUP]).not.toBeDefined(); + }); + }); }); }); diff --git a/tests/karma.config.js b/tests/karma.config.js index 846e8f7be91..1f903f58210 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -52,7 +52,8 @@ module.exports = function(config) { // only test these files, others are not ready and mess // up with the global namespace/classes/state 'apps/files_sharing/js/app.js', - 'apps/files_sharing/js/sharedfilelist.js' + 'apps/files_sharing/js/sharedfilelist.js', + 'apps/files_sharing/js/share.js' ], testFiles: ['apps/files_sharing/tests/js/*.js'] }]; |