diff options
-rw-r--r-- | apps/files/js/breadcrumb.js | 23 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 27 | ||||
-rw-r--r-- | apps/files_sharing/appinfo/app.php | 2 | ||||
-rw-r--r-- | apps/files_sharing/css/sharebreadcrumb.css | 34 | ||||
-rw-r--r-- | apps/files_sharing/js/share.js | 38 | ||||
-rw-r--r-- | apps/files_sharing/js/sharebreadcrumbview.js | 103 | ||||
-rw-r--r-- | apps/files_sharing/js/sharetabview.js | 4 | ||||
-rw-r--r-- | apps/files_sharing/tests/js/sharedbreadcrumviewSpec.js | 224 | ||||
-rw-r--r-- | core/css/styles.css | 5 | ||||
-rw-r--r-- | tests/karma.config.js | 2 |
10 files changed, 440 insertions, 22 deletions
diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js index 98de7aa374c..ff9700456da 100644 --- a/apps/files/js/breadcrumb.js +++ b/apps/files/js/breadcrumb.js @@ -45,6 +45,7 @@ if (options.getCrumbUrl) { this.getCrumbUrl = options.getCrumbUrl; } + this._detailViews = []; }; /** * @memberof OCA.Files @@ -52,6 +53,7 @@ BreadCrumb.prototype = { $el: null, dir: null, + dirInfo: null, /** * Total width of all breadcrumbs @@ -79,6 +81,20 @@ } }, + setDirectoryInfo: function(dirInfo) { + if (dirInfo !== this.dirInfo) { + this.dirInfo = dirInfo; + this.render(); + } + }, + + /** + * @param {Backbone.View} detailView + */ + addDetailView: function(detailView) { + this._detailViews.push(detailView); + }, + /** * Returns the full URL to the given directory * @@ -122,6 +138,13 @@ } $crumb.addClass('last'); + _.each(this._detailViews, function(view) { + view.render({ + dirInfo: this.dirInfo + }); + $crumb.append(view.$el); + }, this); + // in case svg is not supported by the browser we need to execute the fallback mechanism if (!OC.Util.hasSVGSupport()) { OC.Util.replaceSVG(this.$el); diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 18534db3ee9..c53fa4f3d66 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -473,7 +473,7 @@ * Displays the details view for the given file and * selects the given tab * - * @param {string} fileName file name for which to show details + * @param {string|OCA.Files.FileInfoModel} fileName file name or FileInfoModel for which to show details * @param {string} [tabId] optional tab id to select */ showDetailsView: function(fileName, tabId) { @@ -487,7 +487,7 @@ /** * Update the details view to display the given file * - * @param {string} fileName file name from the current list + * @param {string|OCA.Files.FileInfoModel} fileName file name from the current list or a FileInfoModel object * @param {boolean} [show=true] whether to open the sidebar if it was closed */ _updateDetailsView: function(fileName, show) { @@ -518,13 +518,16 @@ OC.Apps.showAppSidebar(this._detailsView.$el); } - var $tr = this.findFileEl(fileName); - var model = this.getModelForFile($tr); + if (fileName instanceof OCA.Files.FileInfoModel) { + var model = fileName; + } else { + var $tr = this.findFileEl(fileName); + var model = this.getModelForFile($tr); + $tr.addClass('highlighted'); + } this._currentFileModel = model; - $tr.addClass('highlighted'); - this._detailsView.setFileInfo(model); this._detailsView.$el.scrollTop(0); }, @@ -1646,6 +1649,7 @@ // first entry is the root this.dirInfo = result.shift(); + this.breadcrumb.setDirectoryInfo(this.dirInfo); if (this.dirInfo.permissions) { this.setDirectoryPermissions(this.dirInfo.permissions); @@ -2021,7 +2025,7 @@ function updateInList(fileInfo) { self.updateRow(tr, fileInfo); - self._updateDetailsView(fileInfo.name, false); + self._updateDetailsView(fileInfo, false); } // TODO: too many nested blocks, move parts into functions @@ -2954,6 +2958,15 @@ if (this._detailsView) { this._detailsView.addDetailView(detailView); } + }, + + /** + * Register a view to be added to the breadcrumb view + */ + registerBreadCrumbDetailView: function(detailView) { + if (this.breadcrumb) { + this.breadcrumb.addDetailView(detailView); + } } }; diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 7da295afddd..850c2c05977 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -42,7 +42,9 @@ $eventDispatcher->addListener( function() { \OCP\Util::addScript('files_sharing', 'share'); \OCP\Util::addScript('files_sharing', 'sharetabview'); + \OCP\Util::addScript('files_sharing', 'sharebreadcrumbview'); \OCP\Util::addStyle('files_sharing', 'sharetabview'); + \OCP\Util::addStyle('files_sharing', 'sharebreadcrumb'); } ); diff --git a/apps/files_sharing/css/sharebreadcrumb.css b/apps/files_sharing/css/sharebreadcrumb.css new file mode 100644 index 00000000000..792c8a31b7c --- /dev/null +++ b/apps/files_sharing/css/sharebreadcrumb.css @@ -0,0 +1,34 @@ +/** + * @copyright 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +div.crumb span.icon-share, +div.crumb span.icon-public { + display: inline-block; + cursor: pointer; + opacity: 0.2; + margin-right: 6px; +} + +div.crumb span.icon-share.shared, +div.crumb span.icon-public.shared { + opacity: 0.7; +} diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 64fc7ef7296..073cc77e5aa 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -36,19 +36,7 @@ var oldCreateRow = fileList._createRow; fileList._createRow = function(fileData) { var tr = oldCreateRow.apply(this, arguments); - var sharePermissions = fileData.permissions; - if (fileData.mountType && fileData.mountType === "external-root"){ - // for external storages we can't use the permissions of the mountpoint - // instead we show all permissions and only use the share permissions from the mountpoint to handle resharing - sharePermissions = sharePermissions | (OC.PERMISSION_ALL & ~OC.PERMISSION_SHARE); - } - if (fileData.type === 'file') { - // files can't be shared with delete permissions - sharePermissions = sharePermissions & ~OC.PERMISSION_DELETE; - - // create permissions don't mean anything for files - sharePermissions = sharePermissions & ~OC.PERMISSION_CREATE; - } + var sharePermissions = OCA.Sharing.Util.getSharePermissions(fileData); tr.attr('data-share-permissions', sharePermissions); if (fileData.shareOwner) { tr.attr('data-share-owner', fileData.shareOwner); @@ -185,6 +173,9 @@ } }); fileList.registerTabView(shareTab); + + var breadCrumbSharingDetailView = new OCA.Sharing.ShareBreadCrumbView({shareTab: shareTab}); + fileList.registerBreadCrumbDetailView(breadCrumbSharingDetailView); }, /** @@ -248,6 +239,27 @@ text += ', +' + (count - maxRecipients); } return text; + }, + + /** + * @param {Array} fileData + * @returns {String} + */ + getSharePermissions: function(fileData) { + var sharePermissions = fileData.permissions; + if (fileData.mountType && fileData.mountType === "external-root"){ + // for external storages we can't use the permissions of the mountpoint + // instead we show all permissions and only use the share permissions from the mountpoint to handle resharing + sharePermissions = sharePermissions | (OC.PERMISSION_ALL & ~OC.PERMISSION_SHARE); + } + if (fileData.type === 'file') { + // files can't be shared with delete permissions + sharePermissions = sharePermissions & ~OC.PERMISSION_DELETE; + + // create permissions don't mean anything for files + sharePermissions = sharePermissions & ~OC.PERMISSION_CREATE; + } + return sharePermissions; } }; })(); diff --git a/apps/files_sharing/js/sharebreadcrumbview.js b/apps/files_sharing/js/sharebreadcrumbview.js new file mode 100644 index 00000000000..36bbddfaec4 --- /dev/null +++ b/apps/files_sharing/js/sharebreadcrumbview.js @@ -0,0 +1,103 @@ +/* global Handlebars, OC */ + +/** + * @copyright 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2016 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +(function() { + 'use strict'; + + var BreadCrumbView = OC.Backbone.View.extend({ + tagName: 'span', + events: { + click: '_onClick' + }, + _dirInfo: undefined, + + /** @type OCA.Sharing.ShareTabView */ + _shareTab: undefined, + + initialize: function(options) { + this._shareTab = options.shareTab; + }, + + render: function(data) { + this._dirInfo = data.dirInfo || null; + + if (this._dirInfo !== null && (this._dirInfo.path !== '/' || this._dirInfo.name !== '')) { + var isShared = data.dirInfo && data.dirInfo.shareTypes && data.dirInfo.shareTypes.length > 0; + this.$el.removeClass('shared icon-public icon-share'); + if (isShared) { + this.$el.addClass('shared'); + if (data.dirInfo.shareTypes.indexOf(OC.Share.SHARE_TYPE_LINK) !== -1) { + this.$el.addClass('icon-public'); + } else { + this.$el.addClass('icon-share'); + } + } else { + this.$el.addClass('icon-share'); + } + this.$el.show(); + this.delegateEvents(); + } else { + this.$el.removeClass('shared icon-public icon-share'); + this.$el.hide(); + } + + return this; + }, + _onClick: function(e) { + e.preventDefault(); + + var fileInfoModel = new OCA.Files.FileInfoModel(this._dirInfo); + var self = this; + fileInfoModel.on('change', function() { + self.render({ + dirInfo: self._dirInfo + }); + }); + this._shareTab.on('sharesChanged', function(shareModel) { + var shareTypes = []; + var shares = shareModel.getSharesWithCurrentItem(); + + for(var i = 0; i < shares.length; i++) { + if (shareTypes.indexOf(shares[i].share_type) === -1) { + shareTypes.push(shares[i].share_type); + } + } + + if (shareModel.hasLinkShare()) { + shareTypes.push(OC.Share.SHARE_TYPE_LINK); + } + + // Since the dirInfo isn't updated we need to do this dark hackery + self._dirInfo.shareTypes = shareTypes; + + self.render({ + dirInfo: self._dirInfo + }); + }); + OCA.Files.App.fileList.showDetailsView(fileInfoModel, 'shareTabView'); + } + }); + + OCA.Sharing.ShareBreadCrumbView = BreadCrumbView; +})(); diff --git a/apps/files_sharing/js/sharetabview.js b/apps/files_sharing/js/sharetabview.js index 2c7070aa3d5..7bb1f1229d0 100644 --- a/apps/files_sharing/js/sharetabview.js +++ b/apps/files_sharing/js/sharetabview.js @@ -50,6 +50,10 @@ if (this.model) { this.$el.html(this.template()); + if (_.isUndefined(this.model.get('sharePermissions'))) { + this.model.set('sharePermissions', OCA.Sharing.Util.getSharePermissions(this.model.attributes)); + } + // TODO: the model should read these directly off the passed fileInfoModel var attributes = { itemType: this.model.isDirectory() ? 'folder' : 'file', diff --git a/apps/files_sharing/tests/js/sharedbreadcrumviewSpec.js b/apps/files_sharing/tests/js/sharedbreadcrumviewSpec.js new file mode 100644 index 00000000000..b2193f4458b --- /dev/null +++ b/apps/files_sharing/tests/js/sharedbreadcrumviewSpec.js @@ -0,0 +1,224 @@ +/** + * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +describe('OCA.Sharing.ShareBreadCrumbView tests', function() { + var BreadCrumb = OCA.Files.BreadCrumb; + var SharedBreadCrum = OCA.Sharing.ShareBreadCrumbView; + + describe('Rendering', function() { + var bc; + var sbc; + var shareTab; + beforeEach(function() { + bc = new BreadCrumb({ + getCrumbUrl: function(part, index) { + // for testing purposes + return part.dir + '#' + index; + } + }); + shareTab = new OCA.Sharing.ShareTabView(); + sbc = new SharedBreadCrum({ + shareTab: shareTab + }); + bc.addDetailView(sbc); + }); + afterEach(function() { + bc = null; + sbc = null; + shareModel = null; + }); + it('Do not render in root', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/', + type: 'dir', + name: '' + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory(''); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(0); + expect(bc.$el.find('.shared').length).toEqual(0); + expect(bc.$el.find('.icon-public').length).toEqual(0); + }); + it('Render in dir', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir' + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory('/foo'); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(1); + expect(bc.$el.find('.shared').length).toEqual(0); + expect(bc.$el.find('.icon-public').length).toEqual(0); + }); + it('Render shared if dir is shared with user', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [OC.Share.SHARE_TYPE_USER] + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory('/foo'); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(1); + expect(bc.$el.find('.shared').length).toEqual(1); + expect(bc.$el.find('.icon-public').length).toEqual(0); + }); + it('Render shared if dir is shared with group', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [OC.Share.SHARE_TYPE_GROUP] + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory('/foo'); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(1); + expect(bc.$el.find('.shared').length).toEqual(1); + expect(bc.$el.find('.icon-public').length).toEqual(0); + }); + it('Render shared if dir is shared by link', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [OC.Share.SHARE_TYPE_LINK] + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory('/foo'); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(0); + expect(bc.$el.find('.shared').length).toEqual(1); + expect(bc.$el.find('.icon-public').length).toEqual(1); + }); + it('Render shared if dir is shared with remote', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [OC.Share.SHARE_TYPE_REMOTE] + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory('/foo'); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(1); + expect(bc.$el.find('.shared').length).toEqual(1); + expect(bc.$el.find('.icon-public').length).toEqual(0); + }); + it('Render link shared if at least one is a link share', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [ + OC.Share.SHARE_TYPE_USER, + OC.Share.SHARE_TYPE_GROUP, + OC.Share.SHARE_TYPE_LINK, + OC.Share.SHARE_TYPE_EMAIL, + OC.Share.SHARE_TYPE_REMOTE + ] + }); + bc.setDirectoryInfo(dirInfo); + bc.setDirectory('/foo'); + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(0); + expect(bc.$el.find('.shared').length).toEqual(1); + expect(bc.$el.find('.icon-public').length).toEqual(1); + }); + it('Remove shared status from user share', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [OC.Share.SHARE_TYPE_USER] + }); + + bc.setDirectory('/foo'); + bc.setDirectoryInfo(dirInfo); + bc.render(); + + var mock = sinon.createStubInstance(OCA.Files.FileList); + mock.showDetailsView = function() { }; + OCA.Files.App.fileList = mock; + var spy = sinon.spy(mock, 'showDetailsView'); + bc.$el.find('.icon-share').click(); + + expect(spy.calledOnce).toEqual(true); + + var model = sinon.createStubInstance(OC.Share.ShareItemModel); + model.getSharesWithCurrentItem = function() { return [] }; + model.hasLinkShare = function() { return false; }; + + shareTab.trigger('sharesChanged', model); + + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(1); + expect(bc.$el.find('.shared').length).toEqual(0); + expect(bc.$el.find('.icon-public').length).toEqual(0); + }); + it('Add link share to user share', function() { + var dirInfo = new OC.Files.FileInfo({ + id: 42, + path: '/foo', + type: 'dir', + shareTypes: [OC.Share.SHARE_TYPE_USER] + }); + + bc.setDirectory('/foo'); + bc.setDirectoryInfo(dirInfo); + bc.render(); + + var mock = sinon.createStubInstance(OCA.Files.FileList); + mock.showDetailsView = function() { }; + OCA.Files.App.fileList = mock; + var spy = sinon.spy(mock, 'showDetailsView'); + bc.$el.find('.icon-share').click(); + + expect(spy.calledOnce).toEqual(true); + + var model = sinon.createStubInstance(OC.Share.ShareItemModel); + model.getSharesWithCurrentItem = function() { return [ + {share_type: OC.Share.SHARE_TYPE_USER} + ] }; + model.hasLinkShare = function() { return true; }; + + shareTab.trigger('sharesChanged', model); + + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + expect(bc.$el.find('.icon-share').length).toEqual(0); + expect(bc.$el.find('.shared').length).toEqual(1); + expect(bc.$el.find('.icon-public').length).toEqual(1); + }); + }); +}); diff --git a/core/css/styles.css b/core/css/styles.css index d94a31c4cf1..84d3229bda9 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -894,12 +894,15 @@ div.crumb.hidden { display: none; } div.crumb a, -div.crumb span { +div.crumb > span { position: relative; top: 12px; padding: 14px 24px 14px 17px; color: #555; } +div.crumb.last a { + padding-right: 0px; +} div.crumb:first-child a { position: relative; top: 13px; diff --git a/tests/karma.config.js b/tests/karma.config.js index 111af7a1559..d80b5bbd759 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -54,7 +54,7 @@ module.exports = function(config) { 'apps/files_sharing/js/app.js', 'apps/files_sharing/js/sharedfilelist.js', 'apps/files_sharing/js/share.js', - 'apps/files_sharing/js/external.js', + 'apps/files_sharing/js/sharebreadcrumbview.js', 'apps/files_sharing/js/public.js', 'apps/files_sharing/js/sharetabview.js' ], |