1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-30 13:03:53 +01:00

show collection and folder groupings together

This commit is contained in:
Kyle Spearrin 2017-11-22 12:08:31 -05:00
parent 7f0d8c99e3
commit d42e6ca3fd
12 changed files with 147 additions and 442 deletions

2
dist/.publish vendored

@ -1 +1 @@
Subproject commit 697d06985c1b57fdc445195b31391bd99fa1d7e9 Subproject commit d425a19baf91e8a81b27df881600b5a73416dce3

View File

@ -124,12 +124,6 @@ angular
refreshFromServer: false refreshFromServer: false
} }
}) })
.state('backend.user.shared', {
url: '^/shared',
templateUrl: 'app/vault/views/vaultShared.html',
controller: 'vaultSharedController',
data: { pageTitle: 'Shared' }
})
.state('backend.user.settings', { .state('backend.user.settings', {
url: '^/settings', url: '^/settings',
templateUrl: 'app/settings/views/settings.html', templateUrl: 'app/settings/views/settings.html',
@ -375,7 +369,7 @@ angular
// user is guaranteed to be authenticated becuase of previous check // user is guaranteed to be authenticated becuase of previous check
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) { if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
// clear vault rootScope when visiting org admin section // clear vault rootScope when visiting org admin section
$rootScope.vaultCiphers = $rootScope.vaultFolders = null; $rootScope.vaultCiphers = $rootScope.vaultGroupings = null;
authService.getUserProfile().then(function (profile) { authService.getUserProfile().then(function (profile) {
var orgs = profile.organizations; var orgs = profile.organizations;

View File

@ -95,7 +95,7 @@ angular
_service.logOut = function () { _service.logOut = function () {
tokenService.clearTokens(); tokenService.clearTokens();
cryptoService.clearKeys(); cryptoService.clearKeys();
$rootScope.vaultFolders = $rootScope.vaultCiphers = null; $rootScope.vaultGroupings = $rootScope.vaultCiphers = null;
_userProfile = null; _userProfile = null;
}; };

View File

@ -2,9 +2,9 @@
.module('bit.vault') .module('bit.vault')
.controller('vaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, .controller('vaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants) { passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants, $filter) {
$analytics.eventTrack('vaultAddCipherController', { category: 'Modal' }); $analytics.eventTrack('vaultAddCipherController', { category: 'Modal' });
$scope.folders = $rootScope.vaultFolders; $scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.constants = constants; $scope.constants = constants;
$scope.selectedType = constants.cipherType.login.toString(); $scope.selectedType = constants.cipherType.login.toString();
$scope.cipher = { $scope.cipher = {

View File

@ -5,19 +5,22 @@
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants) { cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants) {
$scope.loading = true; $scope.loading = true;
$scope.ciphers = []; $scope.ciphers = [];
$scope.folderCount = 0;
$scope.collectionCount = 0;
$scope.firstCollectionId = null;
$scope.constants = constants; $scope.constants = constants;
$scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders; $scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders;
$scope.folderIdFilter = undefined; $scope.groupingIdFilter = undefined;
$scope.typeFilter = undefined; $scope.typeFilter = undefined;
if ($state.params.refreshFromServer) { if ($state.params.refreshFromServer) {
$rootScope.vaultFolders = $rootScope.vaultCiphers = null; $rootScope.vaultGroupings = $rootScope.vaultCiphers = null;
} }
$scope.$on('$viewContentLoaded', function () { $scope.$on('$viewContentLoaded', function () {
if ($rootScope.vaultFolders && $rootScope.vaultCiphers) { if ($rootScope.vaultGroupings && $rootScope.vaultCiphers) {
$scope.loading = false; $scope.loading = false;
loadFolderData($rootScope.vaultFolders); loadGroupingData($rootScope.vaultGroupings);
loadCipherData($rootScope.vaultCiphers); loadCipherData($rootScope.vaultCiphers);
return; return;
} }
@ -26,20 +29,34 @@
}); });
function loadDataFromServer() { function loadDataFromServer() {
var folderPromise = apiService.folders.list({}, function (folders) { var decGroupings = [{
var decFolders = [{
id: null, id: null,
name: 'No Folder' name: 'No Folder',
folder: true
}]; }];
var collectionPromise = apiService.collections.listMe({ writeOnly: false }, function (collections) {
for (var i = 0; i < collections.Data.length; i++) {
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
decCollection.collection = true;
decGroupings.push(decCollection);
}
$scope.collectionCount = collections.Data.length;
}).$promise;
var folderPromise = apiService.folders.list({}, function (folders) {
for (var i = 0; i < folders.Data.length; i++) { for (var i = 0; i < folders.Data.length; i++) {
var decFolder = cipherService.decryptFolderPreview(folders.Data[i]); var decFolder = cipherService.decryptFolderPreview(folders.Data[i]);
decFolders.push(decFolder); decFolder.folder = true;
decGroupings.push(decFolder);
} }
$scope.folderCount = folders.Data.length;
loadFolderData(decFolders);
}).$promise; }).$promise;
var groupingPromise = $q.all([collectionPromise, folderPromise]).then(function () {
loadGroupingData(decGroupings);
});
var cipherPromise = apiService.ciphers.list({}, function (ciphers) { var cipherPromise = apiService.ciphers.list({}, function (ciphers) {
var decCiphers = []; var decCiphers = [];
@ -48,31 +65,38 @@
decCiphers.push(decCipher); decCiphers.push(decCipher);
} }
folderPromise.then(function () { groupingPromise.then(function () {
loadCipherData(decCiphers); loadCipherData(decCiphers);
}); });
}).$promise; }).$promise;
$q.all([cipherPromise, folderPromise]).then(function () { $q.all([cipherPromise, groupingPromise]).then(function () {
$scope.loading = false; $scope.loading = false;
}); });
} }
function loadFolderData(decFolders) { function loadGroupingData(decGroupings) {
$rootScope.vaultFolders = $filter('orderBy')(decFolders, folderSort); $rootScope.vaultGroupings = $filter('orderBy')(decGroupings, ['folder', groupingSort]);
var collections = $filter('filter')($rootScope.vaultGroupings, { collection: true });
if (collections && collections.length) {
$scope.firstCollectionId = collections[0].id;
}
} }
function loadCipherData(decCiphers) { function loadCipherData(decCiphers) {
angular.forEach($rootScope.vaultFolders, function (folderValue, folderIndex) { angular.forEach($rootScope.vaultGroupings, function (grouping, groupingIndex) {
folderValue.collapsed = $localStorage.collapsedFolders && grouping.collapsed = $localStorage.collapsedFolders &&
(folderValue.id || 'none') in $localStorage.collapsedFolders; (grouping.id || 'none') in $localStorage.collapsedFolders;
angular.forEach(decCiphers, function (cipherValue) { angular.forEach(decCiphers, function (cipherValue) {
if (cipherValue.favorite) { if (cipherValue.favorite) {
cipherValue.sort = -1; cipherValue.sort = -1;
} }
else if (cipherValue.folderId == folderValue.id) { else if (grouping.folder && cipherValue.folderId == grouping.id) {
cipherValue.sort = folderIndex; cipherValue.sort = groupingIndex;
}
else if (grouping.collection && cipherValue.collectionIds.indexOf(grouping.id) > -1) {
cipherValue.sort = groupingIndex;
} }
}); });
}); });
@ -110,7 +134,7 @@
return chunks; return chunks;
} }
function folderSort(item) { function groupingSort(item) {
if (!item.id) { if (!item.id) {
return ''; return '';
} }
@ -123,12 +147,12 @@
'Edit the item and copy it manually instead.'); 'Edit the item and copy it manually instead.');
}; };
$scope.collapseExpand = function (folder, favorite) { $scope.collapseExpand = function (grouping, favorite) {
if (!$localStorage.collapsedFolders) { if (!$localStorage.collapsedFolders) {
$localStorage.collapsedFolders = {}; $localStorage.collapsedFolders = {};
} }
var id = favorite ? 'favorite' : (folder.id || 'none'); var id = favorite ? 'favorite' : (grouping.id || 'none');
if (id in $localStorage.collapsedFolders) { if (id in $localStorage.collapsedFolders) {
delete $localStorage.collapsedFolders[id]; delete $localStorage.collapsedFolders[id];
} }
@ -276,8 +300,8 @@
}); });
addModel.result.then(function (addedFolder) { addModel.result.then(function (addedFolder) {
$rootScope.vaultFolders.push(addedFolder); $rootScope.vaultGroupings.push(addedFolder);
loadFolderData($rootScope.vaultFolders); loadGroupingData($rootScope.vaultGroupings);
}); });
}; };
@ -288,9 +312,9 @@
apiService.folders.del({ id: folder.id }, function () { apiService.folders.del({ id: folder.id }, function () {
$analytics.eventTrack('Deleted Folder'); $analytics.eventTrack('Deleted Folder');
var index = $rootScope.vaultFolders.indexOf(folder); var index = $rootScope.vaultGroupings.indexOf(folder);
if (index > -1) { if (index > -1) {
$rootScope.vaultFolders.splice(index, 1); $rootScope.vaultGroupings.splice(index, 1);
} }
}); });
}; };
@ -319,7 +343,7 @@
}); });
}; };
$scope.collections = function (cipher) { $scope.editCollections = function (cipher) {
var modal = $uibModal.open({ var modal = $uibModal.open({
animation: true, animation: true,
templateUrl: 'app/vault/views/vaultCipherCollections.html', templateUrl: 'app/vault/views/vaultCipherCollections.html',
@ -333,11 +357,14 @@
if (response.collectionIds && !response.collectionIds.length) { if (response.collectionIds && !response.collectionIds.length) {
removeCipherFromScopes(cipher); removeCipherFromScopes(cipher);
} }
else if (response.collectionIds) {
cipher.collectionIds = response.collectionIds;
}
}); });
}; };
$scope.filterFolder = function (folder) { $scope.filterGrouping = function (grouping) {
$scope.folderIdFilter = folder.id; $scope.groupingIdFilter = grouping.id;
if ($.AdminLTE && $.AdminLTE.layout) { if ($.AdminLTE && $.AdminLTE.layout) {
$timeout(function () { $timeout(function () {
@ -357,7 +384,7 @@
}; };
$scope.clearFilters = function () { $scope.clearFilters = function () {
$scope.folderIdFilter = undefined; $scope.groupingIdFilter = undefined;
$scope.typeFilter = undefined; $scope.typeFilter = undefined;
if ($.AdminLTE && $.AdminLTE.layout) { if ($.AdminLTE && $.AdminLTE.layout) {
@ -367,12 +394,22 @@
} }
}; };
$scope.folderFilter = function (folder) { $scope.groupingFilter = function (grouping) {
return $scope.folderIdFilter === undefined || folder.id === $scope.folderIdFilter; return $scope.groupingIdFilter === undefined || grouping.id === $scope.groupingIdFilter;
}; };
$scope.cipherFilter = function (cipher) { $scope.cipherFilter = function (grouping) {
return $scope.typeFilter === undefined || cipher.type === $scope.typeFilter; return function (cipher) {
var matchesGrouping = grouping === null;
if (!matchesGrouping && grouping.folder && cipher.folderId === grouping.id) {
matchesGrouping = true;
}
else if (!matchesGrouping && grouping.collection && cipher.collectionIds.indexOf(grouping.id) > -1) {
matchesGrouping = true;
}
return matchesGrouping && ($scope.typeFilter === undefined || cipher.type === $scope.typeFilter);
};
}; };
$scope.unselectAll = function () { $scope.unselectAll = function () {

View File

@ -2,9 +2,9 @@
.module('bit.vault') .module('bit.vault')
.controller('vaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, .controller('vaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
passwordService, cipherId, $analytics, $rootScope, authService, $uibModal, constants) { passwordService, cipherId, $analytics, $rootScope, authService, $uibModal, constants, $filter) {
$analytics.eventTrack('vaultEditCipherController', { category: 'Modal' }); $analytics.eventTrack('vaultEditCipherController', { category: 'Modal' });
$scope.folders = $rootScope.vaultFolders; $scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.cipher = {}; $scope.cipher = {};
$scope.readOnly = false; $scope.readOnly = false;
$scope.constants = constants; $scope.constants = constants;

View File

@ -2,9 +2,9 @@
.module('bit.vault') .module('bit.vault')
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics, .controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
$rootScope) { $rootScope, $filter) {
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' }); $analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
$scope.folders = $rootScope.vaultFolders; $scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.count = ids.length; $scope.count = ids.length;
$scope.save = function () { $scope.save = function () {

View File

@ -1,239 +0,0 @@
angular
.module('bit.vault')
.controller('vaultSharedController', function ($scope, apiService, cipherService, $analytics, $q, $localStorage,
$uibModal, $filter, $rootScope, authService, cryptoService) {
$scope.ciphers = [];
$scope.collections = [];
$scope.loading = true;
$scope.$on('$viewContentLoaded', function () {
var collectionPromise = apiService.collections.listMe({ writeOnly: false }, function (collections) {
var decCollections = [];
for (var i = 0; i < collections.Data.length; i++) {
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
decCollection.collapsed = $localStorage.collapsedCollections &&
decCollection.id in $localStorage.collapsedCollections;
decCollections.push(decCollection);
}
$scope.collections = decCollections;
}).$promise;
var cipherPromise = apiService.ciphers.listDetails({}, function (ciphers) {
var decCiphers = [];
for (var i = 0; i < ciphers.Data.length; i++) {
var decCipher = cipherService.decryptCipherPreview(ciphers.Data[i]);
decCiphers.push(decCipher);
}
if (decCiphers.length) {
$scope.collections.push({
id: null,
name: 'Unassigned',
collapsed: $localStorage.collapsedCollections && 'unassigned' in $localStorage.collapsedCollections
});
}
$scope.ciphers = decCiphers;
}).$promise;
$q.all([collectionPromise, cipherPromise]).then(function () {
$scope.loading = false;
});
});
$scope.clipboardError = function (e) {
alert('Your web browser does not support easy clipboard copying. ' +
'Edit the item and copy it manually instead.');
};
$scope.attachments = function (cipher) {
authService.getUserProfile().then(function (profile) {
return {
isPremium: profile.premium,
orgUseStorage: cipher.organizationId && !!profile.organizations[cipher.organizationId].maxStorageGb
};
}).then(function (perms) {
if (cipher.organizationId && !perms.orgUseStorage) {
$uibModal.open({
animation: true,
templateUrl: 'app/views/paidOrgRequired.html',
controller: 'paidOrgRequiredController',
resolve: {
orgId: function () { return cipher.organizationId; }
}
});
return;
}
if (!cipher.organizationId && !perms.isPremium) {
$uibModal.open({
animation: true,
templateUrl: 'app/views/premiumRequired.html',
controller: 'premiumRequiredController'
});
return;
}
if (!cipher.organizationId && !cryptoService.getEncKey()) {
toastr.error('You cannot use this feature until you update your encryption key.', 'Feature Unavailable');
return;
}
var attachmentModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultAttachments.html',
controller: 'vaultAttachmentsController',
resolve: {
cipherId: function () { return cipher.id; }
}
});
attachmentModel.result.then(function (hasAttachments) {
cipher.hasAttachments = hasAttachments;
});
});
};
$scope.filterByCollection = function (collection) {
return function (cipher) {
if (!cipher.collectionIds || !cipher.collectionIds.length) {
return collection.id === null;
}
return cipher.collectionIds.indexOf(collection.id) > -1;
};
};
$scope.collectionSort = function (item) {
if (!item.id) {
return '';
}
return item.name.toLowerCase();
};
$scope.collapseExpand = function (collection) {
if (!$localStorage.collapsedCollections) {
$localStorage.collapsedCollections = {};
}
var id = collection.id || 'unassigned';
if (id in $localStorage.collapsedCollections) {
delete $localStorage.collapsedCollections[id];
}
else {
$localStorage.collapsedCollections[id] = true;
}
};
$scope.editCipher = function (cipher) {
var editModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultEditCipher.html',
controller: 'vaultEditCipherController',
resolve: {
cipherId: function () { return cipher.id; }
}
});
editModel.result.then(function (returnVal) {
var rootCipher = findRootCipher(cipher) || { meta: {} },
index;
if (returnVal.action === 'edit') {
index = $scope.ciphers.indexOf(cipher);
if (index > -1) {
returnVal.data.collectionIds = $scope.ciphers[index].collectionIds;
$scope.ciphers[index] = returnVal.data;
if ($rootScope.vaultCiphers) {
index = $rootScope.vaultCiphers.indexOf(rootCipher);
if (index > -1) {
$rootScope.vaultCiphers[index] = returnVal.data;
}
}
}
}
else if (returnVal.action === 'partialEdit') {
cipher.folderId = rootCipher.folderId = returnVal.data.folderId;
cipher.favorite = rootCipher.favorite = returnVal.data.favorite;
}
else if (returnVal.action === 'delete') {
index = $scope.ciphers.indexOf(cipher);
if (index > -1) {
$scope.ciphers.splice(index, 1);
}
removeRootCipher(rootCipher);
}
});
};
$scope.editCollections = function (cipher) {
var modal = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultCipherCollections.html',
controller: 'vaultCipherCollectionsController',
resolve: {
cipherId: function () { return cipher.id; }
}
});
modal.result.then(function (response) {
if (response.collectionIds) {
cipher.collectionIds = response.collectionIds;
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this cipher
// which means it should be removed by calling removeRootCipher(findRootCipher(cipher))
}
});
};
$scope.removeCipher = function (cipher, collection) {
if (!confirm('Are you sure you want to remove this item (' + cipher.name + ') from the ' +
'collection (' + collection.name + ') ?')) {
return;
}
var request = {
collectionIds: []
};
for (var i = 0; i < cipher.collectionIds.length; i++) {
if (cipher.collectionIds[i] !== collection.id) {
request.collectionIds.push(cipher.collectionIds[i]);
}
}
apiService.ciphers.putCollections({ id: cipher.id }, request).$promise.then(function (response) {
$analytics.eventTrack('Removed From Collection');
cipher.collectionIds = request.collectionIds;
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this cipher
// which means it should be removed by calling removeRootCipher(findRootCipher(cipher))
});
};
function findRootCipher(cipher) {
if ($rootScope.vaultCiphers) {
var rootCiphers = $filter('filter')($rootScope.vaultCiphers, { id: cipher.id });
if (rootCiphers && rootCiphers.length) {
return rootCiphers[0];
}
}
return null;
}
function removeRootCipher(rootCipher) {
if (rootCipher && rootCipher.id) {
var index = $rootScope.vaultCiphers.indexOf(rootCipher);
if (index > -1) {
$rootScope.vaultCiphers.splice(index, 1);
}
}
}
});

View File

@ -26,20 +26,19 @@
</div> </div>
<h1> <h1>
My Vault My Vault
<small> <small class="visible-md-inline visible-lg-inline">
<span ng-pluralize <span ng-pluralize count="folderCount" when="{'1': '{} folder', 'other': '{} folders'}"></span>,
count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0" <span ng-pluralize count="collectionCount" when="{'1': '{} collection', 'other': '{} collections'}"></span>, &amp;
when="{'1': '{} folder', 'other': '{} folders'}"></span>,
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span> <span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
</small> </small>
</h1> </h1>
</section> </section>
<section class="content"> <section class="content">
<div ng-show="loading && !vaultFolders.length"> <div ng-show="loading && !vaultGroupings.length">
<p>Loading...</p> <p>Loading...</p>
</div> </div>
<div class="box box-primary" ng-class="{'collapsed-box': favoriteCollapsed}" style="margin-bottom: 40px;" <div class="box box-primary" ng-class="{'collapsed-box': favoriteCollapsed}" style="margin-bottom: 40px;"
ng-show="vaultFolders.length && folderIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)"> ng-show="vaultGroupings.length && groupingIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"> <h3 class="box-title">
<i class="fa fa-star"></i> <i class="fa fa-star"></i>
@ -74,7 +73,7 @@
<table class="table table-striped table-hover table-vmiddle"> <table class="table table-striped table-hover table-vmiddle">
<tbody> <tbody>
<tr ng-repeat="cipher in favoriteCiphers = (ciphers | filter: { favorite: true } | <tr ng-repeat="cipher in favoriteCiphers = (ciphers | filter: { favorite: true } |
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id"> filter: cipherFilter(null) | filter: (main.searchVaultText || '')) track by cipher.id">
<td style="width: 70px;"> <td style="width: 70px;">
<div class="btn-group" data-append-to="body"> <div class="btn-group" data-append-to="body">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
@ -97,7 +96,7 @@
</a> </a>
</li> </li>
<li ng-show="cipher.organizationId && cipher.edit"> <li ng-show="cipher.organizationId && cipher.edit">
<a href="#" stop-click ng-click="collections(cipher)"> <a href="#" stop-click ng-click="editCollections(cipher)">
<i class="fa fa-fw fa-cubes"></i> Collections <i class="fa fa-fw fa-cubes"></i> Collections
</a> </a>
</li> </li>
@ -137,59 +136,69 @@
</div> </div>
</div> </div>
</div> </div>
<div class="box" ng-class="{'collapsed-box': folder.collapsed}" <div class="box" ng-class="{'collapsed-box': grouping.collapsed}"
ng-repeat="folder in filteredVaultFolders = (vaultFolders | filter: folderFilter) track by folder.id" ng-repeat="grouping in filteredVaultGroupings = (vaultGroupings | filter: groupingFilter) track by grouping.id"
ng-show="vaultFolders.length && (!main.searchVaultText || folderCiphers.length)"> ng-show="vaultGroupings.length && (!main.searchVaultText || groupingCiphers.length)"
ng-style="firstCollectionId && grouping.id === firstCollectionId && {'margin-top': '40px'}">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"> <h3 class="box-title">
<i class="fa" ng-class="{'fa-folder-open': folder.id !== null, 'fa-folder-open-o': folder.id === null}"></i> <i class="fa" ng-if="grouping.folder"
{{folder.name}} ng-class="{'fa-folder-open': grouping.id !== null, 'fa-folder-open-o': grouping.id === null}"></i>
<small ng-pluralize count="folderCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small> <i class="fa fa-cubes" ng-if="grouping.collection"></i>
{{grouping.name}}
<small ng-pluralize count="groupingCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
</h3> </h3>
<div class="box-tools"> <div class="box-tools">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"> <button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cog"></i> <span class="caret"></span> <i class="fa fa-cog"></i> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right"> <ul class="dropdown-menu dropdown-menu-right" ng-if="grouping.folder">
<li> <li>
<a href="#" stop-click ng-click="addCipher(folder)"> <a href="#" stop-click ng-click="addCipher(grouping)">
<i class="fa fa-fw fa-plus-circle"></i> New Item <i class="fa fa-fw fa-plus-circle"></i> New Item
</a> </a>
</li> </li>
<li ng-show="folder.id"> <li ng-show="grouping.id">
<a href="#" stop-click ng-click="editFolder(folder)"> <a href="#" stop-click ng-click="editFolder(grouping)">
<i class="fa fa-fw fa-pencil"></i> Edit Folder <i class="fa fa-fw fa-pencil"></i> Edit Folder
</a> </a>
</li> </li>
<li ng-show="canDeleteFolder(grouping)">
<a href="#" stop-click ng-click="deleteFolder(grouping)" class="text-red">
<i class="fa fa-fw fa-trash"></i> Delete Folder
</a>
</li>
<li> <li>
<a href="#" stop-click ng-click="selectFolder(folder, $event)"> <a href="#" stop-click ng-click="selectFolder(grouping, $event)">
<i class="fa fa-fw fa-check-square-o"></i> Select All <i class="fa fa-fw fa-check-square-o"></i> Select All
</a> </a>
</li> </li>
<li ng-show="canDeleteFolder(folder)"> </ul>
<a href="#" stop-click ng-click="deleteFolder(folder)" class="text-red"> <ul class="dropdown-menu dropdown-menu-right" ng-if="grouping.collection">
<i class="fa fa-fw fa-trash"></i> Delete Folder <li>
<a href="#" stop-click ng-click="selectFolder(grouping, $event)">
<i class="fa fa-fw fa-check-square-o"></i> Select All
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand" <button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
ng-click="collapseExpand(folder)"> ng-click="collapseExpand(grouping)">
<i class="fa" ng-class="{'fa-minus': !folder.collapsed, 'fa-plus': folder.collapsed}"></i> <i class="fa" ng-class="{'fa-minus': !grouping.collapsed, 'fa-plus': grouping.collapsed}"></i>
</button> </button>
</div> </div>
</div> </div>
<div class="box-body" ng-class="{'no-padding': folderCiphers.length}"> <div class="box-body" ng-class="{'no-padding': groupingCiphers.length}">
<div ng-show="!folderCiphers.length"> <div ng-show="!groupingCiphers.length">
<p>No items in this folder.</p> <p>No items in this collection/folder.</p>
<button type="button" ng-click="addCipher(folder)" class="btn btn-default btn-flat">Add an Item</button> <button type="button" ng-click="addCipher(grouping)" class="btn btn-default btn-flat">Add an Item</button>
</div> </div>
<div class="table-responsive" ng-show="folderCiphers.length"> <div class="table-responsive" ng-show="groupingCiphers.length">
<table class="table table-striped table-hover table-vmiddle"> <table class="table table-striped table-hover table-vmiddle">
<tbody> <tbody>
<tr ng-repeat="cipher in folderCiphers = (ciphers | filter: { folderId: folder.id } | <tr ng-repeat="cipher in groupingCiphers = (ciphers | filter: cipherFilter(grouping) |
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id"> filter: (main.searchVaultText || '')) track by cipher.id">
<td style="width: 70px;"> <td style="width: 70px;">
<div class="btn-group" data-append-to="body"> <div class="btn-group" data-append-to="body">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
@ -212,7 +221,7 @@
</a> </a>
</li> </li>
<li ng-show="cipher.organizationId && cipher.edit"> <li ng-show="cipher.organizationId && cipher.edit">
<a href="#" stop-click ng-click="collections(cipher)"> <a href="#" stop-click ng-click="editCollections(cipher)">
<i class="fa fa-fw fa-cubes"></i> Collections <i class="fa fa-fw fa-cubes"></i> Collections
</a> </a>
</li> </li>
@ -298,19 +307,37 @@
<h3 class="control-sidebar-heading"> <h3 class="control-sidebar-heading">
<i class="fa fa-folder fa-fw"></i> Folders <i class="fa fa-folder fa-fw"></i> Folders
</h3> </h3>
<div ng-show="loading && !vaultFolders.length"> <div ng-show="loading && !vaultGroupings.length">
<p>Loading...</p> <p>Loading...</p>
</div> </div>
<div class="control-sidebar-section"> <div class="control-sidebar-section">
<ul class="control-sidebar-menu" ng-show="!loading && vaultFolders.length"> <ul class="control-sidebar-menu" ng-show="!loading && folders.length">
<li ng-repeat="folder in vaultFolders track by folder.id"> <li ng-repeat="folder in folders = (vaultGroupings | filter: {folder: true}) track by folder.id">
<a href="#" stop-click ng-click="filterFolder(folder)"> <a href="#" stop-click ng-click="filterGrouping(folder)">
<i class="fa fa-check fa-fw" ng-if="folder.id === folderIdFilter"></i> <i class="fa fa-check fa-fw" ng-if="folder.id === groupingIdFilter"></i>
<i class="fa fa-caret-right fa-fw" ng-if="folder.id !== folderIdFilter"></i> <i class="fa fa-caret-right fa-fw" ng-if="folder.id !== groupingIdFilter"></i>
{{folder.name}} {{folder.name}}
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<h3 class="control-sidebar-heading">
<i class="fa fa-cubes fa-fw"></i> Collections
</h3>
<div ng-show="loading && !vaultGroupings.length">
<p>Loading...</p>
</div>
<div class="control-sidebar-section">
<ul class="control-sidebar-menu" ng-show="!loading && collections.length">
<li ng-repeat="collection in collections =
(vaultGroupings | filter: {collection: true}) track by collection.id">
<a href="#" stop-click ng-click="filterGrouping(collection)">
<i class="fa fa-check fa-fw" ng-if="collection.id === groupingIdFilter"></i>
<i class="fa fa-caret-right fa-fw" ng-if="collection.id !== groupingIdFilter"></i>
{{collection.name}}
</a>
</li>
</ul>
</div>
</div> </div>
</aside> </aside>

View File

@ -1,108 +0,0 @@
<section class="content-header">
<h1>
Shared
<small>
<span ng-pluralize
count="collections.length > 0 && ciphers.length ? collections.length - 1 : collections.length"
when="{'1': '{} collection', 'other': '{} collections'}"></span>,
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
</small>
</h1>
</section>
<section class="content">
<p ng-show="loading && !collections.length">Loading...</p>
<div class="callout callout-default" style="background: #fff;" ng-show="!loading && !collections.length && !ciphers.length">
<h4>Nothing shared <i class="fa fa-frown-o"></i></h4>
<p>
You do not have any items or collections being shared with you.
To start sharing, create an organization or ask an existing organization to invite you.
</p>
<a ui-sref="backend.user.settingsCreateOrg" class="btn btn-default btn-flat">
Create an Organization
</a>
</div>
<div class="box" ng-class="{'collapsed-box': collection.collapsed}" ng-repeat="collection in collections |
orderBy: collectionSort track by collection.id"
ng-show="collections.length">
<div class="box-header with-border">
<h3 class="box-title">
<i class="fa" ng-class="{'fa-cubes': collection.id, 'fa-sitemap': !collection.id}"></i>
{{collection.name}}
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
</h3>
<div class="box-tools">
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
ng-click="collapseExpand(collection)">
<i class="fa" ng-class="{'fa-minus': !collection.collapsed, 'fa-plus': collection.collapsed}"></i>
</button>
</div>
</div>
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
<div ng-show="!collectionCiphers.length && collection.id">
<p>No items in this collection.</p>
<p>
Share an item to this collection by selecting <i class="fa fa-share-alt"></i> <b>Share</b> or
<i class="fa fa-cubes"></i> <b>Collections</b> from the item's options (<i class="fa fa-cog"></i>) menu.
</p>
</div>
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
<div class="table-responsive" ng-show="collectionCiphers.length">
<table class="table table-striped table-hover table-vmiddle">
<tbody>
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
orderBy: ['name', 'subTitle']) track by cipher.id">
<td style="width: 70px;">
<div class="btn-group" data-append-to="body">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cog"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="#" stop-click ng-click="editCipher(cipher)">
<i class="fa fa-fw fa-pencil"></i> Edit
</a>
</li>
<li>
<a href="#" stop-click ng-click="attachments(cipher)">
<i class="fa fa-fw fa-paperclip"></i> Attachments
</a>
</li>
<li ng-show="cipher.edit">
<a href="#" stop-click ng-click="editCollections(cipher)">
<i class="fa fa-fw fa-cubes"></i> Collections
</a>
</li>
<li ng-show="cipher.meta.password">
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
data-clipboard-text="{{cipher.meta.password}}">
<i class="fa fa-fw fa-clipboard"></i> Copy Password
</a>
</li>
<li ng-show="cipher.edit">
<a href="#" stop-click ng-click="removeCipher(cipher, collection)"
ng-if="collection.id" class="text-red">
<i class="fa fa-fw fa-remove"></i> Remove
</a>
</li>
</ul>
</div>
</td>
<td class="vault-icon">
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
fallback-src="images/fa-globe.png" />
</td>
<td>
<a href="#" stop-click ng-click="editCipher(cipher)">{{cipher.name}}</a>
<i class="fa fa-star text-muted" title="Favorite" ng-show="cipher.favorite"></i>
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
stop-prop></i><br />
<div class="text-sm text-muted">{{cipher.subTitle}}</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>

View File

@ -57,11 +57,6 @@
</li> </li>
</ul> </ul>
</li> </li>
<li class="treeview" ng-class="{active: $state.is('backend.user.shared')}">
<a ui-sref="backend.user.shared">
<i class="fa fa-share-alt fa-fw"></i> <span>Shared</span>
</a>
</li>
<li class="treeview" ng-class="{active: $state.is('backend.user.tools') || <li class="treeview" ng-class="{active: $state.is('backend.user.tools') ||
$state.is('backend.user.reportsBreach')}"> $state.is('backend.user.reportsBreach')}">
<a ui-sref="backend.user.tools"><i class="fa fa-wrench fa-fw"></i> <span>Tools</span></a> <a ui-sref="backend.user.tools"><i class="fa fa-wrench fa-fw"></i> <span>Tools</span></a>

View File

@ -223,7 +223,6 @@
<script src="app/vault/vaultEditFolderController.js"></script> <script src="app/vault/vaultEditFolderController.js"></script>
<script src="app/vault/vaultAddFolderController.js"></script> <script src="app/vault/vaultAddFolderController.js"></script>
<script src="app/vault/vaultShareCipherController.js"></script> <script src="app/vault/vaultShareCipherController.js"></script>
<script src="app/vault/vaultSharedController.js"></script>
<script src="app/vault/vaultCipherCollectionsController.js"></script> <script src="app/vault/vaultCipherCollectionsController.js"></script>
<script src="app/vault/vaultMoveCiphersController.js"></script> <script src="app/vault/vaultMoveCiphersController.js"></script>
<script src="app/vault/vaultAttachmentsController.js"></script> <script src="app/vault/vaultAttachmentsController.js"></script>