diff --git a/dist/.publish b/dist/.publish
index 697d06985c..d425a19baf 160000
--- a/dist/.publish
+++ b/dist/.publish
@@ -1 +1 @@
-Subproject commit 697d06985c1b57fdc445195b31391bd99fa1d7e9
+Subproject commit d425a19baf91e8a81b27df881600b5a73416dce3
diff --git a/src/app/config.js b/src/app/config.js
index 8b447a5b4e..fc8b4b55f6 100644
--- a/src/app/config.js
+++ b/src/app/config.js
@@ -124,12 +124,6 @@ angular
refreshFromServer: false
}
})
- .state('backend.user.shared', {
- url: '^/shared',
- templateUrl: 'app/vault/views/vaultShared.html',
- controller: 'vaultSharedController',
- data: { pageTitle: 'Shared' }
- })
.state('backend.user.settings', {
url: '^/settings',
templateUrl: 'app/settings/views/settings.html',
@@ -375,7 +369,7 @@ angular
// user is guaranteed to be authenticated becuase of previous check
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
// clear vault rootScope when visiting org admin section
- $rootScope.vaultCiphers = $rootScope.vaultFolders = null;
+ $rootScope.vaultCiphers = $rootScope.vaultGroupings = null;
authService.getUserProfile().then(function (profile) {
var orgs = profile.organizations;
diff --git a/src/app/services/authService.js b/src/app/services/authService.js
index 91b3ec245e..8ee9297a47 100644
--- a/src/app/services/authService.js
+++ b/src/app/services/authService.js
@@ -95,7 +95,7 @@ angular
_service.logOut = function () {
tokenService.clearTokens();
cryptoService.clearKeys();
- $rootScope.vaultFolders = $rootScope.vaultCiphers = null;
+ $rootScope.vaultGroupings = $rootScope.vaultCiphers = null;
_userProfile = null;
};
diff --git a/src/app/vault/vaultAddCipherController.js b/src/app/vault/vaultAddCipherController.js
index b2401eceb8..8aafa45636 100644
--- a/src/app/vault/vaultAddCipherController.js
+++ b/src/app/vault/vaultAddCipherController.js
@@ -2,9 +2,9 @@
.module('bit.vault')
.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' });
- $scope.folders = $rootScope.vaultFolders;
+ $scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.constants = constants;
$scope.selectedType = constants.cipherType.login.toString();
$scope.cipher = {
diff --git a/src/app/vault/vaultController.js b/src/app/vault/vaultController.js
index 4646680709..5ddd940b96 100644
--- a/src/app/vault/vaultController.js
+++ b/src/app/vault/vaultController.js
@@ -5,19 +5,22 @@
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants) {
$scope.loading = true;
$scope.ciphers = [];
+ $scope.folderCount = 0;
+ $scope.collectionCount = 0;
+ $scope.firstCollectionId = null;
$scope.constants = constants;
$scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders;
- $scope.folderIdFilter = undefined;
+ $scope.groupingIdFilter = undefined;
$scope.typeFilter = undefined;
if ($state.params.refreshFromServer) {
- $rootScope.vaultFolders = $rootScope.vaultCiphers = null;
+ $rootScope.vaultGroupings = $rootScope.vaultCiphers = null;
}
$scope.$on('$viewContentLoaded', function () {
- if ($rootScope.vaultFolders && $rootScope.vaultCiphers) {
+ if ($rootScope.vaultGroupings && $rootScope.vaultCiphers) {
$scope.loading = false;
- loadFolderData($rootScope.vaultFolders);
+ loadGroupingData($rootScope.vaultGroupings);
loadCipherData($rootScope.vaultCiphers);
return;
}
@@ -26,20 +29,34 @@
});
function loadDataFromServer() {
- var folderPromise = apiService.folders.list({}, function (folders) {
- var decFolders = [{
- id: null,
- name: 'No Folder'
- }];
+ var decGroupings = [{
+ id: null,
+ 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++) {
var decFolder = cipherService.decryptFolderPreview(folders.Data[i]);
- decFolders.push(decFolder);
+ decFolder.folder = true;
+ decGroupings.push(decFolder);
}
-
- loadFolderData(decFolders);
+ $scope.folderCount = folders.Data.length;
}).$promise;
+ var groupingPromise = $q.all([collectionPromise, folderPromise]).then(function () {
+ loadGroupingData(decGroupings);
+ });
+
var cipherPromise = apiService.ciphers.list({}, function (ciphers) {
var decCiphers = [];
@@ -48,31 +65,38 @@
decCiphers.push(decCipher);
}
- folderPromise.then(function () {
+ groupingPromise.then(function () {
loadCipherData(decCiphers);
});
}).$promise;
- $q.all([cipherPromise, folderPromise]).then(function () {
+ $q.all([cipherPromise, groupingPromise]).then(function () {
$scope.loading = false;
});
}
- function loadFolderData(decFolders) {
- $rootScope.vaultFolders = $filter('orderBy')(decFolders, folderSort);
+ function loadGroupingData(decGroupings) {
+ $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) {
- angular.forEach($rootScope.vaultFolders, function (folderValue, folderIndex) {
- folderValue.collapsed = $localStorage.collapsedFolders &&
- (folderValue.id || 'none') in $localStorage.collapsedFolders;
+ angular.forEach($rootScope.vaultGroupings, function (grouping, groupingIndex) {
+ grouping.collapsed = $localStorage.collapsedFolders &&
+ (grouping.id || 'none') in $localStorage.collapsedFolders;
angular.forEach(decCiphers, function (cipherValue) {
if (cipherValue.favorite) {
cipherValue.sort = -1;
}
- else if (cipherValue.folderId == folderValue.id) {
- cipherValue.sort = folderIndex;
+ else if (grouping.folder && cipherValue.folderId == grouping.id) {
+ cipherValue.sort = groupingIndex;
+ }
+ else if (grouping.collection && cipherValue.collectionIds.indexOf(grouping.id) > -1) {
+ cipherValue.sort = groupingIndex;
}
});
});
@@ -110,7 +134,7 @@
return chunks;
}
- function folderSort(item) {
+ function groupingSort(item) {
if (!item.id) {
return '';
}
@@ -123,12 +147,12 @@
'Edit the item and copy it manually instead.');
};
- $scope.collapseExpand = function (folder, favorite) {
+ $scope.collapseExpand = function (grouping, favorite) {
if (!$localStorage.collapsedFolders) {
$localStorage.collapsedFolders = {};
}
- var id = favorite ? 'favorite' : (folder.id || 'none');
+ var id = favorite ? 'favorite' : (grouping.id || 'none');
if (id in $localStorage.collapsedFolders) {
delete $localStorage.collapsedFolders[id];
}
@@ -276,8 +300,8 @@
});
addModel.result.then(function (addedFolder) {
- $rootScope.vaultFolders.push(addedFolder);
- loadFolderData($rootScope.vaultFolders);
+ $rootScope.vaultGroupings.push(addedFolder);
+ loadGroupingData($rootScope.vaultGroupings);
});
};
@@ -288,9 +312,9 @@
apiService.folders.del({ id: folder.id }, function () {
$analytics.eventTrack('Deleted Folder');
- var index = $rootScope.vaultFolders.indexOf(folder);
+ var index = $rootScope.vaultGroupings.indexOf(folder);
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({
animation: true,
templateUrl: 'app/vault/views/vaultCipherCollections.html',
@@ -333,11 +357,14 @@
if (response.collectionIds && !response.collectionIds.length) {
removeCipherFromScopes(cipher);
}
+ else if (response.collectionIds) {
+ cipher.collectionIds = response.collectionIds;
+ }
});
};
- $scope.filterFolder = function (folder) {
- $scope.folderIdFilter = folder.id;
+ $scope.filterGrouping = function (grouping) {
+ $scope.groupingIdFilter = grouping.id;
if ($.AdminLTE && $.AdminLTE.layout) {
$timeout(function () {
@@ -357,7 +384,7 @@
};
$scope.clearFilters = function () {
- $scope.folderIdFilter = undefined;
+ $scope.groupingIdFilter = undefined;
$scope.typeFilter = undefined;
if ($.AdminLTE && $.AdminLTE.layout) {
@@ -367,12 +394,22 @@
}
};
- $scope.folderFilter = function (folder) {
- return $scope.folderIdFilter === undefined || folder.id === $scope.folderIdFilter;
+ $scope.groupingFilter = function (grouping) {
+ return $scope.groupingIdFilter === undefined || grouping.id === $scope.groupingIdFilter;
};
- $scope.cipherFilter = function (cipher) {
- return $scope.typeFilter === undefined || cipher.type === $scope.typeFilter;
+ $scope.cipherFilter = function (grouping) {
+ 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 () {
diff --git a/src/app/vault/vaultEditCipherController.js b/src/app/vault/vaultEditCipherController.js
index 09430b052d..03be2c9925 100644
--- a/src/app/vault/vaultEditCipherController.js
+++ b/src/app/vault/vaultEditCipherController.js
@@ -2,9 +2,9 @@
.module('bit.vault')
.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' });
- $scope.folders = $rootScope.vaultFolders;
+ $scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.cipher = {};
$scope.readOnly = false;
$scope.constants = constants;
diff --git a/src/app/vault/vaultMoveCiphersController.js b/src/app/vault/vaultMoveCiphersController.js
index 3e9573c0e7..0a117b8deb 100644
--- a/src/app/vault/vaultMoveCiphersController.js
+++ b/src/app/vault/vaultMoveCiphersController.js
@@ -2,9 +2,9 @@
.module('bit.vault')
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
- $rootScope) {
+ $rootScope, $filter) {
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
- $scope.folders = $rootScope.vaultFolders;
+ $scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.count = ids.length;
$scope.save = function () {
diff --git a/src/app/vault/vaultSharedController.js b/src/app/vault/vaultSharedController.js
deleted file mode 100644
index b37f7bd8a3..0000000000
--- a/src/app/vault/vaultSharedController.js
+++ /dev/null
@@ -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);
- }
- }
- }
- });
diff --git a/src/app/vault/views/vault.html b/src/app/vault/views/vault.html
index def3d5afa0..c11bb3b9a3 100644
--- a/src/app/vault/views/vault.html
+++ b/src/app/vault/views/vault.html
@@ -26,20 +26,19 @@
My Vault
-
- ,
+
+ ,
+ , &
-
+
+ ng-show="vaultGroupings.length && groupingIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)">